diff --git a/.gitattributes b/.gitattributes
index 8c0c9b8b3f..11a25d44e3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,4 @@
/dev/unicode/17.0.0/** -text
+/src/main/perl/lib/unicore/Name.pl text eol=lf
/src/main/java/org/perlonjava/runtime/regex/PerlUnicodeBlockData.java text eol=lf
/src/main/java/org/perlonjava/runtime/regex/PerlUnicodeNumericValueData.java text eol=lf
diff --git a/AGENTS.md b/AGENTS.md
index 5324d3b6b0..bbf0d2c91c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -202,6 +202,7 @@
| 2026-08-17 | (no source work lost — stale workers removed) Failed `make` runs were interrupted after their known Joni failures, but their Gradle unit-shard workers survived and competed with later builds. | Agent sent Ctrl-C to the parent build session before all parallel workers had exited. Recovery: identified stale workers by PID, start time, and shard work directory, terminated only those exact PIDs, and left the current build and sibling repositories untouched. Fix: let failed parallel `make` runs finish naturally, or verify and clean up their exact child PIDs before starting another build. |
| 2026-08-17 | (no source work lost — CPAN run rerun) A concurrent `make` replaced the development shadow JAR while an active `jcpan` process was spawning a child JVM, causing a transient `ClassNotFoundException`. | Agent waited for another worktree's build but did not wait for the same worktree's bounded CPAN runs before rebuilding `target/perlonjava-5.44.0.jar`. Recovery: let `make` finish and rerun the affected CPAN target. Fix: never rebuild a worktree's development JAR while that worktree has active `jperl` or `jcpan` processes. |
| 2026-08-18 | (no source work lost — wrong local WIP ref recovered) A test-snapshot cherry-pick landed in the original checkout instead of its newly created continuation worktree. | Agent chained `git worktree add` and `git cherry-pick` while the shell remained in the original working directory. Recovery: preserved the mistaken commit on a recovery branch, restored the original WIP ref to its exact prior commit without reset, then cherry-picked in the intended worktree. Fix: run post-creation Git commands with the new worktree as the explicit working directory and verify `git branch --show-current` before committing. |
+| 2026-08-20 | (no source work lost — green build evidence discarded) A coordinator cherry-picked an integrated worker commit into a checkout while `make` was still validating an earlier supposedly immutable commit. | Integration and validation shared one worktree, and the coordinator continued integration before the build session drained. Recovery: let the exact build processes finish untouched, mark the result invalid regardless of exit status, and rerun from an immutable barrier. Fix: never mutate, cherry-pick, rebase, or regenerate a checkout with an active build/test gate; integrate in a separate worktree or wait for the gate to finish. |
When you cause a new incident, append a row here in the same commit
that fixes it. Future agents need to see that these warnings are real.
@@ -273,6 +274,13 @@ PerlOnJava does **not** implement the following Perl features:
**NEVER modify or delete existing tests.** Tests are the source of truth. If a test fails, fix the code, not the test. When in doubt, verify expected behavior with system Perl (`perl`, not `jperl`).
+**NEVER mutate a checkout while a build or test gate is running in it.** A gate
+validates one immutable source commit. Wait for the process and all children to
+finish before cherry-picking, rebasing, editing, or regenerating that checkout,
+or run integration in a separate worktree. If mutation occurs, let the exact
+processes drain without interruption, discard the result even if green, and
+rerun from a clean immutable barrier.
+
**ALWAYS validate new unit tests with standard Perl before relying on them.** Unit tests in `src/test/resources/unit` must encode standard Perl behavior, not PerlOnJava-specific behavior. For any new or changed unit test, run it with `perl` or `prove` first, capture the output, and only then use it to drive PerlOnJava fixes:
```bash
prove -r src/test/resources/unit > /tmp/prove_unit_perl.txt 2>&1; echo "EXIT: $?" >> /tmp/prove_unit_perl.txt
diff --git a/Makefile b/Makefile
index 2c5ea71907..819f35f984 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,6 @@
-.PHONY: all clean test test-unit test-interpreter check-thread-test-sources check-thread-core-test-sources check-thread-ecosystem-test-sources check-thread-regex-test-sources test-thread-tooling test-threads test-threads-core test-threads-core-platform test-threads-core-mode test-threads-windows test-threads-regex test-threads-release test-threads-ecosystem test-bundled-modules test-cpan-distroprefs test-exiftool test-all test-gradle test-gradle-unit test-gradle-all test-gradle-parallel test-maven-parallel build run wrapper check-java-gradle dev ci sbom sbom-java sbom-perl sbom-clean check-links
+.PHONY: all clean test test-unit test-interpreter check-thread-test-sources check-thread-core-test-sources check-thread-ecosystem-test-sources check-thread-regex-test-sources test-thread-tooling test-threads test-threads-core test-threads-core-platform test-threads-core-mode test-threads-windows test-threads-regex test-threads-release test-threads-ecosystem test-bundled-modules test-cpan-distroprefs test-exiftool test-all test-gradle test-gradle-unit test-gradle-all test-gradle-parallel test-maven-parallel build run wrapper check-java-gradle dev ci sbom sbom-java sbom-perl sbom-clean check-links perl5-update perl5-sync
+
+PERL ?= perl
THREAD_DIST_DIRS := perl5/dist/threads/t perl5/dist/threads-shared/t perl5/dist/Thread-Queue/t perl5/dist/Thread-Semaphore/t
THREAD_PLATFORM_TESTS := \
@@ -82,6 +84,12 @@ THREAD_ECOSYSTEM_UPSTREAM_TESTS := \
all: build
+perl5-update:
+ $(PERL) dev/import-perl5/update_perl5.pl
+
+perl5-sync:
+ $(PERL) dev/import-perl5/update_perl5.pl --sync $(if $(FILTER),--filter "$(FILTER)",)
+
# CI build - optimized for CI/CD environments
ci: check-java-gradle
ifeq ($(OS),Windows_NT)
@@ -127,6 +135,15 @@ else
./gradlew classes testUnitParallel --parallel shadowJar
endif
+# Focused vendored-Joni unit gate for parser/matcher iteration. A full `make`
+# remains required before pushing or updating a PR.
+test-joni: check-java-gradle
+ifeq ($(OS),Windows_NT)
+ gradlew.bat testJoni
+else
+ ./gradlew testJoni
+endif
+
# `make dev` is disabled on purpose.
#
# It used to be a "build without running tests" shortcut, but that is
diff --git a/dev/design/cpan-compiler-tooling-batch-20260813.md b/dev/design/cpan-compiler-tooling-batch-20260813.md
index 0321517c66..3fbbc30a51 100644
--- a/dev/design/cpan-compiler-tooling-batch-20260813.md
+++ b/dev/design/cpan-compiler-tooling-batch-20260813.md
@@ -143,5 +143,5 @@ was kept in the PerlOnJava validation set.
## References
- [Module porting guide](../../docs/guides/module-porting.md)
-- [Executable regex callbacks](executable-regex-callbacks.md)
+- [Regex callout contract](../../docs/design/joni-callout-fork.md)
- Skills: `debug-perlonjava`, `port-cpan-module`
diff --git a/dev/design/cpan-runtime-parity-experiment.md b/dev/design/cpan-runtime-parity-experiment.md
index 884b430e9c..adbb01f51b 100644
--- a/dev/design/cpan-runtime-parity-experiment.md
+++ b/dev/design/cpan-runtime-parity-experiment.md
@@ -220,5 +220,5 @@ Runtime outcome:
- `dev/design/caller_line_number_fix.md`
- `dev/design/utf8_flag_parity.md`
-- `dev/design/regex_jruby_joni.md`
+- `dev/implementation/regex.md`
- `.agents/skills/debug-perlonjava/SKILL.md`
diff --git a/dev/design/cpan-workaround-elimination.md b/dev/design/cpan-workaround-elimination.md
index 346007a2ef..6765d8216e 100644
--- a/dev/design/cpan-workaround-elimination.md
+++ b/dev/design/cpan-workaround-elimination.md
@@ -1120,7 +1120,7 @@ Ubuntu and Windows CI jobs passed.
- `dev/design/patch-and-cpan-prefs-layout.md`
- `dev/design/cpan-runtime-parity-experiment.md`
- `dev/design/catalyst-support.md`
-- `dev/design/regex_alternatives.md`
+- `dev/design/phase36-regex-parity.md`
- `dev/design/shared_ast_transformer.md`
- `.agents/skills/port-cpan-module/SKILL.md`
- `.agents/skills/debug-perlonjava/SKILL.md`
diff --git a/dev/design/executable-regex-callbacks.md b/dev/design/executable-regex-callbacks.md
deleted file mode 100644
index c2f93bd898..0000000000
--- a/dev/design/executable-regex-callbacks.md
+++ /dev/null
@@ -1,656 +0,0 @@
-# Executable Regex Callbacks
-
-## Status
-
-- **Current phase:** Phase 3 — backtracking and dynamic scope
-- **Started:** 2026-08-09
-- **Implementation status:** Callout engine and plain callback bridge integrated
-- **Prerequisite:** Satisfied by the namespaced callout-enabled Joni integration
-- **Primary targets:** `(?{ ... })`, `(?(?{ ... })yes|no)`, and `(??{ ... })`
-
-## Decision Summary
-
-Executable regex callbacks are implementable, but not by adding more textual
-rewrites to `RegexPreprocessor` and not by wrapping `java.util.regex.Matcher`.
-The matcher must invoke Perl while it owns the current backtracking state.
-
-The preferred implementation is:
-
-1. Preserve source-level regex code blocks as compiled `RuntimeCode` closures.
-2. Represent an executable regex as a pattern skeleton plus a callback table.
-3. Route executable patterns to a minimally extended Joni backend.
-4. Add generic callout instructions and backtracking notifications to a maintained
- Joni fork; keep that fork independent of PerlOnJava runtime classes.
-5. Keep ordinary patterns on the existing Java backend and declarative recursive
- patterns on the unmodified Joni path introduced by PR #895.
-
-Joni 2.2.7 has an explicit bytecode matcher and backtracking stack, which makes it
-a useful base, but it has no in-match callout API. `WarnCallback` only reports
-compile warnings, `Matcher.interrupt()` only cancels matching, and
-`Regex.setUserObject()` stores opaque data without invoking it. The opaque user
-object is still a suitable place for a generic callout table after extending the
-engine.
-
-## Motivation
-
-PR #895 leaves explicit capability policies for executable regex paths. These
-are high-value shared blockers rather than isolated test failures:
-
-- Type::Tiny constructs regex closures such as `qr/x(?{$z})/` and side-effecting
- callbacks such as `qr/f(?{ ++$count })oo/`.
-- Regexp::Common uses callback conditions for validation and `(??{ ... })` for
- dynamically selected patterns.
-- Object::InsideOut uses a self-referential `(??{ ... })` balanced-parentheses
- pattern.
-- Perl core regex suites exercise callback timing, provisional captures,
- backtracking, lexical scope, dynamic scope, and runtime pattern compilation.
-
-A correct shared implementation can remove capability policies and improve
-multiple core regex files. A partial preprocessor substitution would make these
-modules appear to work while silently skipping user code, so unsupported cases
-must remain fatal until their semantics are implemented.
-
-## Goals
-
-- Execute literal `(?{ BLOCK })` callbacks at the correct match position.
-- Preserve lexical closures created by `qr//`, `m//`, and `s///` on both the JVM
- and interpreter backends.
-- Make provisional captures and match variables visible inside callbacks.
-- Restore dynamically localized state when backtracking crosses a callback.
-- Support callback conditions `(?(?{ BLOCK })yes|no)`.
-- Support `(??{ EXPR })` as a nested dynamic regex program, including recursion.
-- Preserve Perl's distinction between source-literal executable blocks and
- executable source introduced by runtime interpolation under `use re 'eval'`.
-- Retain the fast existing engine paths for regexes without executable code.
-- Provide standard-Perl differential tests before enabling each semantic tier.
-
-## Non-goals
-
-- Replacing every regex with Joni.
-- Rewriting Perl callbacks into Java expressions.
-- Treating callbacks as post-match hooks.
-- Making all callback side effects transactional.
-- Silently treating unsupported callbacks as zero-width no-ops.
-- Completing every unrelated Perl regex feature in the same project.
-- JIT-compiling the callback-aware matcher in the first implementation.
-
-## Current Architecture and Gaps
-
-### Parser
-
-`StringSegmentParser.parseRegexCodeBlock()` already parses literal callback bodies
-as Perl AST nodes. It currently chooses among:
-
-- constant folding and a synthetic empty named capture for `$^R`;
-- construction-time execution of a very narrow increment/decrement form;
-- `RegexMarkers.CODE_BLOCK` or `RegexMarkers.RECURSIVE_PATTERN` for unsupported
- blocks.
-
-All three representations flatten or discard information needed by a real
-matcher. In particular, a pattern string cannot retain a lexical closure.
-
-### Runtime
-
-`RuntimeRegex` currently compiles a string plus modifiers and caches by those
-values. Match variables are populated from a completed match. Executable code
-needs a per-regex callback table, provisional match state during execution, and a
-cache identity that does not conflate distinct closures with identical source.
-
-### Regex engines
-
-`java.util.regex` does not expose its backtracking transitions. Splitting a
-pattern around callbacks and matching each fragment atomically is incorrect when
-a later fragment fails and an earlier quantifier or alternative must retry.
-
-PR #895 introduces a backend-neutral `RegexMatcher` and a Joni backend for
-declarative subpattern recursion. That is the required routing seam, but the Joni
-2.2.7 parser, compiler, opcode set, and bytecode matcher still have no callout
-node or instruction.
-
-## Required Perl Semantics
-
-The implementation must be driven by observed standard-Perl behavior. The
-following statements are design requirements to verify in Phase 0, not a
-substitute for differential tests.
-
-### Callback timing
-
-- `(?{ BLOCK })` is a zero-width assertion executed when the matcher reaches it.
-- It can execute more than once because of quantifiers, alternatives, lookarounds,
- and backtracking.
-- Its scalar result becomes `$^R`; its truth value does not decide whether a
- plain `(?{ ... })` succeeds.
-- Exceptions and non-local control flow must follow Perl behavior rather than be
- converted into a match failure without evidence.
-
-### Provisional match state
-
-While a callback is running, these values must describe the current partial
-match:
-
-- `$_` and `pos($_)`;
-- numbered and named captures;
-- `$^N`;
-- `$^R`;
-- `@-`, `@+`, `%+`, and `%-` where applicable;
-- the pre-match variable, `$&`, and `$'` where Perl makes them observable at
- that point.
-
-The runtime therefore needs a stack of active match views. Updating only the
-global "last successful match" after `find()` returns is insufficient and breaks
-nested regexes invoked from callbacks.
-
-### Backtracking and side effects
-
-Do not roll back every side effect. Perl can leave ordinary assignments, I/O,
-warnings, and invoked subroutine effects visible even when the regex later
-backtracks over the callback.
-
-State created through `local`, together with automatically localized regex state
-such as `$^R`, must unwind at the corresponding backtracking boundary. The
-matcher must therefore record a `DynamicVariableManager` local-level checkpoint
-on entry and restore to it when the callout frame is discarded. This is a
-dynamic-scope checkpoint, not a general mutation journal.
-
-The exact treatment of tied variables, magic, `pos()`, and callbacks inside
-lookarounds must be established with differential tests.
-
-### Callback conditions
-
-`(?(?{ BLOCK })yes|no)` executes the block in scalar context and selects a branch
-from its truth value. It is distinct from plain `(?{ BLOCK })` and requires a
-branching matcher instruction rather than a precomputed condition.
-
-### Dynamic patterns
-
-`(??{ EXPR })` executes `EXPR` at the current match position and treats its
-result as a regex program. The nested program must participate in outer
-backtracking. Running one nested match atomically is insufficient: if a later
-outer suffix fails, the nested program may need to yield another alternative.
-
-The implementation must distinguish returned regex objects from returned strings,
-preserve applicable modifiers and capture behavior, support recursive results,
-and prevent unbounded Java recursion. Cache behavior, `/o`, and empty or undefined
-results require differential coverage.
-
-### Compile-time safety
-
-Literal code in a source regex is compiled as part of the surrounding Perl unit.
-Code introduced through runtime interpolation is subject to Perl's `use re
-'eval'` safety rule. Embedding an already compiled `qr//` containing callbacks
-must not be confused with injecting new source text.
-
-## Proposed Architecture
-
-```text
-Perl source
- |
- v
-String/regex parser
- |-- literal/interpolation AST
- `-- callback BlockNode ASTs
- |
- v
-JVM compiler or bytecode compiler
- |-- evaluates pattern interpolation at construction time
- `-- compiles each callback as a lexical RuntimeCode closure
- |
- v
-RuntimeRegexTemplate
- |-- engine pattern skeleton
- |-- callback descriptors and RuntimeCode table
- |-- source-origin and lexical re-eval policy
- `-- modifiers / callsite identity
- |
- v
-RuntimeRegex
- |-- Java engine: ordinary supported patterns
- |-- Joni engine: declarative recursion
- `-- callout-Joni engine: executable callbacks
- |
- v
-RegexCalloutBridge
- |-- installs provisional match view
- |-- invokes RuntimeCode in scalar context
- |-- tracks dynamic-local checkpoints
- `-- restores nested match state on exit/backtrack
-```
-
-### Frontend representation
-
-Add an explicit AST representation rather than inserting callback marker strings
-into ordinary interpolation:
-
-```java
-final class RegexTemplateNode extends AbstractNode {
- List parts;
-}
-
-sealed interface RegexTemplatePart {
- record Pattern(Node scalarExpression) implements RegexTemplatePart {}
- record Callback(BlockNode block, CallbackKind kind) implements RegexTemplatePart {}
-}
-
-enum CallbackKind {
- CODE, CONDITION, DYNAMIC_PATTERN
-}
-```
-
-The final class names may differ, but the representation must keep Perl AST and
-pattern text separate. Callback bodies must use the existing anonymous-code
-closure analysis and emission paths rather than inventing another evaluator.
-Their invocation semantics are block-like, however, not an ordinary named
-subroutine call: they must not introduce a spurious caller frame or fresh `@_`,
-and regex-controlled `local` frames may need to outlive the Java method call that
-evaluates the block.
-
-Both compiler backends must construct the same runtime abstraction:
-
-```java
-final class RuntimeRegexTemplate {
- RuntimeScalar patternSkeleton;
- List callbacks;
- RegexSourceOrigin sourceOrigin;
-}
-
-final class RuntimeRegexCallback {
- int id;
- CallbackKind kind;
- RuntimeCode code;
-}
-```
-
-The callback table belongs to each runtime regex value. Two closures produced
-from the same source string must not share captured lexical cells accidentally.
-
-### Internal pattern encoding
-
-The engine-facing pattern may contain private callout tokens that refer to table
-indices, but these tokens are an internal serialization of structured data, not
-user-visible preprocessing fallbacks. The encoding must be collision-proof and
-must never be accepted from untrusted interpolated pattern text as an already
-compiled callback.
-
-The extended Joni parser should create dedicated callout AST nodes from these
-tokens. It must not attempt to parse Perl source code.
-
-### Generic Joni extension
-
-Maintain a small Joni fork with a runtime-neutral interface similar to:
-
-```java
-interface CalloutHandler {
- CalloutResult execute(int calloutId, MatchView view);
- void unwind(Object backtrackToken);
-}
-
-record CalloutResult(
- CalloutAction action,
- Object value,
- Object backtrackToken) {}
-
-enum CalloutAction {
- CONTINUE, FAIL, SELECT_TRUE, SELECT_FALSE, ENTER_DYNAMIC_PATTERN
-}
-```
-
-The concrete contract should use minimal allocation, but it needs to communicate:
-
-- callback identity;
-- current byte position and provisional capture boundaries;
-- result/action;
-- a token to unwind dynamic state when the matcher backtracks past the callout.
-
-Add matcher instructions conceptually equivalent to:
-
-- `CALLOUT id` for `(?{ ... })`;
-- `CALLOUT_CONDITION id, false_target` for callback conditions;
-- `CALLOUT_PATTERN id` for `(??{ ... })`;
-- a callout stack-frame type whose pop/unwind path notifies the handler.
-
-Keep all Perl-specific operations in `RegexCalloutBridge`. The Joni fork must not
-depend on `RuntimeCode`, `RuntimeScalar`, or other PerlOnJava types. This keeps the
-fork reviewable and makes an upstream generic callout proposal possible.
-
-### Dynamic pattern execution
-
-`CALLOUT_PATTERN` cannot delegate to a separate matcher that returns only its
-first result. The preferred implementation compiles the returned pattern into a
-Joni program and enters it as a nested regex call frame sharing the outer input,
-current position, and required capture context.
-
-Phase 5 must first determine which Joni structures can safely be shared and
-which capture-number translations Perl requires. If nested programs cannot be
-entered without invasive Joni changes, stop and revise the design rather than
-shipping atomic nested matching.
-
-### Runtime match-state bridge
-
-Introduce an active match-state stack rather than mutating one static completed
-matcher in place:
-
-```java
-final class ActiveRegexMatch {
- RuntimeRegex regex;
- RuntimeScalar target;
- RegexMatcher matcher;
- MatchView provisionalView;
- RuntimeScalar previousCodeResult;
-}
-```
-
-`RegexCalloutBridge` pushes or updates this state before invoking Perl and restores
-the previous active view afterward. Nested matches from callback code then get a
-new stack entry and cannot destroy the outer provisional captures.
-
-The existing `RegexState` and `DynamicVariableManager` are starting points, but
-`RegexState` currently models completed global state. It will need either a
-provisional-view extension or a separate callback match state.
-
-### Dynamic-local unwind
-
-Before executing a callback:
-
-1. Record `DynamicVariableManager.getLocalLevel()`.
-2. Install the callback's provisional match view and automatic regex locals.
-3. Invoke its `RuntimeCode` body in scalar context through a regex-block entry
- point that preserves the surrounding `@_` and caller identity.
-4. Transfer ownership of callback-created dynamic-local frames to the matcher
- instead of applying the ordinary subroutine-exit unwind.
-5. Retain the resulting local-level token in the Joni callout stack frame.
-
-On normal forward continuation, localized values remain active for the part of
-the match governed by Perl semantics. When Joni backtracks across that frame,
-the bridge calls `popToLocalLevel(savedLevel)` and restores the prior `$^R` and
-provisional match view. At final match completion or failure, all remaining
-callout frames must be unwound exactly once.
-
-This interaction needs explicit handling for exceptions, regex timeouts, matcher
-interruption, and nested callbacks. If the existing `RuntimeCode.apply()` cleanup
-cannot transfer dynamic-scope ownership, add a narrow `applyRegexCallback()` (or
-equivalent execution mode); do not bypass cleanup by mutating the dynamic stack
-after it has already been restored.
-
-### Caching
-
-Split cacheable engine structure from closure-bearing runtime values:
-
-- Declarative patterns may continue using the existing pattern/modifier cache.
-- An executable regex wrapper is always per construction unless proven safe.
-- A compiled callout skeleton may be cached by normalized skeleton, modifiers,
- encoding mode, and engine version, while its callback table remains per value.
-- `/o` uses callsite identity and retains the first constructed callback closure
- according to standard-Perl behavior.
-- Dynamic `(??{ ... })` subprogram caching must be specified by differential tests;
- do not infer it from ordinary pattern caching.
-
-### Serialization and stringification
-
-`qr//` stringification must expose Perl-compatible source text, never internal
-callout tokens or callback IDs. Storable support must be investigated separately:
-serializing a closure-bearing regex may require the same restrictions as
-serializing `RuntimeCode` closures.
-
-## Rejected Approaches
-
-### More `RegexPreprocessor` substitutions
-
-Textual substitutions cannot invoke code at the correct backtracking point and
-cannot retain lexical closures. This includes replacing callbacks with empty
-groups, synthetic captures, lookarounds, or post-match callbacks.
-
-### Splitting around Java matchers
-
-A wrapper cannot ask `java.util.regex.Matcher` for the next internal alternative
-of an earlier fragment after a later fragment fails. Making delegated fragments
-atomic changes Perl semantics.
-
-### Executing callbacks while constructing the regex
-
-This gets timing, repetition, captures, and backtracking wrong. The existing
-narrow increment/decrement construction-time path should be removed once the
-real callback path is available.
-
-### Blanket transactional rollback
-
-Rolling back arbitrary assignments and external side effects would not match
-Perl. Only dynamically scoped and automatically localized match state should be
-restored at backtracking boundaries.
-
-### JNI to the system Perl regex engine
-
-The system engine cannot directly execute PerlOnJava `RuntimeCode` closures or
-share PerlOnJava lexical cells. A native bridge would also add platform and
-deployment costs without removing the callback-runtime integration problem.
-
-### Full custom regex VM as the first step
-
-A custom VM remains a fallback if the Joni spike fails, but it duplicates a large
-working matcher before the callback semantics have been validated. Extend the
-explicit-stack backend already entering PerlOnJava through PR #895 first.
-
-## Differential Test Plan
-
-Create `src/test/resources/unit/regex_executable_callbacks.t`. As required for
-unit tests, validate every addition with standard Perl before relying on it for
-PerlOnJava behavior.
-
-### Phase 0 semantic matrix
-
-Cover at least:
-
-1. Plain callback execution and scalar `$^R` results.
-2. False callback results that do not fail plain `(?{ ... })`.
-3. Package-variable and lexical-closure reads/writes.
-4. Multiple execution under alternation, greedy/lazy quantifiers, and failure.
-5. Ordinary side effects after backtracking.
-6. `local` restoration after forward progress, backtracking, success, and failure.
-7. `$1`, named captures, `$^N`, `$^R`, `@-`, `@+`, `$_`, and `pos()`.
-8. Positive and negative lookarounds.
-9. Nested regex matches invoked by callback code.
-10. Callback exceptions and surrounding `eval`.
-11. Callback conditions with true, false, and backtracked branches.
-12. `(??{ ... })` returning strings, `qr//` values, empty values, and recursive
- patterns.
-13. Literal callbacks versus interpolated executable source with and without
- `use re 'eval'`.
-14. `qr//` closure lifetime after the creating lexical scope exits.
-15. `/g`, `/c`, `/o`, substitution, and byte/Unicode target behavior.
-
-Suggested baseline command:
-
-```bash
-prove src/test/resources/unit/regex_executable_callbacks.t \
- > /tmp/prove_regex_executable_callbacks_perl.txt 2>&1
-printf 'EXIT: %s\n' "$?" \
- >> /tmp/prove_regex_executable_callbacks_perl.txt
-```
-
-After each implementation phase, run both PerlOnJava backends with `timeout` and
-capture complete output in files.
-
-### Perl core gates
-
-Use `perl dev/tools/perl_test_runner.pl` for broad counts and focused `jperl`
-runs only under `timeout`:
-
-- `perl5_t/t/re/rxcode.t`
-- `perl5_t/t/re/reg_eval.t`
-- `perl5_t/t/re/reg_eval_scope.t`
-- `perl5_t/t/re/pat_re_eval.t`
-- callback-related sections of `pat.t`, `pat_advanced.t`, and `subst.t`
-
-Passing the focused unit file is not sufficient to claim full callback support.
-Record reached assertions and remaining categories for each core file.
-
-### CPAN policy-removal gates
-
-- Run the original Type::Tiny callback tests without
- `SkipRegexCallbackTests.patch` on both backends.
-- Run the unchanged Regexp::Common suite and classify remaining non-callback
- failures separately.
-- Run Object::InsideOut's unchanged self-recursive pattern and its relevant suite.
-- Remove a distropref or patch only after its unchanged source suite passes or the
- remaining policy has a separately documented capability reason.
-
-### Regression and performance gates
-
-- Run full `make` after every implementation phase.
-- Preserve Java-regex behavior and performance for non-executable patterns.
-- Compare Joni recursive-pattern tests introduced by PR #895 before and after the
- fork.
-- Add timeout/step-limit tests for callbacks that recursively start matches.
-- Measure allocation and throughput for ordinary patterns to ensure callback
- state is not allocated on their fast path.
-
-## Implementation Phases
-
-### Phase 0: Differential semantics and Joni spike
-
-- Add the standard-Perl semantic matrix.
-- Prototype a generic no-op callout node, opcode, handler, and unwind notification
- in a temporary Joni fork.
-- Prove that a callback can observe provisional captures and runs again after
- backtracking.
-- Decide fork publication/versioning and upstreaming strategy.
-
-**Exit criteria:** The differential output is captured; the Joni prototype can
-enter and unwind a generic callback without PerlOnJava dependencies; no working
-tree-only fork changes remain.
-
-### Phase 1: Structured frontend and runtime template
-
-- Add callback-aware regex AST/template nodes.
-- Compile callback blocks as lexical `RuntimeCode` closures in both backends.
-- Construct per-value callback tables and collision-proof engine skeletons.
-- Preserve existing fatal behavior until the matcher execution path exists.
-
-**Exit criteria:** JVM and interpreter construct equivalent templates; closure
-identity and lifetime tests pass without executing callbacks during construction.
-
-### Phase 2: Plain `(?{ ... })`
-
-- Integrate the callout-enabled Joni dependency.
-- Add `CALLOUT`, provisional match views, `$^R`, and nested match-state stacking.
-- Support package and lexical variable access.
-- Keep unsupported semantic combinations fatal rather than silently approximated.
-
-**Exit criteria:** The Phase 0 plain-callback matrix passes on standard Perl, JVM,
-and interpreter; relevant Type::Tiny callback tests pass unchanged.
-
-### Phase 3: Backtracking and dynamic scope
-
-- Add callout backtracking frames and exact-once unwind.
-- Restore `local` and automatic regex state without reverting ordinary side
- effects.
-- Cover lookarounds, quantifiers, failure, exceptions, interruption, and timeout.
-
-**Exit criteria:** Backtracking/localization sections of `rxcode.t` and
-`reg_eval_scope.t` match standard Perl on both backends.
-
-### Phase 4: Callback conditions
-
-- Parse and represent `(?(?{ ... })yes|no)`.
-- Add conditional callout branching.
-- Validate provisional `$^N` and captures used by Regexp::Common validators.
-
-**Exit criteria:** Target Regexp::Common callback-condition cases and focused core
-tests pass unchanged.
-
-### Phase 5: `(??{ ... })` dynamic programs
-
-- Add nested program compilation/execution inside the Joni matcher.
-- Integrate nested alternatives with outer backtracking.
-- Specify capture numbering, modifier inheritance, recursion limits, and caching.
-- Support self-referential runtime regex values without Java stack recursion.
-
-**Exit criteria:** Differential dynamic-pattern matrix, Object::InsideOut's
-self-recursive pattern, and applicable `reg_eval.t`/`rxcode.t` sections pass.
-
-### Phase 6: Runtime source, hardening, and policy removal
-
-- Implement `use re 'eval'` checks for runtime-injected executable source.
-- Finish `/g`, `/c`, `/o`, substitution, byte/Unicode, stringification, warning,
- timeout, and serialization behavior.
-- Run complete core and CPAN gates.
-- Remove obsolete markers, construction-time callback hacks, CPAN patches, and
- capability policies only when their gates pass.
-- Update feature matrix and user documentation.
-
-**Exit criteria:** Full `make` passes; unchanged target suites have recorded results;
-removed policies are justified by passing source-first gates.
-
-## Risks and Mitigations
-
-| Risk | Mitigation |
-|---|---|
-| Joni fork becomes large or hard to rebase | Keep the callout SPI generic and opcode patch minimal; stop after Phase 0 if nested integration requires broad unrelated changes. |
-| Callback match state corrupts nested regexes | Use an explicit active-match stack and differential nested-match tests. |
-| Dynamic locals leak after failure or timeout | Store local-level tokens in matcher frames and exercise every exit path. |
-| Cache reuses the wrong lexical closure | Separate cached skeletons from per-value callback tables; include callsite identity for `/o`. |
-| Internal callback IDs can be injected by user strings | Use structured source-origin metadata and collision-proof private tokens rejected from runtime text. |
-| `(??{ ... })` is treated atomically | Require nested alternatives to participate in outer backtracking before enabling the feature. |
-| Core suite gains apparent passes from no-op fallback | Keep unsupported callbacks fatal and remove no-op behavior only through explicit test gates. |
-| Ordinary regexes regress | Preserve engine dispatch fast paths and run PR #895 plus full `make` regression gates. |
-
-## Open Questions
-
-1. Can the Joni callout extension be accepted upstream, or should PerlOnJava
- publish a namespaced fork from the start?
-2. What is the minimal provisional-capture API that avoids allocating `Region`
- snapshots for every callback?
-3. Should active regex state become thread-local immediately, or can it be layered
- over current static fields without compromising concurrent use?
-4. How exactly does standard Perl scope callback-created `local` values during
- successful forward continuation and quantifier repetition?
-5. Which captures created by a returned `(??{ ... })` pattern are visible to the
- outer pattern, and how should Joni group numbers be translated?
-6. What are Perl's cache rules when `(??{ ... })` repeatedly returns equal strings
- or the same `qr//` object?
-7. How should closure-bearing regex values interact with Storable?
-8. Which non-local control-flow operations are legal from a regex callback, and
- how should both compiler backends propagate them?
-
-Do not resolve these by intuition. Add standard-Perl differential cases and
-record the observed output in this document as phases proceed.
-
-## Progress Tracking
-
-### Current Status: Phase 6 delivered; mixed source composition complete
-
-### Completed Phases
-
-- [x] Phase 0: Differential semantics and Joni spike
-- [x] Phase 1: Structured frontend and runtime template
-- [x] Phase 2: Plain `(?{ ... })`
-- [x] Phase 3: Backtracking and dynamic scope
-- [x] Phase 4: Callback conditions
-- [x] Phase 5: `(??{ ... })` dynamic programs (2026-08-16)
-- [x] Phase 6: Runtime source and hardening (2026-08-16)
-
-### Next Steps
-
-1. Resolve exact runtime-eval source names, warning locations, and diagnostic
- differences.
-2. Remove only capability policies justified by unchanged-source suite results.
-3. Extend focused differential coverage for tied and magical callback paths
- that throw or recurse.
-
-### Blockers
-
-- `pat_re_eval.t` executes all 555 planned assertions on both backends, with
- 420 passing and 135 remaining semantic or diagnostic failures. The focused
- mixed-source gate is 16/16, `reg_eval_scope.t` is 49/49, and Object::InsideOut
- is 6/6 on both backends.
-
-## Related Documents and Skills
-
-- `dev/design/regex_jruby_joni.md` — older broad Joni migration notes
-- `dev/design/regex_parser_integration.md` — regex AST/parser strategy
-- `dev/design/regex_preprocessing_fixes.md` — known preprocessing gaps and counts
-- `dev/design/regex_alternatives.md` — engine alternatives
-- `dev/implementation/regex.md` — earlier full-VM and hybrid architecture notes
-- `dev/design/cpan-workaround-elimination.md` — compatibility-policy design
- introduced by PR #895 (not present on `master` until that PR is merged)
-- `.agents/skills/debug-perlonjava/SKILL.md` — required debugging and differential
- workflow
diff --git a/dev/design/jcpan-packed-crypto-tooling.md b/dev/design/jcpan-packed-crypto-tooling.md
index b29f8e3217..274f5e524a 100644
--- a/dev/design/jcpan-packed-crypto-tooling.md
+++ b/dev/design/jcpan-packed-crypto-tooling.md
@@ -59,8 +59,8 @@ Ignore a target only when its unchanged suite also fails under system Perl.
### Open Questions
- `ExtUtils::ParseXS` 3.64 uses executable `(??{ ... })` regex callbacks.
- That capability is deliberately tracked by
- `dev/design/executable-regex-callbacks.md`; it surfaced only through
+ The current callback contract is tracked by
+ `docs/design/joni-callout-fork.md`; it surfaced only through
HTTP::Message's optional Brotli recommendation during the Catalyst run and
must not be papered over with a source patch.
- `Proc::ProcessTable`'s native upstream suite still fails under system Perl in
@@ -70,7 +70,7 @@ Ignore a target only when its unchanged suite also fails under system Perl.
## Related documentation and skills
-- `dev/design/executable-regex-callbacks.md`
+- `docs/design/joni-callout-fork.md`
- `docs/guides/module-porting.md`
- `.agents/skills/debug-perlonjava/SKILL.md`
- `.agents/skills/port-cpan-module/SKILL.md`
diff --git a/dev/design/perl-regex-library-rfc.md b/dev/design/perl-regex-library-rfc.md
index da9973b573..37b194165b 100644
--- a/dev/design/perl-regex-library-rfc.md
+++ b/dev/design/perl-regex-library-rfc.md
@@ -176,7 +176,5 @@ behavior or stabilize transitional routing decisions.
Related documents:
- `dev/design/phase36-regex-parity.md`
-- `dev/design/executable-regex-callbacks.md`
-- `dev/design/regex_engines.md`
-- `dev/design/regex_jruby_joni.md`
- `dev/implementation/regex.md`
+- `docs/design/joni-callout-fork.md`
diff --git a/dev/design/phase36-regex-differential-20260817.md b/dev/design/phase36-regex-differential-20260817.md
deleted file mode 100644
index e4b47f09cc..0000000000
--- a/dev/design/phase36-regex-differential-20260817.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# Phase 36 Forced-Backend Regex Differential — 2026-08-17
-
-## Scope
-
-This report records the 80-file `perl5_t/t/re` corpus with the temporary Java
-and Joni matcher selector on both PerlOnJava execution backends. The corpus
-uses the clean pre-PR-1002 binary built from commit `ba85bdc05`; its SHA-256 is:
-
-```text
-04be3d3d52348dbda4de1ff187df5cd9d788a274ab14bbb5170bafe879cd12cf
-```
-
-The historical comparison is PR 958's captured runner log:
-
-```text
-../PerlOnJava/logs/test_20260815_080000_958.log
-```
-
-That log contains all 80 regex files and records 50,273/94,771 passing
-assertions. Complete logs, runner JSON, and normalized comparison JSON remain
-outside Git under `/tmp/phase36-regex-*`.
-
-PR 1002 merged and PR 1005's package-localization fix was prepared after this
-binary was frozen. In particular, PR 1005 closes `pat_advanced.t` tests
-922–933. Those changes are intentionally not mixed into this same-commit
-matrix; a later verification rerun must use one rebuilt current-master binary
-for all four legs.
-
-## Method
-
-Each matrix leg inherits one matcher selection and, for interpreter legs,
-global interpreter selection. `perl_test_runner.pl` owns a hard timeout and
-process group for every `jperl` child. Full output is captured to a log and the
-runner writes per-file JSON. The displayed 300-second value is the base limit;
-the runner's configured heavy-file overrides raise `pat_psycho*` and other
-designated heavy fixtures to 600 seconds.
-
-```text
-JPERL_REGEX_BACKEND=java perl dev/tools/perl_test_runner.pl \
- --timeout 300 --jobs 5 --output /tmp/phase36-regex-java-jvm.json perl5_t/t/re
-
-JPERL_REGEX_BACKEND=joni perl dev/tools/perl_test_runner.pl \
- --timeout 300 --jobs 5 --output /tmp/phase36-regex-joni-jvm.json perl5_t/t/re
-
-JPERL_REGEX_BACKEND=java JPERL_INTERPRETER=1 \
- perl dev/tools/perl_test_runner.pl --timeout 300 --jobs 6 \
- --output /tmp/phase36-regex-java-interpreter.json perl5_t/t/re
-
-JPERL_REGEX_BACKEND=joni JPERL_INTERPRETER=1 \
- perl dev/tools/perl_test_runner.pl --timeout 300 --jobs 6 \
- --output /tmp/phase36-regex-joni-interpreter.json perl5_t/t/re
-```
-
-The comparison gate now reports execution errors, incomplete files, and
-zero-TAP results explicitly. `--expected-files 80 --fail-on-invalid` rejects an
-incomplete corpus independently of the existing per-file pass-count regression
-gate.
-
-## Matrix Summary
-
-| Matcher | Execution backend | Passing / planned | Delta vs PR 958 | Timeouts | Status |
-|---|---|---:|---:|---:|---|
-| Java | JVM | 49,923 / 94,823 | -350 / +52 | 0 | complete |
-| Joni | JVM | 32,479 / 77,612 | -17,794 / -17,159 | 10 | complete |
-| Java | interpreter | 50,021 / 94,823 | -252 / +52 | 0 | complete |
-| Joni | interpreter | 32,483 / 77,612 | -17,790 / -17,159 | 10 | complete |
-
-## Complete Java/JVM Comparison
-
-The forced-Java/JVM leg covered all 80 files. It has four per-file pass-count
-regressions against PR 958:
-
-| File | PR 958 | Candidate | Delta | Initial classification |
-|---|---:|---:|---:|---|
-| `pat.t` | 1099/1302 | 239/1302 | -860 | shared source-provenance abort |
-| `pat_thr.t` | 1099/1302 | 239/1302 | -860 | same direct failure in thread wrapper |
-| `reg_mesg.t` | 1692/2525 | 1664/2521 | -28 | diagnostics and plan change |
-| `regexp_normal.t` | 2193/2210 | 2175/2210 | -18 | matcher semantics |
-
-`pat{,_thr}.t` stops immediately after test 239 with:
-
-```text
-Eval-group not allowed at runtime, use re 'eval'
-```
-
-This is not an optimizer/debug transcript difference or a Java/Joni routing
-difference. The forced-Joni leg stops at the same point. The failing literal
-uses single-quote regex delimiters, `m'a(?{ ... })b'`; initial reduction shows
-that this parse path produces a plain pattern string and loses trusted literal
-callback provenance before matcher selection.
-
-The net aggregate loss masks substantial progress since PR 958. In particular,
-`pat_re_eval{,_thr}.t` improves from 0/555 to 423/555 each,
-`pat_advanced{,_thr}.t` improves from 1376/1687 to 1511/1687 each,
-`reg_eval_scope.t` reaches 49/49, and `rxcode.t` reaches 40/42.
-
-## Complete Joni/JVM Comparison
-
-The forced-Joni leg covers all 80 files but falls to 32,479/77,612: 17,794
-fewer passing assertions and 17,159 fewer planned assertions than PR 958. It
-contains 27 per-file pass-count regressions, 33 execution issues, and 29
-zero-TAP files. Ten files reach their runner-owned bounds with zero TAP:
-
-- 300 seconds: `regex_sets_compat.t`, `regexp.t`, `regexp_noamp.t`,
- `regexp_notrie.t`, `regexp_qr.t`, `regexp_qr_embed.t`, and
- `regexp_trielist.t`.
-- 600 seconds: `pat_psycho.t`, `pat_psycho_thr.t`, and
- `regexp_qr_embed_thr.t`.
-
-The cluster is matcher-specific: the same files complete in the forced-Java
-leg. Reduction must determine whether each stall occurs during Joni compile or
-search before any broad implementation change.
-
-Completed Joni/JVM files also expose semantic gaps independent of timeout:
-
-- `pat{,_thr}.t`: 239/1302 each, sharing the matcher-independent literal
- callback provenance abort described above.
-- `pat_advanced{,_thr}.t`: 1271/1687 each, versus forced Java 1511/1687.
-- `reg_mesg.t`: 1364/2486, versus forced Java 1664/2521.
-- `reg_posixcc.t`: 2052/2560, versus forced Java 2560/2560.
-- `regexp_normal.t`: 2175/2210 on both matcher selections.
-- `regexp_unicode_prop.t`: 1039/1110; its thread wrapper is 1040/1110.
-
-`speed.t` exits incomplete at 1/59 after 451 seconds and `speed_thr.t` exits
-incomplete at 1/59 after 301 seconds. These are internal-watchdog/incomplete
-results rather than runner timeouts, but still lose 25 passing assertions per
-file against PR 958.
-
-## Complete Java JVM/Interpreter Comparison
-
-The forced-Java interpreter leg covers all 80 files at 50,021/94,823. It has
-the same zero runner timeouts and planned count as forced-Java/JVM, while
-gaining 98 passing assertions in aggregate. That aggregate gain is entirely
-driven by `alpha_assertions.t` (2188/2320 on JVM and 2293/2320 on interpreter,
-+105); four interpreter-specific losses remain:
-
-| File | JVM | Interpreter | Delta |
-|---|---:|---:|---:|
-| `reg_eval_scope.t` | 49/49 | 45/49 | -4 |
-| `pat_advanced.t` | 1511/1687 | 1510/1687 | -1 |
-| `pat_advanced_thr.t` | 1511/1687 | 1510/1687 | -1 |
-| `qr.t` | 4/4 | 3/4 | -1 |
-
-Against PR 958, the interpreter leg has five pass-count regressions: the same
-four Java/JVM corpus regressions plus `qr.t` at -1. These execution-backend
-differences are shared runtime/compiler parity gaps, not Java-vs-Joni matcher
-differences.
-
-## Complete Joni JVM/Interpreter Comparison
-
-The forced-Joni interpreter leg covers all 80 files at 32,483/77,612. Its ten
-timeouts, 33 execution issues, 29 zero-TAP files, and planned count are
-identical to Joni/JVM. It gains four passing assertions in aggregate, again
-masking execution-backend differences:
-
-| File | JVM | Interpreter | Delta |
-|---|---:|---:|---:|
-| `reg_eval_scope.t` | 49/49 | 45/49 | -4 |
-| `qr.t` | 4/4 | 3/4 | -1 |
-| `alpha_assertions.t` | 2099/2320 | 2108/2320 | +9 |
-
-On the interpreter execution backend, selecting Joni instead of Java changes
-50,021/94,823 to 32,483/77,612: 17,538 fewer passing and 17,211 fewer planned
-assertions, with 29 regressing and three improving files. The loss categories
-match the JVM engine differential, so the primary blocker is matcher selection
-rather than execution-backend compilation.
-
-## Classification and Follow-up
-
-- Source policy: fix the matcher-independent single-quote literal callback
- provenance abort after this report PR, with a focused standard-Perl oracle.
-- Matcher semantics: separate the shared `regexp_normal.t` loss from
- Joni-only `pat_advanced.t` and POSIX/property losses.
-- Diagnostics: treat `reg_mesg.t` independently from matching behavior.
-- Timeout behavior: reduce the Joni timeout cluster under individual hard
- timeouts; do not hide it through Java fallback.
-- Optimizer/debug transcript: none of the regressions above are currently
- classified as transcript-only exclusions.
-
-The Phase 1 exit criterion is not met. Joni fails to retain every assertion
-that passes under Java, introduces ten matcher-specific timeouts on both
-execution backends, and exposes independent semantic and diagnostic losses.
-The four relevant pairwise comparisons (Java/Joni within each execution
-backend and JVM/interpreter within each matcher selection) are complete and
-retained under `/tmp/phase36-compare-*`.
diff --git a/dev/design/phase36-regex-parity.md b/dev/design/phase36-regex-parity.md
index 0e93bf7851..e1a13b9c20 100644
--- a/dev/design/phase36-regex-parity.md
+++ b/dev/design/phase36-regex-parity.md
@@ -2,387 +2,317 @@
## Goal
-Implement Perl 5.44 regular-expression semantics on both PerlOnJava execution
-backends and make the vendored Joni fork the sole production matcher. Java
-`Pattern` and the backend selector are temporary differential tools and must be
-removed before completion.
+Implement the regex semantics of the latest imported upstream Perl on both
+PerlOnJava execution backends, with the vendored Joni fork as the sole
+production matcher.
-The contract includes matching, captures, match state, callbacks, dynamic
-patterns, diagnostics, warnings, source locations, byte/Unicode behavior,
-direct/thread parity, unchanged Perl core tests, and unchanged CPAN consumers.
-Optimizer programs and debug transcripts are reported separately from language
-semantics.
-
-The immutable no-regression comparison point is:
+The immutable no-regression baseline is:
```text
../PerlOnJava/logs/test_20260815_080000_958.log
```
-## Final Architecture
+An implementation PR may merge incrementally when every valid baseline test row
+retains at least its PR 958 passing count and at least one row improves. That is
+a merge boundary, not project completion. Completion requires every phase and
+final-acceptance item below.
+
+## Required Architecture
-- The forked Joni engine is implemented and maintained in this repository.
- Joni owns regex grammar and matcher semantics: captures, conditions,
- recursion, lookarounds, case folding, control verbs, backtracking-visible
- state, byte/Unicode matching, and property membership.
+ recursion, lookarounds, folding, control verbs, backtracking-visible state,
+ byte/Unicode matching, properties, and native compile diagnostics.
- PerlOnJava owns source policy: interpolation provenance, lexical hints and
- warnings, `use re 'eval'`, executable Perl closures, user-defined properties,
- source locations, and final Perl diagnostic rendering.
-- The fork remains runtime-neutral. Callouts use internal IDs and a matcher-local
- handler API; Joni never receives Perl source or PerlOnJava runtime objects.
-- Upstream Joni/JCodings package names and all copyright/authorship notices stay
- intact under `third_party/`. Standalone packaging relocates them to
- `org.perlonjava.internal`, avoiding public namespace collisions without
- rewriting the maintained source namespace.
-- Joni is the default for every closure-bearing pattern throughout migration.
- No callback, condition, control verb, recursive program, or dynamic source may
- fall back to Java. Ordinary constants move to Joni as their native gates pass.
-- Final production code has one matcher. Ordinary patterns allocate no callout
- state, and a match operation never runs two engines because regex side effects
- are observable.
+ warning masks, `use re 'eval'`, executable Perl closures, user-defined
+ properties, source locations, and final Perl diagnostic rendering.
+- No production regex may route to `java.util.regex`, including ordinary
+ constant patterns. Historical selector-policy tests use a test-scope-only
+ model that cannot enter production artifacts.
+- The maintained Joni/JCodings source packages remain unchanged under
+ `third_party/`; standalone packaging relocates them to
+ `org.perlonjava.internal` to avoid public collisions.
+- Every original Joni/JCodings copyright, authorship, and license notice is
+ preserved byte-for-byte.
+- Joni remains runtime-neutral. Callouts expose internal IDs and matcher-local
+ handlers, never Perl source or PerlOnJava runtime objects.
+- Ordinary patterns allocate no callout state. One match operation runs exactly
+ one engine because regex side effects are observable.
+- Imported Perl and Unicode inputs track the latest upstream `perl5/`
+ checkout. Do not pin a fixed Perl SHA or release.
## Preprocessing Boundary
-Classify every current regex preprocessing rule as one of:
+Every transformation must fit one category:
1. Perl source policy retained in a small frontend scanner.
2. Backend-neutral spelling normalization retained outside the matcher.
-3. Matcher semantics moved into Joni parser/compiler/matcher internals.
-4. Java-only translation or stack workaround deleted with the Java backend.
+3. Matcher semantics implemented in Joni parser/compiler/matcher internals.
+4. Java-only translation or workaround deleted.
Text rewriting must not emulate behavior dependent on capture close order,
-backtracking, matcher regions, encoding, or callback execution. As native fixes
-land, remove corresponding `RegexPreprocessor` code immediately and rerun the
-affected corpus before taking another slice.
-
-## Current Validated Position
-
-- The reproducible differential baseline and conditions/control-verb phase are
- complete.
-- `(*MARK:NAME)`, named `(*SKIP:NAME)`, `$REGMARK`, `$REGERROR`, callback unwind,
- and native `(*THEN)` branch boundaries are in the Joni path.
-- Absolute, forward, backward, whole-pattern, and signed-relative numbered
- subpattern calls parse natively in Joni; the adapter rewrite is gone.
-- Recursive callouts observe captures from the just-completed recursive frame.
-- Folded exact-search candidate bounds are safe for long ASCII-strict literals,
- and native numbered-call diagnostics retain the Perl source location.
-- The last complete forced-Joni `pat_advanced.t` gate executes 1,687/1,687 and
- passes 1,646 on JVM and interpreter, with identical 41-row residuals. The
- integrated signed-IV range fix removes row 1651 with zero introductions in
- exact A/B evidence; a fresh combined serial gate remains required because a
- later run stopped before the complete plan under concurrent CPAN load.
-- The current imported `reg_mesg.t` passes 1,794/2,613 on each backend with an
- identical status/test-number vector.
-- Forced-Joni Unicode property comparison is 83,648/83,648 on both backends.
- The Hyphen/IsHyphen warning correction removes the final exact 32 residuals
- with zero introductions while preserving dynamic property interpolation.
-- The shared deterministic pinned-Perl Unicode generator covers all current
- property families plus compact Perl default simple/full/reverse case-fold
- metadata. General_Category compatibility aliases, native named-call/parser
- safety, the runtime-neutral Joni property-value matcher, signed-IV user-
- property ranges, POSIX compatibility, and generated fold data are integrated.
- The analyser fold-safety slice, final binary aliases, generated named-sequence
- lookup, property-wildcard execution, Hyphen diagnostics, native extended
- classes, property/class fold closure, and nested property-wildcard lexing are
- integrated in local staging.
-- Raw `\N{name}` source now survives frontend and matcher compilation without
- the temporary `=POJSEQ=` transport. Generated and lexical multi-code-point
- names and ordinary scalar names resolve through Joni; the temporary
- generated-sequence-only routing distinction is deleted.
-- `master` contains the validated named-character diagnostics plus native
- `(?(DEFINE)...)`, ordinary lookbehind, branch reset, and plain `\N`; its exact
- head passed warning-free local, Ubuntu, and Windows gates.
-- Byte/Unicode provenance and fold policy, dotted-U+ diagnostics, the ordinary-
- pattern Joni default, a dynamic-pattern edge contract, and the first obsolete
- import retirement are integrated. Fold, property, resolver-cache, and the
- classified runtime-diagnostic residuals are closed.
-- Native DEFINE, ordinary lookbehind, and branch reset now route through Joni;
- their feature-specific Java rewrites and branch-reset capture-map adapter are
- deleted. Plain Perl `\N` is a native Joni non-line-feed atom, including
- intervals, `/s` independence, and Perl's character-class diagnostic.
-- Unknown, empty, malformed/dotted U+, and missing-brace named-character groups
- have native frontend/runtime diagnostics.
-- Forced-Joni `pat_re_eval.t` now executes and passes 555/555 on both JVM and
- interpreter. Perl's release-build `-D` diagnostic is preserved without
- enabling PerlOnJava's unrelated internal compiler trace.
-- Each `pat.t` variant executes 1,301/1,302; JVM passes 1,225 and interpreter
- passes 1,234. The remaining JVM-only dynamic code-array rows are active work.
-- The four Java/Joni × JVM/interpreter legs now have one 80-file comparison
- ledger on the pre-successor artifact. It identifies stable extended-class,
- regexp, charset, fold-grind, and bounded-speed negative clusters plus several
- zero-TAP/execution records. The complete matrix must be repeated on the exact
- successor artifact before acceptance; older aggregate figures are not release
- evidence.
-- Exact `/aa` routing/folding gates pass on native Joni, and the Java `/aa`
- workaround is removed.
-- Perl grouped nested-quantifier semantics and extended-mode quantifier
- modifiers are native Joni behavior; the exact `regexp.t` differential removes
- seven failures with no introductions.
-- Named `(*ACCEPT:NAME)`, `(*FAIL:NAME)`, and `(*F:NAME)` carry control state
- through native Joni bytecode and publish Perl-compatible `$REGMARK` and
- `$REGERROR`; the exact `regexp.t` differential removes three failures with no
- introductions.
-- Negative lookbehind accepts capture enclosures and uses ACCEPT-aware width
- analysis in native Joni; named cut errors remain authoritative before an
- unnamed FAIL. The combined exact `regexp.t` differential has no introductions.
-- Perl `/xx` character-class whitespace and nested inline `x`/`xx` mode changes
- are native Joni lexer/parser behavior. The corresponding `regexp.t` identities
- pass without introductions on both execution backends.
-- Reverse full-fold alternatives can repartition across adjacent source
- literals without changing single-literal lookbehind width. The targeted
- `regexp.t` identity and the existing literal/backreference fold contract pass
- on default and forced Joni for JVM and interpreter.
-- Perl's exact `L_` General_Category compatibility spelling resolves as `LC`
- before loose alias normalization, so uncased letters no longer enter that
- class. The focused system-Perl oracle, four runtime legs, and imported
- `regexp.t` identity agree.
+backtracking, matcher regions, encoding, or callback execution. When a native
+Joni fix lands, remove its corresponding preprocessor/runtime scanner in the
+same tranche.
+
+Remaining scanner-removal queue:
+
+- [ ] Make Joni's property resolver accept every raw built-in Perl spelling
+ currently expanded by `JoniRegexPattern.translateUserDefinedProperties`,
+ including Script, Script_Extensions, Block, Age/Present_In, and aliases.
+- [ ] Keep raw user-defined property tokens through Joni parsing and resolve or
+ defer them through the mode-specific callback cache; remove never-match or
+ match-all text substitutions used to represent deferred properties.
+- [ ] Preserve ordinary/extended-class context and source positions in Joni so
+ the adapter no longer scans bracket depth or generates replacement classes.
+- [ ] Retain only runtime-neutral trusted-callout materialization and documented
+ Perl source-policy checks outside Joni; delete the scanner when these gates
+ pass.
## Execution Phases
### Phase 0 — Reproducible differential baseline
-- Run the same 80 `perl5_t/t/re` files on JVM/interpreter and forced Java/Joni.
-- Save complete logs and machine-readable results outside the tree.
-- Reject zero-TAP, timeout, truncated, or incomplete records before comparing.
-- Classify failures as matcher semantics, source policy, diagnostics, shared
- runtime behavior, or optimizer/debug-only output.
+- [x] Derive the complete regex-bearing test ledger mechanically.
+- [x] Preserve direct and `_thr.t` identities separately.
+- [x] Reject zero-TAP, timeout, truncated, incomplete, or malformed records.
+- [x] Emit machine-readable file/test comparison evidence against PR 958.
-Exit: the baseline is repeatable and every next slice has exact rows and an
-oracle.
+Exit: every implementation slice has exact rows, a standard-Perl oracle, and a
+repeatable baseline.
### Phase 1 — Ordinary-pattern Joni parity
-- Complete byte/Unicode variant selection from pattern and subject provenance.
-- Close captures, duplicate names, branch reset, regions, bounds, zero-width
- search progression, `\G`, `/g`, `/c`, `/o`, substitution, reuse, and nested
- match-state restoration.
-- Route each ordinary-pattern family to Joni only after focused and complete
- differential gates show zero introductions.
+- [ ] Close all remaining PR 958 ordinary-pattern regressions.
+- [ ] Prove capture, duplicate-name, branch-reset, region/bounds, zero-width
+ progression, `\G`, `/g`, `/c`, `/o`, substitution, reuse, and nested
+ match-state behavior.
+- [ ] Prove byte/Unicode pattern and subject variant selection.
+- [x] Delete the disconnected production backend selector while preserving its
+ historical compatibility tests under test scope.
-Exit: every semantic assertion passing on Java passes on forced Joni, with JVM
-and interpreter agreement.
+Exit: every ordinary regex executes in Joni with no baseline regression and
+JVM/interpreter agreement.
### Phase 2 — Conditions and backtracking-visible state
-- Numbered/named/assertion/recursion/callback conditions and `(DEFINE)`.
-- `(*MARK:NAME)`, named `(*SKIP:NAME)`, `(*PRUNE)`, `(*COMMIT)`, `(*THEN)`,
- `$REGMARK`, `$REGERROR`, cut boundaries, recursion limits, and unwind.
-- Capture-close order, provisional match variables, dynamic locals, and callback
- side effects on the selected path.
+- [x] Numbered, named, assertion, recursion, callback, and `DEFINE` conditions.
+- [x] `(*MARK:NAME)`, named `(*SKIP:NAME)`, `(*PRUNE)`, `(*COMMIT)`,
+ `(*THEN)`, `(*ACCEPT)`, `(*FAIL)`, `$REGMARK`, and `$REGERROR`.
+- [x] Capture publication/clearing, recursion frames, callback unwind, and
+ destructive-cut boundaries.
-Exit: focused Perl oracles and relevant `pat_advanced.t`, `rxcode.t`, and
-`reg_eval_scope.t` sections agree.
+Exit: focused standard-Perl oracles and affected imported rows agree.
### Phase 3 — Unicode and native pattern syntax
-- Add a development generator that reads and analyzes the repository's pinned
- Perl 5.44 Unicode tables, then emits checked-in Java source for Joni property
- names, loose aliases, value families, ranges, and case-fold metadata. The same
- script emits resolver oracle fixtures plus input/output checksums. Generated
- Java is reproducible and its second consecutive generation must be diff-free;
- hand-written Java is limited to reviewed precedence and behavior that cannot
- be derived from the source tables.
-- Finish Block, Script, Script_Extensions, General_Category, binary,
- compatibility, wildcard, versioned `Age`, POSIX, `\h`/`\H`, and user-property
- behavior from pinned Perl 5.44 Unicode data.
-- Preserve property-family and user-callback precedence, signed-IV-wide scalar
- domains, and byte/Unicode warnings; do not duplicate ICU behavior when the
- pinned data or ICU/JCodings already provides it.
-- Complete `\N{name}` single/multi/empty atoms, classes, lexical translators,
- caching, and exact extended-class diagnostics.
-- Complete `/d`, `/u`, `/a`, `/aa`, literal/backreference/class/property case
- folding and optimizer search safety.
-- Complete Perl escapes, subpattern calls, lookbehind width/255-character rules,
- branch-reset lexical targets, `\K`, recursion safety, and invalid-pattern
- diagnostics natively in Joni.
-
-Exit: semantic `regexp_unicode_prop.t`, `pat.t`, and `pat_advanced.t` assertions
-complete without masking supported syntax through `JPERL_UNIMPLEMENTED=warn`.
+- [x] Generate checked-in Java property, alias, range, named-character, and
+ case-fold data from the latest `perl5/` tables with provenance and a
+ diff-free second generation.
+- [x] General Category, Script, Block, Script Extensions, POSIX, binary,
+ compatibility, wildcard, user-property, signed-wide, and fold foundations.
+- [x] Native `\N{name}`, extended classes, DEFINE, lookbehind, branch reset,
+ plain `\N`, recursion, numeric references, false ranges, and subpattern
+ calls.
+- [x] Finish lexical custom-charname cache identity, source byte/Unicode mode,
+ scoped user-property options, and nested class fold provenance.
+- [x] Finish native POSIX grammar, recovery, warning order, and diagnostics.
+- [ ] Finish remaining non-POSIX native range/parser diagnostics.
+- [ ] Refresh complete Unicode, `pat.t`, and `pat_advanced.t` gates.
+
+Exit: Unicode and native syntax corpora execute without compatibility masking.
### Phase 4 — Runtime source and diagnostics
-- Preserve package, lexical context, warning masks/categories, filename, line,
- syntax position, and byte/Unicode identity through literal, runtime, and eval
- compilation.
-- Complete recursive/nested `(??{...})`, mixed literal/runtime executable
- source, tied/localized interpolation, object stringification, and `/g`/`/c`/
- `/o` behavior across callbacks and exceptions.
-- Finish native warning collection plus exact Perl wording, markers,
- fatal-versus-warning behavior, and source suffixes.
-- Close all 555 `pat_re_eval.t` assertions on both execution backends; classify
- shared non-regex eval defects separately but fix any that block regex source.
+- [x] Runtime callbacks, nested continuations, lexical warning policy, eval
+ capture lifetime, thread cloning, source spelling, and callback unwind.
+- [x] Native numeric, named-character, quantifier, group-name, extended-class,
+ range, and selected POSIX diagnostics.
+- [x] `pat_re_eval.t` semantic contract.
+- [ ] Finish all remaining same-source `reg_mesg.t` diagnostic families.
+- [x] Finish analyser warning-policy rows.
+- [ ] Finish remaining debug-trace rows.
+- [ ] Refresh complete `regexp.t`, `reg_mesg.t`, and runtime-source gates.
-Exit: runtime-generated regexes and diagnostics agree with standard Perl.
+Exit: generated regexes, warnings, fatality, categories, text, and locations
+agree with standard Perl on JVM and interpreter.
### Phase 5 — Remove migration scaffolding
-- Delete every Java matcher field, route, syntax rewrite, fallback, and the
- backend selector.
-- Delete matcher-semantic preprocessing after each replacement gate is green.
-- Keep only the documented Perl source-policy/frontend layer.
-- Remove regex patches introduced by `dev/import-perl5/sync.pl`, then restore
- exact upstream files with targeted sync.
+- [x] Remove Java matcher storage and production routing.
+- [x] Remove native-replaced extended-class, branch-reset, lookbehind, DEFINE,
+ recursion, numeric-reference, and matcher-semantic adapter rewrites.
+- [x] Remove obsolete imported regex patches and prove targeted sync idempotent.
+- [ ] Delete remaining matcher-semantic preprocessing, source scanners, and
+ unreachable adapters after their native gates pass.
+- [x] Prove no environment setting can select a production Java matcher.
-Exit: Joni is the only production matcher and imported regex tests are
-unchanged upstream files.
+Exit: Joni is the sole matcher and only documented Perl source policy remains
+outside it.
### Phase 6 — Release and documentation
-- Run all direct and `_thr.t` regex files on JVM/interpreter and compare the
- complete `dev/tools/perl_test_runner.pl` output file-by-file with PR 958.
-- Run unchanged Type::Tiny, Regexp::Common, Object::InsideOut, and every CPAN
- suite affected by removed regex capability policy.
-- Run performance, packaging, notices/licenses, warning-free build, Ubuntu,
- Windows, and full CI gates.
-- Update `docs/reference/feature-matrix.md`, including the expected-Joni feature
- set around lines 390–409 and every currently missing Perl regex feature.
-- Rewrite `dev/implementation/regex.md` as the clear as-implemented architecture.
-- Update `docs/design/joni-callout-fork.md` for the shipped fork API, packaging,
- namespace, callback/unwind contract, and Unicode ownership.
-- Delete wholly redundant documents; reduce rationale-bearing older documents
- to concise summaries pointing to the canonical implementation documents.
-
-Exit: all semantic and release gates pass and documentation matches shipped
-behavior.
+- [ ] Pass immutable complete 622-file JVM acceptance against PR 958.
+- [ ] Pass the complete regex-bearing ledger on the interpreter and reconcile
+ every JVM/interpreter semantic difference.
+- [ ] Pass direct/thread parity, bounded `pat_psycho*` and `speed*`,
+ affected CPAN suites, packaging, notice/license, and warmed performance gates.
+- [x] Pass the five-run warmed ordinary-regex comparison: candidate median
+ 12.23s versus exact-parent 12.68s under alternating contended runs.
+- [ ] Pass warning-free `make`, Ubuntu, Windows, and complete CI.
+- [ ] Reconcile `docs/reference/feature-matrix.md`,
+ `dev/implementation/regex.md`, and `docs/design/joni-callout-fork.md`
+ with shipped behavior.
+- [ ] Delete redundant documents; retain only concise rationale summaries that
+ point to canonical implementation documents.
+
+Exit: release evidence and public/internal documentation match the code.
+
+## Current Release Gate
+
+The current integration source composition ends at `702bb2853`. Its
+constituent source tranches are each
+warning-free full-build green. Raw property, warning, escaped-class callback,
+byte-NEL line break, and word-boundary whitespace coverage pass on JVM and
+interpreter. The byte-NEL tranche makes unchanged `regexp.t` 2210/2210 on both
+backends. The native word-boundary tranches make `uniprops10.t`
+20,070/20,070 on both backends, recovering 1,190 assertions from their exact
+parents with no introduced assertion IDs; direct Joni and focused boundary
+oracles are green. One exact combined build of this composition is the current
+checkpoint barrier. The integrated overload-copy increment correction
+passes unchanged `lib/overload_fallback.t` 4/4 on JVM and interpreter,
+adjacent overload gates, and a warning-free full build. The preceding exact
+combined code barrier `18b9a0133` passes packaging, licensed Joni, all unit
+shards, and the shadow JAR. PR 1087 remains the next
+incremental release PR; update it only from an exact warning-free integration
+head whose complete comparator has no pass-count decrease from PR 958 and no
+new invalid, missing, timeout, truncated, incomplete, or zero-TAP row.
+
+The authoritative 622-file, jobs-5, timeout-300 gate at `d3a4bb074` completed
+with 677871/695187 assertions passing and 95 improved rows. Fresh private-tree
+isolation found no regex pass decrease: both `pat.t` variants improve by 147
+passing assertions. Plan/platform drift explains four raw decreases; missing
+private-import inputs explain two new porting errors. The
+`lib/overload_fallback.t` 3/4 decrease is fixed at `cbee747f1`.
+`io/socket.t` is environmental rather than a product decrease: the exact
+one-file runner outside the restricted sandbox passes 25/25 with zero
+errors/timeouts, while the sandbox denies localhost bind and clamps
+`SO_SNDBUF` to 816; focused JVM/interpreter option probes outside it both
+return 32768. There is no remaining reproduced non-regex pass-count blocker.
+The complete runner, comparator, isolation, dossier, 622-file list, reducers,
+and verified checksum manifest are
+durably retained under
+`../PerlOnJava/logs/test_20260821_080900_d3a4bb074_a16_*`; do not repeat the
+full corpus merely to rediscover this evidence.
+
+### Execution Tracker
+
+- [x] Reproducible PR 958 baseline and strict comparator.
+- [x] Conditions, callbacks, control verbs, and backtracking-visible state.
+- [x] Runtime regex source, lexical context, callback unwind, and diagnostics.
+- [ ] Ordinary-pattern Joni parity: prove the full corpus with native byte and
+ Unicode matching and no Java matcher fallback.
+- [ ] Unicode and pattern syntax: complete nested scoped extended-class
+ interpolation and remaining corpus-derived roots.
+- [x] Production Java matcher/backend selector and legacy `RegexPreprocessor`
+ are absent; source audit finds only test-scope backend-policy assertions.
+- [x] Obsolete imported regex patches are removed and targeted sync is
+ idempotent.
+- [ ] Remove residual temporary source-policy scanners identified by the final
+ corpus.
+- [ ] Complete integration, dual-backend/direct-thread/CPAN/performance gates,
+ platform CI, documentation reconciliation, and post-merge checks.
+
+Active ownership:
+
+- P3: close the ordinary native matcher checklist and implement the largest
+ remaining disjoint matcher-state root, if any.
+- P4: implement the general frontend array-match/substitution compile warning
+ root that accounts for the missing planned `pat.t`/`pat_thr.t` assertion.
+- P5: generate and wire current-Perl InSC/InPC data covering 2,120 residual
+ assertions; the warn-retirement dossier is complete.
+- P6: classify and close the largest remaining native `reg_mesg.t`
+ Parser/Lexer diagnostic family.
+- Coordinator: integration, conflict resolution, immutable acceptance, PR/CI,
+ plan state, combined build, and release evidence.
## Ordered Next Steps
-1. Integrate the native `/xx`, adjacent full-fold partition, and `L_` alias
- batch on the exact merged predecessor. Run one warning-free full build plus
- zero-introduction `regexp.t` and fold/property gates, then publish a review
- PR and require exact-head Ubuntu/Windows CI.
-2. Complete recursive and runtime `(??{...})` as native nested Joni execution:
- preserve captures, `$^R`, `pos`, modes, byte/Unicode provenance, callback
- unwind, backtracking re-evaluation, and recursion safety. Route every embedded
- closure to Joni and delete constant inlining, progressive errors, and the
- dynamic Java adapter as their gates pass.
-3. Complete byte/Unicode pattern provenance through runtime interpolation and
- template composition, then finish `/d`/`u`/`a`/`aa` forward/reverse literal,
- class, property, and backreference folding from generated data. Require
- direct Joni plus ordinary/forced JVM/interpreter zero-introduction gates.
-4. Finish the remaining lexical `use re 'strict'`, unescaped-brace, and non-hex
- diagnostic families. Refresh complete
- `reg_mesg.t`, `pat.t`, and `pat_advanced.t` maps after each combined batch.
-5. Repeat the four-leg 80-file Java/Joni × JVM/interpreter matrix on the exact
- successor artifact and compare every file with the PR 958 log. Resolve every
- regression, zero-TAP record, timeout, truncation, or incomplete file before
- user acceptance.
-6. Remove each proven-obsolete regex transformation from `dev/import-perl5`
- sync sources, regenerate a private unpatched corpus twice, prove byte-for-byte
- idempotence, and run the affected upstream tests without editing them.
-7. Use the refreshed impact report to move all remaining ordinary constants to
- native Joni, deleting their Java routes and matcher-semantic preprocessing in
- the same validated slices. Keep `pat_re_eval.t` at 555/555 throughout.
-8. Delete Java matching, selector, fallback state, and unreachable preprocessors;
- then run direct/thread regex, CPAN, performance, packaging, notice/license,
- warning-free build, Ubuntu, Windows, and full CI gates.
-9. Update the feature matrix and final as-implemented/fork documents, remove or
- summarize redundant design documents, rebase the final stack on `master`, and
- run the complete PR 958 parity audit before declaring Phase 36 complete.
-
-## Parallel Work
-
-- Coordinator/integration: canonical stack, PR 958 comparison, PR/CI readiness,
- plan state, ownership, and conflict resolution.
-- Native syntax/matcher: one non-overlapping Joni grammar or matcher feature per
- branch with direct fork tests and Perl reducers.
-- Unicode: one classified property family per branch from the exact residual
- artifact.
-- Differential/release: immutable row sets, normalized comparators, fallback
- impact ranking, import sync, CPAN and platform gates.
-- Documentation: feature inventory and final architecture documents after the
- corresponding behavior is validated.
-
-Workers use isolated worktrees and append-only handoff mailboxes. Assignments
-state exact base, owned/excluded files, oracle, complete gates, correction
-budget, and delivery evidence. Workers self-monitor CPU and may admit at most
-three concurrent expensive jobs globally; timing-sensitive final gates are
-serial. Workers normally stop after focused and complete affected-corpus gates
-and deliver local commits without pushing. The coordinator batches two to four
-non-overlapping deliveries, runs one warning-free full `make` on the combined
-head, and only then pushes or updates the PR. A worker-local full build is
-reserved for build-system changes or focused evidence of broad cross-suite risk.
-`pat_psycho*` and `speed*` may use two CPU-heavy workers, while `pat*`,
-`pat_advanced*`, and memory-sensitive fixtures remain one-worker exclusive.
-Every `jperl`, `jcpan`, and `prove` process has a hard timeout.
+1. Run one exact combined warning-free `make` at the current clean integration
+ head, including P3 a18 and P6 a35.
+2. Refresh focused Unicode,
+ `regexp.t`, `regex_sets.t`, `charset.t`, `pat.t`,
+ `pat_advanced.t`, `pat_re_eval.t`, and `reg_mesg.t` gates on that exact
+ immutable head.
+3. Preserve the documented fixture-invalid and sandbox-environment dispositions.
+ Refresh the comparator evidence for the exact integration
+ head without repeating the already-retained discovery run. Push a head with no PR-958
+ pass-count regression to PR 1087, run CI, and make the incremental PR
+ reviewable.
+4. Continue P4 documentation, P5 generated Indic properties, P6 pat/diagnostic
+ work, and remaining native Unicode/runtime roots in a new WIP
+ PR. Repeat focused tests per semantic tranche and one combined build per
+ integration batch.
+5. Delete remaining production migration scaffolding and prove all constants,
+ closures, conditions, verbs, recursion, dynamic source, byte strings, and
+ Unicode strings execute through Joni.
+6. Run complete JVM/interpreter acceptance, direct/thread parity, affected CPAN
+ suites, five warmed performance samples, packaging, notices/licenses,
+ warning-free build, and platform CI.
+7. Reconcile final documentation and remove redundant design material.
+8. After the final implementation PR is merged to `master`, remove automatic
+ regex `JPERL_UNIMPLEMENTED=warn` injection from
+ `dev/tools/perl_test_runner.pl`; rerun the complete corpus; then delete the
+ RuntimeRegex warning-plus-never-match downgrade and obsolete tests/docs.
+9. On final `master`, review shipped behavior against
+ `pod/perlreref.pod`, `pod/perlrecharclass.pod`,
+ `pod/perlrequick.pod`, `pod/perlrepository.pod`, `pod/perlre.pod`,
+ `pod/perlretut.pod`, and `pod/perlrebackslash.pod`. Record every
+ supported, partial, divergent, and missing capability in
+ `docs/reference/feature-matrix.md` with evidence.
## Test and Delivery Contract
-- Never modify or delete existing tests. Validate every NEW Perl fixture with
- system Perl before PerlOnJava.
-- Capture complete output in files. Use `perl dev/tools/perl_test_runner.pl`,
- never `jperl`, to drive the fork-based core runner.
-- Compare JVM and interpreter results, direct and thread wrappers, and forced
- backends where the temporary selector still exists.
-- A semantic slice needs its focused oracle, direct Joni tests where applicable,
- and complete affected corpus with zero introductions. Each combined
- integration batch needs one warning-free `make` before push or PR update.
-- Preserve original Joni/JCodings notices and verify relocated packaging.
-- Never push master. Use focused branches, attributed commits, PRs, and current
- master rebases after upstream merges.
-
-## Performance Gate
-
-Before deleting Java matching, run five warmed ordinary-pattern measurements on
-each backend. Joni median runtime must be within 25% of the Java baseline, add
-no timeout, and not materially increase steady-state allocation. Performance
-failure blocks backend removal, not semantic fixes.
-
-## Execution Tracker
-
-- [x] Phase 0 — reproducible differential baseline
-- [ ] Phase 1 — ordinary-pattern Joni parity
-- [x] Phase 2 — conditions and backtracking-visible state
-- [ ] Phase 3 — Unicode and native pattern syntax
-- [ ] Phase 4 — runtime source and diagnostics
-- [ ] Phase 5 — remove migration scaffolding
-- [ ] Phase 6 — release and documentation
-
-A checked phase means its focused semantic implementation is complete. Release
-gates may reopen it if a semantic regression appears.
-
-### Active phase detail
-
-- [x] Pinned Perl Unicode property-data generators and freshness gates
-- [x] General Category, Script, Block, POSIX, binary-membership, and signed-wide
- property ranges
-- [x] Runtime-neutral Joni property-value matcher
-- [x] Replace every Java property-wildcard execution site with the Joni matcher
-- [x] Parse nested property-value regex syntax in Joni and remove adapter
- materialization of the selected ranges
-- [x] Complete Hyphen warning/category/source-position diagnostics
-- [x] Pinned Perl simple/full/reverse case-fold data
-- [x] Native fold adapter and unsafe optimizer-boundary suppression
-- [x] Property/class fold closure
-- [x] Fold-mode and byte/Unicode provenance context
-- [ ] Forward/reverse literal expansion and backreference folding
-- [x] Generated Perl named-sequence lookup and native sequence resolution
-- [x] Remove temporary named-sequence encoding from native Joni pattern source
-- [x] Restore canonical multi-code-point named-sequence extended-class diagnostics
-- [x] Restore Perl diagnostics for unknown/empty/malformed named sequences
-- [x] Native `(?[...])` with zero diagnostic regressions
-- [x] Native `(?(DEFINE)...)` and removal of its adapter rewrite
-- [x] Native ordinary lookbehind and removal of its Java translation
-- [x] Native branch reset and removal of its capture-map adapter
-- [x] Native plain `\N` non-newline atom and interval forms
-- [ ] Native recursive/runtime `(??{...})` and removal of dynamic adapters
-- [ ] Retire proven-obsolete `dev/import-perl5` regex patches
-- [ ] Refresh the complete Unicode, `pat.t`, `pat_advanced.t`, `reg_mesg.t`, and
- 80-file forced-Joni gates on one integrated artifact
+- Never modify imported or existing tests to make them pass. Add focused
+ reducers and verify every new unit fixture with system Perl first.
+- Every semantic slice needs system-Perl, JVM, interpreter, direct-Joni where
+ applicable, and complete affected-corpus zero-introduction evidence.
+- Use `dev/tools/perl_test_runner.pl` with system `perl`, not `jperl`.
+- Compare complete output with the PR 958 baseline; reject missing, invalid,
+ timeout, truncated, incomplete, or newly zero-TAP records. Preserve and
+ classify the baseline's platform/skip-only zero-TAP rows separately.
+- Give concurrent corpus runs private writable test state. Match the baseline's
+ concurrency and cleanup for release evidence; label faster variants as
+ reconnaissance only.
+- Run at most three expensive jobs concurrently. Workers self-monitor load;
+ final timing-sensitive gates are serial.
+- Wrap every `jperl`, `jcpan`, and `prove` invocation in `timeout` and
+ save complete output outside the repository.
+- Build only with `make`. Batch two to four deliveries per full build where
+ risk permits; a worker-local full build is reserved for broad-risk changes.
+- Do not push an integration head until its exact `make` is warning-free.
+- Keep `perl5/` development-only. A development make rule may clone it when
+ absent or pull latest upstream when present, then run
+ `dev/import-perl5/sync.pl`. Sync must include required generated inputs such
+ as `unicore/Name.pl`, preserve needed non-regex patches, and be idempotent.
## Final Acceptance
-- [ ] Every semantic regex test passing in PR 958 still passes.
-- [ ] Complete runner output is compared file-by-file with the PR 958 log.
-- [ ] JVM/interpreter and direct/thread results agree.
-- [ ] Forced Joni covers constants, closures, conditions, control verbs,
- recursion, dynamic source, byte strings, and Unicode strings.
-- [ ] `pat_psycho*` and `speed*` pass under bounded parallelism.
+- [ ] Every semantic row passing in PR 958 still passes.
+- [ ] Complete 622-file JVM output is compared file-by-file with PR 958.
+- [ ] Complete regex-bearing JVM/interpreter and direct/thread results agree.
+- [ ] Joni covers constants, closures, conditions, control verbs, recursion,
+ dynamic source, byte strings, and Unicode strings.
+- [x] `(*MARK:NAME)` and named control-verb state execute in Joni.
+- [x] Bounded `pat_psycho*` and `speed*` semantic/performance closure.
- [ ] No supported regex test needs `JPERL_UNIMPLEMENTED=warn`.
-- [ ] Joni is the sole production matcher; Java routing/selector code is gone.
-- [ ] Matcher-semantic preprocessing is gone.
-- [ ] Obsolete import patches are removed and targeted sync is idempotent.
-- [ ] Feature matrix and final architecture documents match implementation.
-- [ ] Original copyright/authorship notices are preserved.
-- [ ] Performance, warning-free `make`, packaging, license, Ubuntu, Windows,
- and CI gates pass.
+- [x] Joni is the sole production matcher; selector compatibility code is
+ test-scope-only and absent from production packaging.
+- [x] Matcher-semantic `RegexPreprocessor` and production Java matcher are gone.
+- [x] Obsolete regex import patches are gone.
+- [ ] Temporary source-policy scaffolding is gone.
+- [x] Targeted latest-upstream sync is reproducible and idempotent.
+- [ ] Feature matrix and architecture/fork documents match implementation.
+- [ ] Original copyright/authorship/license notices are preserved.
+- [ ] Warmed performance, CPAN, packaging, warning-free build, Ubuntu, Windows,
+ and CI pass.
+- [ ] Post-merge warn-mode removal and POD capability review are complete.
diff --git a/dev/design/regex_alternatives.md b/dev/design/regex_alternatives.md
deleted file mode 100644
index a8fc3c7957..0000000000
--- a/dev/design/regex_alternatives.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Open-source regex engines to look at
-
----
-
-### 1. **PCRE2 (C)**
-
-* **Closest match to Perl semantics.**
- Supports subroutine calls, recursion, verbs, backtracking control, `\K`, `\G`, named captures, etc.
-* **Mature and very fast.**
- Highly optimized, tested in production everywhere.
-* **Drawback:** written in C. You’d either need a JNI bridge (performance overhead + complexity) or port its VM design to Java/bytecode.
-* **Good fit if:** you want an authoritative reference for how the opcodes and backtracking model can look. Even just studying PCRE2’s IR and backtracking stack gives you a roadmap.
-
----
-
-### 2. **Oniguruma / Onigmo (C, Ruby’s regex)**
-
-* **Used by Ruby.**
- Similar richness: subroutines, conditionals, callouts, verb-like constructs.
-* **Unicode-aware.**
- Strong support for multibyte encodings.
-* **Same issue:** C codebase → would require port or JNI.
-* **Good fit if:** you want a more modular codebase than PCRE2 and are interested in multi-encoding support.
-
----
-
-### 3. **RE2 (C++, Google)**
-
-* **Non-backtracking engine.**
- Designed for safety and guaranteed linear time.
-* **Not a fit** if your goal is Perl fidelity, since RE2 *intentionally omits* Perl features like backreferences, recursion, or verbs.
-* **Good fit if:** you wanted a “safe mode” for sandboxing untrusted regex, but not for Perl emulation.
-
----
-
-### 4. **Joni (Java, JRuby project)**
-
-* **Oniguruma port to Java.**
- Used by JRuby to give Ruby-like regex semantics on the JVM.
-* **Already integrates with JVM.**
- No JNI bridge needed; you can inspect and possibly adapt its VM design.
-* **Not Perl-complete.** But it’s the closest JVM-side codebase with backtracking and features beyond `java.util.regex`.
-* **Good fit if:** you want something you can drop into PerlOnJava as a starting point and then extend toward Perl’s semantics.
-
----
-
-### 5. **PCRE-J (various Java ports)**
-
-* Some partial PCRE ports exist in Java, though many are outdated and incomplete.
-* **Good fit if:** you find a maintained one, but otherwise riskier than Joni.
-
----
-
-### Strategy for PerlOnJava
-
-* **Short term:** leverage Joni. It’s Java, battle-tested (JRuby depends on it), and already has a backtracking model + subroutines. You can embed it, then progressively extend its instruction set to cover Perl-only constructs (`(?{ })`, `(??{ })`, cut verbs).
-* **Mid term:** study PCRE2 as the “gold standard” and port the missing instructions (the C VM maps very directly to Java bytecode).
-* **Long term:** you’ll probably end up with your own forked/extended VM specialized for PerlOnJava, but you save months of design by standing on Joni/PCRE2 first.
-
-
diff --git a/dev/design/regex_engines.md b/dev/design/regex_engines.md
deleted file mode 100644
index 9fef1285a9..0000000000
--- a/dev/design/regex_engines.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Strategy for Supporting Multiple Regex Engines in PerlOnJava
-
-## Overview
-
-The goal of this document is to outline a strategy for integrating multiple regex engines into the PerlOnJava project. This will allow the system to support different regex engines such as Java's built-in regex, RE2J, and ICU4J, providing flexibility and performance benefits depending on the use case. This document will guide the implementation team through the necessary steps to achieve this integration.
-
-## Objectives
-
-1. **Abstract Regex Operations**: Define interfaces that abstract the operations common to all regex engines.
-2. **Implement Adapters**: Create adapter classes for each regex engine that implement these interfaces.
-3. **Modify Existing Classes**: Update existing classes, such as `RuntimeRegex`, to utilize these interfaces.
-4. **Ensure Compatibility**: Maintain compatibility with existing functionality and ensure that the new system is extensible for future regex engines.
-
-## Architecture
-
-### 1. Define Interfaces
-
-We will define two primary interfaces to abstract the regex operations:
-
-- **RegexPattern Interface**: Represents a compiled regex pattern.
- - `RegexMatcher matcher(String input)`: Returns a matcher for the given input string.
-
-- **RegexMatcher Interface**: Represents the operations that can be performed with a regex pattern.
- - `boolean matches()`: Checks if the entire input sequence matches the pattern.
- - `String replaceAll(String replacement)`: Replaces every subsequence of the input sequence that matches the pattern with the given replacement string.
-
-### 2. Implement Adapters
-
-For each regex engine, implement the `RegexPattern` and `RegexMatcher` interfaces.
-
-#### Java Regex Adapter
-
-- **JavaRegexPattern**: Implements `RegexPattern` using Java's `Pattern`.
-- **JavaRegexMatcher**: Implements `RegexMatcher` using Java's `Matcher`.
-
-#### RE2J Adapter
-
-- **Re2jRegexPattern**: Implements `RegexPattern` using RE2J's `Pattern`.
-- **Re2jRegexMatcher**: Implements `RegexMatcher` using RE2J's `Matcher`.
-
-#### ICU4J Adapter
-
-- **Icu4jRegexPattern**: Implements `RegexPattern` using ICU4J's regex capabilities.
-- **Icu4jRegexMatcher**: Implements `RegexMatcher` using ICU4J's regex capabilities.
-
-### 3. Modify Existing Classes
-
-Update the `RuntimeRegex` class to use the new interfaces. This involves:
-
-- Replacing direct usage of Java's `Pattern` and `Matcher` with `RegexPattern` and `RegexMatcher`.
-- Allowing the selection of the regex engine at runtime, possibly through configuration or a factory pattern.
-
-### 4. Factory Pattern for Engine Selection
-
-Implement a factory class to manage the creation of `RegexPattern` instances based on the desired regex engine. This factory will:
-
-- Read configuration settings to determine which regex engine to use.
-- Instantiate the appropriate `RegexPattern` adapter.
-
-### 5. Caching Strategy
-
-Maintain the existing caching strategy in `RuntimeRegex` to ensure compiled patterns are reused efficiently. Ensure that the cache can handle patterns from different engines without conflict.
-
-### 6. Testing and Validation
-
-- **Unit Tests**: Develop comprehensive unit tests for each adapter to ensure they conform to the expected behavior.
-- **Integration Tests**: Test the integration of multiple regex engines within the PerlOnJava environment.
-- **Performance Testing**: Evaluate the performance of each regex engine to guide users in selecting the appropriate engine for their needs.
-
-## Implementation Plan
-
-1. **Define Interfaces**: Create `RegexPattern` and `RegexMatcher` interfaces.
-2. **Develop Adapters**: Implement adapters for Java, RE2J, and ICU4J regex engines.
-3. **Update RuntimeRegex**: Modify `RuntimeRegex` to use the new interfaces and integrate the factory pattern.
-4. **Implement Factory**: Develop a factory class for regex engine selection.
-5. **Testing**: Conduct unit, integration, and performance testing.
-6. **Documentation**: Update documentation to reflect changes and provide guidance on using different regex engines.
-
-
diff --git a/dev/design/regex_jruby_joni.md b/dev/design/regex_jruby_joni.md
deleted file mode 100644
index 5a454292ba..0000000000
--- a/dev/design/regex_jruby_joni.md
+++ /dev/null
@@ -1,102 +0,0 @@
-
-## Migration Plan to Joni Regex
-
-> **Scope note (2026-08-09):** This is an older broad migration sketch. PR #895
-> implements a narrower backend-neutral matcher and Joni path for declarative
-> recursive subpatterns. Executable `(?{ ... })`, callback conditions, and
-> `(??{ ... })` require in-match callouts that stock Joni 2.2.7 does not expose.
-> See [`executable-regex-callbacks.md`](executable-regex-callbacks.md) for the
-> current callback design, semantics, phases, and progress tracking.
-
-### 1. **Dependencies and Imports**
-- Add Joni dependency to the project
-- Replace `java.util.regex.Pattern` and `java.util.regex.Matcher` imports with Joni equivalents
-- Import Joni's `Regex`, `Matcher`, `Option`, and `Region` classes
-
-### 2. **Pattern Compilation Changes**
-- Replace `Pattern.compile()` with Joni's `new Regex()` constructor
-- Convert Java regex flags to Joni Options (e.g., `CASE_INSENSITIVE` → `Option.IGNORECASE`)
-- Update the pattern caching mechanism to store Joni Regex objects instead of Pattern objects
-
-### 3. **Matcher Creation and Usage**
-- Replace `pattern.matcher(input)` with Joni's matcher creation approach
-- Update matcher method calls:
- - `find()` → Joni's `search()` or `match()` methods
- - `group()` → Joni's region-based group extraction
- - `start()/end()` → Joni's region-based position methods
- - `groupCount()` → Joni's capture group handling
-
-### 4. **Region Handling**
-- Implement Joni's Region object for managing match positions
-- Adjust the position tracking logic for `\G` assertions and global matches
-- Update the `matcher.region()` calls to use Joni's search region specification
-
-### 5. **Capture Group Management**
-- Modify capture group extraction to use Joni's byte-based approach
-- Handle the conversion between byte positions and character positions for Unicode strings
-- Update the globalMatcher storage to accommodate Joni's matcher structure
-
-### 6. **Flag Conversion**
-- Create a mapping between Perl/Java regex flags and Joni Options
-- Update `RegexFlags.toPatternFlags()` to return Joni Option values
-- Handle any Joni-specific options that might benefit Perl compatibility
-
-### 7. **String Encoding Considerations**
-- Ensure proper encoding handling (UTF-8/UTF-16) when converting strings to byte arrays for Joni
-- Update position calculations to account for multi-byte characters
-- Adjust substring operations for capture groups and match positions
-
-### 8. **Error Handling**
-- Update exception handling for Joni-specific exceptions
-- Adapt error messages for regex compilation failures
-
-### 9. **Performance Optimizations**
-- Consider reusing byte array conversions where possible
-- Optimize the caching mechanism for Joni Regex objects
-- Profile and adjust buffer sizes for Joni operations
-
-### 10. **Testing and Compatibility**
-- Ensure all existing regex features work with Joni
-- Test Unicode handling improvements
-- Verify performance characteristics
-- Check for any regex syntax differences between Java and Joni that might affect existing patterns
-
-
-## Using JRuby Strings with Joni
-
-### 1. **Native Integration**
-- Joni was specifically designed for JRuby, so JRuby strings (RubyString) have built-in support for Joni operations
-- Direct byte array access without conversion overhead
-- Encoding information is already embedded in RubyString
-
-### 2. **Simplified Position Handling**
-- JRuby strings maintain both byte positions and character positions
-- Automatic handling of multi-byte character boundaries
-- Built-in methods for converting between byte and character indices
-
-### 3. **Encoding Management**
-- RubyString carries encoding information with the string data
-- Automatic encoding negotiation for regex operations
-- Better support for Perl's encoding semantics
-
-### 4. **Performance Improvements**
-- Eliminate repeated String ↔ byte[] conversions
-- Reuse existing byte arrays from RubyString
-- Reduce memory allocations during regex operations
-
-### 5. **Better Perl Compatibility**
-- JRuby strings support mutable operations similar to Perl
-- Copy-on-write semantics that match Perl's behavior
-- Support for binary strings and mixed encodings
-
-## Additional Considerations
-
-### Migration Impact
-- Would need to update RuntimeScalar to use RubyString internally
-- String operations throughout PerlOnJava would benefit from JRuby's string handling
-- Could leverage JRuby's existing regex variable implementations ($1, $2, $&, etc.)
-
-### API Compatibility
-- JRuby provides string methods that closely match Perl's string operations
-- Could potentially reuse JRuby's regex match data structures
-- Simplified implementation of Perl's pos() functionality
diff --git a/dev/design/regex_parser_integration.md b/dev/design/regex_parser_integration.md
deleted file mode 100644
index 97d81ef2f5..0000000000
--- a/dev/design/regex_parser_integration.md
+++ /dev/null
@@ -1,303 +0,0 @@
-# Regex Parser Integration Strategy for PerlOnJava
-
-## Overview
-
-This document outlines a strategy for integrating an existing, mature regex parser into PerlOnJava to replace the current string-manipulation-based regex preprocessing approach with proper semantic parsing and validation.
-
-## Current State Analysis
-
-### Problems with Current Approach
-
-1. **"Cheating" with hardcoded string matching**: Current validation uses `s.equals()` and `s.contains()` for pattern detection
-2. **Fragile preprocessing**: String manipulation approach is error-prone and hard to maintain
-3. **Limited semantic understanding**: No proper AST representation of regex structure
-4. **Incomplete validation**: Edge cases in backreferences, character classes, and complex constructs
-5. **Architectural inconsistency**: Mix of pre-validation and post-processing creates conflicts
-
-### Current Architecture
-
-```
-Perl Regex String → RegexPreprocessor → Java-compatible String → Java Pattern
- ↓
- String manipulation + hardcoded validation
-```
-
-## Proposed Integration Strategy
-
-### Phase 1: ANTLR-based PCRE Parser Integration
-
-#### Recommended Parser: bkiers/pcre-parser
-
-- **Repository**: https://github.com/bkiers/pcre-parser
-- **Technology**: ANTLR 4 grammar for PCRE (Perl Compatible Regular Expressions)
-- **License**: MIT (compatible with PerlOnJava)
-- **Maturity**: Active project with comprehensive PCRE support
-
-#### Integration Architecture
-
-```
-Perl Regex String → PCRE Parser → AST → Semantic Validator → AST Transformer → Java Pattern
- ↓ ↓ ↓ ↓
- ANTLR 4 Tree Proper validation Java-compatible AST
-```
-
-### Phase 2: Implementation Plan
-
-#### Step 1: Dependency Integration
-
-Add ANTLR PCRE parser dependency to `build.gradle`:
-
-```gradle
-dependencies {
- implementation 'nl.bigo:pcre-parser:1.0.0' // Version TBD
- implementation 'org.antlr:antlr4-runtime:4.9.3'
-}
-```
-
-#### Step 2: Core Integration Classes
-
-1. **EnhancedRegexPreprocessor**: Main entry point
-2. **RegexASTValidator**: Semantic validation using AST
-3. **RegexASTTransformer**: Transform Perl AST to Java-compatible form
-4. **RegexErrorReporter**: Convert ANTLR errors to PerlCompilerException
-
-#### Step 3: Fallback Strategy
-
-Maintain current preprocessing as fallback for:
-- Parser failures
-- Unsupported constructs
-- Performance-critical paths (if needed)
-
-### Phase 3: Detailed Implementation
-
-#### Core Classes Structure
-
-```java
-public class EnhancedRegexPreprocessor {
- public static String preProcessRegex(String regex, RegexFlags flags) {
- try {
- // Parse regex into AST
- ParseTree ast = parseRegex(regex);
-
- // Semantic validation
- validateRegexAST(ast, flags);
-
- // Transform for Java compatibility
- return transformAST(ast, flags);
-
- } catch (RegexParseException e) {
- // Fallback to current approach
- return RegexPreprocessor.preProcessRegex(regex, flags);
- }
- }
-
- private static ParseTree parseRegex(String regex) throws RegexParseException {
- PCRELexer lexer = new PCRELexer(CharStreams.fromString(regex));
- PCREParser parser = new PCREParser(new CommonTokenStream(lexer));
-
- // Configure error handling
- parser.removeErrorListeners();
- parser.addErrorListener(new RegexErrorListener());
-
- return parser.pcre();
- }
-}
-```
-
-#### Semantic Validation
-
-```java
-public class RegexASTValidator extends PCREBaseVisitor {
- private int captureGroupCount = 0;
- private Set namedGroups = new HashSet<>();
- private RegexFlags flags;
-
- public void validate(ParseTree tree, RegexFlags flags) {
- this.flags = flags;
- visit(tree);
- }
-
- @Override
- public Void visitBackreference(BackreferenceContext ctx) {
- int groupNum = Integer.parseInt(ctx.NUMBER().getText());
- if (groupNum > captureGroupCount) {
- throw new PerlCompilerException("Reference to nonexistent group");
- }
- return super.visitBackreference(ctx);
- }
-
- @Override
- public Void visitNamedBackreference(NamedBackreferenceContext ctx) {
- String groupName = ctx.NAME().getText();
- if (!namedGroups.contains(groupName)) {
- throw new PerlCompilerException("Reference to nonexistent named group");
- }
- return super.visitNamedBackreference(ctx);
- }
-}
-```
-
-#### AST Transformation
-
-```java
-public class RegexASTTransformer extends PCREBaseVisitor {
- private StringBuilder result = new StringBuilder();
- private RegexFlags flags;
-
- public String transform(ParseTree tree, RegexFlags flags) {
- this.flags = flags;
- visit(tree);
- return result.toString();
- }
-
- @Override
- public String visitCharacterClass(CharacterClassContext ctx) {
- // Transform Perl character class to Java equivalent
- // Handle POSIX classes, Unicode properties, etc.
- return super.visitCharacterClass(ctx);
- }
-
- @Override
- public String visitQuantifier(QuantifierContext ctx) {
- // Handle possessive quantifiers, etc.
- return super.visitQuantifier(ctx);
- }
-}
-```
-
-## Benefits of Integration
-
-### 1. Architectural Improvements
-
-- **Clean separation**: Parsing, validation, and transformation as distinct phases
-- **Semantic understanding**: True AST-based processing instead of string manipulation
-- **Extensibility**: Easy to add new Perl regex features
-- **Maintainability**: Grammar-based approach is self-documenting
-
-### 2. Validation Improvements
-
-- **Proper backreference validation**: Check against actual capture group structure
-- **Character class validation**: Parse and validate character classes semantically
-- **Quantifier validation**: Understand quantifier context and nesting
-- **Named group validation**: Track and validate named capture groups
-
-### 3. Error Reporting
-
-- **Precise error locations**: ANTLR provides exact position information
-- **Semantic error messages**: Context-aware error reporting
-- **Perl-compatible messages**: Transform ANTLR errors to Perl-style messages
-
-## Migration Strategy
-
-### Phase A: Parallel Implementation (Low Risk)
-
-1. Implement enhanced preprocessor alongside current one
-2. Add feature flag to enable/disable new parser
-3. Run both parsers in test suite, compare results
-4. Gradually enable for specific regex patterns
-
-### Phase B: Gradual Replacement (Medium Risk)
-
-1. Replace preprocessing for simple patterns first
-2. Use fallback for complex patterns initially
-3. Expand coverage as confidence grows
-4. Monitor performance and correctness
-
-### Phase C: Full Migration (High Confidence)
-
-1. Make enhanced preprocessor the default
-2. Keep current preprocessor as emergency fallback
-3. Remove fallback after extensive testing
-4. Clean up deprecated code
-
-## Performance Considerations
-
-### Potential Concerns
-
-1. **Parsing overhead**: ANTLR parsing may be slower than string manipulation
-2. **Memory usage**: AST creation requires additional memory
-3. **Startup time**: ANTLR parser initialization cost
-
-### Mitigation Strategies
-
-1. **Caching**: Cache parsed ASTs for frequently used patterns
-2. **Lazy initialization**: Initialize parser only when needed
-3. **Pattern analysis**: Use fast path for simple patterns
-4. **Benchmarking**: Measure and optimize critical paths
-
-## Testing Strategy
-
-### Unit Tests
-
-1. **Parser integration tests**: Verify ANTLR parser works correctly
-2. **Validation tests**: Test semantic validation logic
-3. **Transformation tests**: Verify AST transformation correctness
-4. **Error handling tests**: Test error reporting and fallback
-
-### Integration Tests
-
-1. **Regex test suite**: Run existing `t/re/regexp.t` with new parser
-2. **Performance tests**: Compare performance with current approach
-3. **Compatibility tests**: Ensure Perl compatibility maintained
-
-### Regression Tests
-
-1. **Fallback tests**: Verify fallback mechanism works
-2. **Edge case tests**: Test complex regex patterns
-3. **Error message tests**: Verify error message compatibility
-
-## Implementation Timeline
-
-### Week 1-2: Research and Setup
-- Evaluate ANTLR PCRE parser options
-- Set up build integration
-- Create basic integration framework
-
-### Week 3-4: Core Implementation
-- Implement EnhancedRegexPreprocessor
-- Basic AST validation and transformation
-- Fallback mechanism
-
-### Week 5-6: Testing and Refinement
-- Comprehensive test suite
-- Performance optimization
-- Error handling improvements
-
-### Week 7-8: Integration and Deployment
-- Gradual rollout with feature flags
-- Monitor test results and performance
-- Documentation and cleanup
-
-## Alternative Approaches Considered
-
-### 1. Custom Regex Parser
-
-**Pros**: Full control, optimized for PerlOnJava needs
-**Cons**: Significant development effort, maintenance burden
-
-### 2. Perl Regex Engine Integration (JNI)
-
-**Pros**: Perfect Perl compatibility
-**Cons**: JNI complexity, deployment challenges, performance overhead
-
-### 3. Regex Engine Libraries (RE2, etc.)
-
-**Pros**: High performance, proven reliability
-**Cons**: Limited Perl compatibility, different semantics
-
-## Conclusion
-
-Integrating an ANTLR-based PCRE parser represents the best balance of:
-- **Architectural cleanliness**: Proper separation of concerns
-- **Implementation effort**: Leverage existing, mature parser
-- **Perl compatibility**: PCRE provides good Perl regex coverage
-- **Maintainability**: Grammar-based approach is sustainable
-
-This approach eliminates the "cheating" problem by providing true semantic understanding of regex patterns while maintaining compatibility and performance.
-
-## References
-
-- [bkiers/pcre-parser](https://github.com/bkiers/pcre-parser) - ANTLR 4 PCRE Parser
-- [ANTLR 4 Documentation](https://github.com/antlr/antlr4/blob/master/doc/index.md)
-- [PCRE Specification](https://www.pcre.org/current/doc/html/pcre2syntax.html)
-- [Perl Regular Expressions](https://perldoc.perl.org/perlre)
diff --git a/dev/design/regex_preprocessing_fixes.md b/dev/design/regex_preprocessing_fixes.md
deleted file mode 100644
index a93c7f0a7b..0000000000
--- a/dev/design/regex_preprocessing_fixes.md
+++ /dev/null
@@ -1,282 +0,0 @@
-# Regex Preprocessing Fixes
-
-## Overview
-
-This document tracks regex preprocessing issues discovered while running `re/pat.t`, `re/pat_advanced.t`, and related tests with `JPERL_UNIMPLEMENTED=warn`.
-
-## Completed Fixes
-
-### 1. Invalid quantifier brace handling consuming regex metacharacters
-
-**Root cause:** `handleQuantifier()` in `RegexPreprocessor.java` used `s.indexOf('}', start)` to find the closing brace of a potential quantifier, but this search crossed character class boundaries and regex groups. For example, in `{ (?> [^{}]+ | (??{...}) )* }`, the `{` at the start was treated as a potential quantifier, and `indexOf('}')` found the `}` inside the character class `[^{}]`, consuming everything in between (including `(?>` and `[^{`) as literal text.
-
-**Fix:** When `handleQuantifier` determines that braces don't form a valid quantifier (content contains non-numeric characters), it now only escapes the opening `{` as `\{` and returns immediately, letting the main regex loop process subsequent characters normally. Previously it consumed and escaped the entire `{...}` range.
-
-**Files changed:** `RegexPreprocessor.java` — `handleQuantifier()` method
-
-### 2. `\x{...}` hex escape with non-hex characters
-
-**Root cause:** The hex escape handler used `Integer.parseInt(hexStr, 16)` which throws `NumberFormatException` for strings containing non-hex characters (e.g., `\x{9bq}`). Inside character classes, this was caught and re-thrown as a fatal `PerlCompilerException`, killing the test run. Outside character classes, the escape was passed through to Java's regex engine which also rejected it.
-
-**Perl behavior:** `\x{9bq}` extracts the valid hex prefix `9b` (value 0x9B) and ignores the remaining characters. `\x{x9b}` has no valid prefix, so the value is 0. Underscores are allowed (removed by preprocessing) but other non-hex chars terminate the hex number.
-
-**Fix:** All three `\x{...}` handlers now extract the valid hex prefix instead of requiring the entire content to be valid hex:
-- `handleRegexCharacterClassEscape()` — inside `[...]` (was the fatal crash)
-- `handleEscapeSequences()` — outside `[...]`
-- Range endpoint parser — for character class ranges
-
-**Files changed:** `RegexPreprocessorHelper.java`
-
-### 3. Bare `\xNN` with non-hex characters
-
-**Root cause:** Bare `\x` (without braces) was passed through to Java's regex engine, which expects exactly 2 hex digits after `\x`. Patterns like `\xk` or `\x4j` caused `PatternSyntaxException`.
-
-**Perl behavior:** `\x` takes up to 2 hex digits. `\xk` = `\x00` followed by literal `k`. `\x4j` = `\x04` followed by literal `j`.
-
-**Fix:** Added explicit bare `\x` handling that parses up to 2 hex digits and emits `\x{HH}` format when fewer than 2 valid hex digits are found.
-
-**Files changed:** `RegexPreprocessorHelper.java` — `handleEscapeSequences()` method
-
-### 4. NullPointerException when regex fails with JPERL_UNIMPLEMENTED=warn
-
-**Root cause:** When regex compilation fails and gets downgraded to a warning, the catch block in `RuntimeRegex.compile()` set the error pattern but didn't set `regex.patternString`. Downstream code (e.g., `replaceRegex()`) checked `regex.patternString == null` and triggered recompilation with a null pattern, causing NPE in `convertPythonStyleGroups(null).replaceAll(...)`.
-
-**Fix:**
-1. Set `regex.patternString` in the catch block when downgrading to warning
-2. Added null guard in `preProcessRegex()` to treat null input as empty string
-
-**Files changed:** `RuntimeRegex.java`, `RegexPreprocessor.java`
-
-## Known Remaining Issues
-
-### Test Pass Rates (after all fixes)
-
-| Test | Before fixes | After fixes | Remaining failures |
-|------|-------------|-------------|-------------------|
-| `re/pat.t` | 428/1298 | **1077**/1298 (all run) | 221 fail |
-| `re/pat_advanced.t` | 63/1298 | **1308**/1625 | 317 fail + 53 not reached |
-| `re/pat_rt_report.t` | 2397/2515 | **2431**/2515 (ran 2508) | 77 fail + 7 not reached |
-| `re/regexp_unicode_prop.t` | — | **1017**/1096 | 79 fail + 14 not reached |
-| `re/reg_eval_scope.t` | 6/49 | 7/49 | 42 fail |
-| `uni/variables.t` | 66880/66880 | 66880/66880 | 0 |
-
-### Early Termination (crashes blocking remaining tests)
-
-| Test | Crash point | Cause | Tests blocked |
-|------|------------|-------|---------------|
-| pat.t | **No crash** — all 1298 tests now run | N/A | 0 |
-| pat_advanced.t | Line 2308 (test 1625) | `\p{Is_q}` — package-scoped user property (`Some::Is_q`) | 53 tests |
-| pat_rt_report.t | Line 1158 (test 2508) | `(?1)` — numbered group recursion not supported | 7 tests |
-| regexp_unicode_prop.t | Line 543 (test 1096) | `\pf`/`\Pf` invalid property generates warnings instead of errors | 14 tests |
-
-### Failure Categories
-
-#### A. `\G` anchor (26 failures in pat.t)
-
-The `\G` assertion (match at pos()) has significant issues:
-- **Floating `\G`** patterns like `/a+\G/` fail — Java doesn't support `\G` except at pattern start
-- **`\G` in loops** (`/\G.../gc` iteration) doesn't maintain position correctly
-- Tests: pat.t 455-518
-
-**Difficulty: Medium-High.** Requires custom `\G` tracking in the match engine; Java's `\G` only works at the start of a match attempt.
-
-#### B. `(?{...})` code blocks (36 failures in pat.t, 5 in pat_advanced.t, 5 in pat_rt_report.t)
-
-Regex embedded code blocks are replaced with no-op groups. This breaks:
-- **`$^R`** — result of last `(?{...})` (tests 308-310)
-- **`pos()` inside `(?{...})`** (tests 470-494)
-- **Package/lexical variable access** inside `(?{...})` (tests 522-525)
-- **Eval-group runtime checks** — "Eval-group not allowed at runtime" (tests 300-304)
-
-**Difficulty: Very High.** Would require integrating the Perl compiler into the regex engine to execute code at match time.
-
-#### C. `$^N` — last successful capture (20 failures in pat_advanced.t)
-
-`$^N` is not updated after successful group captures. Tests 69-88 all fail.
-- Both outside regex and inside `(?{...})` usage fails
-- `$^N` is automatically localized — not implemented
-
-**Difficulty: Medium.** Requires tracking the last successfully matched group in the match result.
-
-#### D. `(??{...})` recursive patterns (5 failures in pat.t)
-
-Non-constant recursive patterns are replaced with empty groups. Tests 293-297 (complicated backtracking, recursion with `(??{})`) all fail.
-
-**Difficulty: Very High.** Same as `(?{...})` — requires runtime code execution.
-
-#### E. `(*ACCEPT)`, `(*FAIL)` control verbs (5 failures in pat.t)
-
-Regex control verbs are not supported by Java's regex engine. Tests 357-373 (ACCEPT and CLOSE buffer tests).
-
-**Difficulty: High.** Would require a custom regex engine or post-processing layer.
-
-#### F. `@-` / `@+` / `@{^CAPTURE}` arrays (12 failures in pat.t, 5 in pat_rt_report.t)
-
-The match position arrays have bugs:
-- **Wrong values** for capture group positions (tests 381-438)
-- **Stale values** not cleared after new match (tests 439-441)
-- **Read-only protection** throws wrong exception type: `UnsupportedOperationException` instead of `Modification of a read-only value attempted` (test 449)
-- **Interpolation in patterns** — `@-` and `@+` should not be interpolated (pat_rt_report.t 151-154)
-- **Undefined values** in `@-`/`@+` after match (pat_rt_report.t 213)
-
-**Difficulty: Medium.** The data is available from Java's `Matcher`; needs more careful mapping to Perl semantics.
-
-#### G. `qr//` stringification and modifiers (4 failures in pat.t)
-
-- `qr/\b\v$/xism` stringifies as `(?^imsx:\b\v$)` but should be `(?^msix:\\b\\v$)` — backslashes not escaped in stringification (test 315)
-- **`/u` modifier** not tracked: `use feature 'unicode_strings'` should add `/u` flag (tests 323-327)
-
-**Difficulty: Low-Medium.** Stringification fix is straightforward; `/u` modifier tracking needs scope awareness.
-
-#### H. `\N{name}` charnames (25 failures in pat_advanced.t)
-
-Named character escapes have extensive issues:
-- **Empty `\N{}`** not handled correctly (tests 794-809)
-- **`\N{PLUS SIGN}`** — named characters not expanded in regex (tests 831-833)
-- **`\N{U+0041}`** in character class — `[\N{SPACE}\N{U+0041}]` fails (test 836)
-- **Charname validation** — leading digit, comma, latin1 symbol errors not produced (tests 821-828)
-- **Charname caching** with `$1` — not implemented (tests 798-801)
-- **Cedilla/NO-BREAK SPACE** in names — error handling missing (tests 816-819)
-
-**Difficulty: Medium-High.** `\N{U+XXXX}` is partially implemented; full charnames support needs the `charnames` module.
-
-#### I. Useless `(?c)` / `(?g)` / `(?o)` warnings (13 failures in pat_advanced.t)
-
-Perl warns about useless regex modifiers (`/c`, `/g`, `/o` are match-operator flags, not regex flags). PerlOnJava silently ignores them without producing warnings.
-
-**Difficulty: Low.** Add warning emission in the regex flag parser.
-
-#### J. Bare `\x` hex escape edge cases (5 failures in pat_advanced.t)
-
-Our fix handles the crash but the test strings don't match correctly:
-- `\x4j` produces `\004j` but regex `[\x4j]{2}` doesn't match it (test 101)
-- `\xk` produces `\000k` but regex `[\xk]{2}` doesn't match it (test 102)
-- `\xx`, `\xxa`, `\x9_b` — regex character class expansion doesn't match the test string (tests 103-105)
-
-The issue is that the test string and the regex pattern both use `\x` escapes, but the regex preprocessor and the string processor handle them differently. The test expects both to produce the same character.
-
-**Difficulty: Low-Medium.** The regex-side `\x` handling needs to produce character classes that match what the string-side produces.
-
-#### K. Conditional `(?(1)...)` with `$` anchor — Bug 41010 (48 failures in pat_rt_report.t)
-
-The largest single failure category. Patterns like `/([ ]*$)(?(1))/` don't match correctly. This is a systematic issue with conditionals referencing a group that ends with `$` anchor.
-
-**Difficulty: Medium.** Likely a subtle difference in how Java handles the interaction between `$` anchor in a group and conditional backreference.
-
-#### L. `$REGMARK` / `${^PREMATCH}` etc. (6 failures in pat_rt_report.t)
-
-`$REGMARK` (set by `(*MARK:name)`) is not implemented. Tests 2458-2463.
-
-**Difficulty: High.** Requires `(*MARK)` verb support.
-
-#### M. `(?1)` numbered group recursion / `(?&name)` named recursion (pat_advanced.t, pat_rt_report.t)
-
-`(?1)` and `(?&name)` syntax for recursing into capture groups is not recognized. Now downgradable with `JPERL_UNIMPLEMENTED=warn` (no longer crashes tests), but the patterns silently fail to match.
-
-**Difficulty: Very High.** Java's regex engine has no recursion support. Would need a custom engine or PCRE/JNI bridge.
-
-#### N. `\p{isAlpha}` POSIX-style Unicode property (crash in pat.t)
-
-The POSIX-style Unicode property syntax `\p{isAlpha}`, `\p{isSpace}` is not recognized. This causes the fatal error that stops pat.t at line 1247, blocking 666 remaining tests.
-
-**Difficulty: Low-Medium.** Map POSIX-style aliases (`isAlpha` → `Alpha`, `isSpace` → `Space`, etc.) in the Unicode property handler.
-
-#### O. Empty clause in alternation (3 failures in pat.t)
-
-Empty alternatives in patterns like `/(|a)/` or the "0 match in alternation" test don't work correctly.
-
-**Difficulty: Low-Medium.** Likely a regex preprocessing issue.
-
-#### P. Miscellaneous (small counts)
-
-| Issue | Tests | Difficulty |
-|-------|-------|-----------|
-| Look around edge cases | pat.t 332-333 | Medium |
-| REG_INFTY (quantifier limit) | pat.t 250 | Low |
-| POSIX class error message format | pat.t 348 | Low |
-| Lookbehind limit (Java) | pat.t 252 | Hard (engine limit) |
-| Empty pattern pmop flags | pat_rt_report.t 44 | Medium |
-| Nested split | pat_rt_report.t 85 | Medium |
-| Ill-formed UTF-8 in class | pat_rt_report.t 140 | Medium |
-| Pattern in loop (prev success) | pat_rt_report.t 2469-2470 | Medium |
-| Long string patterns | pat_advanced.t 805-813 | Medium |
-| `/d` to `/u` modifier change | pat_advanced.t 807-808 | Low-Medium |
-
-#### Q. Package-scoped user-defined Unicode properties (crash in pat_advanced.t)
-
-`\p{Is_q}` defined in package `Some` as `Some::Is_q` is not found because user-defined property lookup only checks `main::` package. Perl uses the current package when resolving `\p{...}` names. This crashes pat_advanced.t at line 2308 (test 1625), blocking 53 tests.
-
-**Difficulty: Medium.** Need to pass the current package context to the regex preprocessor and try the current package before falling back to `main::`.
-
-#### R. Invalid single-char `\pX`/`\PX` properties (crash in regexp_unicode_prop.t)
-
-Invalid single-character properties like `\pf`, `\Pq` are passed through to Java's regex engine which throws `PatternSyntaxException`. This is caught and wrapped as `PerlJavaUnimplementedException`, which under `JPERL_UNIMPLEMENTED=warn` generates warnings instead of proper errors. Test 1096 in regexp_unicode_prop.t expects 0 warnings but gets 8 (from `\pf`, `\Pf`, `\pq`, `\Pq`), then crashes.
-
-**Fix approach:** Validate single-char properties in the preprocessor (only `\pL`, `\pM`, `\pN`, etc. are valid — single Unicode general category letters). Invalid ones should throw `PerlCompilerException` (not `PerlJavaUnimplementedException`).
-
-**Difficulty: Low.** Add validation for single-char `\p`/`\P` properties in `RegexPreprocessorHelper`.
-
-#### S. `/i` flag not passed to user-defined property subs (regexp_unicode_prop.t)
-
-Perl calls user-defined property subs with `$caseless=1` when the `/i` flag is active, allowing subs to return a wider character set for case-insensitive matching. PerlOnJava always calls the sub with an empty argument list. This causes 2 test failures in regexp_unicode_prop.t (tests 1061, 1077) and several in pat_advanced.t.
-
-**Fix approach:** Pass the `/i` flag through the regex preprocessor to `tryUserDefinedProperty`, which then passes `1` as the first argument to the property sub.
-
-**Difficulty: Medium.** Requires threading the case-insensitive flag through several method calls in the regex preprocessing pipeline.
-
-### Priority Recommendations
-
-**Quick wins (Low difficulty, high impact):**
-1. ~~**`\p{isAlpha}` aliases** — unblocks 666 pat.t tests (category N)~~ **DONE** — pat.t now runs all 1298 tests
-2. **Invalid `\pX` single-char properties** — unblocks 14 regexp_unicode_prop.t tests (category R)
-3. **Useless `(?c)`/`(?g)`/`(?o)` warnings** — fixes 13 pat_advanced.t tests (category I)
-4. **POSIX class error message** — fix message format (category P)
-5. **REG_INFTY error** — add quantifier limit check (category P)
-
-**Medium effort, significant impact:**
-6. **Package-scoped user properties** — unblocks 53 pat_advanced.t tests (category Q)
-7. **`/i` caseless flag for user properties** — fixes ~4 tests (category S)
-8. **`(?(1)...)` with `$` anchor** — fixes 48 pat_rt_report.t tests (category K)
-9. **`@-`/`@+` position arrays** — fixes 17 tests across files (category F)
-10. **`$^N` last capture** — fixes 20 pat_advanced.t tests (category C)
-11. **Bare `\x` edge cases** — fixes 5 pat_advanced.t tests (category J)
-12. **`\N{name}` charnames** — fixes 25 pat_advanced.t tests (category H)
-
-**Hard / architectural (major work):**
-13. **`\G` anchor** — 26 pat.t tests (category A)
-14. **`(?{...})` code blocks** — 46 tests total (category B)
-15. **`(?1)` recursion / `(?&name)` / `(*ACCEPT)` / `(*MARK)`** — engine limitations (categories E, L, M)
-
-## Progress Tracking
-
-### Current Status: Major user-defined property and regex cache fixes (2026-04-10)
-
-### Completed
-- [x] Fix 1: handleQuantifier brace consumption (2026-04-10)
-- [x] Fix 2: \x{...} hex escape with non-hex chars (2026-04-10)
-- [x] Fix 3: Bare \xNN with non-hex chars (2026-04-10)
-- [x] Fix 4: NPE on failed regex with JPERL_UNIMPLEMENTED=warn (2026-04-10)
-- [x] Failure analysis and categorization (2026-04-10)
-- [x] Fix 5: \p{isAlpha} case-insensitive Is prefix, add Space/Alnum/Punct aliases (2026-04-10)
-- [x] Fix 6: \p{Property=Value} syntax (2026-04-10)
-- [x] Fix 7: Named capture groups with underscores — U95 encoding (2026-04-10)
-- [x] Fix 8: User-defined property resolution — refactor resolvePropertyReference to return UnicodeSet (2026-04-10)
- - Properties using +utf8:: references (e.g., +utf8::Uppercase, &utf8::ASCII) were failing because
- the old code returned Java regex patterns that ICU4J's UnicodeSet couldn't parse
- - Created resolvePropertyReferenceAsSet() and resolveStandardPropertyAsSet() methods
-- [x] Fix 9: Regex cache preventing deferred recompilation (2026-04-10)
- - ensureCompiledForRuntime() now evicts stale cache entries before recompiling
-- [x] Fix 10: Cache user-defined property sub results (2026-04-10)
- - Matches Perl behavior of calling each property sub only once
- - Fixes "Called twice" errors from subs with `state` variables
-- [x] Fix 11: Titlecase/TitlecaseLetter/Lt property aliases (2026-04-10)
-- [x] Fix 12: (?&name) named group recursion downgraded to regexUnimplemented (2026-04-10)
-- [x] Fix 13: (?digit) numbered recursion downgraded to regexUnimplemented (2026-04-10)
-
-### Files Modified
-- `src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java`
-- `src/main/java/org/perlonjava/runtime/regex/RegexPreprocessorHelper.java`
-- `src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java`
-- `src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java`
-- `src/main/java/org/perlonjava/runtime/regex/CaptureNameEncoder.java`
-- `src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java`
diff --git a/dev/design/test_pass_rate_improvement_plan.md b/dev/design/test_pass_rate_improvement_plan.md
index 8290a5f734..70d8d84510 100644
--- a/dev/design/test_pass_rate_improvement_plan.md
+++ b/dev/design/test_pass_rate_improvement_plan.md
@@ -2,7 +2,11 @@
> **Historical document (superseded).** Counts and priorities below are a
> point-in-time planning snapshot. Use the current test reports, roadmap, and
-> feature matrix when choosing new compatibility work.
+> feature matrix when choosing new compatibility work. This plan predates the
+> Phase 36 Joni cutover: references below to Java `Pattern`, Java-first matching,
+> or `RegexPreprocessor` describe the retired implementation. See
+> `dev/design/phase36-regex-parity.md` and `docs/reference/feature-matrix.md` for
+> the current architecture.
**Date:** 2025-03-31
**Branch:** fix/sregex-args-propagation
diff --git a/dev/implementation/regex.md b/dev/implementation/regex.md
index 9f0317c910..7ad23fffad 100644
--- a/dev/implementation/regex.md
+++ b/dev/implementation/regex.md
@@ -1,147 +1,232 @@
# Regex implementation
-## Scope and target
-
-PerlOnJava maintains a source fork of Joni as its Perl-compatible regex engine.
-The target architecture has one production matcher: Joni owns parsing,
-backtracking, captures, conditions, recursion, control verbs, Unicode matching,
-case folding, and matcher-visible callbacks. PerlOnJava owns Perl source policy,
-runtime integration, lexical warnings, diagnostics, and callback closures.
-
-Migration is not complete. Automatic routing uses Joni for ordinary patterns.
-Setting `JPERL_REGEX_BACKEND=java` or the `jperl.regex.backend=java` system
-property retains the legacy matcher solely as a differential baseline; explicit
-`joni` selects the production route. The Java matcher, selector, and Java-only
-rewrites are temporary migration scaffolding.
-
-## Compilation and routing
-
-`RuntimeRegex` compiles Perl regex values and keeps byte-string and Unicode
-variants where subject provenance can affect semantics. `RegexBackendPolicy`
-selects the temporary Java route or Joni. `JoniRegexPattern` converts only the
-remaining frontend-owned representations, builds the PerlOnJava Joni syntax,
-and compiles the vendored engine.
-
-Patterns requiring matcher semantics unavailable in Java select Joni. These
-include subpattern calls, recursion, Perl conditions, structured executable or
-dynamic callbacks, control verbs, ASCII-strict folding, and native named
-character sequences. Embedded closures select Joni even when their result is
-constant because execution and unwind remain observable at match time.
-
-The matcher exposes the same `RegexMatcher` interface to JVM-compiled and
-interpreter execution. Match, substitution, `/g`, `/c`, `pos`, captures, and
-match variables are coordinated by `RuntimeRegex`; backend-specific offsets are
-converted to Perl character positions at that boundary.
-
-## Vendored Joni fork
-
-Production Joni sources live in `third_party/joni` under their original
-`org.joni` namespace. The root build compiles the fork and its upstream tests as
-dedicated source sets. Packaging relocates Joni to
+## Runtime boundary
+
+PerlOnJava has one production regex engine: the maintained Joni fork in
+`third_party/joni`. `RuntimeRegex` is the Perl runtime boundary and
+`JoniRegexPattern` is the only adapter that constructs a matcher. The JVM and
+bytecode-interpreter execution backends call this same runtime code; neither has
+its own regex implementation.
+
+`JPERL_REGEX_BACKEND` and `jperl.regex.backend` do not select a production
+matcher. The production `RegexBackendPolicy` class has been removed; a
+test-scope-only model preserves the immutable migration assertions and cannot
+enter the standalone artifact. Likewise,
+`JoniRegexPattern.compatibilityPatternDescription` is a display-only
+normalization for legacy assertions and debug descriptions. The raw
+`sourcePattern` is what Joni compiles and what source diagnostics describe.
+
+This separation is the central maintenance rule:
+
+- PerlOnJava owns source provenance, lexical policy, runtime state, and Perl
+ callbacks.
+- Joni owns matcher-visible grammar and behavior.
+- Compatibility descriptions must never become matcher input.
+
+## Responsibility map
+
+| Responsibility | Production classes |
+| --- | --- |
+| Parse literal/interpolated regex source and capture executable closures | `StringSegmentParser`, `RegexLiteralAnalyzer` |
+| Preserve trusted callback provenance while assembling interpolated patterns | `RuntimeRegexTemplate`, `RuntimeRegexCallback` |
+| Compile runtime source admitted by `use re 'eval'` in its Perl lexical context | `RuntimeRegexSourceCompiler` |
+| Cache compiled variants and implement Perl-visible match/substitution state | `RuntimeRegex`, `RegexFlags` |
+| Adapt encoding, diagnostics, resolver hooks, callbacks, and Joni match results | `JoniRegexPattern` and its `JoniRegexMatcher` / `PerlCalloutHandler` nested classes |
+| Resolve Perl Unicode properties and names with pinned-data precedence | `UnicodeResolver`, `NamedCharacterExpansion`, `PerlUnicode*Data` |
+| Parse and execute matcher semantics without Perl runtime dependencies | `org.joni.Regex`, `Parser`, `Analyser`, `ArrayCompiler`, `ByteCodeMachine` |
+| Expose runtime-neutral host hooks | `CalloutHandler`, `MatchView`, `DynamicPatternResult`, `CharacterPropertyResolver`, `NamedCharacterResolver`, `PerlPropertyValueMatcher`, `WideScalarCodec` |
+
+## From Perl source to a compiled pattern
+
+`StringSegmentParser` distinguishes literal source, interpolation, and
+parser-created executable blocks. It emits `regexCallback` nodes inside a
+`regexTemplate`; JVM lowering and interpreter lowering preserve the same node
+contract.
+
+`RuntimeRegexTemplate` assembles the runtime value. Only parser-created callback
+wrappers allocate entries in the callback table. They are represented by
+private slot sentinels, not by matcher syntax; sentinel starts in interpolated
+text are doubled, so untrusted text cannot acquire callback provenance.
+Embedding a callback-bearing `qr//` validates and renumbers its slots.
+`maskCallouts()` hides trusted slots while runtime Perl source is parsed, and
+`materializeTrustedCallouts()` turns only validated, in-range slots into Joni
+tokens immediately before compilation. `trustedCalloutCount` supplies the
+second bounds check. A lone interpolated `qr//` retains its compiled identity,
+preserving its flags, callbacks, and overload behavior.
+
+`RuntimeRegexSourceCompiler` handles executable regex source admitted by
+`use re 'eval'`. It compiles with the construction site's package, lexical
+cells, hints, warning bits, byte provenance, and operation flags, using a fresh
+synthetic `(eval N)` filename at line 1. Dynamic source is therefore checked as
+Perl source before it becomes a Joni program; it is not evaluated by the regex
+engine.
+
+`RuntimeRegex.compileSynchronized()` performs the remaining Perl-side checks,
+constructs `RegexFlags`, and creates `JoniRegexPattern` variants. Its cache key
+includes source and modifiers, lexical debug and `re 'strict'` state, trusted
+callout count, effective byte provenance, and custom-`charnames` translator
+discriminator. The discriminator is the translator scalar's string
+representation; the translator and its `NamedCharacterCache` are separately
+retained for deferred recompilation. User-defined Unicode properties are
+preloaded outside the compiler lock when Perl code may run. Deferred
+source-compile placeholders retain their exact compilation context and are
+replaced before matching.
+
+`JoniRegexPattern` supplies Joni `Syntax`, options, resolver hooks, warning
+mapping, and trusted-token materialization. Production matcher input otherwise
+retains the admitted source spelling. Native Joni parses and executes groups,
+captures, calls and recursion, conditions, lookarounds, control verbs,
+quantifiers, ordinary and extended character classes, named characters,
+properties, case folding, callbacks, and dynamic subprograms.
+
+## Matching and Perl state
+
+A compiled `RuntimeRegex` can hold an ordinary Joni variant, a Unicode variant,
+and a byte-pattern variant for byte-backed `/d` case-fold behavior.
+`selectRecursivePattern()` chooses among them from the pattern and subject
+provenance. Every match operation creates a matcher and, when needed, a
+matcher-local callback handler; compiled patterns do not share provisional
+callback state.
+
+`RuntimeRegex` owns Perl-visible behavior around the matcher: scalar and list
+context, `pos`, `/g`, `/c`, `\G`, substitutions, match variables, named and
+numbered captures, preserve-match state, and last-successful-pattern reuse.
+Joni reports byte offsets. The adapter maps them to Perl character offsets
+before publishing `$&`, `$1`, `@-`, `@+`, and `pos`.
+
+Global matching implements Perl's empty-match rule explicitly: after returning
+one zero-width match at an offset, the next attempt first asks for a consuming
+match at that same offset with all further empty results suppressed. Only then
+may it advance by one Perl character. Substitution uses the same semantic rule.
+
+## Executable callbacks and dynamic patterns
+
+The engine-facing skeleton uses `(?{=CALL:})` and
+`(?{=DYNAMIC:})`. The fork parses these as dedicated nodes and opcodes; it
+does not parse or execute Perl source.
+
+`JoniRegexPattern.PerlCalloutHandler` publishes provisional capture state and
+runs the captured Perl closure in scalar context. Each callback token
+checkpoints Perl regex/capture state, the prior `$^R`, dynamic-local level,
+result metadata, and, for dynamic evaluation, the prior capture view. Callback
+position is published and restored around invocation rather than stored in the
+token. Joni's matcher stack calls `unwind` when backtracking abandons the token
+and `complete` when the selected path keeps it. The adapter finishes the
+top-level invocation; nested Joni continuations finish their own handlers. The
+bridge restores dynamic scope on both normal and exceptional paths.
+
+A dynamic `(??{...})` expression is evaluated only when its opcode is reached.
+The returned string or `qr//` becomes a nested Joni continuation. Its
+alternatives remain resumable by the outer matcher, while its capture numbering
+stays private. Control verbs operate at the current matcher-program boundary;
+they are not post-match rewrites.
+
+The runtime-neutral fork API and exact unwind contract are documented in
+[`joni-callout-fork.md`](../../docs/design/joni-callout-fork.md).
+
+## Encoding and Unicode ownership
+
+Unicode patterns and subjects use UTF-8 plus explicit character-to-byte and
+byte-to-character maps. Byte programs use ISO-8859-1 and identity maps.
+`PerlUtfString` together with Joni's `WideScalarCodec` provides a reversible
+internal representation for surrogate and above-Unicode Perl scalar values.
+
+Unicode behavior has three data owners:
+
+- Checked-in `PerlUnicode*Data` classes and
+ `third_party/joni/.../PerlUnicodeCaseFoldData` encode current-Perl property,
+ alias, name, and fold semantics that must not drift with libraries. Joni's
+ `WordBreakData`, `SentenceBreakData`, and `LineBreakData` separately encode
+ boundary data.
+- ICU supplies `UnicodeSet` operations and fallback Unicode APIs where Perl does
+ not require a pinned override.
+- JCodings supplies encodings and baseline code-point/fold primitives used by
+ Joni. `RuntimeRegex`, `RegexFlags`, and `UnicodeResolver` choose Perl-specific
+ `/d`, `/u`, `/a`, `/aa`, byte provenance, and property-fold policy; the
+ maintained fork enforces those choices and adds pinned multi-code-point fold
+ behavior.
+
+`UnicodeResolver` implements Perl alias grammar and precedence: cached
+user-defined properties are consulted before colliding built-ins, then pinned
+Perl aliases and generated tables, followed by explicitly accepted ICU
+property/value fallbacks. It also implements property-value wildcards,
+script-run policy, inclusive and wide ranges, and per-property case-fold
+eligibility through Joni's
+`CharacterPropertyResolver`. `NamedCharacterResolver` uses the compiled
+`NamedCharacterCache`; `PerlPropertyValueMatcher` evaluates wildcard value
+expressions without depending on PerlOnJava runtime classes.
+
+The generator registry is
+`dev/tools/perl_unicode_data_generators.json`. It records the consumed Perl
+checkout, Unicode version, input hashes, generated outputs, and output hashes.
+`dev/tools/generate_perl_unicode_data.pl --check` is the deterministic
+regeneration gate for the registered property and fold tables. Scalar-name and
+named-sequence tables currently have standalone generators,
+`generate_perl_unicode_scalar_name_data.pl` and
+`generate_perl_unicode_named_sequence_data.pl`; they are not registered in that
+manifest and therefore require explicit regeneration and byte comparison. The
+Joni word, sentence, and line boundary tables are produced by
+`generate_joni_word_break_data.pl`, `generate_joni_boundary_data.pl`, and
+`generate_joni_line_break_data.pl`. They are not entries in this manifest, so
+refreshing them requires an explicit regeneration and byte comparison. The
+policy is current-checkout provenance: a recorded commit and hashes identify one
+generation, while deliberate `make perl5-update` and `make perl5-sync`
+operations may advance the next one.
+
+## Fork and distribution
+
+The fork retains upstream `org.joni` packages in source. Gradle adds its
+production sources to `main`; imported fork tests use the separate `joniTest`
+source set. The standalone JAR relocates Joni to
`org.perlonjava.internal.joni` and JCodings to
-`org.perlonjava.internal.jcodings`, avoiding classpath collisions without
-rewriting the maintained source namespace.
-
-The fork includes native support required by PerlOnJava for:
-
-- absolute, relative, named, recursive, and whole-pattern subpattern calls;
-- numbered, named, assertion, recursion, and callback conditions;
-- `(*ACCEPT)`, `(*MARK:NAME)`, named `(*SKIP:NAME)`, `(*PRUNE)`, `(*COMMIT)`,
- and `(*THEN)` with matcher-owned unwind boundaries;
-- runtime-neutral callouts and dynamic nested matchers;
-- wide scalar ranges beyond Unicode and Perl property resolution;
-- Perl-specific parser, diagnostic, and case-fold hooks.
-
-Original Joni, JCodings, Unicode, and Perl copyright and authorship notices are
-retained. The packaged JAR includes license texts and a modification notice;
-the SBOM identifies the vendored components.
-
-## Executable and dynamic callbacks
-
-The Perl parser creates structured callback parts rather than embedding raw Perl
-source in a regex string. Runtime assembly assigns callback IDs and emits
-trusted engine tokens such as `(?{=CALL:})` and
-`(?{=DYNAMIC:})`. Interpolated runtime text cannot create trusted tokens.
-
-Joni invokes a matcher-local `CalloutHandler` with provisional captures and the
-current byte position. Callback tokens checkpoint regex state, `$^R`, and
-dynamic locals. Backtracking unwinds each token exactly once; successful
-completion closes active scopes while retaining the successful callback result.
-Dynamic `(??{ ... })` programs retain their alternatives as resumable nested
-matcher continuations rather than atomic post-processing.
-
-`$^N` follows capture-close order. `$+` remains the highest-numbered defined
-capture. Nested matches save and restore outer match state, and callback control
-flow cannot target loops or labels outside its pseudo block.
-
-## Unicode properties and named characters
-
-`UnicodeResolver` implements Perl property aliases and defaults from generated,
-pinned Unicode 17 data. Generators and checked-in sources live under `dev/tools`
-and `dev/unicode/17.0.0`; freshness tests require byte-for-byte regeneration.
-ICU4J supplies general Unicode operations, while generated Perl tables define
-the compatibility surface where Perl aliases, defaults, or versions differ.
-
-Property-value wildcard expressions are compiled and executed by
-`org.joni.PerlPropertyValueMatcher`; Java `Pattern` no longer evaluates them.
-The resolver currently materializes the selected ranges through the adapter
-because Joni's character-property lexer does not yet own nested regex syntax
-inside `\p{...}`. Removing that materialization is an active migration step.
-
-Generated named-sequence data resolves Perl's multi-code-point `\N{name}` values
-before ordinary scalar Unicode names. Custom `charnames` translators and source
-provenance remain frontend/runtime responsibilities. Temporary encoded sequence
-tokens and their diagnostic restoration are still being removed.
-
-## Case folding
-
-Generated Perl tables contain default full mappings, simple-fold classes,
-reverse multi-character sequences, and Turkic exclusions. The fork has a
-runtime-neutral fold adapter and suppresses exact-search optimisation where a
-full fold makes its literal bound unsafe.
-
-Property/class closure, explicit `/d`, `/u`, `/a`, and `/aa` context, reverse
-literal expansion, and backreference folding are not yet complete. Until those
-paths carry Perl provenance natively, the adapter retains the corresponding
-semantic protections.
-
-## Preprocessing boundary
-
-Preprocessing may preserve source policy or bridge a feature not yet represented
-by Joni, but it must not emulate backtracking semantics. Current temporary work
-includes Java syntax adaptation, range materialisation for property wildcards,
-named-sequence encoding, extended-character-class lowering, and the
-`(?(DEFINE)...)` rewrite. Each is deleted with the same change that proves its
-native replacement against Perl.
-
-The frontend continues to own trusted callback construction, `use re 'eval'`
-security, lexical warning policy, user-defined Unicode properties, original
-source locations, and Perl-facing diagnostics. Those are source/runtime policy,
-not matcher behavior.
-
-## Validation contract
-
-New semantic fixtures are validated with system Perl before either PerlOnJava
-backend. Focused changes run direct Joni tests plus forced-Joni JVM and
-interpreter fixtures. Combined batches run warning-free `make`, packaging, and
-generated-data freshness checks.
-
-The release gate compares `dev/tools/perl_test_runner.pl` output file-by-file
-with `../PerlOnJava/logs/test_20260815_080000_958.log`. Complete Unicode,
-`reg_mesg.t`, `pat.t`, `pat_advanced.t`, four 80-file forced-Joni legs,
-`pat_psycho*`, and `speed*` must finish without unexplained regressions,
-timeouts, incomplete TAP, or backend disagreement.
-
-## Remaining migration gates
-
-- finish native extended character classes and `(?(DEFINE)...)`;
-- complete property/class, literal, backreference, and provenance-aware folding;
-- move nested property-value regex parsing into Joni;
-- close runtime-source and exact diagnostic differences;
-- remove Java matching, backend selection, and matcher-semantic preprocessing;
-- remove obsolete imported-test patches and verify sync idempotence;
-- pass performance, packaging, Linux, Windows, and complete CI gates.
-
-The ordered execution plan and acceptance tracker are in
-`dev/design/phase36-regex-parity.md`. The fork API and ownership contract are in
-`docs/design/joni-callout-fork.md`.
+`org.perlonjava.internal.jcodings`, preventing conflicts with embedding
+applications.
+
+The JAR carries the upstream Joni license, the JCodings license, and the
+PerlOnJava fork notice under `META-INF/licenses`. Its SBOM records vendored Joni
+and the Joni-to-JCodings dependency. `verifyJoniPackaging` and
+`dev/tools/verify-joni-packaging.pl` enforce relocation, notice bytes, component
+metadata, and dependency edges.
+
+## Remaining source-policy boundaries
+
+The Java code in the regex package may use small Java regular expressions as
+text scanners; those utilities are not match engines for Perl patterns. The
+remaining production boundaries are explicit:
+
+- `RuntimeRegex` handles lexical/source policy such as `\Q` interpolation,
+ unescaped-left-brace warnings, literal diagnostic markers, and recognition of
+ runtime executable source before invoking `RuntimeRegexSourceCompiler`.
+- `JoniRegexPattern.analyzePerlSyntax()` records adapter policy needed before or
+ around compilation, including the Perl prohibition on `\K` in lookaround and
+ optimization/control-state flags. It does not rewrite matcher semantics.
+- `JoniRegexPattern.validateExtendedPropertyPolicy()` still scans extended
+ classes to reject Unicode string properties and unresolved user properties
+ whose composition cannot yet be represented through the runtime-neutral
+ property hook. Removing this bounded scan requires Joni to carry enough class
+ context for those two policies.
+- `RegexDiagnosticFormatter` and the Joni `WarnCallback` mapping retain Perl's
+ message text, source spelling, marker position, warning category, lexical
+ warning mask, and fatal-versus-warning decision. Unsupported syntax is fatal
+ unless the development-only `JPERL_UNIMPLEMENTED=warn` downgrade is selected.
+
+## Verification
+
+For a regex change, validate new or changed Perl fixtures with system Perl
+first. Run focused PerlOnJava tests under both execution backends with a timeout
+and captured output. Direct fork changes also require `make test-joni`.
+
+The repository gates are:
+
+- `make` for the maintained fork, packaging checks, and unit shards;
+- `make check-links` when documentation links change and `lychee` is installed;
+- `perl dev/tools/generate_perl_unicode_data.pl --check` for generated Unicode
+ inputs and outputs;
+- `perl dev/tools/perl_test_runner.pl perl5_t/t/re/` for the imported regex
+ corpus, compared file by file rather than by aggregate totals alone.
+
+`JPERL_UNIMPLEMENTED=warn` may deliberately turn unsupported compilation into a
+warning and a never-match pattern. It is a diagnostic aid, never evidence that
+supported syntax works. The active acceptance checklist remains
+[`phase36-regex-parity.md`](../design/phase36-regex-parity.md); this document
+describes architecture rather than project status.
diff --git a/dev/implementation/signal-handling.md b/dev/implementation/signal-handling.md
index 439fe76a4e..54e069433f 100644
--- a/dev/implementation/signal-handling.md
+++ b/dev/implementation/signal-handling.md
@@ -183,7 +183,7 @@ factorial(1000000); # Not interruptible
**Solution:** Don't check every subroutine call (too expensive)
**Impact:** Rare edge case - 99% of code has loops
-### 2. Regex Engine (Java Pattern/Matcher)
+### 2. Regex Engine (Joni)
**Problem:**
```perl
@@ -191,7 +191,8 @@ alarm(1);
"aaa...aaa" =~ /(a+)+b/; # Catastrophic backtracking - NOT interruptible
```
-**Why:** `matcher.find()` is Java library call, not interruptible
+**Why:** A native Joni search runs inside one JVM call and does not currently
+poll PerlOnJava's signal state while backtracking.
**Workaround:** Wrap regex in timeout thread when alarm active
**Status:** Not yet implemented (future enhancement)
@@ -394,4 +395,3 @@ sleep 10;
This implementation provides a **lightweight, efficient, and practical** solution for alarm()-based signal handling in PerlOnJava. While not 100% compatible with Perl (particularly regarding regex interruption), it covers 85-90% of real-world use cases with negligible performance overhead.
The key insight is to check signals at **strategic points** (loop entries, I/O) rather than everywhere, achieving both correctness and performance.
-
diff --git a/dev/import-perl5/README.md b/dev/import-perl5/README.md
index b9309fee68..b3c05f2e42 100644
--- a/dev/import-perl5/README.md
+++ b/dev/import-perl5/README.md
@@ -21,6 +21,34 @@ The import system helps maintain modules that are (nearly) identical to their pe
## Quick Start
+### Update the development Perl checkout
+
+```bash
+make perl5-update
+make perl5-sync
+make perl5-sync FILTER=Name.pl
+# If the platform Perl is older than current blead requires:
+make PERL=/path/to/system/perl perl5-sync FILTER=Name.pl
+```
+
+`perl5-update` clones the official Perl repository into the gitignored
+`perl5/` directory when absent. For an existing checkout (including a
+`perl5/` symlink to an adjacent checkout), it refuses tracked edits, detached
+HEAD, missing or mismatched upstream configuration, and non-fast-forward
+updates before changing files. The checked-out branch must be the branch the
+remote currently advertises as its default; the helper refuses any other
+branch instead of silently switching it. It fetches the configured upstream
+and advances the checked-out branch with `--ff-only`, then prints the exact
+consumed commit.
+Ignored and untracked generated files do not make the checkout falsely dirty.
+
+`perl5-sync` performs that safe update and then replays the complete import
+manifest. `FILTER` passes exactly one `--only` value. These explicit developer
+targets may access the network; ordinary `make`, tests, packaging, and CI do
+not depend on them. `PERL` may select a compatible system Perl when the
+platform default is too old for current blead's Unicode generators; never use
+`jperl` for the importer.
+
### Synchronize All Configured Imports
```bash
@@ -37,10 +65,10 @@ perl dev/import-perl5/sync.pl --only File-DosGlob
Substring match against each row’s `source:` and `target:` fields. Prefer this after adding a single module so unrelated trees under `src/main/perl/lib/` are not overwritten. Run `perl dev/import-perl5/sync.pl --help` for details.
-### Refresh the pinned Unicode generator snapshot
+### Refresh the current upstream Unicode generator snapshot
The six tracked payloads under `dev/unicode/17.0.0/` are ordinary manifest
-imports from the pinned `perl5/lib/unicore` checkout. Refresh only that complete
+imports from the current upstream `perl5/lib/unicore` checkout. Refresh only that complete
group with its unique target prefix:
```bash
@@ -50,7 +78,7 @@ perl dev/import-perl5/sync.pl --only dev/unicode/17.0.0
The command updates `version`, the three alias/block files, and the two
`extracted/` property files. Their repository-authored `README.md` is not an
import target and remains untouched. Review source/target hashes and regenerate
-the affected checked-in Java data after intentionally advancing the pinned Perl
+the affected checked-in Java data after intentionally advancing the upstream Perl
or Unicode baseline.
### Add a New Module
@@ -83,7 +111,8 @@ Main synchronization script that imports files from perl5/ based on config.yaml.
**Features:**
- Copies individual files or entire directories
- Refreshes the minimal tracked Unicode generator snapshot through explicit manifest rows
-- Generates pinned-source test artifacts such as `unicore/TestProp.pl`
+- Generates current-upstream test artifacts such as `unicore/TestProp.pl`
+- Generates the core `unicore/Name.pl` source artifact before copying it
- Applies patches automatically
- Creates necessary directories
- Validates sources exist
@@ -102,7 +131,7 @@ Protected targets (`protected: true` in YAML) are always excluded from bulk dire
The upstream `uniprops01.t` through `uniprops10.t` wrappers load
`perl5_t/lib/unicore/TestProp.pl`. Perl does not commit that file: its build
-generates it from `perl5/lib/unicore/mktables` and the pinned Unicode database.
+generates it from `perl5/lib/unicore/mktables` and the current upstream Unicode database.
PerlOnJava therefore generates a losslessly split fixture explicitly:
```bash
@@ -110,7 +139,7 @@ perl dev/import-perl5/sync.pl --only TestProp.pl
```
Generation runs in a temporary copy of `perl5/lib/unicore`, so it does not
-modify the pinned source tree. The importer retains every canonical assertion,
+modify the current upstream source tree. The importer retains every canonical assertion,
splits the ten existing `TESTCHUNK` sections into ignored `TestProp-01.pl`
through `TestProp-10.pl` files, and installs a small `TestProp.pl` dispatcher.
Each unchanged upstream wrapper consequently parses only its selected section
@@ -126,6 +155,23 @@ filtered syncs neither generate nor remove them. Missing Unicode inputs or an
incompatible host Perl make the sync fail with an actionable diagnostic rather
than silently leaving the ten wrappers unavailable.
+#### Generated Unicode name table
+
+`_charnames.pm` loads `unicore/Name.pl` as a normal bundled core resource.
+Upstream creates this file with `perl5/lib/unicore/mktables`, but does not keep
+it in its source checkout. The manifest therefore first generates
+`perl5/lib/unicore/Name.pl` from the current checkout, reports its SHA-256,
+and then copies it through the ordinary source/target import row:
+
+```bash
+perl dev/import-perl5/sync.pl --only Name.pl
+```
+
+The invoking host Perl must be 5.36 or newer, because the current `mktables`
+uses `builtin`. The generator writes only the current checkout's missing source
+artifact and publishes it atomically; the normal sync copy remains responsible
+for the bundled `src/main/perl/lib/unicore/Name.pl` resource.
+
### add_module.pl
Interactive tool to add new modules to the sync configuration.
diff --git a/dev/import-perl5/config.yaml b/dev/import-perl5/config.yaml
index f45961dd22..4880b9bda6 100644
--- a/dev/import-perl5/config.yaml
+++ b/dev/import-perl5/config.yaml
@@ -95,7 +95,7 @@ imports:
target: perl5_t/Porting
type: directory
- # Minimal pinned Unicode 17.0 generator inputs. Keep these as explicit file
+ # Minimal current-upstream Unicode 17.0 generator inputs. Keep these as explicit file
# imports so clean checkouts can reproduce the checked-in Java data without
# importing the complete perl5/lib/unicore tree.
- source: perl5/lib/unicore/version
@@ -127,12 +127,21 @@ imports:
type: file
# perl5_t/lib otherwise remains empty to avoid module loading conflicts.
- # TestProp.pl is generated from the pinned Unicode inputs because upstream
+ # TestProp.pl is generated from the current upstream Unicode inputs because upstream
# does not store this build artifact in the perl5 source tree.
- source: perl5/lib/unicore/mktables
target: perl5_t/lib/unicore/TestProp.pl
type: generated_unicode_testprop
+ # Name.pl is a normal core resource, generated by mktables but omitted from
+ # the current source checkout. Generate it there before the ordinary copy.
+ - source: perl5/lib/unicore/mktables
+ target: perl5/lib/unicore/Name.pl
+ type: generated_unicode_name_source
+
+ - source: perl5/lib/unicore/Name.pl
+ target: src/main/perl/lib/unicore/Name.pl
+
# Bulk import all core test files from perl5/t/ to perl5_t/t/
# This creates a complete Perl 5 test environment under perl5_t/
- source: perl5/t
@@ -288,7 +297,6 @@ imports:
# From core library
- source: perl5/lib/_charnames.pm
target: src/main/perl/lib/_charnames.pm
- patch: _charnames.pm.patch
# From core library
- source: perl5/lib/charnames.pm
diff --git a/dev/import-perl5/patches/App-Cpan.pm.patch b/dev/import-perl5/patches/App-Cpan.pm.patch
index cd789092d3..06e8291780 100644
--- a/dev/import-perl5/patches/App-Cpan.pm.patch
+++ b/dev/import-perl5/patches/App-Cpan.pm.patch
@@ -1,14 +1,20 @@
---- perl5/cpan/CPAN/lib/App/Cpan.pm 2026-04-10 11:04:21
-+++ src/main/perl/lib/App/Cpan.pm 2026-08-10 10:12:19
-@@ -652,6 +652,7 @@
+--- perl5/cpan/CPAN/lib/App/Cpan.pm
++++ src/main/perl/lib/App/Cpan.pm
+@@ -647,11 +647,13 @@
+ $logger->error( "Skipping $arg because I couldn't find a matching namespace." );
+ next;
+ };
++ my $requested_distribution_id = eval { $module->distribution->id };
+
+ _clear_cpanpm_output();
$action->( $arg );
my $error = _cpanpm_output_indicates_failure();
-+ $error ||= _cpanpm_status_indicates_failure();
++ $error ||= _cpanpm_status_indicates_failure($requested_distribution_id);
push @errors, $error if $error;
}
-@@ -752,7 +753,7 @@
+@@ -752,7 +754,7 @@
BEGIN {
my $epic_fail_words = join '|',
@@ -17,21 +23,29 @@
fail(?:ed)? Cannot\s+install );
sub _cpanpm_output_indicates_failure
-@@ -763,6 +764,17 @@
- return A_MODULE_FAILED_TO_INSTALL if $last_line =~ /\b(?:Cannot\s+install)\b/i;
+@@ -764,6 +766,25 @@
$result || ();
-+ }
+ }
+
+sub _cpanpm_status_indicates_failure
+ {
++ my $requested_distribution_id = shift;
++ $requested_distribution_id =~ s{^./../}{}
++ if defined $requested_distribution_id;
++
+ # CPAN already records structured phase status for every distribution in
+ # the current command, including recursively installed prerequisites.
+ # Prefer that state when App::Cpan's legacy last-output-line heuristic is
+ # fooled by trailing hints or report suggestions.
+ my @failed = CPAN::Shell->find_failed($CPAN::CurrentCommandId);
-+ return A_MODULE_FAILED_TO_INSTALL if grep { $_->[5] } @failed;
++ return A_MODULE_FAILED_TO_INSTALL if grep {
++ $_->[5]
++ || (defined $requested_distribution_id
++ && $_->[1] eq $requested_distribution_id)
++ } @failed;
+ return;
- }
++ }
}
+ sub _cpanpm_output_indicates_success
diff --git a/dev/import-perl5/patches/IPC-Cmd.pm.patch b/dev/import-perl5/patches/IPC-Cmd.pm.patch
index 9c8115568b..1ae5fbdd1a 100644
--- a/dev/import-perl5/patches/IPC-Cmd.pm.patch
+++ b/dev/import-perl5/patches/IPC-Cmd.pm.patch
@@ -1,8 +1,14 @@
--- perl5/cpan/IPC-Cmd/lib/IPC/Cmd.pm
+++ src/main/perl/lib/IPC/Cmd.pm
-@@ -8,5 +8,9 @@
+@@ -7,7 +7,14 @@
+ use constant IS_VMS => $^O eq 'VMS' ? 1 : 0;
+ use constant IS_WIN32 => $^O eq 'MSWin32' ? 1 : 0;
use constant IS_HPUX => $^O eq 'hpux' ? 1 : 0;
- use constant IS_WIN98 => (IS_WIN32 and !Win32::IsWinNT()) ? 1 : 0;
+- use constant IS_WIN98 => (IS_WIN32 and !Win32::IsWinNT()) ? 1 : 0;
++ use constant IS_WIN98 => (IS_WIN32 and do {
++ require Win32;
++ !Win32::IsWinNT();
++ }) ? 1 : 0;
+ use constant IS_PERLONJAVA => do {
+ require Config;
+ $Config::Config{perlonjava} ? 1 : 0;
@@ -10,7 +16,7 @@
use constant ALARM_CLASS => __PACKAGE__ . '::TimeOut';
use constant SPECIAL_CHARS => qw[< > | &];
use constant QUOTE => do { IS_WIN32 ? q["] : q['] };
-@@ -38,7 +42,7 @@
+@@ -38,7 +45,7 @@
require Time::HiRes; Time::HiRes->import();
require Win32 if IS_WIN32;
};
@@ -19,3 +25,73 @@
eval {
my $wait_start_time = Time::HiRes::clock_gettime(&Time::HiRes::CLOCK_MONOTONIC);
+@@ -190,6 +197,7 @@
+ sub can_capture_buffer {
+ my $self = shift;
+
++ return 1 if IS_PERLONJAVA;
+ return 1 if $USE_IPC_RUN && $self->can_use_ipc_run;
+ return 1 if $USE_IPC_OPEN3 && $self->can_use_ipc_open3;
+ return;
+@@ -1345,9 +1353,18 @@
+ qq[: Command '$pp_cmd' aborted by alarm after $timeout seconds]
+ }, ALARM_CLASS } if $timeout;
+ alarm $timeout || 0;
++
++ ### PerlOnJava has no fork(), so forced IPC::Run/Open3 selection must
++ ### still use the JVM ProcessBuilder backend.
++ if (IS_PERLONJAVA) {
++ $self->_debug("# Using PerlOnJava::Process. Have buffer: $have_buffer")
++ if $DEBUG;
++ $ok = $self->_perlonjava_run(
++ $cmd, $_out_handler, $_err_handler, $timeout, $verbose
++ );
+
+ ### IPC::Run is first choice if $USE_IPC_RUN is set.
+- if( !IS_WIN32 and $USE_IPC_RUN and $self->can_use_ipc_run( 1 ) ) {
++ } elsif( !IS_WIN32 and $USE_IPC_RUN and $self->can_use_ipc_run( 1 ) ) {
+ ### ipc::run handlers needs the command as a string or an array ref
+
+ $self->_debug( "# Using IPC::Run. Have buffer: $have_buffer" )
+@@ -1407,8 +1424,41 @@
+ ? ($ok, $err, \@buffer, \@buff_out, \@buff_err)
+ : ($ok, $err )
+ : $ok
++
++
++}
++
++sub _perlonjava_run {
++ my ($self, $cmd, $outhand, $errhand, $timeout, $verbose) = @_;
+
++ require Config;
++ require PerlOnJava::Process;
++ my $argv = ref($cmd) eq 'ARRAY' ? [@$cmd] : IS_WIN32
++ ? [($ENV{COMSPEC} || 'cmd.exe'), '/d', '/s', '/c', $cmd]
++ : [($Config::Config{sh} || '/bin/sh'), '-c', $cmd];
+
++ my $result = PerlOnJava::Process::run_process(
++ argv => $argv,
++ timeout => $timeout,
++ tee => 0,
++ );
++ $outhand->($result->{stdout}) if length($result->{stdout} // '');
++ $errhand->($result->{stderr}) if length($result->{stderr} // '');
++
++ if ($result->{timed_out}) {
++ $self->error(loc("Command '%1' timed out", ref($cmd) ? "@$cmd" : $cmd));
++ return $self->ok(0);
++ }
++ if (length($result->{error} // '')) {
++ $self->error($result->{error});
++ return $self->ok(0);
++ }
++ if (($result->{exit_code} // -1) != 0) {
++ $self->error(loc("Command '%1' exited with value %2",
++ ref($cmd) ? "@$cmd" : $cmd, $result->{exit_code}));
++ return $self->ok(0);
++ }
++ return $self->ok(1);
+ }
+
+ sub _open3_run_win32 {
diff --git a/dev/import-perl5/patches/_charnames.pm.patch b/dev/import-perl5/patches/_charnames.pm.patch
deleted file mode 100644
index a7e682e0f8..0000000000
--- a/dev/import-perl5/patches/_charnames.pm.patch
+++ /dev/null
@@ -1,32 +0,0 @@
---- perl5/lib/_charnames.pm 2025-12-11 14:13:49
-+++ src/main/perl/lib/_charnames.pm 2026-04-08 09:40:13
-@@ -137,6 +137,12 @@
- return if $txt;
-
- $txt = do "unicore/Name.pl";
-+ if (!defined $txt || $txt eq '') {
-+ # unicore/Name.pl not available (e.g. on PerlOnJava);
-+ # set $txt to a truthy placeholder so we don't retry and crash.
-+ # viacode() will fall back to _java_viacode() for name lookups.
-+ $txt = "# unavailable\n";
-+ }
- Internals::SvREADONLY($txt, 1);
- }
-
-@@ -805,6 +811,16 @@
- if (defined $algorithmic) {
- $viacode{$hex} = $algorithmic;
- return $algorithmic;
-+ }
-+
-+ # Try Java-backed ICU4J name lookup if available (PerlOnJava).
-+ # This provides complete Unicode name data without needing unicore/Name.pl.
-+ if (defined &_java_viacode) {
-+ my $java_name = _java_viacode(CORE::hex $hex);
-+ if (defined $java_name && $java_name ne '') {
-+ $viacode{$hex} = $java_name;
-+ return $java_name;
-+ }
- }
-
- # Return the official name, if exists. It's unclear to me (khw) at
diff --git a/dev/import-perl5/sync.pl b/dev/import-perl5/sync.pl
index 7e459af9dc..e9780e70cc 100755
--- a/dev/import-perl5/sync.pl
+++ b/dev/import-perl5/sync.pl
@@ -84,11 +84,12 @@ sub apply_patch {
my ($target, $patch_file) = @_;
# --no-backup-if-mismatch prevents creating .orig files
- my $cmd = "patch --no-backup-if-mismatch -p0 '$target' < '$patch_file'";
+ my $cmd = "patch --no-backup-if-mismatch -r - -p0 '$target' < '$patch_file'";
print " Applying patch: $patch_file\n";
my $result = system($cmd);
if ($result != 0) {
+ unlink "$target.rej", "$target.orig";
warn " Warning: patch failed with exit code $result\n";
return 0;
}
@@ -200,6 +201,7 @@ sub split_unicode_testprop {
my @manifest = (
"\n# PerlOnJava lossless TESTCHUNK dispatcher\n",
"# Canonical pinned mktables SHA-256: $canonical_sha\n",
+ "# Canonical current-upstream mktables SHA-256: $canonical_sha\n",
);
for my $section (@sections) {
my @counts = map { "$_=$section->{counts}{$_}" }
@@ -236,7 +238,7 @@ sub split_unicode_testprop {
}
# Generate the Unicode property test fixture from a pristine copy of the
-# pinned source data. Upstream deliberately does not commit TestProp.pl; its
+# current upstream source data. Upstream deliberately does not commit TestProp.pl; its
# normal build creates it with lib/unicore/mktables -maketest.
sub generate_unicode_testprop {
my ($generator_relative, $target, $project_root) = @_;
@@ -249,9 +251,9 @@ sub generate_unicode_testprop {
);
for my $required (@required) {
unless (-f $required) {
- warn " ERROR: Cannot generate Unicode TestProp.pl; missing pinned "
- . "generation prerequisite: $required\n"
- . " Restore perl5/lib/unicore from the pinned perl5 source "
+ warn " ERROR: Cannot generate Unicode TestProp.pl; missing pinned generation prerequisite "
+ . "(compatibility alias; selected source is the current checkout, not a historical SHA): $required\n"
+ . " Restore perl5/lib/unicore from the current upstream source "
. "before running this sync.\n\n";
return 0;
}
@@ -265,7 +267,7 @@ sub generate_unicode_testprop {
};
unless (copy_directory($unicode_source, $temporary_unicode,
$project_root, [], [])) {
- warn " ERROR: Cannot copy pinned Unicode data for TestProp.pl generation.\n\n";
+ warn " ERROR: Cannot copy current upstream Unicode data for TestProp.pl generation.\n\n";
return 0;
}
@@ -275,7 +277,7 @@ sub generate_unicode_testprop {
warn " ERROR: Cannot enter project root for TestProp.pl generation: $!\n\n";
return 0;
}
- print " Generating with pinned Unicode data: $generator_relative\n";
+ print " Generating with current upstream Unicode data: $generator_relative\n";
my $result = system($^X, $generator_relative,
'-C', $temporary_unicode,
'-T', $generated,
@@ -291,7 +293,7 @@ sub generate_unicode_testprop {
? "terminated by signal " . ($result & 127)
: "exit code " . ($result >> 8);
warn " ERROR: Unicode TestProp.pl generation failed ($exit).\n"
- . " Ensure the pinned perl5/lib/unicore data is complete and the "
+ . " Ensure the current upstream perl5/lib/unicore data is complete and the "
. "host Perl can run mktables.\n\n";
return 0;
}
@@ -385,6 +387,104 @@ sub generate_unicode_testprop {
return 1;
}
+# Generate the core Name.pl table in the checked-out perl5 source before its
+# ordinary manifest row copies it into the bundled library. Upstream does not
+# commit this build product, but _charnames.pm loads it as a normal core file.
+sub generate_unicode_name_source {
+ my ($generator_relative, $target, $project_root) = @_;
+ if ($^V lt v5.36.0) {
+ warn " ERROR: Cannot generate Unicode Name.pl with $^X ($^V).\n"
+ . " Current perl5/lib/unicore/mktables uses builtin, which requires "
+ . "Perl 5.36 or newer. Re-run sync.pl with a compatible host Perl.\n\n";
+ return 0;
+ }
+
+ my $generator = File::Spec->catfile($project_root, $generator_relative);
+ my $unicode_source = dirname($generator);
+ for my $required ($generator,
+ File::Spec->catfile($unicode_source, 'version'),
+ File::Spec->catfile($unicode_source, 'UnicodeData.txt')) {
+ unless (-f $required) {
+ warn " ERROR: Cannot generate Unicode Name.pl; missing checkout "
+ . "generation prerequisite: $required\n\n";
+ return 0;
+ }
+ }
+
+ my $temporary = tempdir('perlonjava-namepl-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+ my $temporary_unicode = File::Spec->catdir($temporary, 'unicore');
+ make_path($temporary_unicode) or do {
+ warn " ERROR: Cannot create temporary Unicode generation directory: $!\n\n";
+ return 0;
+ };
+ unless (copy_directory($unicode_source, $temporary_unicode,
+ $project_root, [], [])) {
+ warn " ERROR: Cannot copy checked-out Unicode data for Name.pl generation.\n\n";
+ return 0;
+ }
+
+ my $original_dir = getcwd();
+ unless (chdir $project_root) {
+ warn " ERROR: Cannot enter project root for Name.pl generation: $!\n\n";
+ return 0;
+ }
+ print " Generating Unicode Name.pl with $^X: $generator_relative\n";
+ my $result = system($^X, $generator_relative, '-C', $temporary_unicode, '-q');
+ my $generation_error = $!;
+ unless (chdir $original_dir) {
+ die "sync.pl: cannot restore working directory '$original_dir': $!\n";
+ }
+ if ($result != 0) {
+ my $exit = $result == -1
+ ? "could not start: $generation_error"
+ : ($result & 127)
+ ? "terminated by signal " . ($result & 127)
+ : "exit code " . ($result >> 8);
+ warn " ERROR: Unicode Name.pl generation failed ($exit).\n"
+ . " Re-run sync.pl with a host Perl that can run the current "
+ . "perl5/lib/unicore/mktables.\n\n";
+ return 0;
+ }
+
+ my $generated = File::Spec->catfile($temporary_unicode, 'Name.pl');
+ unless (-s $generated) {
+ warn " ERROR: Unicode generator completed without producing Name.pl.\n\n";
+ return 0;
+ }
+ open my $generated_fh, '<', $generated or do {
+ warn " ERROR: Cannot inspect generated Name.pl: $!\n\n";
+ return 0;
+ };
+ binmode $generated_fh;
+ local $/;
+ my $contents = <$generated_fh>;
+ close $generated_fh;
+ my $actual_sha = sha256_hex($contents);
+ my $target_dir = dirname($target);
+ make_path($target_dir) unless -d $target_dir;
+ my ($target_fh, $staged) = tempfile('.Name-sync-XXXXXX', DIR => $target_dir,
+ UNLINK => 0);
+ binmode $target_fh;
+ print {$target_fh} $contents or do {
+ warn " ERROR: Cannot stage generated Name.pl: $!\n\n";
+ close $target_fh;
+ unlink $staged;
+ return 0;
+ };
+ close $target_fh or do {
+ warn " ERROR: Cannot close staged Name.pl: $!\n\n";
+ unlink $staged;
+ return 0;
+ };
+ rename $staged, $target or do {
+ warn " ERROR: Cannot publish generated Name.pl: $!\n\n";
+ unlink $staged;
+ return 0;
+ };
+ print " Generated Name.pl (SHA-256 $actual_sha)\n";
+ return 1;
+}
+
sub usage {
print <<'USAGE';
PerlOnJava perl5 import sync — see dev/import-perl5/config.yaml
@@ -515,6 +615,13 @@ sub main {
next;
}
}
+ elsif ($type eq 'generated_unicode_name_source') {
+ unless (generate_unicode_name_source($import->{source}, $target,
+ $project_root)) {
+ $error_count++;
+ next;
+ }
+ }
elsif ($type eq 'directory') {
unless (-d $source) {
warn " ERROR: Source directory not found: $source\n\n";
@@ -565,17 +672,36 @@ sub main {
};
}
- # Copy file
+ my $patch_applied = 0;
+ # Stage file and its patch before replacing the target.
print " Copying to: $import->{target}\n";
- unless (copy($source, $target)) {
+ my ($temporary, $temporary_path) = tempfile('.import-XXXXXX', DIR => $target_dir, UNLINK => 0);
+ close $temporary;
+ unless (copy($source, $temporary_path)) {
warn " ERROR: Copy failed: $!\n\n";
$error_count++;
next;
}
+ if ($import->{patch}) {
+ my $patch_file = File::Spec->catfile($patches_dir, $import->{patch});
+ unless (-f $patch_file && apply_patch($temporary_path, $patch_file)) {
+ unlink $temporary_path;
+ $error_count++;
+ next;
+ }
+ $patch_applied = 1;
+ }
+ rename $temporary_path, $target or do {
+ warn " ERROR: Cannot publish staged import: $!\n\n";
+ unlink $temporary_path;
+ $error_count++;
+ next;
+ };
+ $import->{_patch_applied} = $patch_applied;
}
# Apply patch if specified
- if ($import->{patch}) {
+ if ($import->{patch} && !$import->{_patch_applied}) {
my $patch_file = File::Spec->catfile($patches_dir, $import->{patch});
unless (-f $patch_file) {
warn " ERROR: Patch file not found: $patch_file\n\n";
diff --git a/dev/import-perl5/t/transactional_patch_apply.t b/dev/import-perl5/t/transactional_patch_apply.t
new file mode 100644
index 0000000000..ea521a1436
--- /dev/null
+++ b/dev/import-perl5/t/transactional_patch_apply.t
@@ -0,0 +1,33 @@
+use strict;
+use warnings;
+use Test::More;
+use File::Temp qw(tempdir);
+use File::Spec;
+
+my $root = File::Spec->rel2abs(File::Spec->catdir(File::Spec->curdir));
+require File::Spec->catfile($root, 'dev', 'import-perl5', 'sync.pl');
+my $dir = tempdir('import patch space XXXX', TMPDIR => 1, CLEANUP => 1);
+my $target = File::Spec->catfile($dir, 'target file.txt');
+my $patch = File::Spec->catfile($dir, 'bad.patch');
+open my $fh, '>', $target or die $!; print {$fh} "old\n"; close $fh;
+open $fh, '>', $patch or die $!; print {$fh} "--- target\n+++ target\n@@ -1 +1 @@\n-missing\n+new\n"; close $fh;
+ok(!apply_patch($target, $patch), 'bad patch fails');
+open $fh, '<', $target or die $!; is(<$fh>, "old\n", 'failed patch leaves target unchanged'); close $fh;
+opendir my $directory, $dir or die $!;
+my @artifacts = grep { /\.(?:rej|orig)\z/ } readdir $directory;
+closedir $directory;
+is_deeply(\@artifacts, [], 'failed patch leaves no artifacts');
+
+my $good_patch = File::Spec->catfile($dir, 'good patch.diff');
+open $fh, '>', $good_patch or die $!;
+print {$fh} "--- target\n+++ target\n@@ -1 +1 @@\n-old\n+new\n";
+close $fh;
+ok(apply_patch($target, $good_patch), 'valid patch succeeds for a target path containing spaces');
+open $fh, '<', $target or die $!;
+is(<$fh>, "new\n", 'successful patch publishes the new bytes');
+close $fh;
+opendir $directory, $dir or die $!;
+@artifacts = grep { /(?:\.(?:rej|orig)\z|^\.import-)/ } readdir $directory;
+closedir $directory;
+is_deeply(\@artifacts, [], 'successful patch leaves no reject, backup, or temporary sibling');
+done_testing;
diff --git a/dev/import-perl5/t/update_perl5.t b/dev/import-perl5/t/update_perl5.t
new file mode 100644
index 0000000000..e96a3991c4
--- /dev/null
+++ b/dev/import-perl5/t/update_perl5.t
@@ -0,0 +1,172 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Cwd qw(abs_path);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use IPC::Open3;
+use Symbol qw(gensym);
+use Test::More;
+
+my $helper = abs_path(File::Spec->catfile($FindBin::Bin, '..', 'update_perl5.pl'));
+my $tmp = tempdir('perlonjava-perl5-update-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $seed = File::Spec->catdir($tmp, 'seed');
+my $upstream = File::Spec->catdir($tmp, 'upstream.git');
+my $checkout = File::Spec->catdir($tmp, 'perl checkout');
+
+git($tmp, 'init', '-q', $seed);
+git($seed, 'config', 'user.name', 'Import Test');
+git($seed, 'config', 'user.email', 'import-test@example.invalid');
+write_file(File::Spec->catfile($seed, 'tracked.txt'), "one\n");
+git($seed, 'add', 'tracked.txt');
+git($seed, 'commit', '-q', '-m', 'initial');
+git($seed, 'branch', '-M', 'blead');
+git($tmp, 'init', '-q', '--bare', $upstream);
+git($seed, 'remote', 'add', 'origin', $upstream);
+git($seed, 'push', '-q', '-u', 'origin', 'blead');
+git_dir($upstream, 'symbolic-ref', 'HEAD', 'refs/heads/blead');
+my $initial = git_output($seed, 'rev-parse', 'HEAD');
+
+my ($status, $out, $err) = run_helper(
+ '--perl-root', $checkout,
+ '--repository', $upstream,
+);
+is($status, 0, 'absent checkout is cloned from configured upstream') or diag $err;
+is(git_output($checkout, 'rev-parse', 'HEAD'), $initial, 'clone consumes latest upstream commit');
+like($out, qr/Perl upstream commit: \Q$initial\E/, 'clone prints exact consumed commit');
+
+write_file(File::Spec->catfile($seed, 'tracked.txt'), "two\n");
+git($seed, 'add', 'tracked.txt');
+git($seed, 'commit', '-q', '-m', 'upstream fast forward');
+git($seed, 'push', '-q');
+my $second = git_output($seed, 'rev-parse', 'HEAD');
+($status, $out, $err) = run_helper('--perl-root', $checkout, '--repository', $upstream);
+is($status, 0, 'clean checkout fast-forwards') or diag $err;
+is(git_output($checkout, 'rev-parse', 'HEAD'), $second, 'fast-forward reaches latest upstream tip');
+like($out, qr/Perl upstream commit: \Q$second\E/, 'fast-forward prints exact consumed commit');
+
+write_file(File::Spec->catfile($checkout, 'generated-untracked.txt'), "generated\n");
+($status, $out, $err) = run_helper('--perl-root', $checkout, '--repository', $upstream);
+is($status, 0, 'untracked generated files do not make checkout dirty') or diag $err;
+
+my $tracking_before_dirty = git_output($checkout, 'rev-parse', 'origin/blead');
+write_file(File::Spec->catfile($checkout, 'tracked.txt'), "local edit\n");
+write_file(File::Spec->catfile($seed, 'tracked.txt'), "three\n");
+git($seed, 'add', 'tracked.txt');
+git($seed, 'commit', '-q', '-m', 'upstream while dirty');
+git($seed, 'push', '-q');
+($status, $out, $err) = run_helper('--perl-root', $checkout, '--repository', $upstream);
+isnt($status, 0, 'tracked local edit is refused');
+like($err, qr/tracked local changes exist/, 'dirty refusal explains the safety condition');
+is(git_output($checkout, 'rev-parse', 'origin/blead'), $tracking_before_dirty,
+ 'dirty checkout is refused before fetch');
+is(git_output($checkout, 'rev-parse', 'HEAD'), $second, 'dirty refusal leaves HEAD unchanged');
+
+write_file(File::Spec->catfile($checkout, 'tracked.txt'), "two\n");
+($status, $out, $err) = run_helper('--perl-root', $checkout, '--repository', $upstream);
+is($status, 0, 'checkout updates after tracked edit is removed') or diag $err;
+my $third = git_output($checkout, 'rev-parse', 'HEAD');
+
+git($checkout, 'config', 'user.name', 'Import Test');
+git($checkout, 'config', 'user.email', 'import-test@example.invalid');
+write_file(File::Spec->catfile($checkout, 'local.txt'), "local commit\n");
+git($checkout, 'add', 'local.txt');
+git($checkout, 'commit', '-q', '-m', 'local commit');
+my $local_head = git_output($checkout, 'rev-parse', 'HEAD');
+write_file(File::Spec->catfile($seed, 'upstream.txt'), "remote commit\n");
+git($seed, 'add', 'upstream.txt');
+git($seed, 'commit', '-q', '-m', 'divergent upstream commit');
+git($seed, 'push', '-q');
+($status, $out, $err) = run_helper('--perl-root', $checkout, '--repository', $upstream);
+isnt($status, 0, 'non-fast-forward checkout is refused');
+like($err, qr/not a fast-forward/, 'non-fast-forward refusal is actionable');
+is(git_output($checkout, 'rev-parse', 'HEAD'), $local_head, 'non-fast-forward refusal leaves HEAD unchanged');
+
+my $real_checkout = File::Spec->catdir($tmp, 'adjacent-perl5');
+($status, $out, $err) = run_helper('--perl-root', $real_checkout, '--repository', $upstream);
+is($status, 0, 'second clean checkout is cloned') or diag $err;
+my $linked_checkout = File::Spec->catfile($tmp, 'perl5-link');
+symlink $real_checkout, $linked_checkout or die "Cannot create symlink: $!";
+($status, $out, $err) = run_helper('--perl-root', $linked_checkout, '--repository', $upstream);
+is($status, 0, 'symlink to adjacent checkout is supported') or diag $err;
+my $latest = git_output($seed, 'rev-parse', 'HEAD');
+like($out, qr/Perl upstream commit: \Q$latest\E/, 'symlink update reports exact provenance');
+
+my $wrong_repo = File::Spec->catdir($tmp, 'wrong.git');
+git($tmp, 'init', '-q', '--bare', $wrong_repo);
+($status, $out, $err) = run_helper('--perl-root', $real_checkout, '--repository', $wrong_repo);
+isnt($status, 0, 'unexpected configured repository is refused');
+like($err, qr/does not match/, 'repository refusal identifies unsafe relationship');
+
+my $fake_sync = File::Spec->catfile($tmp, 'fake-sync.pl');
+my $sync_log = File::Spec->catfile($tmp, 'sync-args');
+write_file($fake_sync, <<'FAKE_SYNC');
+use strict;
+use warnings;
+open my $fh, '>', $ENV{SYNC_LOG} or die $!;
+print {$fh} join "\0", @ARGV;
+close $fh or die $!;
+FAKE_SYNC
+($status, $out, $err) = run_helper(
+ { SYNC_LOG => $sync_log }, '--perl-root', $real_checkout,
+ '--repository', $upstream, '--sync', '--sync-script', $fake_sync,
+);
+is($status, 0, 'full sync invocation succeeds against local fixture') or diag $err;
+is(read_file($sync_log), '', 'full sync passes no filter arguments');
+
+($status, $out, $err) = run_helper(
+ { SYNC_LOG => $sync_log }, '--perl-root', $real_checkout,
+ '--repository', $upstream, '--sync', '--filter', 'Name.pl',
+ '--sync-script', $fake_sync,
+);
+is($status, 0, 'filtered sync invocation succeeds against local fixture') or diag $err;
+is(read_file($sync_log), "--only\0Name.pl", 'filtered sync passes exactly one --only argument pair');
+
+done_testing();
+
+sub run_helper {
+ my $environment = ref $_[0] eq 'HASH' ? shift : {};
+ local %ENV = (%ENV, %$environment);
+ my $stderr = gensym;
+ my $pid = open3(undef, my $stdout, $stderr, $^X, $helper, @_);
+ my $out = do { local $/; <$stdout> // '' };
+ my $err = do { local $/; <$stderr> // '' };
+ waitpid $pid, 0;
+ return ($? >> 8, $out, $err);
+}
+
+sub git {
+ my ($directory, @arguments) = @_;
+ system 'git', '-C', $directory, @arguments;
+ die "git @arguments failed\n" if $? != 0;
+}
+
+sub git_dir {
+ my ($directory, @arguments) = @_;
+ system 'git', '--git-dir', $directory, @arguments;
+ die "git --git-dir @arguments failed\n" if $? != 0;
+}
+
+sub git_output {
+ my ($directory, @arguments) = @_;
+ open my $pipe, '-|', 'git', '-C', $directory, @arguments or die $!;
+ my $output = do { local $/; <$pipe> // '' };
+ close $pipe or die "git @arguments failed\n";
+ $output =~ s/\s+\z//;
+ return $output;
+}
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>', $path or die "Cannot write $path: $!";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!";
+}
+
+sub read_file {
+ my ($path) = @_;
+ open my $fh, '<', $path or die "Cannot read $path: $!";
+ local $/;
+ return <$fh> // '';
+}
diff --git a/dev/import-perl5/t/update_perl5_latest_branch.t b/dev/import-perl5/t/update_perl5_latest_branch.t
new file mode 100644
index 0000000000..080f3fcfb9
--- /dev/null
+++ b/dev/import-perl5/t/update_perl5_latest_branch.t
@@ -0,0 +1,103 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Cwd qw(abs_path);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use IPC::Open3;
+use Symbol qw(gensym);
+use Test::More;
+
+my $helper = abs_path(File::Spec->catfile($FindBin::Bin, '..', 'update_perl5.pl'));
+my $tmp = tempdir('perlonjava-perl5-latest-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $seed = File::Spec->catdir($tmp, 'seed');
+my $upstream = File::Spec->catdir($tmp, 'upstream.git');
+my $checkout = File::Spec->catdir($tmp, 'checkout');
+
+git($tmp, 'init', '-q', $seed);
+git($seed, 'config', 'user.name', 'Import Test');
+git($seed, 'config', 'user.email', 'import-test@example.invalid');
+write_file(File::Spec->catfile($seed, 'tracked.txt'), "blead one\n");
+git($seed, 'add', 'tracked.txt');
+git($seed, 'commit', '-q', '-m', 'blead initial');
+git($seed, 'branch', '-M', 'blead');
+git($tmp, 'init', '-q', '--bare', $upstream);
+git($seed, 'remote', 'add', 'origin', $upstream);
+git($seed, 'push', '-q', '-u', 'origin', 'blead');
+git_dir($upstream, 'symbolic-ref', 'HEAD', 'refs/heads/blead');
+
+my ($status, $out, $err) = run_helper(
+ '--perl-root', $checkout,
+ '--repository', $upstream,
+);
+is($status, 0, 'default upstream branch is accepted') or diag $err;
+
+git($seed, 'checkout', '-q', '-b', 'maint');
+write_file(File::Spec->catfile($seed, 'tracked.txt'), "maint one\n");
+git($seed, 'add', 'tracked.txt');
+git($seed, 'commit', '-q', '-m', 'maint initial');
+git($seed, 'push', '-q', '-u', 'origin', 'maint');
+git($checkout, 'fetch', '-q', 'origin', 'maint:refs/remotes/origin/maint');
+git($checkout, 'checkout', '-q', '-b', 'maint', '--track', 'origin/maint');
+
+git($seed, 'checkout', '-q', 'blead');
+write_file(File::Spec->catfile($seed, 'latest.txt'), "latest\n");
+git($seed, 'add', 'latest.txt');
+git($seed, 'commit', '-q', '-m', 'latest blead');
+git($seed, 'push', '-q');
+
+my $head_before = git_output($checkout, 'rev-parse', 'HEAD');
+my $tracking_before = git_output($checkout, 'rev-parse', 'origin/blead');
+($status, $out, $err) = run_helper(
+ '--perl-root', $checkout,
+ '--repository', $upstream,
+);
+isnt($status, 0, 'clean maintenance branch is refused');
+like($err, qr/branch maint is not the latest upstream branch blead/,
+ 'refusal identifies the remote default branch');
+is(git_output($checkout, 'symbolic-ref', '--short', 'HEAD'), 'maint',
+ 'helper does not switch branches');
+is(git_output($checkout, 'rev-parse', 'HEAD'), $head_before,
+ 'helper does not move the maintenance checkout');
+is(git_output($checkout, 'rev-parse', 'origin/blead'), $tracking_before,
+ 'non-default branch is refused before fetch');
+
+done_testing;
+
+sub run_helper {
+ my $stderr = gensym;
+ my $pid = open3(undef, my $stdout, $stderr, $^X, $helper, @_);
+ my $out = do { local $/; <$stdout> // '' };
+ my $err = do { local $/; <$stderr> // '' };
+ waitpid $pid, 0;
+ return ($? >> 8, $out, $err);
+}
+
+sub git {
+ my ($directory, @arguments) = @_;
+ system 'git', '-C', $directory, @arguments;
+ die "git @arguments failed\n" if $? != 0;
+}
+
+sub git_dir {
+ my ($directory, @arguments) = @_;
+ system 'git', '--git-dir', $directory, @arguments;
+ die "git --git-dir @arguments failed\n" if $? != 0;
+}
+
+sub git_output {
+ my ($directory, @arguments) = @_;
+ open my $pipe, '-|', 'git', '-C', $directory, @arguments or die $!;
+ my $output = do { local $/; <$pipe> // '' };
+ close $pipe or die "git @arguments failed\n";
+ $output =~ s/\s+\z//;
+ return $output;
+}
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>', $path or die "Cannot write $path: $!";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!";
+}
diff --git a/dev/import-perl5/update_perl5.pl b/dev/import-perl5/update_perl5.pl
new file mode 100644
index 0000000000..984ca8b127
--- /dev/null
+++ b/dev/import-perl5/update_perl5.pl
@@ -0,0 +1,139 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Cwd qw(abs_path getcwd);
+use File::Spec;
+use FindBin;
+use Getopt::Long qw(GetOptions);
+
+my $project_root = abs_path(File::Spec->catdir($FindBin::Bin, '..', '..'));
+my $perl_root = File::Spec->catdir($project_root, 'perl5');
+my $repository = $ENV{PERL5_REPOSITORY} // 'https://github.com/Perl/perl5.git';
+my $sync_script = File::Spec->catfile($project_root, 'dev', 'import-perl5', 'sync.pl');
+my $sync = 0;
+my $filter;
+my $help = 0;
+GetOptions(
+ 'perl-root=s' => \$perl_root,
+ 'repository=s' => \$repository,
+ 'sync-script=s' => \$sync_script,
+ 'sync' => \$sync,
+ 'filter=s' => \$filter,
+ 'help' => \$help,
+) or usage(2);
+usage(0) if $help;
+die "--filter requires --sync\n" if defined $filter && !$sync;
+die "--filter must be non-empty\n" if defined $filter && !length $filter;
+$perl_root = File::Spec->rel2abs($perl_root);
+
+if (!-e $perl_root && !-l $perl_root) {
+ run('git', 'clone', '--origin', 'origin', $repository, $perl_root);
+} else {
+ die "$perl_root is not a Git checkout\n"
+ unless -d $perl_root && -d File::Spec->catdir($perl_root, '.git');
+}
+
+my $dirty = capture('git', '-C', $perl_root, 'status', '--porcelain', '--untracked-files=no');
+die "Refusing to update $perl_root: tracked local changes exist\n" if length $dirty;
+my $branch = capture_line('git', '-C', $perl_root, 'symbolic-ref', '--quiet', '--short', 'HEAD');
+die "Refusing to update $perl_root: detached HEAD\n" unless length $branch;
+my $upstream = capture_line('git', '-C', $perl_root, 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}');
+die "Refusing to update $perl_root: branch $branch has no upstream\n" unless length $upstream;
+my ($remote) = split m{/}, $upstream, 2;
+die "Refusing to update $perl_root: invalid upstream $upstream\n" unless defined $remote && length $remote;
+my $remote_url = capture_line('git', '-C', $perl_root, 'remote', 'get-url', $remote);
+die "Refusing to update $perl_root: upstream remote $remote_url does not match $repository\n"
+ unless normalized_repository($remote_url) eq normalized_repository($repository);
+my $latest_branch = remote_default_branch($perl_root, $remote);
+die "Refusing to update $perl_root: checked-out branch $branch is not the latest upstream branch $latest_branch\n"
+ unless $branch eq $latest_branch;
+die "Refusing to update $perl_root: upstream $upstream does not track $remote/$latest_branch\n"
+ unless $upstream eq "$remote/$latest_branch";
+
+run('git', '-C', $perl_root, 'fetch', $remote);
+unless (succeeds('git', '-C', $perl_root, 'merge-base', '--is-ancestor', 'HEAD', $upstream)) {
+ die "Refusing to update $perl_root: $upstream is not a fast-forward of HEAD\n";
+}
+run('git', '-C', $perl_root, 'merge', '--ff-only', $upstream);
+my $commit = capture_line('git', '-C', $perl_root, 'rev-parse', 'HEAD');
+print "Perl upstream commit: $commit\n";
+
+if ($sync) {
+ my @command = ($^X, $sync_script);
+ push @command, '--only', $filter if defined $filter;
+ my $old = getcwd();
+ chdir $project_root or die "Cannot enter $project_root: $!\n";
+ run(@command);
+ chdir $old or die "Cannot restore $old: $!\n";
+}
+
+sub normalized_repository {
+ my ($value) = @_;
+ $value =~ s{\Agit\@github\.com:}{https://github.com/}i;
+ $value =~ s{\Assh://git\@github\.com/}{https://github.com/}i;
+ $value =~ s{/+\z}{};
+ $value =~ s{\.git\z}{};
+ return $value;
+}
+
+sub remote_default_branch {
+ my ($checkout, $remote) = @_;
+ my $advertisement = capture('git', '-C', $checkout, 'ls-remote', '--symref', $remote, 'HEAD');
+ my ($branch) = $advertisement =~ /^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m;
+ die "Refusing to update $checkout: cannot determine the latest branch advertised by $remote\n"
+ unless defined $branch && length $branch;
+ return $branch;
+}
+
+sub run {
+ my (@command) = @_;
+ print '+ ', join(' ', map { quote($_) } @command), "\n";
+ system @command;
+ die "Command failed (exit " . ($? >> 8) . "): $command[0]\n" if $? != 0;
+}
+
+sub succeeds {
+ my (@command) = @_;
+ print '+ ', join(' ', map { quote($_) } @command), "\n";
+ system @command;
+ return $? == 0;
+}
+
+sub capture {
+ my (@command) = @_;
+ open my $pipe, '-|', @command or die "Cannot run $command[0]: $!\n";
+ local $/;
+ my $output = <$pipe> // '';
+ close $pipe or return '';
+ return $output;
+}
+
+sub capture_line {
+ my (@command) = @_;
+ my $value = capture(@command);
+ $value =~ s/\s+\z//;
+ return $value;
+}
+
+sub quote {
+ my ($value) = @_;
+ return $value if $value =~ /\A[-A-Za-z0-9_.\/:\@]+\z/;
+ $value =~ s/'/'"'"'/g;
+ return "'$value'";
+}
+
+sub usage {
+ my ($status) = @_;
+ print <<'USAGE';
+Usage: perl dev/import-perl5/update_perl5.pl [options]
+ --perl-root PATH checkout path (default: ./perl5)
+ --repository URL expected upstream repository
+ --sync-script PATH sync implementation (default: dev/import-perl5/sync.pl)
+ --sync run the complete import manifest after updating
+ --filter TEXT pass exactly one --only filter to sync.pl (requires --sync)
+
+Existing checkouts must be on the branch advertised as the repository's HEAD.
+The helper never switches branches; it refuses non-default branches instead.
+USAGE
+ exit $status;
+}
diff --git a/dev/prompts/alternation-capture-issue-analysis.md b/dev/prompts/alternation-capture-issue-analysis.md
index 25960f0536..6da21accdf 100644
--- a/dev/prompts/alternation-capture-issue-analysis.md
+++ b/dev/prompts/alternation-capture-issue-analysis.md
@@ -1,5 +1,9 @@
# Alternation Capture Issue - Deep Dive Analysis
+> **Historical analysis:** This predates the Phase 36 Joni cutover. Its Java
+> `Matcher` comparisons explain the retired backend and are not claims about
+> current production matching, which uses the vendored Joni fork exclusively.
+
## Date
2025-10-07
diff --git a/dev/prompts/conditional-pattern-transformation-strategy.md b/dev/prompts/conditional-pattern-transformation-strategy.md
index 869b9ae7c8..3f31313a7a 100644
--- a/dev/prompts/conditional-pattern-transformation-strategy.md
+++ b/dev/prompts/conditional-pattern-transformation-strategy.md
@@ -1,5 +1,9 @@
# Conditional Pattern Transformation Strategy
+> **Historical strategy:** This predates the Phase 36 Joni cutover. Production
+> conditionals are parsed and matched natively by the vendored Joni fork; the
+> Java `Pattern` transformations below are retained only as design history.
+
## Discovery
Java regex does NOT support `(?(condition)yes|no)` syntax - it throws "Unknown inline modifier" error.
diff --git a/dev/tools/README.md b/dev/tools/README.md
index f228647e5b..5078cd27ba 100644
--- a/dev/tools/README.md
+++ b/dev/tools/README.md
@@ -22,6 +22,79 @@ lane, while `pat_psycho*` and `speed*` run afterward in a dedicated lane
controlled by `--cpu-heavy-jobs` (default 2). This permits useful CPU
parallelism without making their watchdogs contend with `pat*`.
+### generate_regex_test_ledger.pl
+**Purpose:** Derive the current Phase 36 regex corpus without a hand-maintained
+file list or pinned Perl revision.
+
+The tool includes every current `perl5_t/t/re/*.t` file, scans `op/` and `uni/`
+for regex-bearing tests, resolves test references from the feature matrix,
+Phase 36 plan, and comparison policy, and records direct/thread pairs and unit
+gates separately. It emits canonical JSON plus an optional one-path-per-line
+runner list:
+
+```bash
+perl dev/tools/generate_regex_test_ledger.pl \
+ --runner-list /tmp/phase36-regex-files.txt \
+ --output /tmp/phase36-regex-ledger.json
+```
+
+Use the same list to compare only those exact identities against a broader
+historical runner log:
+
+```bash
+perl dev/tools/compare_test_results.pl \
+ --file-list /tmp/phase36-regex-files.txt \
+ baseline.log candidate.json
+```
+
+The generated counts describe the current latest upstream checkout. Refresh
+comparisons may record hashes for that run, but they must not encode a
+historical Perl revision as the expected corpus.
+
+### run_phase36_regex_acceptance.pl
+
+**Purpose:** Compose the Phase 36 current-checkout ledger, bounded JVM and
+interpreter runner legs, PR-958-normalized fail-closed comparisons, and exact
+Joni packaging check into one immutable-artifact manifest. It records the
+starting/final clean tracked-source state, current `perl5/` SHA as provenance,
+and the bounded `jperl -v` injected SHA matched to that source, along with
+list-form commands, exit statuses, counts, and SHA-256s for retained artifacts.
+
+The coordinator runs the real corpus only after creating the exact JAR/SBOM.
+Workers use `--prepare-only` with injected fake tool paths to test the
+composition without starting the corpus; the real invocation is:
+
+```bash
+perl dev/tools/run_phase36_regex_acceptance.pl \
+ --baseline logs/test_20260815_080000_958.log \
+ --artifact-dir /tmp/phase36-acceptance \
+ --jar target/perlonjava-standalone.jar --sbom build/reports/sbom.json
+```
+
+### verify-joni-packaging.pl
+
+**Purpose:** Fail closed on the final standalone JAR/SBOM boundary. The
+two-argument verifier checks relocated Joni/JCodings class ownership, exact
+checked-in notice bytes, unique Joni/JCodings CycloneDX identities, and the
+declared Joni-to-JCodings dependency edge. It resolves notice sources relative
+to the repository, so it may run from an artifact directory:
+
+```bash
+perl dev/tools/verify-joni-packaging.pl standalone.jar merged-sbom.json
+```
+
+For a system-Perl oracle of an imported core test, remember that `t/test.pl`
+replaces `@INC` with the imported `../lib`. Preload host-core modules needed by
+the test before that reset. For example, the reproducible `op/do.t` oracle is:
+
+```bash
+cd perl5_t/t
+perl -Mstrict -MErrno -MPerlIO -MPerlIO::scalar op/do.t
+```
+
+This distinguishes a real TAP count from a zero-TAP host-module startup error;
+it does not patch or otherwise modify the imported test.
+
### reorganize_tests.sh
**Purpose:** Reorganize test directory structure to separate PerlOnJava unit tests from standard Perl module tests.
diff --git a/dev/tools/check_test_manifest.pl b/dev/tools/check_test_manifest.pl
index 522f70c1a7..fa9a55e1b5 100644
--- a/dev/tools/check_test_manifest.pl
+++ b/dev/tools/check_test_manifest.pl
@@ -15,7 +15,7 @@
my ($expected, $path) = $line =~ /^([0-9a-f]{64})\s{2}(.+)$/
or die "Malformed manifest line $. in $manifest\n";
if (!-f $path) {
- warn "Missing pinned test source: $path\n";
+ warn "Missing manifest test source: $path\n";
$failed = 1;
next;
}
@@ -23,7 +23,7 @@
binmode $source;
my $actual = Digest::SHA->new(256)->addfile($source)->hexdigest;
if ($actual ne $expected) {
- warn "Pinned test source differs: $path\n";
+ warn "Manifest test source differs: $path\n";
$failed = 1;
}
}
diff --git a/dev/tools/compare_test_logs.pl b/dev/tools/compare_test_logs.pl
index 40cfd61a50..924a405a42 100755
--- a/dev/tools/compare_test_logs.pl
+++ b/dev/tools/compare_test_logs.pl
@@ -2,6 +2,7 @@
use strict;
use warnings;
use Getopt::Long;
+use FindBin;
=head1 NAME
@@ -21,6 +22,8 @@ =head1 SYNOPSIS
--normalize-pr958-artifacts
Normalize two exact, independently reconstructed
PR 958 transcript artifacts; raw counts are printed
+ --strict-acceptance Run the fail-closed PR-958 acceptance comparison
+ --output FILE Write strict-acceptance JSON evidence to FILE
--sort-by FIELD Sort by: name, diff, before, after (default: diff)
--help Show this help message
@@ -51,6 +54,8 @@ =head1 EXAMPLES
my $help = 0;
my $show_flakes = 0; # Set --show-flakes to include known-flake files in regression list
my $normalize_pr958_artifacts = 0;
+my $strict_acceptance = 0;
+my $output_file;
my @artifact_normalizations;
# These are exact transcript signatures, not a file-level whitelist. They are
@@ -118,6 +123,8 @@ =head1 EXAMPLES
'sort-by=s' => \$sort_by,
'show-flakes!' => \$show_flakes,
'normalize-pr958-artifacts!' => \$normalize_pr958_artifacts,
+ 'strict-acceptance!' => \$strict_acceptance,
+ 'output=s' => \$output_file,
'help|h' => \$help,
) or die "Error in command line arguments\n";
@@ -137,6 +144,10 @@ =head1 EXAMPLES
--normalize-pr958-artifacts
Normalize exact PR 958 transcript signatures and show
both raw and normalized counts
+ --strict-acceptance Run the fail-closed PR-958 gate: reject regressions,
+ missing files, execution errors, timeout, zero TAP,
+ truncated/incomplete TAP, and malformed input
+ --output FILE Save strict-acceptance machine-readable JSON evidence
--sort-by FIELD Sort by: name, diff, before, after (default: diff)
--help Show this help message
@@ -150,6 +161,17 @@ =head1 EXAMPLES
my ($old_log, $new_log) = @ARGV;
+if ($strict_acceptance) {
+ my @command = ($^X, "$FindBin::Bin/compare_test_results.pl",
+ '--fail-on-regression', '--fail-on-invalid',
+ '--normalize-pr958-artifacts');
+ push @command, ('--output', $output_file) if defined $output_file;
+ push @command, $old_log, $new_log;
+ system @command;
+ die "Cannot execute strict acceptance comparator: $!\n" if $? == -1;
+ exit($? >> 8);
+}
+
# Parse a log file and extract test results
sub parse_log {
my ($file, $side) = @_;
diff --git a/dev/tools/compare_test_results.pl b/dev/tools/compare_test_results.pl
index cd911a302b..02316c5fcb 100644
--- a/dev/tools/compare_test_results.pl
+++ b/dev/tools/compare_test_results.pl
@@ -11,6 +11,8 @@
my $expected_files;
my $output_file;
my $path_prefix;
+my $file_list;
+my $normalize_pr958_artifacts = 0;
my $help = 0;
GetOptions(
'fail-on-regression!' => \$fail_on_regression,
@@ -18,21 +20,30 @@
'expected-files=i' => \$expected_files,
'output=s' => \$output_file,
'path-prefix=s' => \$path_prefix,
+ 'file-list=s' => \$file_list,
+ 'normalize-pr958-artifacts!' => \$normalize_pr958_artifacts,
'help' => \$help,
) or usage(2);
usage(0) if $help;
my ($baseline_file, $candidate_file) = @ARGV;
usage(2) unless defined $baseline_file && defined $candidate_file && @ARGV == 2;
+die "--path-prefix and --file-list are mutually exclusive\n"
+ if defined $path_prefix && defined $file_list;
-my $baseline = load_results($baseline_file);
-my $candidate = load_results($candidate_file);
+my $baseline = load_results($baseline_file, 'baseline');
+my $candidate = load_results($candidate_file, 'candidate');
if (defined $path_prefix) {
$path_prefix = canonical_file($path_prefix);
- $path_prefix .= '/' unless $path_prefix =~ m{/$};
+ $path_prefix .= '/' unless $path_prefix =~ m{/$} || $path_prefix =~ /\.t\z/;
$baseline = filter_results($baseline, $path_prefix);
$candidate = filter_results($candidate, $path_prefix);
}
+if (defined $file_list) {
+ my $selected = load_file_list($file_list);
+ $baseline = filter_results_by_file($baseline, $selected);
+ $candidate = filter_results_by_file($candidate, $selected);
+}
my $comparison = compare_results($baseline, $candidate);
$comparison->{expected_files} = $expected_files if defined $expected_files;
@@ -58,14 +69,18 @@ sub usage {
zero-TAP results, or an --expected-files mismatch
--expected-files NUM Require exactly NUM candidate files
--output FILE Save the normalized comparison as JSON
- --path-prefix PATH Compare only files below this canonical path
+ --path-prefix PATH Compare one test file or files below a canonical path
+ --file-list FILE Compare only exact test paths listed one per line
+ --normalize-pr958-artifacts
+ Normalize the two exact reconstructed PR-958 baseline
+ transcript signatures and retain raw counts in JSON
--help Show this help
USAGE
exit $status;
}
sub load_results {
- my ($path) = @_;
+ my ($path, $side) = @_;
open my $fh, '<:raw', $path or die "Cannot open $path: $!\n";
my $content = do { local $/; <$fh> };
close $fh;
@@ -85,30 +100,66 @@ sub load_results {
my %normalized;
for my $file (keys %{$document->{results}}) {
my $result = $document->{results}{$file};
- $normalized{canonical_file($file)} = {
+ my $entry = {
ok => 0 + ($result->{ok_count} // 0),
total => 0 + ($result->{total_tests} // 0),
status => $result->{status} // 'unknown',
+ planned => 0 + ($result->{planned_tests} // $result->{total_tests} // 0),
+ actual => 0 + ($result->{actual_tests_run} // $result->{total_tests} // 0),
+ incomplete => 0 + ($result->{incomplete_tests} // 0),
+ exit_code => 0 + ($result->{exit_code} // 0),
};
+ normalize_pr958_artifact(canonical_file($file), $entry, $side);
+ $normalized{canonical_file($file)} = $entry;
}
return \%normalized;
}
my %results;
for my $line (split /\n/, $content) {
- next unless $line =~ /^\[\s*\d+\/\d+\]\s+(\S+)\s+\.\.\.\s+\S+\s+(\d+)\/(\d+)\s+ok\b/;
- my ($file, $ok, $total) = ($1, $2, $3);
- $results{canonical_file($file)} = {
+ next unless $line =~ /^\[\s*\d+\/\d+\]\s+(\S+)\s+\.\.\.\s+(\S+)\s+(\d+)\/(\d+)\s+ok\b/;
+ my ($file, $marker, $ok, $total) = ($1, $2, $3, $4);
+ my %status_for = (
+ 'T' => 'timeout', 'I' => 'incomplete', '!' => 'error',
+ '?' => 'unknown',
+ );
+ my $entry = {
ok => 0 + $ok,
total => 0 + $total,
- status => $ok == $total ? 'pass' : 'partial',
+ status => $status_for{$marker} // ($ok == $total ? 'pass' : 'partial'),
+ planned => 0 + $total,
+ actual => 0 + $total,
+ incomplete => $marker eq 'I' ? 1 : 0,
+ exit_code => $marker eq '!' || $marker eq 'T' ? 1 : 0,
};
+ normalize_pr958_artifact(canonical_file($file), $entry, $side);
+ $results{canonical_file($file)} = $entry;
}
die "Captured runner log $path contains no per-file result lines\n"
unless keys %results;
return \%results;
}
+sub normalize_pr958_artifact {
+ my ($file, $entry, $side) = @_;
+ return unless $normalize_pr958_artifacts;
+ my %artifacts = (
+ 'perl5_t/t/op/do.t|94|99' => [68, 71,
+ 'PR 958 duplicated its first 28 TAP assertions'],
+ 'perl5_t/t/japh/abigail.t|110|130' => [109, 130,
+ 'PR 958 logged one irreproducible extra pass'],
+ );
+ my $artifact = $artifacts{join('|', $file, $entry->{ok}, $entry->{total})};
+ return unless $artifact;
+ $entry->{raw_ok} = $entry->{ok};
+ $entry->{raw_total} = $entry->{total};
+ $entry->{normalization} = $artifact->[2];
+ ($entry->{ok}, $entry->{total}) = @{$artifact}[0, 1];
+ $entry->{planned} = $entry->{total};
+ $entry->{actual} = $entry->{total};
+ $entry->{normalization_side} = $side;
+}
+
sub canonical_file {
my ($file) = @_;
$file =~ s{\\}{/}g;
@@ -125,10 +176,32 @@ sub filter_results {
return \%filtered;
}
+sub load_file_list {
+ my ($path) = @_;
+ open my $fh, '<:raw', $path or die "Cannot open file list $path: $!\n";
+ my %selected;
+ while (my $line = <$fh>) {
+ $line =~ s/\r?\n\z//;
+ $line =~ s/^\s+|\s+$//g;
+ next if $line eq '' || $line =~ /^#/;
+ $selected{canonical_file($line)} = 1;
+ }
+ close $fh or die "Cannot close file list $path: $!\n";
+ die "File list $path contains no test paths\n" unless keys %selected;
+ return \%selected;
+}
+
+sub filter_results_by_file {
+ my ($results, $selected) = @_;
+ my %filtered = map { $_ => $results->{$_} }
+ grep { $selected->{$_} } keys %$results;
+ return \%filtered;
+}
+
sub compare_results {
my ($baseline, $candidate) = @_;
my (@regressions, @improvements, @plan_changes, @missing, @added,
- @execution_issues, @zero_tap);
+ @execution_issues, @zero_tap, @truncated);
my ($baseline_ok, $candidate_ok, $baseline_total, $candidate_total) = (0, 0, 0, 0);
$baseline_ok += $_->{ok} for values %$baseline;
@@ -144,11 +217,19 @@ sub compare_results {
status => $result->{status},
ok => $result->{ok},
total => $result->{total},
- } if $result->{status} =~ /\A(?:error|timeout|incomplete)\z/;
+ } if $result->{status} !~ /\A(?:pass|partial|fail)\z/
+ || $result->{exit_code};
push @zero_tap, {
file => $file,
status => $result->{status},
} if $result->{total} == 0;
+ push @truncated, {
+ file => $file,
+ planned => $result->{planned},
+ actual => $result->{actual},
+ incomplete => $result->{incomplete},
+ } if $result->{incomplete}
+ || ($result->{planned} > 0 && $result->{actual} < $result->{planned});
}
for my $file (sort keys %all) {
my ($before, $after) = ($baseline->{$file}, $candidate->{$file});
@@ -190,6 +271,7 @@ sub compare_results {
added_files => \@added,
execution_issues => \@execution_issues,
zero_tap => \@zero_tap,
+ truncated => \@truncated,
};
}
@@ -200,11 +282,12 @@ sub print_report {
print "Candidate: $candidate_file\n";
printf "Passing assertions: %d/%d -> %d/%d (%+d passing, %+d planned)\n",
@{$summary}{qw(baseline_ok baseline_total candidate_ok candidate_total delta_ok delta_total)};
- printf "Files: %d -> %d; regressions=%d improvements=%d missing=%d added=%d execution-issues=%d zero-TAP=%d\n",
+ printf "Files: %d -> %d; regressions=%d improvements=%d missing=%d added=%d execution-issues=%d zero-TAP=%d truncated=%d\n",
$summary->{baseline_files}, $summary->{candidate_files},
scalar(@{$comparison->{regressions}}), scalar(@{$comparison->{improvements}}),
scalar(@{$comparison->{missing_files}}), scalar(@{$comparison->{added_files}}),
- scalar(@{$comparison->{execution_issues}}), scalar(@{$comparison->{zero_tap}});
+ scalar(@{$comparison->{execution_issues}}), scalar(@{$comparison->{zero_tap}}),
+ scalar(@{$comparison->{truncated}});
if (defined $comparison->{expected_files}
&& $summary->{candidate_files} != $comparison->{expected_files}) {
printf "EXPECTED FILE COUNT MISMATCH: expected %d, found %d\n",
@@ -232,6 +315,12 @@ sub print_report {
printf " %s: status=%s\n", @{$_}{qw(file status)}
for @{$comparison->{zero_tap}};
}
+ if (@{$comparison->{truncated}}) {
+ print "\nTRUNCATED OR INCOMPLETE TAP\n";
+ printf " %s: planned=%d actual=%d incomplete=%d\n",
+ @{$_}{qw(file planned actual incomplete)}
+ for @{$comparison->{truncated}};
+ }
}
sub has_invalid_candidate {
@@ -239,6 +328,7 @@ sub has_invalid_candidate {
return 1 if @{$comparison->{missing_files}};
return 1 if @{$comparison->{execution_issues}};
return 1 if @{$comparison->{zero_tap}};
+ return 1 if @{$comparison->{truncated}};
return 1 if defined($required_files)
&& $comparison->{summary}{candidate_files} != $required_files;
return 0;
diff --git a/dev/tools/extract_regex_pod_inventory.pl b/dev/tools/extract_regex_pod_inventory.pl
new file mode 100644
index 0000000000..c0d0a96d56
--- /dev/null
+++ b/dev/tools/extract_regex_pod_inventory.pl
@@ -0,0 +1,22 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use File::Spec;
+use FindBin;
+
+my $root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
+my $perl_root = $ENV{PERLONJAVA_PERL_ROOT} // File::Spec->catdir($root, 'perl5');
+my @pods = qw(perlreref.pod perlrecharclass.pod perlrequick.pod perlrepository.pod perlre.pod perlretut.pod perlrebackslash.pod);
+for my $pod (@pods) {
+ my $path = File::Spec->catfile($perl_root, 'pod', $pod);
+ open my $fh, '<', $path or die "Cannot read $path: $!\n";
+ my (@headings, %constructs);
+ while (my $line = <$fh>) {
+ push @headings, $1 if $line =~ /^=head\d\s+(.+)/;
+ $constructs{$1}++ while $line =~ /(\(\?[^\s)]+|\(\*[^\s)]+|\\[pPNKkQERX])/g;
+ }
+ close $fh;
+ print "FILE\t$pod\n";
+ print "HEADING\t$_\n" for @headings;
+ print "CONSTRUCT\t$_\n" for sort keys %constructs;
+}
diff --git a/dev/tools/generate_perl_unicode_data.pl b/dev/tools/generate_perl_unicode_data.pl
index 7ce60d6194..a558c2ffd5 100644
--- a/dev/tools/generate_perl_unicode_data.pl
+++ b/dev/tools/generate_perl_unicode_data.pl
@@ -15,6 +15,7 @@
my $check = 0;
my $list = 0;
+my $refresh = 0;
my @only;
my $unicode_root;
my $perl_root;
@@ -24,6 +25,7 @@
GetOptions(
'check' => \$check,
'list' => \$list,
+ 'refresh' => \$refresh,
'only=s@' => \@only,
'unicode-root=s' => \$unicode_root,
'perl-root=s' => \$perl_root,
@@ -32,11 +34,18 @@
'help' => \$help,
) or usage(2);
usage(0) if $help;
+die "--refresh cannot be combined with --check or --list\n" if $refresh && ($check || $list);
my $root = defined $root_override ? File::Spec->rel2abs($root_override) : repo_root($FindBin::Bin);
my $manifest = decode_json(read_raw($manifest_path));
+my $schema_version = $manifest->{schema_version} // 0;
die "$manifest_path has unsupported schema_version\n"
- unless ($manifest->{schema_version} // 0) == 1;
+ unless $schema_version == 1 || $schema_version == 2;
+my $current_checkout = $schema_version == 2
+ && ($manifest->{perl_source_policy} // '') eq 'current-checkout';
+die "$manifest_path schema 2 requires perl_source_policy current-checkout\n"
+ if $schema_version == 2 && !$current_checkout;
+die "--refresh requires schema 2 current-checkout provenance\n" if $refresh && !$current_checkout;
my @entries = @{$manifest->{generators} // []};
die "$manifest_path defines no generators\n" unless @entries;
@@ -102,16 +111,34 @@
version => $manifest->{unicode_version},
required => \@required,
);
+if ($refresh) {
+ $manifest->{version_sha256} = sha256_hex(read_raw(File::Spec->catfile($unicode_root, 'version')));
+ for my $entry (@entries) {
+ for my $field ('sources', 'perl_sources') {
+ my $base = $field eq 'sources' ? $unicode_root : ($perl_root // select_perl_root(repo_root => $root, unicode_root => $unicode_root, required => [keys %{$entry->{$field} // {}}]));
+ for my $relative (keys %{$entry->{$field} // {}}) {
+ $entry->{$field}{$relative} = sha256_hex(read_raw(File::Spec->catfile($base, split m{/}, $relative)));
+ }
+ }
+ }
+ for my $relative (keys %{$manifest->{shared_sources} // {}}) {
+ $manifest->{shared_sources}{$relative} = sha256_hex(read_raw(File::Spec->catfile($unicode_root, split m{/}, $relative)));
+ }
+ %source_hash = (%{$manifest->{shared_sources} // {}});
+ %perl_source_hash = ();
+ for my $entry (@entries) {
+ $source_hash{$_} = $entry->{sources}{$_} for keys %{$entry->{sources} // {}};
+ $perl_source_hash{$_} = $entry->{perl_sources}{$_} for keys %{$entry->{perl_sources} // {}};
+ }
+}
read_unicode_version(
path => File::Spec->catfile($unicode_root, 'version'),
expected => $manifest->{unicode_version},
sha256 => $manifest->{version_sha256},
);
for my $relative (sort keys %source_hash) {
- read_pinned_source(
- path => File::Spec->catfile($unicode_root, split m{/}, $relative),
- sha256 => $source_hash{$relative},
- );
+ read_pinned_source(path => File::Spec->catfile($unicode_root, split m{/}, $relative),
+ sha256 => $source_hash{$relative});
}
if (%perl_source_hash) {
$perl_root //= select_perl_root(
@@ -120,10 +147,21 @@
required => [sort keys %perl_source_hash],
);
for my $relative (sort keys %perl_source_hash) {
- read_pinned_source(
- path => File::Spec->catfile($perl_root, split m{/}, $relative),
- sha256 => $perl_source_hash{$relative},
- );
+ read_pinned_source(path => File::Spec->catfile($perl_root, split m{/}, $relative),
+ sha256 => $perl_source_hash{$relative});
+ }
+}
+if ($current_checkout) {
+ my $actual_commit = checkout_identity($perl_root, 'rev-parse', 'HEAD');
+ my $actual_version = perl_language_version($perl_root);
+ if ($refresh) {
+ $manifest->{perl_commit} = $actual_commit;
+ $manifest->{perl_version} = $actual_version;
+ } else {
+ die "Perl checkout revision mismatch: expected $manifest->{perl_commit}, found $actual_commit\n"
+ unless ($manifest->{perl_commit} // '') eq $actual_commit;
+ die "Perl checkout version mismatch: expected $manifest->{perl_version}, found $actual_version\n"
+ unless ($manifest->{perl_version} // '') eq $actual_version;
}
}
@@ -140,7 +178,8 @@
unless $first eq $second;
my $actual_hash = sha256_hex($first);
die "$entry->{name} generated SHA-256 mismatch: expected $entry->{output_sha256}, found $actual_hash\n"
- unless $actual_hash eq $entry->{output_sha256};
+ unless $refresh || $actual_hash eq $entry->{output_sha256};
+ $entry->{output_sha256} = $actual_hash if $refresh;
my $current = -f $output ? read_raw($output) : undef;
if (defined $current && $current eq $first) {
@@ -156,6 +195,7 @@
}
exit 1 if $failed;
+my @staged;
for my $item (@publication) {
my ($entry, $output, $bytes, $hash) = @$item;
my $directory = dirname($output);
@@ -165,9 +205,23 @@
close $temporary or die "Cannot close $temporary_path: $!\n";
die "$entry->{name} staged SHA-256 changed before publication\n"
unless sha256_hex(read_raw($temporary_path)) eq $hash;
+ push @staged, [$entry, $output, $temporary_path, $hash];
+}
+my $manifest_temporary_path;
+if ($refresh) {
+ my ($temporary, $temporary_path) = tempfile('.unicode-manifest-XXXXXX', DIR => dirname($manifest_path), UNLINK => 0);
+ print {$temporary} JSON::PP->new->canonical->pretty->encode($manifest) or die "Cannot write $temporary_path: $!\n";
+ close $temporary or die "Cannot close $temporary_path: $!\n";
+ $manifest_temporary_path = $temporary_path;
+}
+for my $item (@staged) {
+ my ($entry, $output, $temporary_path, $hash) = @$item;
rename $temporary_path, $output or die "Cannot publish $output: $!\n";
print "$entry->{name}: updated ($hash)\n";
}
+if ($refresh) {
+ rename $manifest_temporary_path, $manifest_path or die "Cannot publish $manifest_path: $!\n";
+}
sub run_generator {
my ($script, $source_root, $pinned_perl_root) = @_;
@@ -195,14 +249,37 @@ sub safe_relative_path {
return $path;
}
+sub checkout_identity {
+ my ($root, @arguments) = @_;
+ open my $pipe, '-|', 'git', '-C', $root, @arguments
+ or die "Cannot inspect Perl checkout $root: $!\n";
+ my $value = <$pipe> // '';
+ close $pipe or die "Cannot inspect Perl checkout $root\n";
+ chomp $value;
+ die "Perl checkout $root has no Git identity\n" unless length $value;
+ return $value;
+}
+
+sub perl_language_version {
+ my ($root) = @_;
+ my $text = read_raw(File::Spec->catfile($root, 'patchlevel.h'));
+ my ($revision) = $text =~ /^#define\s+PERL_REVISION\s+(\d+)/m;
+ my ($version) = $text =~ /^#define\s+PERL_VERSION\s+(\d+)/m;
+ my ($subversion) = $text =~ /^#define\s+PERL_SUBVERSION\s+(\d+)/m;
+ die "Cannot derive Perl language version from $root/patchlevel.h\n"
+ unless defined $revision && defined $version && defined $subversion;
+ return join '.', $revision, $version, $subversion;
+}
+
sub usage {
my ($exit) = @_;
print <<'USAGE';
Usage: perl dev/tools/generate_perl_unicode_data.pl [options]
--check verify generated files without changing them
+ --refresh regenerate outputs and transactionally refresh schema-2 provenance
--only NAME process one named generator (repeatable)
--unicode-root PATH use an explicit Perl unicore source directory
- --perl-root PATH use an explicit pinned Perl source checkout
+ --perl-root PATH use an explicit selected/current Perl source checkout
--root PATH resolve manifest paths under PATH (testing/automation)
--list list the complete generated-data inventory
--help show this help
diff --git a/dev/tools/generate_perl_unicode_scalar_name_data.pl b/dev/tools/generate_perl_unicode_scalar_name_data.pl
new file mode 100644
index 0000000000..49bd7ad7a9
--- /dev/null
+++ b/dev/tools/generate_perl_unicode_scalar_name_data.pl
@@ -0,0 +1,175 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Digest::SHA qw(sha256_hex);
+use Encode qw(encode);
+use File::Spec;
+use FindBin;
+use MIME::Base64 qw(encode_base64);
+
+# Unicode data source copyright:
+# © 2025 Unicode®, Inc.
+# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in
+# the U.S. and other countries.
+# For terms of use and license, see https://www.unicode.org/terms_of_use.html
+
+my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
+my $source = $ENV{PERLONJAVA_NAME_PL}
+ // File::Spec->catfile($repo_root, 'src', 'main', 'perl', 'lib', 'unicore', 'Name.pl');
+open my $source_fh, '<:raw', $source or die "Cannot read $source: $!\n";
+local $/;
+my $source_text = <$source_fh>;
+close $source_fh or die "Cannot close $source: $!\n";
+my $source_sha256 = sha256_hex($source_text);
+$source_text =~ /return <<'END';\r?\n(.*)\r?\nEND\r?\n?\z/s
+ or die "$source does not have the expected generated Name.pl format\n";
+my $records = $1;
+$records =~ s/\r\n/\n/g;
+
+my (%first_code_point, @duplicate_names);
+for my $record (split /\n\n/, $records) {
+ my ($code_points, $name, @extra) = split /\n/, $record;
+ die "Malformed Name.pl record: $record\n"
+ if !defined $name || @extra || !length $name;
+ next if $code_points =~ /\s/; # Multi-code-point sequences have a separate owner.
+ die "Invalid scalar code point '$code_points' for '$name'\n"
+ unless $code_points =~ /\A[0-9A-F]{5,6}\z/;
+ die "Non-ASCII or tab character in scalar name '$name'\n"
+ unless $name =~ /\A[\x20-\x7E]+\z/ && $name !~ /\t/;
+ if (exists $first_code_point{$name}) {
+ push @duplicate_names, $name;
+ next; # Match Name.pl's anchored search: the first name row wins.
+ }
+ $first_code_point{$name} = hex $code_points;
+}
+
+my @names = sort keys %first_code_point;
+die "Name.pl contains no scalar names\n" unless @names;
+my $binary = encode_varint(scalar @names);
+my $previous = '';
+for my $name (@names) {
+ my $prefix_length = common_prefix_length($previous, $name);
+ my $suffix = substr($name, $prefix_length);
+ $binary .= encode_varint($prefix_length)
+ . encode_varint(length $suffix)
+ . encode_varint($first_code_point{$name})
+ . encode('ASCII', $suffix);
+ $previous = $name;
+}
+my $encoded = encode_base64($binary, '');
+my @chunks = $encoded =~ /.{1,8000}/g;
+
+print <<'HEADER';
+/*
+ * Generated from the current imported Perl unicore/Name.pl.
+ * Do not edit manually; run dev/tools/generate_perl_unicode_scalar_name_data.pl.
+ *
+ * Name.pl is generated by Perl's mktables from the Unicode Character Database.
+ * Duplicate-name policy matches Perl's anchored table search: the first row wins.
+ * Distinct names for the same code point are all retained as Unicode aliases.
+ *
+ * Unicode data source copyright:
+ * © 2025 Unicode®, Inc.
+ * Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in
+ * the U.S. and other countries.
+ * For terms of use and license, see https://www.unicode.org/terms_of_use.html
+ */
+package org.perlonjava.runtime.regex;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+public final class PerlUnicodeScalarNameData {
+HEADER
+print " public static final String SOURCE_SHA256 = \"$source_sha256\";\n";
+print " public static final int ENTRY_COUNT = ", scalar(@names), ";\n";
+print " public static final int DUPLICATE_NAME_COUNT = ", scalar(@duplicate_names), ";\n\n";
+print " private static final String[] ENCODED = {\n";
+print qq{ "$_",\n} for @chunks;
+print <<'FOOTER';
+ };
+
+ private static final String[] NAMES;
+ private static final int[] CODE_POINTS;
+
+ static {
+ byte[] data = Base64.getDecoder().decode(String.join("", ENCODED));
+ Cursor cursor = new Cursor();
+ int count = readVarint(data, cursor);
+ if (count != ENTRY_COUNT) {
+ throw new IllegalStateException("Invalid generated Perl scalar-name count: " + count);
+ }
+ NAMES = new String[count];
+ CODE_POINTS = new int[count];
+ String previous = "";
+ for (int index = 0; index < count; index++) {
+ int prefixLength = readVarint(data, cursor);
+ int suffixLength = readVarint(data, cursor);
+ int codePoint = readVarint(data, cursor);
+ if (prefixLength > previous.length()
+ || suffixLength > data.length - cursor.offset) {
+ throw new IllegalStateException("Invalid generated Perl scalar-name data");
+ }
+ String suffix = new String(
+ data, cursor.offset, suffixLength, StandardCharsets.US_ASCII);
+ cursor.offset += suffixLength;
+ String name = previous.substring(0, prefixLength) + suffix;
+ NAMES[index] = name;
+ CODE_POINTS[index] = codePoint;
+ previous = name;
+ }
+ if (cursor.offset != data.length) {
+ throw new IllegalStateException("Trailing generated Perl scalar-name data");
+ }
+ }
+
+ public static int codePoint(String name) {
+ if (name == null) return -1;
+ int index = Arrays.binarySearch(NAMES, name);
+ return index < 0 ? -1 : CODE_POINTS[index];
+ }
+
+ static int entryCount() {
+ return NAMES.length;
+ }
+
+ private static int readVarint(byte[] data, Cursor cursor) {
+ int value = 0;
+ int shift = 0;
+ while (cursor.offset < data.length && shift < 35) {
+ int current = data[cursor.offset++] & 0xff;
+ value |= (current & 0x7f) << shift;
+ if ((current & 0x80) == 0) return value;
+ shift += 7;
+ }
+ throw new IllegalStateException("Invalid generated Perl scalar-name varint");
+ }
+
+ private static final class Cursor {
+ private int offset;
+ }
+
+ private PerlUnicodeScalarNameData() {
+ }
+}
+FOOTER
+
+sub common_prefix_length {
+ my ($left, $right) = @_;
+ my $limit = length($left) < length($right) ? length($left) : length($right);
+ my $offset = 0;
+ $offset++ while $offset < $limit
+ && substr($left, $offset, 1) eq substr($right, $offset, 1);
+ return $offset;
+}
+
+sub encode_varint {
+ my ($value) = @_;
+ my $encoded = '';
+ while ($value >= 0x80) {
+ $encoded .= chr(($value & 0x7f) | 0x80);
+ $value >>= 7;
+ }
+ return $encoded . chr($value);
+}
diff --git a/dev/tools/generate_regex_test_ledger.pl b/dev/tools/generate_regex_test_ledger.pl
new file mode 100755
index 0000000000..f5140fb2e4
--- /dev/null
+++ b/dev/tools/generate_regex_test_ledger.pl
@@ -0,0 +1,176 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use File::Find qw(find);
+use File::Spec;
+use Getopt::Long qw(GetOptions);
+use JSON::PP;
+
+my $tests_root = 'perl5_t/t';
+my $unit_root = 'src/test/resources/unit';
+my @references;
+my $runner_list;
+my $output;
+my $help;
+GetOptions(
+ 'tests-root=s' => \$tests_root,
+ 'unit-root=s' => \$unit_root,
+ 'reference=s@' => \@references,
+ 'runner-list=s' => \$runner_list,
+ 'output=s' => \$output,
+ 'help' => \$help,
+) or usage(2);
+usage(0) if $help;
+usage(2) if @ARGV;
+
+@references = (
+ 'docs/reference/feature-matrix.md',
+ 'dev/design/phase36-regex-parity.md',
+ 'dev/tools/compare_test_results.pl',
+) unless @references;
+
+my @core = files_below(File::Spec->catdir($tests_root, 're'));
+my @auxiliary = grep { regex_bearing($_) }
+ map { files_below(File::Spec->catdir($tests_root, $_)) } qw(op uni);
+
+my (%documented, @unresolved);
+for my $reference (@references) {
+ next unless -f $reference;
+ my $contents = read_file($reference);
+ while ($contents =~ /`([^`\n]*?\.t)`/g) {
+ my $spelling = $1;
+ my @resolved = resolve_reference($spelling, $tests_root, $unit_root);
+ if (@resolved) {
+ $documented{$_} = 1 for @resolved;
+ } else {
+ push @unresolved, { source => $reference, spelling => $spelling };
+ }
+ }
+}
+
+my %runner = map { $_ => 1 } (@core, @auxiliary,
+ grep { index($_, "$tests_root/") == 0 } keys %documented);
+my @runner = sort keys %runner;
+my @unit_gates = sort grep { index($_, "$unit_root/") == 0 } keys %documented;
+
+my @thread_pairs;
+for my $thread (grep { /_thr\.t\z/ } @core) {
+ (my $direct = $thread) =~ s/_thr\.t\z/.t/;
+ push @thread_pairs, {
+ direct => $direct,
+ thread => $thread,
+ direct_exists => -f $direct ? JSON::PP::true : JSON::PP::false,
+ };
+}
+
+my $ledger = {
+ schema_version => 1,
+ policy => 'current latest upstream perl5 checkout; no pinned revision',
+ summary => {
+ core_re_files => scalar @core,
+ auxiliary_regex_files => scalar @auxiliary,
+ runner_files => scalar @runner,
+ documented_unit_gates => scalar @unit_gates,
+ direct_thread_pairs => scalar @thread_pairs,
+ unresolved_references => scalar @unresolved,
+ },
+ core_re_files => \@core,
+ auxiliary_regex_files => \@auxiliary,
+ runner_files => \@runner,
+ documented_unit_gates => \@unit_gates,
+ direct_thread_pairs => \@thread_pairs,
+ unresolved_references => \@unresolved,
+};
+
+write_lines($runner_list, @runner) if defined $runner_list;
+my $json = JSON::PP->new->canonical->pretty->encode($ledger);
+if (defined $output) {
+ write_file($output, $json);
+} else {
+ print $json;
+}
+
+sub usage {
+ my ($status) = @_;
+ print <<'USAGE';
+Usage: generate_regex_test_ledger.pl [OPTIONS]
+
+Mechanically derive the current Phase 36 regex test ledger from the complete
+perl5_t/t/re corpus, regex-bearing op/ and uni/ files, and documented test
+references. The checkout is always treated as current upstream input; no Perl
+revision or historical checksum is encoded.
+
+Options:
+ --tests-root DIR imported Perl test root (default: perl5_t/t)
+ --unit-root DIR repository unit-test root
+ --reference FILE documentation/tool reference source (repeatable)
+ --runner-list FILE write one runnable imported test path per line
+ --output FILE write canonical JSON instead of stdout
+ --help show this help
+USAGE
+ exit $status;
+}
+
+sub files_below {
+ my ($root) = @_;
+ return () unless -d $root;
+ my @files;
+ find({
+ no_chdir => 1,
+ wanted => sub {
+ return unless -f $_ && /\.t\z/;
+ my $path = $File::Find::name;
+ $path =~ s{\\}{/}g;
+ push @files, $path;
+ },
+ }, $root);
+ return sort @files;
+}
+
+sub regex_bearing {
+ my ($path) = @_;
+ my $source = read_file($path);
+ return 1 if $source =~ /(?:=~|!~)/;
+ return 1 if $source =~ /\b(?:qr|split|pos)\b/;
+ return 1 if $source =~ /\buse\s+re\b/;
+ return 1 if $source =~ /\\[pPNX]\{/;
+ return 0;
+}
+
+sub resolve_reference {
+ my ($spelling, $tests, $units) = @_;
+ $spelling =~ s{\\}{/}g;
+ my @candidates;
+ if ($spelling =~ m{\Aperl5_t/t/}) {
+ push @candidates, $spelling;
+ } elsif ($spelling =~ m{\A(?:re|op|uni)/}) {
+ push @candidates, "$tests/$spelling";
+ } elsif ($spelling !~ m{/}) {
+ for my $root ($tests, $units) {
+ push @candidates, grep { /\Q$spelling\E\z/ } files_below($root);
+ }
+ }
+ my %seen;
+ return sort grep { -f $_ && !$seen{$_}++ } @candidates;
+}
+
+sub read_file {
+ my ($path) = @_;
+ open my $fh, '<:raw', $path or die "Cannot read $path: $!\n";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $path: $!\n";
+ return $contents;
+}
+
+sub write_lines {
+ my ($path, @lines) = @_;
+ write_file($path, join('', map { "$_\n" } @lines));
+}
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>:raw', $path or die "Cannot write $path: $!\n";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!\n";
+}
diff --git a/dev/tools/perl_test_runner.pl b/dev/tools/perl_test_runner.pl
index 17246ddada..dd96ff0de4 100755
--- a/dev/tools/perl_test_runner.pl
+++ b/dev/tools/perl_test_runner.pl
@@ -428,17 +428,6 @@ sub run_single_test {
| base/lex.t
| comp/parser.t }x
? "warn" : "";
- local $ENV{JPERL_REGEX_CODE_BLOCK_NOOP} = $test_file =~ m{
- re/pat_advanced.t
- | re/overload.t
- | re/regexp.t
- | re/regexp_noamp.t
- | re/regexp_normal.t
- | re/regexp_notrie.t
- | re/regexp_qr.t
- | re/regexp_qr_embed.t
- | re/regexp_trielist.t }x
- ? "1" : "";
local $ENV{JPERL_OPTS} = $test_file =~ m{
re/pat.t
| op/repeat.t
diff --git a/dev/tools/perl_unicode_data_generators.json b/dev/tools/perl_unicode_data_generators.json
index fa54fcd1fb..436eae6f2e 100644
--- a/dev/tools/perl_unicode_data_generators.json
+++ b/dev/tools/perl_unicode_data_generators.json
@@ -1,111 +1,132 @@
{
- "schema_version": 1,
- "unicode_version": "17.0.0",
- "perl_version": "5.44.0",
- "perl_commit": "de80c8ecd40c6d5b677847699e5482b44bc748c6",
- "version_sha256": "8c30575264b2772c7a69c5bb6069a28f0e0a7a0df735871bde2d99ee674316ac",
- "shared_sources": {
- "PropertyAliases.txt": "4441f573caf952ffece1d7c892e7715bd7136dfc26f96eb6f268bf1e474715fb",
- "PropValueAliases.txt": "670d2bebb48649c04fabfbf033308073dcff47946324a8033237254c048b3b01"
- },
- "generators": [
- {
- "name": "age",
- "generator": "dev/tools/generate_perl_unicode_age_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeAgeData.java",
- "output_sha256": "f4ce8ee756e186ff401da5eae03d5ba13581efc169a5bc4f162a533f58ee2bbf",
- "sources": {"DAge.txt": "f8ecdf768bdc210f201abd271d9bc587825618a86a7046a8146cc816393f1998"}
- },
- {
- "name": "bidi-class",
- "generator": "dev/tools/generate_perl_unicode_bidi_class_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeBidiClassData.java",
- "output_sha256": "a762ff311149fd40c84f2390fd2a067a90d6172f7f7f0a652a34079bb4b2f70b",
- "sources": {"extracted/DBidiClass.txt": "4867b4b7f0731ed1bfcd34cc6251211ff1542541fce0734b6fbda139ee80b3a4"}
- },
- {
- "name": "block",
- "generator": "dev/tools/generate_perl_unicode_block_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeBlockData.java",
- "output_sha256": "8bba409ec13fe03da3c47545865917fb251136e1bc531c055deb12849c36b255",
- "sources": {"Blocks.txt": "c0edefaf1a19771e830a82735472716af6bf3c3975f6c2a23ffbe2580fbbcb15"}
- },
- {
- "name": "combining-class",
- "generator": "dev/tools/generate_perl_unicode_combining_class_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeCombiningClassData.java",
- "output_sha256": "9d0c99f58d8e80217914cd30877d358af2676e916dcb2e6bcc865adbc9522b3a",
- "sources": {"extracted/DCombiningClass.txt": "191463abfbd202703c6fd6776a92a23ac44ec65e0476a7f95aa91ca492cef29b"}
- },
- {
- "name": "case-fold",
- "generator": "dev/tools/generate_perl_unicode_case_fold_data.pl",
- "output": "third_party/joni/src/org/joni/PerlUnicodeCaseFoldData.java",
- "output_sha256": "4bab60ee50b08d24b9b69863688c2862c724fb58ba76a749974af074b19c3b82",
- "sources": {
- "CaseFolding.txt": "ff8d8fefbf123574205085d6714c36149eb946d717a0c585c27f0f4ef58c4183",
- "SpecialCasing.txt": "efc25faf19de21b92c1194c111c932e03d2a5eaf18194e33f1156e96de4c9588"
+ "generators" : [
+ {
+ "generator" : "dev/tools/generate_perl_unicode_age_data.pl",
+ "name" : "age",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeAgeData.java",
+ "output_sha256" : "59fcaa610b14c0539069ee385564650f782277ad587775f2f167a07946fc427b",
+ "sources" : {
+ "DAge.txt" : "f8ecdf768bdc210f201abd271d9bc587825618a86a7046a8146cc816393f1998"
+ }
},
- "perl_sources": {
- "regen/regcharclass_multi_char_folds.pl": "b2f896452d2b30da3e04800f478c60c1fd0b03d6b668689b020f1e3cf1f1cdd9",
- "regen/mk_invlists.pl": "20a6e3d507a66f4594586485568134873485b08e23383f3dc4e6b3047569267b",
- "lib/unicore/mktables": "e3ac360c03d18779fea6d6497fbbe53798135da55e3764d3c9f90a79bbf7e8b5",
- "charclass_invlists.inc": "c83c6471e7c188f21a20c6285af83f57d0bd09392f3243e4cc3743f0a5d5052c",
- "regcharclass.h": "b6005d471764b31d04063ccd561c88d165a2a30f9bae9b75172eb7b59672754e",
- "regen/regcharclass.pl": "852a8a7814f08a155d79fead2656fe2b4450ab17a2bce8a1127016119c9c3bc3"
- }
- },
- {
- "name": "decomposition-type",
- "generator": "dev/tools/generate_perl_unicode_decomposition_type_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeDecompositionTypeData.java",
- "output_sha256": "9847d3b794c45557ff6f61618f2b9271633334ab72c3b1c35c4585f174e0a629",
- "sources": {"extracted/DDecompositionType.txt": "f44e5ceaf40edc1fe06ea0404e8bebc7d356dcc38aac076543b6874008a06e3e"}
- },
- {
- "name": "east-asian-width",
- "generator": "dev/tools/generate_perl_unicode_east_asian_width_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeEastAsianWidthData.java",
- "output_sha256": "7878246c37247c85a5bfa53b5392f1decf58552ed2fa41aab9a53fd9e61ed87e",
- "sources": {"extracted/DEastAsianWidth.txt": "0b5523a2217cb318d20b329a05d31eec5af5686ba09d263b85bb75a28989a3a8"}
- },
- {
- "name": "general-category",
- "generator": "dev/tools/generate_perl_unicode_general_category_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeGeneralCategoryData.java",
- "output_sha256": "89bc29ef27536a2aead54ab6a063cbaca878d0545e6bce071078eafe7a259211",
- "sources": {"extracted/DGeneralCategory.txt": "d62e5bab70ca74f099343f71224fa051cb1fdd61a1ab45c0488c44cfc0b6102e"}
- },
- {
- "name": "joining-group",
- "generator": "dev/tools/generate_perl_unicode_joining_group_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeJoiningGroupData.java",
- "output_sha256": "37a10623c068620d0eb0a7a60f8c68ef313b8518c4bd3207e17530fd3cf412e5",
- "sources": {"extracted/DJoinGroup.txt": "bb67e0c00b88acfa5be633967b66b23326844a86e49c6fde7b57960d3af66cae"}
- },
- {
- "name": "numeric-value",
- "generator": "dev/tools/generate_perl_unicode_numeric_value_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeNumericValueData.java",
- "output_sha256": "d040f3f91da08274a90c608f3cbb2202b5ca901c729898b486fd2f1647660b17",
- "sources": {"extracted/DNumValues.txt": "139b976bdc288be01c80f018523da769cf2845109b5a7f0f8a432db64bfedcfa"}
- },
- {
- "name": "script",
- "generator": "dev/tools/generate_perl_unicode_script_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeScriptData.java",
- "output_sha256": "e27079c728d4817a7edd5f3ec7f7c2a009d96909187a84b6a2f15c53933904f6",
- "sources": {
- "Scripts.txt": "9f5e50d3abaee7d6ce09480f325c706f485ae3240912527e651954d2d6b035bf",
- "ScriptExtensions.txt": "ec2107e58825a1586acee8e0911ce18260394ac8b87e535ca325f1ccbeb06bc6"
+ {
+ "generator" : "dev/tools/generate_perl_unicode_bidi_class_data.pl",
+ "name" : "bidi-class",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeBidiClassData.java",
+ "output_sha256" : "a762ff311149fd40c84f2390fd2a067a90d6172f7f7f0a652a34079bb4b2f70b",
+ "sources" : {
+ "extracted/DBidiClass.txt" : "4867b4b7f0731ed1bfcd34cc6251211ff1542541fce0734b6fbda139ee80b3a4"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_block_data.pl",
+ "name" : "block",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeBlockData.java",
+ "output_sha256" : "8bba409ec13fe03da3c47545865917fb251136e1bc531c055deb12849c36b255",
+ "sources" : {
+ "Blocks.txt" : "c0edefaf1a19771e830a82735472716af6bf3c3975f6c2a23ffbe2580fbbcb15"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_combining_class_data.pl",
+ "name" : "combining-class",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeCombiningClassData.java",
+ "output_sha256" : "9d0c99f58d8e80217914cd30877d358af2676e916dcb2e6bcc865adbc9522b3a",
+ "sources" : {
+ "extracted/DCombiningClass.txt" : "191463abfbd202703c6fd6776a92a23ac44ec65e0476a7f95aa91ca492cef29b"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_case_fold_data.pl",
+ "name" : "case-fold",
+ "output" : "third_party/joni/src/org/joni/PerlUnicodeCaseFoldData.java",
+ "output_sha256" : "4bab60ee50b08d24b9b69863688c2862c724fb58ba76a749974af074b19c3b82",
+ "perl_sources" : {
+ "charclass_invlists.inc" : "c83c6471e7c188f21a20c6285af83f57d0bd09392f3243e4cc3743f0a5d5052c",
+ "lib/unicore/mktables" : "e3ac360c03d18779fea6d6497fbbe53798135da55e3764d3c9f90a79bbf7e8b5",
+ "regcharclass.h" : "b6005d471764b31d04063ccd561c88d165a2a30f9bae9b75172eb7b59672754e",
+ "regen/mk_invlists.pl" : "20a6e3d507a66f4594586485568134873485b08e23383f3dc4e6b3047569267b",
+ "regen/regcharclass.pl" : "852a8a7814f08a155d79fead2656fe2b4450ab17a2bce8a1127016119c9c3bc3",
+ "regen/regcharclass_multi_char_folds.pl" : "b2f896452d2b30da3e04800f478c60c1fd0b03d6b668689b020f1e3cf1f1cdd9"
+ },
+ "sources" : {
+ "CaseFolding.txt" : "ff8d8fefbf123574205085d6714c36149eb946d717a0c585c27f0f4ef58c4183",
+ "SpecialCasing.txt" : "efc25faf19de21b92c1194c111c932e03d2a5eaf18194e33f1156e96de4c9588"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_decomposition_type_data.pl",
+ "name" : "decomposition-type",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeDecompositionTypeData.java",
+ "output_sha256" : "9847d3b794c45557ff6f61618f2b9271633334ab72c3b1c35c4585f174e0a629",
+ "sources" : {
+ "extracted/DDecompositionType.txt" : "f44e5ceaf40edc1fe06ea0404e8bebc7d356dcc38aac076543b6874008a06e3e"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_east_asian_width_data.pl",
+ "name" : "east-asian-width",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeEastAsianWidthData.java",
+ "output_sha256" : "7878246c37247c85a5bfa53b5392f1decf58552ed2fa41aab9a53fd9e61ed87e",
+ "sources" : {
+ "extracted/DEastAsianWidth.txt" : "0b5523a2217cb318d20b329a05d31eec5af5686ba09d263b85bb75a28989a3a8"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_general_category_data.pl",
+ "name" : "general-category",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeGeneralCategoryData.java",
+ "output_sha256" : "89bc29ef27536a2aead54ab6a063cbaca878d0545e6bce071078eafe7a259211",
+ "sources" : {
+ "extracted/DGeneralCategory.txt" : "d62e5bab70ca74f099343f71224fa051cb1fdd61a1ab45c0488c44cfc0b6102e"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_joining_group_data.pl",
+ "name" : "joining-group",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeJoiningGroupData.java",
+ "output_sha256" : "37a10623c068620d0eb0a7a60f8c68ef313b8518c4bd3207e17530fd3cf412e5",
+ "sources" : {
+ "extracted/DJoinGroup.txt" : "bb67e0c00b88acfa5be633967b66b23326844a86e49c6fde7b57960d3af66cae"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_numeric_value_data.pl",
+ "name" : "numeric-value",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeNumericValueData.java",
+ "output_sha256" : "d040f3f91da08274a90c608f3cbb2202b5ca901c729898b486fd2f1647660b17",
+ "sources" : {
+ "extracted/DNumValues.txt" : "139b976bdc288be01c80f018523da769cf2845109b5a7f0f8a432db64bfedcfa"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_script_data.pl",
+ "name" : "script",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeScriptData.java",
+ "output_sha256" : "e27079c728d4817a7edd5f3ec7f7c2a009d96909187a84b6a2f15c53933904f6",
+ "sources" : {
+ "ScriptExtensions.txt" : "ec2107e58825a1586acee8e0911ce18260394ac8b87e535ca325f1ccbeb06bc6",
+ "Scripts.txt" : "9f5e50d3abaee7d6ce09480f325c706f485ae3240912527e651954d2d6b035bf"
+ }
+ },
+ {
+ "generator" : "dev/tools/generate_perl_unicode_unikemet_data.pl",
+ "name" : "unikemet",
+ "output" : "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeUnikemetData.java",
+ "output_sha256" : "4adc6e3549ee2d71e4ec6b7f70b5addf1672aac2ab68168ee3b43a5f82de4546",
+ "sources" : {
+ "Unikemet.txt" : "76a3081265e6eb673873f9c93d6f36062e82c7ed027c5c1a592accfbe48c20a5"
+ }
}
- },
- {
- "name": "unikemet",
- "generator": "dev/tools/generate_perl_unicode_unikemet_data.pl",
- "output": "src/main/java/org/perlonjava/runtime/regex/PerlUnicodeUnikemetData.java",
- "output_sha256": "4adc6e3549ee2d71e4ec6b7f70b5addf1672aac2ab68168ee3b43a5f82de4546",
- "sources": {"Unikemet.txt": "76a3081265e6eb673873f9c93d6f36062e82c7ed027c5c1a592accfbe48c20a5"}
- }
- ]
+ ],
+ "perl_commit" : "fa00284182335e5fed578460346efb88843f08da",
+ "perl_source_policy" : "current-checkout",
+ "perl_version" : "5.45.2",
+ "schema_version" : 2,
+ "shared_sources" : {
+ "PropValueAliases.txt" : "670d2bebb48649c04fabfbf033308073dcff47946324a8033237254c048b3b01",
+ "PropertyAliases.txt" : "4441f573caf952ffece1d7c892e7715bd7136dfc26f96eb6f268bf1e474715fb"
+ },
+ "unicode_version" : "17.0.0",
+ "version_sha256" : "8c30575264b2772c7a69c5bb6069a28f0e0a7a0df735871bde2d99ee674316ac"
}
diff --git a/dev/tools/run_phase36_regex_acceptance.pl b/dev/tools/run_phase36_regex_acceptance.pl
new file mode 100644
index 0000000000..4a72888980
--- /dev/null
+++ b/dev/tools/run_phase36_regex_acceptance.pl
@@ -0,0 +1,373 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use Cwd qw(abs_path getcwd);
+use Digest::SHA qw(sha256_hex);
+use File::Basename qw(dirname);
+use File::Spec;
+use Getopt::Long qw(GetOptions);
+use JSON::PP;
+
+my %option = (
+ perl => $^X,
+ ledger_tool => 'dev/tools/generate_regex_test_ledger.pl',
+ runner_tool => 'dev/tools/perl_test_runner.pl',
+ comparator_tool => 'dev/tools/compare_test_results.pl',
+ packaging_tool => 'dev/tools/verify-joni-packaging.pl',
+ jperl => './jperl',
+ timeout => 300,
+ version_timeout => 30,
+ jobs => 5,
+);
+my $help;
+GetOptions(
+ 'baseline=s' => \$option{baseline},
+ 'artifact-dir=s' => \$option{artifact_dir},
+ 'jar=s' => \$option{jar},
+ 'sbom=s' => \$option{sbom},
+ 'perl=s' => \$option{perl},
+ 'source-dir=s' => \$option{source_dir},
+ 'perl5-dir=s' => \$option{perl5_dir},
+ 'jperl=s' => \$option{jperl},
+ 'timeout=i' => \$option{timeout},
+ 'version-timeout=i' => \$option{version_timeout},
+ 'jobs=i' => \$option{jobs},
+ 'ledger-tool=s' => \$option{ledger_tool},
+ 'runner-tool=s' => \$option{runner_tool},
+ 'comparator-tool=s' => \$option{comparator_tool},
+ 'packaging-tool=s' => \$option{packaging_tool},
+ 'prepare-only!' => \$option{prepare_only},
+ 'help' => \$help,
+) or usage(2);
+usage(0) if $help;
+usage(2) if @ARGV;
+
+for my $required (qw(baseline artifact_dir jar sbom)) {
+ die "--$required is required\n" unless defined $option{$required}
+ && length $option{$required};
+}
+die "--timeout must be positive\n" unless $option{timeout} > 0;
+die "--version-timeout must be positive\n" unless $option{version_timeout} > 0;
+die "--jobs must be positive\n" unless $option{jobs} > 0;
+
+my $root = $option{source_dir} // getcwd();
+$option{perl5_dir} //= File::Spec->catdir($root, 'perl5');
+validate_file($option{baseline}, 'PR-958 baseline');
+validate_file($option{jar}, 'standalone JAR');
+validate_file($option{sbom}, 'SBOM');
+validate_directory($option{artifact_dir}, 'artifact directory');
+for my $tool (qw(ledger_tool runner_tool comparator_tool packaging_tool)) {
+ validate_file($option{$tool}, "$tool path");
+}
+validate_program($option{perl}, 'Perl executable');
+validate_program($option{jperl}, 'jperl executable');
+
+my $start_sha = git_sha($root);
+my $start_state = tracked_state($root);
+my $perl5_sha = git_sha($option{perl5_dir});
+my %path = map { $_ => File::Spec->catfile($option{artifact_dir}, $_) } qw(
+ regex-ledger.json
+ regex-files.txt
+ jvm-results.json
+ interpreter-results.json
+ jvm-comparison.json
+ interpreter-comparison.json
+ ledger.log
+ jvm-runner.log
+ interpreter-runner.log
+ jvm-comparison.log
+ interpreter-comparison.log
+ packaging.log
+ jperl-version.log
+ manifest.json
+);
+for my $name (keys %path) {
+ die "Refusing to overwrite retained artifact $path{$name}\n" if -e $path{$name};
+}
+
+my @commands;
+my %statuses;
+run_logged(
+ name => 'jperl-version', command => [$option{jperl}, '-v'],
+ log => $path{'jperl-version.log'}, commands => \@commands, statuses => \%statuses,
+ timeout => $option{version_timeout},
+);
+my $runner_sha = parse_runner_sha($path{'jperl-version.log'}, $start_sha);
+run_logged(
+ name => 'ledger',
+ command => [$option{perl}, $option{ledger_tool},
+ '--runner-list', $path{'regex-files.txt'}, '--output', $path{'regex-ledger.json'}],
+ log => $path{'ledger.log'},
+ commands => \@commands, statuses => \%statuses,
+);
+
+my $ledger = load_json($path{'regex-ledger.json'}, 'ledger');
+die "Ledger has unresolved references\n" if ($ledger->{summary}{unresolved_references} // 0) != 0;
+my @files = load_file_list($path{'regex-files.txt'});
+die "Ledger runner list is empty\n" unless @files;
+my $expected_files = scalar @files;
+
+my @runner_common = ($option{perl}, $option{runner_tool},
+ '--jperl', $option{jperl}, '--timeout', $option{timeout}, '--jobs', $option{jobs});
+run_logged(
+ name => 'jvm-runner',
+ command => [@runner_common, '--output', $path{'jvm-results.json'}, @files],
+ log => $path{'jvm-runner.log'},
+ environment => { JPERL_INTERPRETER => undef },
+ commands => \@commands, statuses => \%statuses,
+);
+run_logged(
+ name => 'interpreter-runner',
+ command => [@runner_common, '--output', $path{'interpreter-results.json'}, @files],
+ log => $path{'interpreter-runner.log'},
+ environment => { JPERL_INTERPRETER => 1 },
+ commands => \@commands, statuses => \%statuses,
+);
+
+for my $leg (
+ ['jvm', $path{'jvm-results.json'}, $path{'jvm-comparison.json'}],
+ ['interpreter', $path{'interpreter-results.json'}, $path{'interpreter-comparison.json'}],
+) {
+ run_logged(
+ name => "$leg->[0]-comparison",
+ command => [$option{perl}, $option{comparator_tool},
+ '--fail-on-regression', '--fail-on-invalid',
+ '--normalize-pr958-artifacts', '--expected-files', $expected_files,
+ '--file-list', $path{'regex-files.txt'}, '--output', $leg->[2],
+ $option{baseline}, $leg->[1]],
+ log => $path{"$leg->[0]-comparison.log"},
+ commands => \@commands, statuses => \%statuses,
+ );
+ verify_comparison($leg->[2], $expected_files);
+}
+run_logged(
+ name => 'packaging',
+ command => [$option{perl}, $option{packaging_tool}, $option{jar}, $option{sbom}],
+ log => $path{'packaging.log'}, commands => \@commands, statuses => \%statuses,
+);
+
+my $final_sha = git_sha($root);
+die "Checkout HEAD changed during acceptance: $start_sha -> $final_sha\n"
+ unless $start_sha eq $final_sha;
+die "Tracked source state changed during acceptance\n"
+ unless tracked_state($root) eq $start_state;
+
+my @retained = sort grep { -f $path{$_} } keys %path;
+my $manifest = {
+ schema_version => 1,
+ mode => $option{prepare_only} ? 'prepare-only' : 'acceptance',
+ source => {
+ starting_sha => $start_sha,
+ final_sha => $final_sha,
+ perl5_sha_as_provenance => $perl5_sha,
+ tracked_state_signature => $start_state,
+ },
+ baseline => abs_file($option{baseline}),
+ artifact_directory => abs_path($option{artifact_dir}),
+ expected_files => $expected_files,
+ verified_runner_sha => $runner_sha,
+ ledger_summary => $ledger->{summary},
+ commands => \@commands,
+ exit_statuses => \%statuses,
+ artifacts => { map { $_ => { path => abs_file($path{$_}), sha256 => sha256_file($path{$_}) } } @retained },
+};
+write_json($path{'manifest.json'}, $manifest);
+print "Phase 36 regex acceptance manifest: $path{'manifest.json'}\n";
+
+sub usage {
+ my ($status) = @_;
+ print <<'USAGE';
+Usage: run_phase36_regex_acceptance.pl --baseline FILE --artifact-dir DIR --jar FILE --sbom FILE [OPTIONS]
+
+Compose the Phase 36 ledger, runner, comparator, and packaging gates into one
+fail-closed immutable-artifact acceptance record. The current perl5 checkout is
+recorded as provenance only, never compared to a pinned revision.
+
+Options:
+ --prepare-only Exercise the composition with injected fake tools;
+ never use this as authority for the real corpus.
+ --perl PATH --jperl PATH Tool executables (default perl / ./jperl)
+ --source-dir DIR Clean source checkout to verify (default cwd)
+ --perl5-dir DIR Current imported perl5 checkout (default ./perl5)
+ --timeout N --jobs N Existing runner bounds and worker budget
+ --version-timeout N Hard bound for the jperl identity probe (default 30)
+ --ledger-tool PATH --runner-tool PATH --comparator-tool PATH
+ --packaging-tool PATH Injectable list-form subprocess tools for testing
+USAGE
+ exit $status;
+}
+
+sub validate_file {
+ my ($path, $label) = @_;
+ die "$label is missing or empty: $path\n" unless -f $path && -s $path;
+}
+
+sub validate_directory {
+ my ($path, $label) = @_;
+ die "$label is missing: $path\n" unless -d $path;
+}
+
+sub validate_program {
+ my ($program, $label) = @_;
+ if ($program =~ m{/}) {
+ die "$label is missing or not executable: $program\n" unless -x $program;
+ return;
+ }
+ for my $directory (split /:/, ($ENV{PATH} // '')) {
+ return if -x File::Spec->catfile($directory, $program);
+ }
+ die "$label is not on PATH: $program\n";
+}
+
+sub git_sha {
+ my ($directory) = @_;
+ die "Git checkout is missing: $directory\n" unless -d $directory;
+ my $output = capture_command(['git', '-C', $directory, 'rev-parse', 'HEAD']);
+ $output =~ s/\s+\z//;
+ die "Cannot determine checkout HEAD for $directory\n" unless $output =~ /\A[0-9a-f]{40}\z/;
+ return $output;
+}
+
+sub tracked_state {
+ my ($directory) = @_;
+ my $status = capture_command(['git', '-C', $directory, 'status', '--porcelain', '--untracked-files=no']);
+ die "Tracked source checkout is not clean\n" if length $status;
+ return sha256_hex(capture_command(['git', '-C', $directory, 'diff', '--binary', 'HEAD']));
+}
+
+sub parse_runner_sha {
+ my ($log, $source_sha) = @_;
+ my $contents = read_raw($log);
+ my @sha = $contents =~ /\b([0-9a-f]{7,40})\b/ig;
+ for my $candidate (@sha) {
+ return $candidate if index($source_sha, lc $candidate) == 0;
+ }
+ die "jperl -v does not report the source Git SHA or prefix\n";
+}
+
+sub load_file_list {
+ my ($file) = @_;
+ open my $fh, '<:raw', $file or die "Cannot read runner list $file: $!\n";
+ my @files;
+ while (my $line = <$fh>) {
+ $line =~ s/\r?\n\z//;
+ next if $line =~ /^\s*(?:#|\z)/;
+ die "Runner list contains an empty path\n" unless length $line;
+ die "Runner list references missing test path: $line\n" unless -f $line;
+ push @files, $line;
+ }
+ close $fh or die "Cannot close runner list $file: $!\n";
+ return @files;
+}
+
+sub read_raw {
+ my ($file) = @_;
+ open my $fh, '<:raw', $file or die "Cannot read $file: $!\n";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $file: $!\n";
+ return $contents;
+}
+
+sub run_logged {
+ my (%arg) = @_;
+ my @command = @{$arg{command}};
+ my $status = run_to_log($arg{log}, $arg{environment}, $arg{timeout}, @command);
+ push @{$arg{commands}}, { name => $arg{name}, argv => \@command,
+ environment => $arg{environment} // {} };
+ $arg{statuses}{$arg{name}} = $status;
+ if ($status == 124 && defined $arg{timeout}) {
+ warn "$arg{name} timed out after $arg{timeout}s; see $arg{log}\n";
+ exit 124;
+ }
+ die "$arg{name} failed with exit status $status; see $arg{log}\n" if $status != 0;
+}
+
+sub run_to_log {
+ my ($log, $environment, $timeout, @command) = @_;
+ my $pid = fork();
+ die "Cannot fork $command[0]: $!\n" unless defined $pid;
+ if ($pid == 0) {
+ open STDOUT, '>:raw', $log or die "Cannot write $log: $!\n";
+ open STDERR, '>&', \*STDOUT or die "Cannot redirect stderr: $!\n";
+ if ($environment) {
+ for my $key (keys %$environment) {
+ defined $environment->{$key} ? ($ENV{$key} = $environment->{$key}) : delete $ENV{$key};
+ }
+ }
+ exec { $command[0] } @command;
+ die "Cannot execute $command[0]: $!\n";
+ }
+ if (defined $timeout) {
+ my $completed = eval {
+ local $SIG{ALRM} = sub { die "acceptance child timeout\n" };
+ alarm $timeout;
+ waitpid($pid, 0);
+ alarm 0;
+ 1;
+ };
+ if (!$completed) {
+ alarm 0;
+ kill 'TERM', $pid;
+ select undef, undef, undef, 0.1;
+ kill 'KILL', $pid if kill 0, $pid;
+ waitpid($pid, 0);
+ return 124;
+ }
+ } else {
+ waitpid($pid, 0);
+ }
+ return $? >> 8 if $? != -1 && ($? & 127) == 0;
+ return 255;
+}
+
+sub capture_command {
+ my ($command) = @_;
+ open my $fh, '-|', @$command or die "Cannot execute $command->[0]: $!\n";
+ my $output = do { local $/; <$fh> };
+ close $fh or die "Command $command->[0] failed with status $?\n";
+ return $output;
+}
+
+sub load_json {
+ my ($file, $label) = @_;
+ open my $fh, '<:raw', $file or die "Cannot read $label $file: $!\n";
+ my $document = eval { JSON::PP->new->utf8->decode(do { local $/; <$fh> }) };
+ close $fh;
+ die "Invalid $label JSON in $file\n" unless $document && ref $document eq 'HASH';
+ return $document;
+}
+
+sub verify_comparison {
+ my ($file, $expected_files) = @_;
+ my $comparison = load_json($file, 'comparison');
+ die "Comparison $file has no summary\n" unless ref $comparison->{summary} eq 'HASH';
+ my $count = $comparison->{summary}{candidate_files};
+ die "Comparison $file has file count drift\n"
+ unless defined $count && $count == $expected_files;
+ for my $key (qw(regressions missing_files added_files execution_issues zero_tap truncated)) {
+ die "Comparison $file has non-empty $key\n"
+ unless ref($comparison->{$key}) eq 'ARRAY' && !@{$comparison->{$key}};
+ }
+}
+
+sub sha256_file {
+ my ($file) = @_;
+ open my $fh, '<:raw', $file or die "Cannot hash $file: $!\n";
+ my $sha = Digest::SHA->new(256);
+ $sha->addfile($fh);
+ close $fh;
+ return $sha->hexdigest;
+}
+
+sub abs_file {
+ my ($file) = @_;
+ return abs_path($file) // File::Spec->rel2abs($file);
+}
+
+sub write_json {
+ my ($file, $document) = @_;
+ open my $fh, '>:raw', $file or die "Cannot write $file: $!\n";
+ print {$fh} JSON::PP->new->canonical->pretty->encode($document);
+ close $fh or die "Cannot close $file: $!\n";
+}
diff --git a/dev/tools/tests/compare_test_results_acceptance.t b/dev/tools/tests/compare_test_results_acceptance.t
new file mode 100644
index 0000000000..308104d27c
--- /dev/null
+++ b/dev/tools/tests/compare_test_results_acceptance.t
@@ -0,0 +1,160 @@
+use strict;
+use warnings;
+
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use JSON::PP;
+use Test::More;
+
+my $root = File::Spec->rel2abs(
+ File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools', 'compare_test_results.pl');
+my $legacy_tool = File::Spec->catfile($root, 'dev', 'tools', 'compare_test_logs.pl');
+my $temporary = tempdir(CLEANUP => 1);
+
+subtest 'progress with complete direct and thread records passes' => sub {
+ my $baseline = write_json('baseline.json', {
+ 'perl5_t/t/re/pat.t' => result(8, 10, 'partial'),
+ 'perl5_t/t/re/pat_thr.t' => result(8, 10, 'partial'),
+ });
+ my $candidate = write_json('progress.json', {
+ 'perl5_t/t/re/pat.t' => result(9, 10, 'partial'),
+ 'perl5_t/t/re/pat_thr.t' => result(10, 10, 'pass'),
+ });
+ my ($status, $output, $report) = run_strict($baseline, $candidate, 'progress-report.json');
+ is($status, 0, 'strict comparison accepts progress');
+ like($output, qr/regressions=0 improvements=2 missing=0/, 'summary reports progress');
+ is_deeply([sort map { $_->{file} } @{$report->{improvements}}],
+ [qw(perl5_t/t/re/pat.t perl5_t/t/re/pat_thr.t)],
+ 'direct and thread files remain separate identities');
+};
+
+subtest 'each invalid candidate class exits nonzero' => sub {
+ my $baseline = write_json('invalid-baseline.json', {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ 'perl5_t/t/re/b.t' => result(2, 2, 'pass'),
+ });
+ my @cases = (
+ [missing => {'perl5_t/t/re/a.t' => result(2, 2, 'pass')}, qr/MISSING FILES/],
+ [regression => {
+ 'perl5_t/t/re/a.t' => result(1, 2, 'partial'),
+ 'perl5_t/t/re/b.t' => result(2, 2, 'pass'),
+ }, qr/REGRESSIONS/],
+ [timeout => {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ 'perl5_t/t/re/b.t' => result(0, 0, 'timeout', exit_code => 124),
+ }, qr/EXECUTION ISSUES/],
+ [error => {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ 'perl5_t/t/re/b.t' => result(0, 0, 'error', exit_code => 1),
+ }, qr/status=error/],
+ [zero_tap => {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ 'perl5_t/t/re/b.t' => result(0, 0, 'pass'),
+ }, qr/ZERO TAP/],
+ [truncated => {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ 'perl5_t/t/re/b.t' => result(1, 2, 'incomplete', actual_tests_run => 1,
+ planned_tests => 2, incomplete_tests => 1),
+ }, qr/TRUNCATED OR INCOMPLETE TAP/],
+ );
+ for my $case (@cases) {
+ my ($name, $results, $diagnostic) = @$case;
+ my $candidate = write_json("$name.json", $results);
+ my ($status, $output) = run_strict($baseline, $candidate, "$name-report.json");
+ is($status, 1, "$name is rejected");
+ like($output, $diagnostic, "$name has an explicit diagnostic");
+ }
+};
+
+subtest 'exact PR-958 artifacts normalize without hiding real loss' => sub {
+ my $baseline = write_log('pr958.log', <<'BASELINE');
+[ 1/3] perl5_t/t/op/do.t ... x 94/99 ok (1.00s)
+[ 2/3] perl5_t/t/japh/abigail.t ... x 110/130 ok (1.00s)
+[ 3/3] perl5_t/t/op/real.t ... x 7/10 ok (1.00s)
+BASELINE
+ my $candidate = write_log('pr958-new.log', <<'CANDIDATE');
+[ 1/3] perl5_t/t/op/do.t ... x 69/71 ok (1.00s)
+[ 2/3] perl5_t/t/japh/abigail.t ... x 109/130 ok (1.00s)
+[ 3/3] perl5_t/t/op/real.t ... x 6/10 ok (1.00s)
+CANDIDATE
+ my ($status, $output, $report) = run_strict($baseline, $candidate,
+ 'pr958-report.json', '--normalize-pr958-artifacts');
+ is($status, 1, 'unlisted real loss still fails');
+ is($report->{summary}{baseline_ok}, 184, 'baseline uses normalized pass counts');
+ is_deeply([map { $_->{file} } @{$report->{regressions}}],
+ ['perl5_t/t/op/real.t'], 'only the real regression remains');
+ like($output, qr/op\/real\.t/, 'human report names the real regression');
+};
+
+subtest 'malformed input is rejected' => sub {
+ my $baseline = write_json('malformed-baseline.json', {
+ 'perl5_t/t/re/a.t' => result(1, 1, 'pass'),
+ });
+ my $malformed = write_log('malformed.log', "not a runner log\n");
+ my ($status, $output) = run_strict($baseline, $malformed, 'malformed-report.json');
+ isnt($status, 0, 'malformed input exits nonzero');
+ like($output, qr/contains no per-file result lines/, 'parse failure is explicit');
+};
+
+subtest 'compare_test_logs exposes the fail-closed acceptance gate' => sub {
+ my $baseline = write_json('wrapper-baseline.json', {
+ 'perl5_t/t/re/a.t' => result(2, 2, 'pass'),
+ });
+ my $candidate = write_json('wrapper-candidate.json', {
+ 'perl5_t/t/re/a.t' => result(1, 2, 'partial'),
+ });
+ my $report = File::Spec->catfile($temporary, 'wrapper-report.json');
+ my @command = ($^X, $legacy_tool, '--strict-acceptance', '--output', $report,
+ $baseline, $candidate);
+ my $output = qx{@command 2>&1};
+ is($? >> 8, 1, 'compatibility entrypoint propagates gate failure');
+ like($output, qr/REGRESSIONS/, 'compatibility entrypoint emits human evidence');
+ ok(-s $report, 'compatibility entrypoint writes JSON evidence');
+};
+
+done_testing;
+
+sub result {
+ my ($ok, $total, $status, %extra) = @_;
+ return {
+ ok_count => $ok,
+ total_tests => $total,
+ status => $status,
+ planned_tests => $extra{planned_tests} // $total,
+ actual_tests_run => $extra{actual_tests_run} // $total,
+ incomplete_tests => $extra{incomplete_tests} // 0,
+ exit_code => $extra{exit_code} // 0,
+ };
+}
+
+sub write_json {
+ my ($name, $results) = @_;
+ return write_log($name, JSON::PP->new->canonical->pretty->encode({results => $results}));
+}
+
+sub write_log {
+ my ($name, $contents) = @_;
+ my $path = File::Spec->catfile($temporary, $name);
+ open my $fh, '>:raw', $path or die "cannot write $path: $!";
+ print {$fh} $contents;
+ close $fh or die "cannot close $path: $!";
+ return $path;
+}
+
+sub run_strict {
+ my ($baseline, $candidate, $report_name, @extra) = @_;
+ my $report_path = File::Spec->catfile($temporary, $report_name);
+ my @command = ($^X, $tool, '--fail-on-regression', '--fail-on-invalid',
+ '--output', $report_path, @extra, $baseline, $candidate);
+ my $output = qx{@command 2>&1};
+ my $status = $? >> 8;
+ my $report;
+ if (-e $report_path) {
+ open my $fh, '<:raw', $report_path or die "cannot read $report_path: $!";
+ $report = JSON::PP->new->decode(do { local $/; <$fh> });
+ close $fh;
+ }
+ return ($status, $output, $report);
+}
diff --git a/dev/tools/tests/compare_test_results_exact_path.t b/dev/tools/tests/compare_test_results_exact_path.t
new file mode 100644
index 0000000000..34a1f7b340
--- /dev/null
+++ b/dev/tools/tests/compare_test_results_exact_path.t
@@ -0,0 +1,44 @@
+use strict;
+use warnings;
+
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use JSON::PP;
+use Test::More tests => 3;
+
+my $root = File::Spec->rel2abs(
+ File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools',
+ 'compare_test_results.pl');
+my $temporary = tempdir(CLEANUP => 1);
+my $baseline = File::Spec->catfile($temporary, 'baseline.json');
+my $candidate = File::Spec->catfile($temporary, 'candidate.json');
+my $json = JSON::PP->new->canonical->encode({
+ results => {
+ 'perl5_t/t/re/a.t' => {
+ ok_count => 2, total_tests => 2, status => 'pass',
+ },
+ 'perl5_t/t/re/ab.t' => {
+ ok_count => 3, total_tests => 3, status => 'pass',
+ },
+ },
+});
+write_file($baseline, $json);
+write_file($candidate, $json);
+
+my @command = ($^X, $tool, '--path-prefix', 'perl5_t/t/re/a.t',
+ $baseline, $candidate);
+my $output = qx{@command 2>&1};
+is($? >> 8, 0, 'one-file path prefix succeeds');
+like($output, qr/Files: 1 -> 1/,
+ 'one-file path prefix selects exactly one result');
+unlike($output, qr/ab\.t/,
+ 'neighboring filename is not selected by an exact test path');
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>:raw', $path or die "Cannot write $path: $!\n";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!\n";
+}
diff --git a/dev/tools/tests/compare_test_results_file_list.t b/dev/tools/tests/compare_test_results_file_list.t
new file mode 100644
index 0000000000..8501ef8866
--- /dev/null
+++ b/dev/tools/tests/compare_test_results_file_list.t
@@ -0,0 +1,61 @@
+use strict;
+use warnings;
+
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use JSON::PP;
+use Test::More tests => 5;
+
+my $root = File::Spec->rel2abs(
+ File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools',
+ 'compare_test_results.pl');
+my $temporary = tempdir(CLEANUP => 1);
+my $baseline = File::Spec->catfile($temporary, 'baseline.json');
+my $candidate = File::Spec->catfile($temporary, 'candidate.json');
+my $file_list = File::Spec->catfile($temporary, 'regex-files.txt');
+
+write_file($baseline, results_json(
+ 'perl5_t/t/re/a.t' => [2, 2],
+ 'perl5_t/t/re/ab.t' => [3, 3],
+ 'perl5_t/t/op/plain.t' => [4, 4],
+));
+write_file($candidate, results_json(
+ 'perl5_t/t/re/a.t' => [1, 2],
+ 'perl5_t/t/re/ab.t' => [3, 3],
+));
+write_file($file_list, "# generated ledger\n./perl5_t/t/re/a.t\n\n");
+
+my @command = ($^X, $tool, '--file-list', $file_list,
+ $baseline, $candidate);
+my $output = qx{@command 2>&1};
+is($? >> 8, 0, 'exact file-list comparison succeeds');
+like($output, qr/Files: 1 -> 1/,
+ 'only the listed identity is compared');
+like($output, qr/a\.t: 2\/2 -> 1\/2/,
+ 'listed regression is retained');
+unlike($output, qr/ab\.t|plain\.t/,
+ 'unlisted neighboring and unrelated files are excluded');
+
+my $conflict = qx{$^X $tool --file-list $file_list --path-prefix perl5_t/t/re $baseline $candidate 2>&1};
+isnt($? >> 8, 0, 'conflicting filters are rejected');
+
+sub results_json {
+ my (%counts) = @_;
+ my %results = map {
+ $_ => {
+ ok_count => $counts{$_}[0],
+ total_tests => $counts{$_}[1],
+ status => $counts{$_}[0] == $counts{$_}[1] ? 'pass' : 'partial',
+ }
+ } keys %counts;
+ return JSON::PP->new->canonical->encode({ results => \%results });
+}
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>:raw', $path or die "Cannot write $path: $!\n";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!\n";
+}
diff --git a/dev/tools/tests/generate_regex_test_ledger.t b/dev/tools/tests/generate_regex_test_ledger.t
new file mode 100644
index 0000000000..216b314d6f
--- /dev/null
+++ b/dev/tools/tests/generate_regex_test_ledger.t
@@ -0,0 +1,77 @@
+use strict;
+use warnings;
+
+use File::Path qw(make_path);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use JSON::PP;
+use Test::More;
+
+my $root = File::Spec->rel2abs(
+ File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools',
+ 'generate_regex_test_ledger.pl');
+my $temporary = tempdir(CLEANUP => 1);
+my $tests = File::Spec->catdir($temporary, 'perl5_t', 't');
+my $units = File::Spec->catdir($temporary, 'unit');
+make_path(map { File::Spec->catdir($tests, $_) } qw(re op uni));
+make_path($units);
+
+write_file(File::Spec->catfile($tests, 're', 'a.t'), "ok 1; qr/a/;\n");
+write_file(File::Spec->catfile($tests, 're', 'a_thr.t'), "ok 1;\n");
+write_file(File::Spec->catfile($tests, 'op', 'match.t'), "'a' =~ /a/;\n");
+write_file(File::Spec->catfile($tests, 'op', 'plain.t'), "ok 1;\n");
+write_file(File::Spec->catfile($tests, 'uni', 'property.t'), "qr/\\p{L}/;\n");
+write_file(File::Spec->catfile($units, 'focused.t'), "ok 1;\n");
+my $reference = File::Spec->catfile($temporary, 'reference.md');
+write_file($reference, "Gates: `focused.t`, `op/plain.t`, `missing.t`.\n");
+my $json_path = File::Spec->catfile($temporary, 'ledger.json');
+my $list_path = File::Spec->catfile($temporary, 'runner.list');
+
+my @command = ($^X, $tool,
+ '--tests-root', $tests,
+ '--unit-root', $units,
+ '--reference', $reference,
+ '--runner-list', $list_path,
+ '--output', $json_path);
+system @command;
+is($? >> 8, 0, 'ledger generation succeeds');
+
+my $ledger = JSON::PP->new->decode(read_file($json_path));
+is($ledger->{summary}{core_re_files}, 2, 'complete re directory is counted');
+is($ledger->{summary}{auxiliary_regex_files}, 2,
+ 'regex-bearing op and uni files are discovered');
+is($ledger->{summary}{runner_files}, 5,
+ 'documented auxiliary imported tests join the runner list');
+is_deeply($ledger->{documented_unit_gates},
+ [File::Spec->catfile($units, 'focused.t')],
+ 'documented unit gate is resolved separately');
+is($ledger->{summary}{direct_thread_pairs}, 1,
+ 'direct/thread pair is recorded');
+ok($ledger->{direct_thread_pairs}[0]{direct_exists},
+ 'thread pair records its direct test');
+is($ledger->{summary}{unresolved_references}, 1,
+ 'unresolved documented gate is explicit');
+
+my @runner = grep { length } split /\n/, read_file($list_path);
+is(scalar @runner, 5, 'runner list contains each canonical path once');
+is(scalar(grep { /plain\.t\z/ } @runner), 1,
+ 'documented non-regex auxiliary test is retained once');
+
+done_testing;
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>:raw', $path or die "Cannot write $path: $!\n";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!\n";
+}
+
+sub read_file {
+ my ($path) = @_;
+ open my $fh, '<:raw', $path or die "Cannot read $path: $!\n";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $path: $!\n";
+ return $contents;
+}
diff --git a/dev/tools/tests/perl_unicode_generator_pipeline.t b/dev/tools/tests/perl_unicode_generator_pipeline.t
index 0f60e58660..b14d858f28 100644
--- a/dev/tools/tests/perl_unicode_generator_pipeline.t
+++ b/dev/tools/tests/perl_unicode_generator_pipeline.t
@@ -21,17 +21,28 @@ make_path($unicode, $perl_root, $tools, $output_dir);
my $version = "17.0.0\n";
my $source = "# test Unicode table\n0041 ; Example\n";
-my $generated = "generated\n";
+my $generated = "# test Unicode table\n0041 ; Example\n# pinned Perl generator source\n";
write_file(File::Spec->catfile($unicode, 'version'), $version);
write_file(File::Spec->catfile($unicode, 'table.txt'), $source);
my $perl_source = "# pinned Perl generator source\n";
write_file(File::Spec->catfile($perl_root, 'source.pl'), $perl_source);
+write_file(File::Spec->catfile($perl_root, 'patchlevel.h'), "#define PERL_REVISION 5\n#define PERL_VERSION 45\n#define PERL_SUBVERSION 2\n");
+system('git', '-C', $perl_root, 'init', '-q') == 0 or die 'Cannot initialize fixture checkout';
+system('git', '-C', $perl_root, 'config', 'user.email', 'test@example.invalid') == 0 or die 'Cannot configure fixture checkout';
+system('git', '-C', $perl_root, 'config', 'user.name', 'Fixture') == 0 or die 'Cannot configure fixture checkout';
+system('git', '-C', $perl_root, 'add', '.') == 0 or die 'Cannot stage fixture checkout';
+system('git', '-C', $perl_root, 'commit', '-qm', 'fixture') == 0 or die 'Cannot commit fixture checkout';
+
write_file(File::Spec->catfile($tools, 'generate.pl'), <<'GENERATOR');
#!/usr/bin/env perl
use strict;
use warnings;
binmode STDOUT, ':raw';
-print "generated\n";
+for my $path ("$ENV{PERLONJAVA_UNICODE_ROOT}/table.txt", "$ENV{PERLONJAVA_PERL_ROOT}/source.pl") {
+ open my $input, '<:raw', $path or die $!;
+ print while <$input>;
+ close $input;
+}
GENERATOR
my $output = File::Spec->catfile($output_dir, 'Data.java');
write_file($output, "stale\n");
@@ -59,6 +70,26 @@ is($status, 0, '--list succeeds without reading source data');
like($log, qr/^example\s+tools\/generate\.pl\s+generated\/Data\.java\s+/m,
'--list exposes manifest provenance');
+# Schema 2 deliberately follows an explicitly supplied current checkout. The
+# old hashes remain provenance for a later regeneration, but do not freeze the
+# checkout to a historical Perl commit.
+my $current_manifest = File::Spec->catfile($temporary, 'current-manifest.json');
+my $current_value = decode_json(read_file($manifest));
+$current_value->{schema_version} = 2;
+$current_value->{perl_source_policy} = 'current-checkout';
+delete $current_value->{perl_commit};
+write_manifest($current_manifest, $current_value);
+write_file(File::Spec->catfile($unicode, 'table.txt'), "moving current source\n");
+($status, $log) = run_pipeline_with_manifest($current_manifest, '--check');
+is($status, 255, 'current-checkout check rejects stale source provenance');
+like($log, qr/table\.txt SHA-256 mismatch/, 'stale current source is named');
+($status, $log) = run_pipeline_with_manifest($current_manifest, '--refresh');
+is($status, 0, 'refresh updates source provenance and dependent output');
+($status, $log) = run_pipeline_with_manifest($current_manifest, '--check');
+is($status, 0, 'refreshed current checkout is checkable');
+write_file(File::Spec->catfile($unicode, 'table.txt'), $source);
+write_file($output, "stale\n");
+
($status, $log) = run_pipeline('--check');
is($status, 1, '--check rejects stale output');
like($log, qr/example: stale generated output/, 'stale output is identified');
@@ -114,12 +145,16 @@ is(read_file($output), $generated, 'nondeterminism leaves published output untou
done_testing;
sub run_pipeline {
- my (@arguments) = @_;
+ return run_pipeline_with_manifest($manifest, @_);
+}
+
+sub run_pipeline_with_manifest {
+ my ($selected_manifest, @arguments) = @_;
my $error = gensym;
my $pid = open3(undef, my $stdout, $error,
$^X, $pipeline,
'--root', $temporary,
- '--manifest', $manifest,
+ '--manifest', $selected_manifest,
'--unicode-root', $unicode,
'--perl-root', $perl_root,
@arguments);
diff --git a/dev/tools/tests/run_phase36_regex_acceptance.t b/dev/tools/tests/run_phase36_regex_acceptance.t
new file mode 100644
index 0000000000..2d61e6e67c
--- /dev/null
+++ b/dev/tools/tests/run_phase36_regex_acceptance.t
@@ -0,0 +1,220 @@
+use strict;
+use warnings;
+
+use File::Path qw(make_path);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use JSON::PP;
+use Test::More;
+
+my $root = File::Spec->rel2abs(
+ File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools',
+ 'run_phase36_regex_acceptance.pl');
+my $temporary = tempdir(CLEANUP => 1);
+my $source = File::Spec->catdir($temporary, 'source');
+make_path($source);
+system('git', 'init', '-q', $source) == 0 or die 'Cannot initialize source fixture';
+system('git', '-C', $source, 'config', 'user.email', 'fixture@example.test') == 0 or die 'Cannot configure fixture';
+system('git', '-C', $source, 'config', 'user.name', 'Fixture') == 0 or die 'Cannot configure fixture';
+write_file(File::Spec->catfile($source, 'tracked.txt'), "fixture\n");
+system('git', '-C', $source, 'add', 'tracked.txt') == 0 or die 'Cannot stage fixture';
+system('git', '-C', $source, 'commit', '-qm', 'fixture') == 0 or die 'Cannot commit fixture';
+my $artifacts = File::Spec->catdir($temporary, 'artifacts');
+make_path($artifacts);
+my $test_file = File::Spec->catfile($temporary, 'focused.t');
+write_file($test_file, "1..1\nok 1\n");
+my $baseline = File::Spec->catfile($temporary, 'pr958.log');
+my $jar = File::Spec->catfile($temporary, 'perlonjava.jar');
+my $sbom = File::Spec->catfile($temporary, 'sbom.json');
+write_file($baseline, "[ 1/1] $test_file ... . 1/1 ok\n");
+write_file($jar, "fake jar\n");
+write_file($sbom, "{}\n");
+
+my $record = File::Spec->catfile($temporary, 'calls.jsonl');
+my $ledger = fake_tool('ledger.pl', <<'LEDGER');
+use JSON::PP;
+my ($list, $output);
+while (@ARGV) {
+ my $arg = shift @ARGV;
+ $list = shift @ARGV if $arg eq '--runner-list';
+ $output = shift @ARGV if $arg eq '--output';
+}
+open my $lfh, '>:raw', $list or die $!;
+print {$lfh} "$ENV{ACCEPT_TEST_FILE}\n";
+close $lfh;
+open my $ofh, '>:raw', $output or die $!;
+print {$ofh} JSON::PP->new->encode({summary => {unresolved_references => 0, runner_files => 1}});
+close $ofh;
+LEDGER
+my $runner = fake_tool('runner.pl', <<'RUNNER');
+use JSON::PP;
+my @args = @ARGV;
+my $output;
+while (@ARGV) {
+ my $arg = shift @ARGV;
+ $output = shift @ARGV if $arg eq '--output';
+}
+open my $rfh, '>>:raw', $ENV{ACCEPT_RECORD} or die $!;
+print {$rfh} JSON::PP->new->canonical->encode({kind => 'runner', argv => \@args,
+ interpreter => $ENV{JPERL_INTERPRETER}}), "\n";
+close $rfh;
+open my $ofh, '>:raw', $output or die $!;
+print {$ofh} JSON::PP->new->encode({results => {}});
+close $ofh;
+RUNNER
+my $comparator = fake_tool('comparator.pl', <<'COMPARATOR');
+use JSON::PP;
+my @args = @ARGV;
+my $output;
+while (@ARGV) {
+ my $arg = shift @ARGV;
+ $output = shift @ARGV if $arg eq '--output';
+}
+open my $rfh, '>>:raw', $ENV{ACCEPT_RECORD} or die $!;
+print {$rfh} JSON::PP->new->canonical->encode({kind => 'comparator', argv => \@args}), "\n";
+close $rfh;
+open my $ofh, '>:raw', $output or die $!;
+print {$ofh} JSON::PP->new->encode({summary => {candidate_files => 1},
+ regressions => [], missing_files => [], added_files => [],
+ execution_issues => [], zero_tap => [], truncated => []});
+close $ofh;
+COMPARATOR
+my $packaging = fake_tool('packaging.pl', <<'PACKAGING');
+use JSON::PP;
+open my $rfh, '>>:raw', $ENV{ACCEPT_RECORD} or die $!;
+print {$rfh} JSON::PP->new->canonical->encode({kind => 'packaging', argv => \@ARGV}), "\n";
+close $rfh;
+print "packaging fake passed\n";
+PACKAGING
+my $jperl = fake_tool('jperl', <<'JPERL');
+#!/usr/bin/env perl
+print "PerlOnJava fake $ENV{ACCEPT_SOURCE_SHA}\n";
+JPERL
+chmod 0755, $jperl or die "Cannot chmod fake jperl: $!";
+open my $git, '-|', 'git', '-C', $source, 'rev-parse', 'HEAD' or die "Cannot run git: $!";
+my $source_sha = do { local $/; <$git> };
+close $git or die "Cannot close git: $?";
+chomp $source_sha;
+
+local $ENV{ACCEPT_TEST_FILE} = $test_file;
+local $ENV{ACCEPT_RECORD} = $record;
+local $ENV{ACCEPT_SOURCE_SHA} = $source_sha;
+my @command = ($^X, $tool, '--prepare-only',
+ '--baseline', $baseline, '--artifact-dir', $artifacts,
+ '--jar', $jar, '--sbom', $sbom,
+ '--source-dir', $source, '--perl5-dir', $source,
+ '--jperl', $jperl,
+ '--ledger-tool', $ledger, '--runner-tool', $runner,
+ '--comparator-tool', $comparator, '--packaging-tool', $packaging,
+ '--timeout', 17, '--jobs', 3);
+system @command;
+is($? >> 8, 0, 'prepare-only composition with fake tools succeeds');
+
+my $manifest = load_json(File::Spec->catfile($artifacts, 'manifest.json'));
+is($manifest->{mode}, 'prepare-only', 'manifest records non-production mode');
+is($manifest->{expected_files}, 1, 'manifest records generated exact file count');
+is($manifest->{source}{starting_sha}, $manifest->{source}{final_sha},
+ 'manifest records unchanged checkout HEAD');
+ok($manifest->{source}{perl5_sha_as_provenance} =~ /^[0-9a-f]{40}$/,
+ 'current perl5 revision is provenance');
+is($manifest->{verified_runner_sha}, $source_sha, 'manifest records verified runner SHA');
+for my $name (qw(regex-ledger.json regex-files.txt jvm-results.json
+ interpreter-results.json jvm-comparison.json interpreter-comparison.json
+ ledger.log jvm-runner.log interpreter-runner.log jvm-comparison.log
+ interpreter-comparison.log packaging.log)) {
+ ok($manifest->{artifacts}{$name}{sha256} =~ /^[0-9a-f]{64}$/,
+ "$name has a retained SHA-256");
+}
+ok($manifest->{artifacts}{'jperl-version.log'}{sha256} =~ /^[0-9a-f]{64}$/,
+ 'retained jperl version log has a SHA-256');
+
+my @calls = map { JSON::PP->new->decode($_) }
+ grep { length } split /\n/, read_file($record);
+my @runner_calls = grep { $_->{kind} eq 'runner' } @calls;
+is(scalar @runner_calls, 2, 'exactly JVM and interpreter runner legs execute');
+is($runner_calls[0]{interpreter} // '', '', 'JVM leg clears interpreter environment');
+is($runner_calls[1]{interpreter}, 1, 'interpreter leg sets interpreter environment');
+for my $call (@runner_calls) {
+ is_deeply([grep { /focused\.t\z/ } @{$call->{argv}}], [$test_file],
+ 'each runner receives the same exact generated file list');
+ ok(grep($_ eq '--timeout', @{$call->{argv}}), 'runner receives existing timeout option');
+}
+my @comparison_calls = grep { $_->{kind} eq 'comparator' } @calls;
+is(scalar @comparison_calls, 2, 'both result legs are compared');
+for my $call (@comparison_calls) {
+ ok(grep($_ eq '--normalize-pr958-artifacts', @{$call->{argv}}),
+ 'comparison enables PR-958 normalization');
+ ok(grep($_ eq '--fail-on-invalid', @{$call->{argv}}),
+ 'comparison is fail-closed for invalid records');
+}
+is(scalar(grep { $_->{kind} eq 'packaging' } @calls), 1,
+ 'exact artifact packaging verification executes once');
+
+my $sleeping_jperl = fake_tool('sleeping-jperl', <<'SLEEPING_JPERL');
+#!/usr/bin/env perl
+sleep 30;
+SLEEPING_JPERL
+chmod 0755, $sleeping_jperl or die "Cannot chmod sleeping fake jperl: $!";
+my $timeout_artifacts = File::Spec->catdir($temporary, 'timeout-artifacts');
+make_path($timeout_artifacts);
+my @timeout_command = @command;
+for (my $i = 0; $i < @timeout_command; $i++) {
+ $timeout_command[$i + 1] = $sleeping_jperl
+ if $timeout_command[$i] eq '--jperl';
+ $timeout_command[$i + 1] = $timeout_artifacts
+ if $timeout_command[$i] eq '--artifact-dir';
+}
+push @timeout_command, '--version-timeout', 1;
+my $timeout_output = capture_tool(@timeout_command);
+is($? >> 8, 124, 'hung jperl identity probe exits with timeout status');
+like($timeout_output, qr/jperl-version timed out after 1s/,
+ 'hung identity probe has a specific timeout diagnostic');
+
+done_testing;
+
+sub fake_tool {
+ my ($name, $contents) = @_;
+ my $path = File::Spec->catfile($temporary, $name);
+ write_file($path, $contents);
+ return $path;
+}
+
+sub capture_tool {
+ my (@command) = @_;
+ pipe my $read, my $write or die "pipe: $!";
+ my $pid = fork();
+ die "fork: $!" unless defined $pid;
+ if ($pid == 0) {
+ close $read;
+ open STDOUT, '>&', $write or die $!;
+ open STDERR, '>&', $write or die $!;
+ exec { $command[0] } @command;
+ die "exec: $!";
+ }
+ close $write;
+ my $output = do { local $/; <$read> };
+ close $read;
+ waitpid($pid, 0);
+ return $output;
+}
+
+sub write_file {
+ my ($path, $contents) = @_;
+ open my $fh, '>:raw', $path or die "Cannot write $path: $!";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $path: $!";
+}
+
+sub read_file {
+ my ($path) = @_;
+ open my $fh, '<:raw', $path or die "Cannot read $path: $!";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $path: $!";
+ return $contents;
+}
+
+sub load_json {
+ return JSON::PP->new->decode(read_file($_[0]));
+}
diff --git a/dev/tools/tests/verify_joni_packaging.t b/dev/tools/tests/verify_joni_packaging.t
new file mode 100644
index 0000000000..788b7df41f
--- /dev/null
+++ b/dev/tools/tests/verify_joni_packaging.t
@@ -0,0 +1,178 @@
+use strict;
+use warnings;
+
+use File::Path qw(make_path);
+use File::Find qw(find);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin;
+use IO::Compress::Zip qw($ZipError);
+use JSON::PP;
+use Test::More;
+
+my $root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..', '..'));
+my $tool = File::Spec->catfile($root, 'dev', 'tools', 'verify-joni-packaging.pl');
+my $temporary = tempdir(CLEANUP => 1);
+my $joni_ref = 'pkg:maven/org.jruby.joni/joni@2.2.7?type=jar';
+my $jcodings_ref = 'pkg:maven/org.jruby.jcodings/jcodings@1.0.64?type=jar';
+
+subtest 'green artifact has relocated classes, exact notices, and SBOM edge' => sub {
+ my ($jar, $sbom) = fixture('green');
+ my ($status, $output) = run_tool($jar, $sbom);
+ is($status, 0, 'green artifact verifies');
+ like($output, qr/verification passed/, 'green artifact reports success');
+};
+
+subtest 'packaging entry failure families are fail-closed' => sub {
+ my ($missing_notice_jar, $sbom) = fixture('missing-notice', omit_notice => 1);
+ rejected($missing_notice_jar, $sbom, qr/missing joni-LICENSE/, 'absent notice');
+ my ($wrong_notice_jar, $same_sbom) = fixture('wrong-notice', wrong_notice => 1);
+ rejected($wrong_notice_jar, $same_sbom, qr/notice bytes differ/, 'wrong notice bytes');
+ my ($unrelocated_jar, $another_sbom) = fixture('unrelocated', unrelocated => 1);
+ rejected($unrelocated_jar, $another_sbom, qr/unrelocated Joni classes/, 'unrelocated class');
+ my ($duplicate_class_jar, $duplicate_class_sbom) =
+ fixture('duplicate-class', duplicate_class => 1);
+ rejected($duplicate_class_jar, $duplicate_class_sbom, qr/duplicate class entry/,
+ 'duplicate relocated class');
+ my ($duplicate_notice_jar, $duplicate_notice_sbom) =
+ fixture('duplicate-notice', duplicate_notice => 1);
+ rejected($duplicate_notice_jar, $duplicate_notice_sbom, qr/duplicate notice/,
+ 'duplicate notice');
+};
+
+subtest 'SBOM failure families are fail-closed' => sub {
+ my ($jar, $sbom) = fixture('wrong-version', sbom => sub { $_[0]{components}[0]{version} = '9.9.9' });
+ rejected($jar, $sbom, qr/wrong joni version/, 'wrong Joni version');
+ ($jar, $sbom) = fixture('wrong-jcodings-version', sbom => sub { $_[0]{components}[1]{version} = '9.9.9' });
+ rejected($jar, $sbom, qr/wrong jcodings version/, 'wrong JCodings version');
+ ($jar, $sbom) = fixture('missing-joni', sbom => sub { shift @{$_[0]{components}} });
+ rejected($jar, $sbom, qr/missing vendored joni/, 'missing Joni component');
+ ($jar, $sbom) = fixture('missing-jcodings', sbom => sub { pop @{$_[0]{components}} });
+ rejected($jar, $sbom, qr/missing vendored jcodings/, 'missing JCodings component');
+ ($jar, $sbom) = fixture('duplicate-purl', sbom => sub { push @{$_[0]{components}}, {
+ type => 'library', 'bom-ref' => 'other', purl => $joni_ref,
+ group => 'example', name => 'other', version => '1' } });
+ rejected($jar, $sbom, qr/duplicate purl/, 'duplicate purl');
+ ($jar, $sbom) = fixture('missing-edge', sbom => sub { $_[0]{dependencies}[0]{dependsOn} = [] });
+ rejected($jar, $sbom, qr/missing Joni -> JCodings dependency edge/, 'missing dependency edge');
+ ($jar, $sbom) = fixture('duplicate-relation', sbom => sub { push @{$_[0]{dependencies}}, { ref => $joni_ref, dependsOn => [$jcodings_ref] } });
+ rejected($jar, $sbom, qr/duplicate Joni dependency relations/, 'duplicate dependency relation');
+ ($jar, $sbom) = fixture('bad-relation', sbom => sub { $_[0]{dependencies}[0]{dependsOn} = {} });
+ rejected($jar, $sbom, qr/dependency relation is malformed/, 'non-array dependency relation');
+ ($jar, $sbom) = fixture('malformed-json');
+ write_file($sbom, '{ malformed');
+ rejected($jar, $sbom, qr/Malformed SBOM JSON/, 'malformed SBOM');
+};
+
+done_testing;
+
+sub fixture {
+ my ($name, %option) = @_;
+ my $tree = File::Spec->catdir($temporary, $name, 'tree');
+ make_path(File::Spec->catdir($tree, 'org', 'perlonjava', 'internal', 'joni'));
+ make_path(File::Spec->catdir($tree, 'org', 'perlonjava', 'internal', 'jcodings'));
+ make_path(File::Spec->catdir($tree, 'META-INF', 'licenses'));
+ write_file(File::Spec->catfile($tree, 'org', 'perlonjava', 'internal', 'joni', 'Regex.class'), 'joni');
+ write_file(File::Spec->catfile($tree, 'org', 'perlonjava', 'internal', 'jcodings', 'Encoding.class'), 'jcodings');
+ if ($option{unrelocated}) {
+ make_path(File::Spec->catdir($tree, 'org', 'joni'));
+ write_file(File::Spec->catfile($tree, 'org', 'joni', 'Regex.class'), 'bad');
+ }
+ my %source = (
+ 'joni-LICENSE.txt' => File::Spec->catfile($root, 'third_party', 'joni', 'LICENSE'),
+ 'jcodings-LICENSE.txt' => File::Spec->catfile($root, 'third_party', 'licenses', 'jcodings-LICENSE.txt'),
+ 'joni-PERLONJAVA-NOTICE.md' => File::Spec->catfile($root, 'third_party', 'joni', 'PERLONJAVA-NOTICE.md'),
+ );
+ for my $notice (keys %source) {
+ next if $option{omit_notice} && $notice eq 'joni-LICENSE.txt';
+ my $bytes = read_file($source{$notice});
+ $bytes = 'wrong' if $option{wrong_notice} && $notice eq 'joni-LICENSE.txt';
+ write_file(File::Spec->catfile($tree, 'META-INF', 'licenses', $notice), $bytes);
+ }
+ my $jar = File::Spec->catfile($temporary, "$name.jar");
+ if ($option{duplicate_class} || $option{duplicate_notice}) {
+ create_zip_fixture($jar, $tree,
+ $option{duplicate_class}
+ ? 'org/perlonjava/internal/joni/Regex.class'
+ : 'META-INF/licenses/joni-LICENSE.txt');
+ } else {
+ system('jar', 'cf', $jar, '-C', $tree, '.');
+ die "jar fixture failed" if $? != 0;
+ }
+ my $document = {
+ components => [
+ { type => 'library', 'bom-ref' => $joni_ref, purl => $joni_ref,
+ group => 'org.jruby.joni', name => 'joni', version => '2.2.7' },
+ { type => 'library', 'bom-ref' => $jcodings_ref, purl => $jcodings_ref,
+ group => 'org.jruby.jcodings', name => 'jcodings', version => '1.0.64' },
+ ],
+ dependencies => [{ ref => $joni_ref, dependsOn => [$jcodings_ref] }],
+ };
+ $option{sbom}->($document) if $option{sbom};
+ my $sbom = File::Spec->catfile($temporary, "$name.json");
+ write_file($sbom, JSON::PP->new->canonical->encode($document));
+ return ($jar, $sbom);
+}
+
+sub create_zip_fixture {
+ my ($jar, $tree, $duplicate) = @_;
+ my @names;
+ find({ no_chdir => 1, wanted => sub {
+ return unless -f $_;
+ my $name = File::Spec->abs2rel($_, $tree);
+ $name =~ s{\\}{/}g;
+ push @names, $name;
+ }}, $tree);
+ @names = sort @names;
+ push @names, $duplicate;
+ my $first = shift @names;
+ my $zip = IO::Compress::Zip->new($jar, Name => $first)
+ or die "Cannot create duplicate-entry fixture: $ZipError";
+ print {$zip} read_file(File::Spec->catfile($tree, split m{/}, $first));
+ for my $name (@names) {
+ $zip->newStream(Name => $name)
+ or die "Cannot add duplicate-entry fixture stream: $ZipError";
+ print {$zip} read_file(File::Spec->catfile($tree, split m{/}, $name));
+ }
+ close $zip or die "Cannot close duplicate-entry fixture: $ZipError";
+}
+
+sub rejected {
+ my ($jar, $sbom, $expected, $name) = @_;
+ my ($status, $output) = run_tool($jar, $sbom);
+ isnt($status, 0, "$name is rejected");
+ like($output, $expected, "$name has specific diagnostic");
+}
+
+sub run_tool {
+ pipe my $read, my $write or die "pipe: $!";
+ my $pid = fork();
+ die "fork: $!" unless defined $pid;
+ if ($pid == 0) {
+ close $read;
+ open STDOUT, '>&', $write or die $!;
+ open STDERR, '>&', $write or die $!;
+ exec { $^X } $^X, $tool, @_[0, 1];
+ die "exec: $!";
+ }
+ close $write;
+ my $output = do { local $/; <$read> };
+ close $read;
+ waitpid($pid, 0);
+ return ($? >> 8, $output);
+}
+
+sub write_file {
+ my ($file, $contents) = @_;
+ open my $fh, '>:raw', $file or die "Cannot write $file: $!";
+ print {$fh} $contents;
+ close $fh or die "Cannot close $file: $!";
+}
+
+sub read_file {
+ my ($file) = @_;
+ open my $fh, '<:raw', $file or die "Cannot read $file: $!";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $file: $!";
+ return $contents;
+}
diff --git a/dev/tools/verify-joni-packaging.pl b/dev/tools/verify-joni-packaging.pl
index 27e6760c92..7fdd614c5d 100644
--- a/dev/tools/verify-joni-packaging.pl
+++ b/dev/tools/verify-joni-packaging.pl
@@ -2,48 +2,128 @@
use strict;
use warnings;
+use Cwd qw(abs_path getcwd);
+use File::Spec;
+use File::Temp qw(tempdir);
+use FindBin qw($Bin);
use JSON::PP;
die "Usage: $0 \n" unless @ARGV == 2;
my ($jar_file, $sbom_file) = @ARGV;
+die "Standalone JAR is missing or empty: $jar_file\n" unless -f $jar_file && -s $jar_file;
+die "SBOM is missing or empty: $sbom_file\n" unless -f $sbom_file && -s $sbom_file;
+$jar_file = abs_path($jar_file);
+$sbom_file = abs_path($sbom_file);
+my $root = abs_path(File::Spec->catdir($Bin, '..', '..'))
+ or die "Cannot resolve repository root from $Bin\n";
+my %notices = (
+ 'joni-LICENSE.txt' => File::Spec->catfile($root, 'third_party', 'joni', 'LICENSE'),
+ 'jcodings-LICENSE.txt' => File::Spec->catfile($root, 'third_party', 'licenses', 'jcodings-LICENSE.txt'),
+ 'joni-PERLONJAVA-NOTICE.md' => File::Spec->catfile($root, 'third_party', 'joni', 'PERLONJAVA-NOTICE.md'),
+);
my %entries;
open my $jar_fh, '-|', 'jar', 'tf', $jar_file
or die "Cannot list $jar_file: $!\n";
while (my $entry = <$jar_fh>) {
chomp $entry;
- $entries{$entry} = 1;
+ $entries{$entry}++;
}
close $jar_fh or die "Cannot list $jar_file: jar exited with status $?\n";
+for my $entry (keys %entries) {
+ next unless $entry =~ m{^(?:org/joni/|org/jcodings/|org/perlonjava/internal/(?:joni|jcodings)/)}
+ && $entry =~ /\.class\z/;
+ die "Standalone JAR contains duplicate class entry $entry\n" if $entries{$entry} != 1;
+}
die "Standalone JAR contains unrelocated Joni classes\n"
- if grep { m{^org/joni/} } keys %entries;
+ if grep { m{^org/joni/.*\.class\z} } keys %entries;
die "Standalone JAR contains unrelocated JCodings classes\n"
- if grep { m{^org/jcodings/} } keys %entries;
+ if grep { m{^org/jcodings/.*\.class\z} } keys %entries;
+die "Standalone JAR contains retired regex backend selector\n"
+ if $entries{'org/perlonjava/runtime/regex/RegexBackendPolicy.class'};
die "Standalone JAR does not contain relocated Joni classes\n"
- unless grep { m{^org/perlonjava/internal/joni/} } keys %entries;
+ unless grep { m{^org/perlonjava/internal/joni/.*\.class\z} } keys %entries;
die "Standalone JAR does not contain relocated JCodings classes\n"
- unless grep { m{^org/perlonjava/internal/jcodings/} } keys %entries;
+ unless grep { m{^org/perlonjava/internal/jcodings/.*\.class\z} } keys %entries;
-for my $notice (qw(
- joni-LICENSE.txt
- jcodings-LICENSE.txt
- joni-PERLONJAVA-NOTICE.md
-)) {
+for my $notice (sort keys %notices) {
die "Standalone JAR is missing $notice\n"
unless $entries{"META-INF/licenses/$notice"};
+ die "Standalone JAR contains duplicate notice $notice\n"
+ unless $entries{"META-INF/licenses/$notice"} == 1;
+ die "Standalone JAR notice bytes differ for $notice\n"
+ unless jar_entry_bytes($jar_file, "META-INF/licenses/$notice") eq read_raw($notices{$notice});
}
-open my $sbom_fh, '<', $sbom_file or die "Cannot open $sbom_file: $!\n";
-local $/;
-my $sbom = JSON::PP->new->utf8->decode(<$sbom_fh>);
-close $sbom_fh;
-
-my ($joni) = grep {
- ($_->{group} // '') eq 'org.jruby.joni'
- && ($_->{name} // '') eq 'joni'
- && ($_->{version} // '') eq '2.2.7'
-} @{$sbom->{components} // []};
-die "Combined SBOM is missing vendored Joni 2.2.7\n" unless $joni;
+my $sbom = load_json($sbom_file);
+my $components = $sbom->{components};
+die "Combined SBOM has no components array\n" unless ref $components eq 'ARRAY';
+my $dependencies = $sbom->{dependencies};
+die "Combined SBOM has no dependencies array\n" unless ref $dependencies eq 'ARRAY';
+my $joni_ref = 'pkg:maven/org.jruby.joni/joni@2.2.7?type=jar';
+my $jcodings_ref = 'pkg:maven/org.jruby.jcodings/jcodings@1.0.64?type=jar';
+assert_unique_component_ids($components);
+assert_component($components, 'org.jruby.joni', 'joni', '2.2.7', $joni_ref);
+assert_component($components, 'org.jruby.jcodings', 'jcodings', '1.0.64', $jcodings_ref);
+my @relations = grep { ($_->{ref} // '') eq $joni_ref } @$dependencies;
+die "Combined SBOM is missing Joni dependency relation\n" unless @relations;
+die "Combined SBOM has duplicate Joni dependency relations\n" unless @relations == 1;
+die "Combined SBOM Joni dependency relation is malformed\n"
+ unless ref $relations[0]{dependsOn} eq 'ARRAY';
+die "Combined SBOM is missing Joni -> JCodings dependency edge\n"
+ unless grep { $_ eq $jcodings_ref } @{$relations[0]{dependsOn}};
print "Joni packaging verification passed\n";
+
+sub read_raw {
+ my ($file) = @_;
+ open my $fh, '<:raw', $file or die "Cannot read $file: $!\n";
+ my $contents = do { local $/; <$fh> };
+ close $fh or die "Cannot close $file: $!\n";
+ return $contents;
+}
+
+sub jar_entry_bytes {
+ my ($jar, $entry) = @_;
+ my $directory = tempdir(CLEANUP => 1);
+ my $original = getcwd();
+ chdir $directory or die "Cannot enter temporary directory: $!\n";
+ my $status = system('jar', 'xf', $jar, $entry);
+ my $error = $!;
+ chdir $original or die "Cannot return to $original: $!\n";
+ die "Cannot extract $entry from $jar: $error\n" if $status != 0;
+ return read_raw(File::Spec->catfile($directory, split m{/}, $entry));
+}
+
+sub load_json {
+ my ($file) = @_;
+ my $document = eval { JSON::PP->new->utf8->decode(read_raw($file)) };
+ die "Malformed SBOM JSON in $file: $@\n" unless $document && ref $document eq 'HASH';
+ return $document;
+}
+
+sub assert_unique_component_ids {
+ my ($components) = @_;
+ my (%refs, %purls);
+ for my $component (@$components) {
+ die "SBOM component is not an object\n" unless ref $component eq 'HASH';
+ for my $field (['bom-ref', \%refs], ['purl', \%purls]) {
+ my ($name, $seen) = @$field;
+ next unless defined $component->{$name} && length $component->{$name};
+ die "Combined SBOM has duplicate $name $component->{$name}\n"
+ if $seen->{$component->{$name}}++;
+ }
+ }
+}
+
+sub assert_component {
+ my ($components, $group, $name, $version, $ref) = @_;
+ my @matching = grep { ($_->{group} // '') eq $group && ($_->{name} // '') eq $name } @$components;
+ die "Combined SBOM is missing vendored $name $version\n" unless @matching;
+ die "Combined SBOM has duplicate $name components\n" unless @matching == 1;
+ my $component = $matching[0];
+ die "Combined SBOM has wrong $name version\n" unless ($component->{version} // '') eq $version;
+ die "Combined SBOM has wrong $name bom-ref\n" unless ($component->{'bom-ref'} // '') eq $ref;
+ die "Combined SBOM has wrong $name purl\n" unless ($component->{purl} // '') eq $ref;
+}
diff --git a/docs/design/joni-callout-fork.md b/docs/design/joni-callout-fork.md
index 8784a22e63..0b37f41669 100644
--- a/docs/design/joni-callout-fork.md
+++ b/docs/design/joni-callout-fork.md
@@ -1,48 +1,74 @@
-# Vendored Joni Callout Engine
-
-## Purpose
-
-Perl executable regex constructs must run while the matcher owns provisional
-captures and its backtracking stack. Stock Joni provides the required
-stack-based matcher but does not expose match-time callbacks or unwind
-notifications. PerlOnJava therefore vendors a minimally extended Joni engine.
-
-This document defines the current architecture and acceptance contract. Change
-history, imported revision details, and completed-work records belong in Git
-commit messages, not here.
-
-## Repository and dependency model
-
-- Joni is a pinned source snapshot in `third_party/joni`. Its production sources
- and upstream tests are dedicated source sets in the root Gradle project. This
- keeps the code boundary explicit without exposing the vendored engine as a
- separately resolved project dependency.
-- The source retains the upstream `org.joni` packages to keep changes reviewable
- and suitable for upstream submission.
-- The vendored project is the only Joni implementation on PerlOnJava's compile
- and runtime classpaths.
-- JCodings remains an upstream binary dependency because PerlOnJava does not
- modify its encoding implementation.
-- Automatic routing temporarily keeps ordinary patterns on Java. Patterns with
- matcher features that Java cannot represent and the forced-Joni compatibility
- gate use the vendored engine. The accepted final architecture removes Java
- matching and the selector after semantic and performance parity.
-
-## Internal callout syntax
-
-The structured Perl regex frontend emits `(?{=CALL:})` for plain and
-conditional callbacks and `(?{=DYNAMIC:})` for dynamic subprograms only in
-an engine-facing skeleton. `` is a non-negative decimal index into the
-callback table owned by that regex value. The fork parses these representations
-into dedicated callout nodes; it never parses Perl source.
+# Vendored Joni callout engine
+
+## Purpose and boundary
+
+Perl executable regex constructs run while the matcher owns provisional
+captures and its backtracking stack. A post-match hook cannot implement their
+semantics. PerlOnJava therefore maintains a Joni source fork with runtime-neutral
+callout and dynamic-subprogram APIs.
+
+The fork is a matcher, not a Perl evaluator. It receives opaque integer IDs and
+calls an interface; it never imports PerlOnJava runtime classes or parses Perl
+callback bodies. The Perl frontend and runtime own source admission, closure
+capture, `use re 'eval'`, diagnostics, and Perl-visible state.
+
+All production regexes use this fork. There is no Java matcher, and the legacy
+`RegexPreprocessor` is absent. Limited Java source-policy scanning and adapter
+token materialization still prepare admitted source for native Joni. The
+remaining scanner rejects extended-class string properties and unresolved user
+properties whose composition needs host context; it does not implement matcher
+semantics. The historical
+`RegexBackendPolicy` model exists only in test scope, while
+`JoniRegexPattern.compatibilityPatternDescription` is a display compatibility
+surface; neither can become alternate matcher input.
+
+## Source and dependency model
+
+Maintained sources live in `third_party/joni/src/org/joni` and retain upstream
+`org.joni` packages so the fork remains reviewable. Gradle adds them to the main
+source set and compiles imported fork tests from the separate `joniTest` source
+set. JCodings remains an upstream binary dependency; the fork uses its encoding
+and baseline fold primitives without depending on a modified JCodings API.
+
+The fork adds parser nodes, bytecode, matcher stack entries, and the following
+public hooks:
+
+- `CalloutHandler`, `CalloutResult`, `CalloutAction`, and `MatchView`;
+- `DynamicPatternResult` for host-supplied nested programs;
+- `CharacterPropertyResolver` and its range result;
+- `NamedCharacterResolver`;
+- `WideScalarCodec`;
+- `PerlPropertyValueMatcher` for runtime-neutral property-value wildcards.
+
+Keep these APIs free of `org.perlonjava` types. Engine tests should be expressible
+with only Joni and JCodings classes.
+
+## Trusted engine syntax
+
+The structured Perl frontend represents executable parts separately from text.
+`RuntimeRegexTemplate` assigns each part an index in the regex value's callback
+table and stores a private slot sentinel. Sentinel starts in interpolated text
+are escaped, and embedded callback-bearing regexes have their slots validated
+and renumbered. Only `materializeTrustedCallouts()` converts validated slots to
+engine-facing syntax immediately before Joni compilation:
+
+```text
+(?{=CALL:})
+(?{=DYNAMIC:})
+```
-Runtime-interpolated pattern text must not be promoted to trusted skeleton
-syntax. The PerlOnJava frontend remains responsible for preserving source
-origin and rejecting injected internal tokens.
+`` is a non-negative decimal table index. A CALL token is also used as an
+internal conditional predicate when it occurs in a regex condition.
+`StringSegmentParser` is the producer of callback AST parts,
+`RuntimeRegexTemplate` builds their callback table, and `trustedCalloutCount`
+bounds the IDs that Joni accepts. `maskCallouts()` shields trusted slots while
+admitted runtime Perl source is reparsed. Literal or interpolated text spelling
+`(?{=CALL:0})` has no slot provenance and therefore cannot address the callback
+table. The fork remains deliberately unaware of Perl source provenance.
-## Engine API
+## Runtime-neutral API
-The fork exposes a runtime-neutral API in `org.joni`:
+The central interfaces are:
```java
interface CalloutHandler {
@@ -50,12 +76,7 @@ interface CalloutHandler {
default DynamicPatternResult executeDynamic(int calloutId, MatchView match);
void unwind(Object backtrackToken);
default void complete(Object successfulToken) { unwind(successfulToken); }
-}
-
-final class DynamicPatternResult {
- Regex getRegex();
- CalloutHandler getCalloutHandler();
- Object getBacktrackToken();
+ default void finish(boolean matched) {}
}
interface MatchView {
@@ -63,223 +84,186 @@ interface MatchView {
int captureCount();
int captureBegin(int capture);
int captureEnd(int capture);
+ int lastClosedCapture();
+ String controlMark();
+}
+
+final class DynamicPatternResult {
+ Regex getRegex();
+ CalloutHandler getCalloutHandler();
+ Object getBacktrackToken();
}
```
-The handler is installed on each `Matcher`, not on the compiled `Regex`, so a
-shared compiled pattern can execute concurrently with runtime-local callback
-state.
-
-`CalloutResult` selects `CONTINUE` or `FAIL` and may carry an opaque unwind
-token. The engine neither interprets that token nor depends on PerlOnJava
-runtime classes.
-
-`DynamicPatternResult` supplies a compiled nested program, its matcher-local
-handler, and the token for the dynamic expression's provisional Perl state. The
-default `executeDynamic` implementation fails fast, so handlers that only use
-plain callouts remain source compatible.
-
-## Execution and unwind contract
-
-1. The callout opcode invokes the handler with the callout ID and a read-only
- view of the current byte position and provisional capture boundaries.
-2. A non-null token is stored in a matcher stack frame before execution
- continues or fails.
-3. Backtracking across that frame calls `unwind(token)` exactly once.
-4. Successful completion calls `complete(token)` for every still-active token in
- reverse execution order. Its default implementation delegates to `unwind`,
- preserving the cleanup-only contract for engine-neutral handlers.
-5. Failed completion, interruption, timeout, and handler exceptions unwind every
- still-active token in reverse execution order.
-6. A `FAIL` result enters normal matcher backtracking after installing the frame,
- so cleanup follows the same path as later-pattern failure.
-7. A dynamic callout resolves its nested program only when execution reaches
- the opcode. The nested matcher yields one result at a time; an outer failure
- resumes the nested matcher at its next alternative before the outer engine
- backtracks past the dynamic frame.
-8. Nested captures remain private to the nested matcher. Its endpoint advances
- the outer input position, while outer capture numbering and match variables
- remain unchanged.
-9. Completing or abandoning a nested continuation propagates completion or
- unwind to all of its active callback tokens exactly once.
-10. Patterns without callout nodes allocate no handler state or callout frames.
-
-Callback conditions use an internal conditional-callout opcode. `CONTINUE`
-selects the yes branch and `FAIL` selects the no branch without treating the
-condition itself as a failed match.
-
-## Matcher-control boundaries
-
-`(*ACCEPT)` is represented as a dedicated AST node and bytecode opcode; it is
-not rewritten into an assertion or callback. At execution it accepts the
-nearest matcher-program boundary:
-
-- At the top level, it completes the whole match at the current input position
- and skips the remaining pattern.
-- In a subpattern call, it completes that call and resumes the caller after the
- call site.
-- In a positive lookahead, it completes the assertion, restores its zero-width
- input position, and resumes the enclosing program.
-- In a dynamic `(??{...})` program, it completes the nested matcher while the
- enclosing matcher continues with its suffix.
-
-Captures opened inside the accepted boundary close at the current input
-position. Captures outside that boundary remain active. Backtracking and
-dynamic-continuation frames abandoned by acceptance are completed exactly once.
-Because suffixes after a control verb are not necessarily reachable, programs
-containing matcher-control opcodes bypass Joni's ordinary mandatory-literal and
-minimum-length optimizer unless that optimizer becomes control-flow aware.
-
-## Structured Perl frontend
-
-Literal callback bodies are represented as lexical `SubroutineNode` closures.
-The parser emits a `regexTemplate` operation whose ordered parts contain normal
-interpolation values and explicit `regexCallback` wrappers. Runtime template
-construction assigns callback IDs and produces the engine-facing skeleton.
-
-Optimistic callbacks use the same structured path. Standalone `(*{ code })`
-callbacks execute as zero-width BLOCK callouts and publish their result through
-`$^R`; `(?(*{ code })yes|no)` predicates execute as CONDITION callouts, select
-their branch from the result's truth, and leave `$^R` unchanged. The matcher
-publishes `$^N` from capture-close order rather than deriving it from `$+`, whose
-meaning is the highest-numbered defined capture.
-
-An interpolated coderef remains ordinary interpolation because only the parser
-can create a callback wrapper. Runtime strings containing Perl eval groups never
-become trusted callback skeletons. They retain the existing security checks and,
-where compatibility mode permits unsupported eval groups, the existing
-zero-width no-op behavior.
-
-At execution, the bridge publishes provisional numbered captures and offsets,
-runs the closure in scalar context, and updates `$^R` for plain callbacks only.
-Each token checkpoints regex state, `$^R`, and the dynamic-local stack. Backtrack
-unwind restores all three; successful completion restores dynamic scope while
-retaining the last successful plain callback result. Dynamic locals created by
-one callback remain active along the matcher's forward path, including later
-callbacks, until that path is unwound or the match completes. The ordinary
-closure epilogue therefore does not tear down a callback's local frame; the
-matcher token owns it.
-
-Callback closures are Perl pseudo blocks, not ordinary subroutine or loop
-boundaries. An unhandled `last`, `next`, `redo`, or `goto` cannot target a loop
-or label outside the callback. The bridge converts escaped control flow to the
-corresponding pseudo-block diagnostic before it can reach surrounding Perl
-frames. Recursive callback re-entry has an engine-owned depth ceiling independent
-of the JVM's configured native stack size.
-
-Every structured executable callback template selects Joni, including a
-callback whose body happens to return a constant: `(?{ 1 })` still has match-time
-side effects and unwind semantics. Runtime-dependent dynamic-pattern expressions
-also select Joni and execute only when their opcode is reached. Semantically safe
-constant expressions may use the compile-time fold path; constants with captures
-or top-level alternatives remain dynamic so they cannot change outer grouping or
-capture numbers.
-
-Literal match targets have one stable scalar identity per compiled call site so
-`pos()` and `/g` survive repeated loop execution without allowing two identical
-literal occurrences to share state.
-
-## Capture view
-
-Capture offsets are byte offsets relative to the matcher's input buffer, matching
-Joni's native representation. Capture zero describes the provisional overall
-match from the current match start to the current instruction position. Unset
-captures return `Region.REGION_NOTPOS`. The bridge converts offsets to Perl
-character positions when publishing match variables.
-
-The view is valid only during `execute`; handlers must copy any data they retain.
-The engine must not allocate a `Region` snapshot for each callout.
-
-## Packaging and namespace isolation
-
-Source code remains in `org.joni`, but the standalone JAR relocates it to
-`org.perlonjava.internal.joni`. JCodings is relocated to
-`org.perlonjava.internal.jcodings` because Joni's API and implementation refer
-to its classes. This lets an embedding application load PerlOnJava beside JRuby
-or stock Joni without linkage collisions.
-
-The standalone JAR contains the unmodified Joni and JCodings license texts and a
-separate PerlOnJava modification notice under `META-INF/licenses`. The SBOM must
-identify the vendored Joni component, its upstream version, MIT license, and
-JCodings dependency.
-
-## Regex preprocessing boundary
-
-Do not move the entire `RegexPreprocessor` into the Joni fork. The ownership
-boundary is:
-
-- PerlOnJava owns Perl source policy and runtime integration: trusted callback
- templates, `use re 'eval'` security, lexical warnings, user-defined Unicode
- properties, Perl diagnostics, named/branch-reset capture mapping, and backend
- selection.
-- A backend-neutral Perl normalization layer may own syntax aliases shared by
- all engines, such as accepted named-group spellings and modifier validation.
-- Joni should own matcher semantics required by Perl, including condition
- execution, backtracking-visible state, native capture behavior, case-folding
- differences, and constructs that cannot be represented faithfully by textual
- rewrites.
-- The Java-only adapter retains Java `Pattern` syntax rewrites and stack-safety
- workarounds only while the Java route exists. The Joni adapter may temporarily
- materialize constructs whose native grammar is not complete, but it must use
- Joni—not Java regex—to execute property-value wildcard expressions.
-
-Migration is incremental, but the destination is fixed: Joni becomes the sole
-production matcher. Each preprocessing rule is classified as source policy,
-backend-neutral syntax, Java adaptation, or matcher semantics. Matcher semantics
-move into focused Joni changes with differential Perl tests. Java adaptation is
-deleted with the Java route; source policy remains in PerlOnJava.
-
-The compatibility inventory is the regex section of
-`docs/reference/feature-matrix.md`. The current selector routes subpattern
-calls, conditions, control verbs, ASCII-strict folds, and every structured
-executable callback template, including runtime `(??{...})`. Ordinary patterns
-remain part of the forced-Joni corpus even before automatic routing changes.
-Semantically safe constant dynamic expressions may still fold to pattern text;
-embedded closures never become compile-time constants because their execution
-and unwind are observable.
-
-## Remaining integration gates
-
-- Complete native extended character classes and a non-executing
- `(?(DEFINE)...)` container, then delete their textual rewrites.
-- Carry Perl charset mode and byte/Unicode provenance through property/class,
- literal, reverse-expansion, and backreference folding.
-- Parse nested property-value regex syntax inside Joni character properties and
- remove adapter range materialisation.
-- Restore original source names and exact Perl diagnostics after temporary
- internal encodings.
-- Remove Java matching, Java-only preprocessing, and backend selection after the
- complete semantic and performance gates pass.
-
-## Verification
-
-- The complete imported Joni suite passes through the root build.
-- Existing recursive-regex tests match stock Joni behavior.
-- Focused tests cover provisional captures, repeated execution after
- backtracking, `FAIL`, nested frames, handler exceptions, interruption, and
- exact-once reverse-order unwind.
-- Optimistic-callback tests cover standalone execution, conditional truth,
- `$^R`, exact `$^N` capture-close order, `$+`, and alternative reachability.
-- The standalone JAR contains no `org/joni` or `org/jcodings` classes, contains
- both relocated trees, and includes all required notices.
-- An embedding smoke test can load stock Joni and PerlOnJava together.
-- Full `make` and the focused direct/thread regex matrix pass.
-- The executable-callback and dynamic-pattern unit tests pass under standard
- Perl and both PerlOnJava execution backends.
-- Dynamic-pattern tests cover delayed expression evaluation, returned strings
- and `qr//` values, private nested captures, outer-suffix backtracking, nested
- callback cleanup, and recursive `qr//` values.
-- Matcher-control tests cover top-level, empty, subpattern-call, positive
- lookahead, and dynamic-program acceptance, including capture endpoints and
- skipped callbacks.
-- `dev/tools/perl_test_runner.pl perl5_t/t/re/` is compared file-by-file with
- `../PerlOnJava/logs/test_20260815_080000_958.log`; no previously passing regex
- file may regress, and changed pass counts or blocked-test totals are reported.
-
-## Stop conditions
-
-- Do not approximate callbacks as post-match hooks.
-- Do not enable a callback form if provisional state or unwind is incomplete.
-- Do not implement dynamic patterns as atomic nested matches; their alternatives
- must participate in outer backtracking.
-- Keep unsupported syntax fatal until its complete semantic gate passes.
+`CalloutResult` chooses `CONTINUE` or `FAIL` and may carry an opaque token.
+`MatchView` is read-only and valid only during the call; capture and position
+values are Joni byte offsets, and unset captures use `Region.REGION_NOTPOS`.
+`lastClosedCapture()` represents capture-close order for Perl's `$^N`, which is
+not equivalent to the highest-numbered active capture. `controlMark()` exposes
+the current backtracking-visible mark.
+
+The handler is installed on a `Matcher`, not on the compiled `Regex`. One
+compiled program can therefore be shared while each operation keeps independent
+callback state. Handlers that do not implement dynamic programs inherit a
+fail-fast `executeDynamic` default.
+
+## Token lifecycle
+
+The matcher and handler share this exact contract:
+
+1. The callout opcode invokes the handler with its ID and provisional view.
+2. A returned non-null token is stored in a matcher stack entry before normal
+ execution or failure continues.
+3. Backtracking across that entry calls `unwind(token)` exactly once.
+4. A token retained by the selected successful path receives
+ `complete(token)` exactly once, in reverse execution order.
+5. A `FAIL` result enters ordinary backtracking after its token is registered.
+6. Active tokens are resolved before `finish(true)` or `finish(false)` closes
+ the matcher invocation.
+7. If a handler throws before returning a token, the handler must clean up state
+ created by that invocation; Joni cannot register a token it never received.
+
+`complete` defaults to `unwind`, so cleanup-only handlers remain valid. The Perl
+bridge overrides completion because a successful callback retains different
+state from an abandoned one. Patterns without callout nodes do not allocate
+Perl callback handlers or callout stack frames.
+
+For the top-level matcher, `JoniRegexMatcher` calls `finish` after Joni resolves
+the token stack. Joni's nested dynamic continuation calls its own handler's
+`finish` when that continuation completes or is abandoned.
+
+## Dynamic subprograms
+
+`executeDynamic` returns a compiled nested `Regex`, its matcher-local handler,
+and the token for the dynamic expression's provisional state. Joni creates a
+nested matcher only when execution reaches the opcode.
+
+The nested matcher is a continuation, not an atomic boolean test. It yields one
+candidate endpoint at a time; if the outer suffix fails, the outer matcher asks
+the continuation for its next alternative before abandoning the dynamic frame.
+Nested captures remain private, while the selected endpoint advances the outer
+input. Completion or abandonment resolves every nested callback token and calls
+the nested handler's `finish` exactly once.
+
+This behavior is required for `(??{...})`: expression evaluation is delayed,
+returned strings and `qr//` values participate in outer backtracking, and a
+recursive returned regex uses the same continuation contract.
+
+## Perl bridge
+
+`JoniRegexPattern.PerlCalloutHandler` is the runtime implementation. Before
+running a closure it publishes provisional numbered captures and offsets. A
+token checkpoints Perl regex/capture state, the prior `$^R`, dynamic-local
+level, result metadata, and, for dynamic evaluation, the prior capture view.
+The callback's `pos` value is published and restored around invocation; it is
+not stored in the token. Control marks are exposed through `MatchView`, not
+copied into every token.
+
+Plain callbacks run in scalar context and update `$^R`; conditional callbacks
+select a branch without changing `$^R`. `$^N` comes from `lastClosedCapture`,
+not from `$+`. Forward execution keeps callback locals visible to later
+callbacks on the same path. `unwind` restores abandoned state; `complete`
+restores dynamic scope while retaining the selected callback result; `finish`
+publishes or restores the final operation state.
+
+Callback closures are Perl pseudo blocks. Unhandled `last`, `next`, `redo`, or
+`goto` cannot escape into an enclosing Perl loop or label. Callback exceptions
+restore the bridge's dynamic level even though no Joni token was returned.
+The PerlOnJava pseudo-block runtime limits recursive callback re-entry to 1,024
+levels independently of JVM native stack size.
+
+Every parser-created executable callback remains match-time observable. A body
+that happens to return a constant is not converted into a non-executable
+pattern. Only semantically safe constant dynamic expressions may use the
+compile-time fold path; captures or top-level alternatives keep a dynamic
+program boundary.
+
+## Matcher control and optimization
+
+Control verbs are native AST nodes and opcodes. `(*ACCEPT)` accepts the nearest
+matcher-program boundary: the whole top-level program, a subpattern call, a
+positive lookahead, or a dynamic nested program. It closes captures opened in
+that boundary at the current position and completes abandoned callback and
+continuation frames exactly once. `(*FAIL)`, `(*PRUNE)`, `(*SKIP)`, `(*THEN)`,
+`(*COMMIT)`, and mark forms likewise participate in native backtracking.
+
+Optimizer facts are derived from the Joni AST. Programs with control-verb state
+or dynamic options bypass ordinary search optimization. Dynamic callout nodes
+have unbounded maximum length, and Perl multi-fold nodes can suppress literal
+and minimum-length facts when those facts would be unsafe. Do not restore an
+optimizer shortcut without testing its control-flow, encoding, and fold
+assumptions.
+
+## Encoding and property hooks
+
+Joni works in encoding byte offsets. Unicode programs use UTF-8; byte programs
+use ISO-8859-1. PerlOnJava maps offsets at the adapter boundary. `WideScalarCodec`
+lets the engine recognize PerlOnJava's reversible internal representation for
+surrogate and above-Unicode scalar values without learning about Perl runtime
+objects.
+
+`CharacterPropertyResolver.Result` carries inclusive Unicode and wide ranges
+plus the property's case-fold policy. The parser preserves that provenance
+through ordinary and extended class algebra. `NamedCharacterResolver` supplies
+scalar names and sequences. `PerlPropertyValueMatcher` compiles the value-side
+regex used by Perl property wildcards with Joni semantics.
+
+JCodings remains responsible for byte traversal and baseline folds. Generated
+current-Perl tables and maintained Joni policy add Perl aliases, boundary rules,
+simple and multi-code-point closure, byte provenance, and `/d`, `/u`, `/a`, and
+`/aa` eligibility.
+
+The generated-data contract is maintained by the PerlOnJava adapter rather than
+the runtime-neutral hook API. Registered property and fold tables are checked by
+`dev/tools/generate_perl_unicode_data.pl --check`; scalar-name,
+named-sequence, and Joni boundary tables currently use standalone generators
+and require explicit regeneration and byte comparison. See the canonical
+[regex implementation document](../../dev/implementation/regex.md#encoding-and-unicode-ownership)
+for ownership and precedence details.
+
+## Packaging and notices
+
+Source packages stay `org.joni`, but the standalone JAR relocates Joni to
+`org.perlonjava.internal.joni` and JCodings to
+`org.perlonjava.internal.jcodings`. This prevents linkage collisions when an
+embedding application also loads JRuby or stock Joni.
+
+The JAR contains byte-identical copies of:
+
+- `third_party/joni/LICENSE` as `META-INF/licenses/joni-LICENSE.txt`;
+- `third_party/licenses/jcodings-LICENSE.txt`;
+- `third_party/joni/PERLONJAVA-NOTICE.md` as
+ `META-INF/licenses/joni-PERLONJAVA-NOTICE.md`.
+
+The combined SBOM identifies vendored Joni 2.2.7, JCodings 1.0.64, their
+licenses, and the Joni-to-JCodings dependency. Do not remove or rewrite upstream
+copyright headers in maintained sources.
+
+## Change verification
+
+A fork change requires evidence at the boundary it modifies:
+
+- `make test-joni` for direct lexer, parser, compiler, optimizer, matcher, and
+ callout behavior;
+- system-Perl validation followed by both PerlOnJava execution backends for
+ changed Perl fixtures;
+- focused callback tests for provisional captures, `$^R`, `$^N`, dynamic
+ locals, exceptions, exact-once unwind/completion, and nested continuations;
+- matcher-control tests for top-level, subpattern, lookahead, and dynamic
+ program boundaries;
+- `make` for the complete unit, fork, packaging, and notice gate;
+- the Gradle `verifyJoniPackaging` task, run by `make`, which invokes
+ `dev/tools/verify-joni-packaging.pl` with the standalone JAR and merged SBOM;
+- the imported `perl5_t/t/re/` corpus compared file by file against its accepted
+ baseline.
+
+Never approximate callbacks as post-match hooks, dynamic patterns as atomic
+nested matches, or control verbs as source rewrites. Unsupported syntax remains
+fatal unless the explicit development-only `JPERL_UNIMPLEMENTED=warn` policy is
+requested; that downgrade is not semantic support.
+
+Related documents:
+
+- [Regex implementation](../../dev/implementation/regex.md)
+- [Phase 36 acceptance plan](../../dev/design/phase36-regex-parity.md)
+- [Standalone library RFC](../../dev/design/perl-regex-library-rfc.md) (proposal only)
diff --git a/docs/reference/feature-matrix.md b/docs/reference/feature-matrix.md
index ee983512da..3028d63cd3 100644
--- a/docs/reference/feature-matrix.md
+++ b/docs/reference/feature-matrix.md
@@ -353,63 +353,60 @@ my @copy = @{$z}; # ERROR
---
## Regular Expressions
-- ✅ **Basic Matching**: Operators `qr//`, `m//`, `s///`, `split` are implemented.
-- ✅ **Regex modifiers**: Modifiers `/p` `/i` `/m` `/s` `/n` `/g` `/c` `/r` `/e` `/ee` `/x` `/xx` are implemented.
-- ✅ **Special variables**: The special variables `$1`, `$2`... are implemented.
-- ✅ **Transliteration**: `tr` and `y` transliteration operators are implemented.
-- ✅ **`pos`**: `pos` operator is implemented.
-- ✅ **`\G`**: `\G` operator in regex is implemented.
-- ✅ **`\N{name}`**: `\N{name}` and `\N{U+hex}` operator for named characters in regex is implemented.
-- ✅ **`\N`**: Not-newline operator.
-- ✅ **lvalue `pos`**: lvalue `pos` operator is implemented.
-- ✅ **`m?pat?`** one-time match is implemented.
-- ✅ **`reset`** resetting one-time match is implemented
-- ✅ **`@-`, `@+`, `%+`, `%-`, `@{^CAPTURE}`, `${^LAST_SUCCESSFUL_PATTERN}` variables**: regex special variables are implemented
-- ✅ **`$&` variables**: `` $` ``, `$&`, `$'`, `$+` special variables are implemented, and aliases: `${^PREMATCH}`, `${^MATCH}`, `${^POSTMATCH}`.
-- ✅ **`[[:pattern:]]`**: `[[:ascii:]]`, `[[:print:]]` are implemented.
-- ✅ **Matching plain strings**: `$var =~ "Test"` is implemented.
-- ✅ **Inline comments**: `(?#comment)` in regex is implemented.
-- ✅ **caret modifier**: `(?^` embedded pattern-match modifier, shorthand equivalent to "d-imnsx".
-- ✅ **\b inside character class**: `[\b]` is supported in regex.
-- ✅ **Vertical whitespace escapes**: Native Joni `\v` matches U+000A..U+000D, U+0085, U+2028, and U+2029; `\V` matches the complement. Direct and character-class forms match Perl, and `reg_posixcc.t` passes 2,560/2,560 on both execution backends.
-- ✅ **Unicode boundary assertions**: `\b{gcb}`, `\b{sb}`, `\b{wb}`, and `\b{lb}` (and their `\B` forms) execute natively in Joni from reproducible pinned Perl 5.44 Unicode 17.0 data. The complete 239,866-assertion generated boundary corpus passes on both backends.
-- ✅ **Variable Interpolation in Regex**: Features like `${var}` for embedding variables.
-- ✅ **Non-capturing groups**: `(?:...)` is implemented.
-- ✅ **Named Capture Groups**: Defining named capture groups using `(?...)` or `(?'name'...)` is supported.
-- ✅ **Backreferences to Named Groups**: Using `\k` or `\g{name}` for backreferences to named groups is supported.
-- ✅ **Relative Backreferences**: Using `\g{-n}` for relative backreferences.
-- ✅ **Basic Unicode Properties**: Common `\p{...}` and `\P{...}` forms such as `\p{L}` execute through Joni. General_Category assignments now enter the forked Joni parser unchanged and resolve to pinned Perl ranges there.
-- 🟡 **Perl Unicode Property Syntax**: Perl-specific properties execute through Joni from generated pinned Perl 5.44 Unicode 17.0 data. Exact assignments use Joni's range-resolver API with explicit per-family fold policy. Property-value wildcard expressions are compiled and executed by the forked Joni matcher, then temporarily materialized as selected ranges by the adapter; Java regex no longer evaluates them. No-fold properties inside composed classes and some generated alias/diagnostic cases remain migration work.
-- 🟡 **Named Unicode Sequences**: A generated pinned 461-entry Perl Unicode 17 table resolves multi-code-point `\\N{name}` sequences before scalar names in strings and Joni patterns. Temporary internal encoding and exact diagnostics for unknown or restricted class uses remain migration work.
-- ✅ **Possessive Quantifiers**: Quantifiers like `*+`, `++`, `?+`, and `{n,m}+`, which disable backtracking, are supported.
-- ✅ **Atomic Grouping**: Use of `(?>...)` for atomic groups is supported.
-- ✅ **`\K` assertion**: Keep left — in `s///`, text before `\K` is preserved; match variables reflect only the portion after `\K`. Ordinary KEEP assertions route through native Joni and no longer use the Java marker rewrite; the adapter still rejects KEEP inside lookaround until the Joni analyser emits Perl's diagnostic directly.
-- ✅ **Preprocessor**: `\Q`, `\L`, `\U`, `\l`, `\u`, `\E` are preprocessed in regex.
-- ✅ **Overloading**: `qr` overloading is implemented. See also [overload pragma](#pragmas).
-- ✅ **Python-style named groups**: `(?P...)` and `(?P=name)` are parsed natively by Joni with Perl capture numbering, duplicate-name behavior, and malformed/unknown-name diagnostics.
-- ✅ **Alpha assertion aliases**: `(*pla:...)`, `(*plb:...)`, `(*nla:...)`, `(*nlb:...)`, `(*atomic:...)`, and the corresponding long spellings are parsed natively by Joni with Perl nesting, capture numbering, backtracking, assertion-condition predicates, and malformed-form diagnostics.
-- 🟡 **Underscored numeric regex escapes**: Joni natively parses Perl spellings such as `\x{0_0_4_1}` and `\o{0_0_1_0_1}` through U+10FFFF, including literal/class forms, bare high-octal UTF-8 code points, truncation behavior, and structural diagnostics. The frontend normalization remains for forced-Java compatibility; exact `use re 'strict'` diagnostics and Perl code points above U+10FFFF through signed IV max remain source-policy/representation debt.
-
-- ✅ **Dynamically-scoped regex variables**: Provisional captures, `$^R`, `$^N`, match positions, and callback locals follow matcher paths and unwind on backtracking.
-- ✅ **Recursive and Dynamic Patterns**: `(?R)`, `(?0)`, and runtime `(??{ code })` execute through Joni. Dynamic expressions may return strings or `qr//` values, nested alternatives participate in outer backtracking without changing outer grouping or capture numbering, and callback and pure-pattern recursion have engine-owned depth ceilings.
-- ✅ **Backtracking Control Verbs**: `(*ACCEPT)`, `(*FAIL)`/`(*F)`, `(*PRUNE)`, `(*SKIP)`, `(*THEN)`, and `(*COMMIT)` execute through Joni with matcher-owned cut boundaries. Atomic groups `(?>...)` are supported.
-- ✅ **Marks and named skip targets**: `(*MARK:NAME)` and its `(*:NAME)` shorthand, named `(*SKIP:NAME)`, `$REGMARK`, and `$REGERROR` execute through Joni and follow the selected backtracking path.
-- 🟡 **Regex Definitions**: `(?(DEFINE)...)` containers and numbered or named calls to their subpatterns execute through Joni. The frontend still rewrites the non-executing container into an equivalent internal form; native Joni ownership and removal of that rewrite remain in progress.
-- ✅ **Lookbehind Assertions**: Fixed and bounded variable-length positive and negative lookbehind assertions execute through Joni, including nested lookahead. Removing the remaining compatibility routing requires the complete lookbehind corpus and native diagnostics to pass.
-- ✅ **Branch Reset Groups**: `(?|...)` resets capture numbering across alternatives and preserves mapped match variables.
-- ✅ **Advanced Subroutine Calls**: Sub-pattern calls with numbered or named references like `(?1)` and `(?&name)` execute through Joni.
-- ✅ **Conditional Expressions**: Numbered and named capture conditions, positive and negative assertion conditions, recursion conditions `(?(R))`, `(?(R1))`, and `(?(R&name))`, executable callback conditions, and optimistic predicates execute through Joni.
-- 🟡 **Extended Unicode Regex Features**: Generated Perl property families, invalid-property gates, and Unicode boundaries execute through Joni. The complete property corpus currently passes 83,616/83,648 identically on JVM and interpreter; the 32 residual assertions are only deprecated `Hyphen`/`IsHyphen` warning diagnostics, while membership assertions pass. Boundary, grapheme, sentence, word, and line corpora have their separate complete gates below.
-- ✅ **Extended Grapheme Clusters**: Native `\b{gcb}`/`\B{gcb}` implement GB1–GB13 and GB999, and `\X` includes repeated GB9c Indic conjuncts. The complete 8,516-assertion GCB/`\X` section of authoritative chunk 05 passes identically on JVM and interpreter.
-- ✅ **Unicode Sentence Boundaries**: Native `\b{sb}`/`\B{sb}` implement SB1–SB11 and SB998 from the reproducible Perl 5.44 Unicode 17.0 sentence-break table. Authoritative chunk 05 passes 14,976/14,976 identically on JVM and interpreter.
-- ✅ **Unicode Word Boundaries**: Native `\b{wb}`/`\B{wb}` implement WB1–WB16 and WB999 from reproducible Perl 5.44 Unicode 17.0 word-break and extended-pictographic tables. Authoritative chunk 10 passes 19,510/19,510 identically on JVM and interpreter.
-- ✅ **Unicode Line Boundaries**: Native `\b{lb}`/`\B{lb}` implement Unicode 17 UAX #14 from reproducible pinned line-break, category, width, and emoji tables. Authoritative chunks 06–09 pass 205,380/205,380 identically on JVM and interpreter.
-- ✅ **Embedded Code in Regex**: `(?{ code })`, optimistic callbacks `(*{ code })`, executable callback conditions, and `(??{ code })` run as lexical closures in Joni with provisional captures and backtracking unwind. Callback `local` frames follow matcher paths, and escaped loop control or `goto` stops at the callback pseudo-block boundary. `$^N` follows capture-close order independently of `$+`.
-- ✅ **Regex Debugging**: Lexically scoped `use/no re 'debug'` and `debugcolor` are supported, including runtime snapshot ownership.
-- ✅ **Runtime Regex Evaluation**: `use re 'eval'` controls whether interpolated patterns containing eval groups may compile. Admitted runtime source is compiled into lexical callback closures and preserves its package, visible lexical cells, Unicode or byte source type, default regex modifiers, and match-once state. Literal callbacks, interpolated `qr//` values (including local, referenced, and tied arrays), raw runtime eval groups, callback conditions, and standalone `(?(DEFINE)...)` containers may be composed in one Joni pattern; dynamic callbacks may return further admitted executable source.
-- ✅ **Regex Compilation Flags**: Lexically scoped default flags from `use/no re '/imsx'` are applied to literal, interpolated, and runtime-compiled regex values.
-- 🟡 **Perl capture and ASCII fold modifiers**: Top-level and scoped `/n` suppress unnamed captures. `/a` and `/aa` matching is available through the Joni path, but native property/class closure, byte/Unicode provenance, literal reverse expansion, and backreference folding are still being moved from adapter protections into the fork.
-- ✅ **Perl Named Captures**: Names may contain underscores, and duplicate named groups preserve Perl-style `%+`/`%-` and backreference behavior.
+
+All production matching uses the vendored Joni fork through `RuntimeRegex` and
+`JoniRegexPattern`; there is no production Java matcher or preprocessor route.
+A test-scope-only `RegexBackendPolicy` model compiles immutable compatibility
+tests but is absent from production packaging. Status below means
+the named behavior is demonstrated by source and a focused gate, not that every
+Perl diagnostic, optimizer path, or complete upstream corpus is closed.
+
+The review covers the current `perl5/pod/perlreref.pod`, `perlrecharclass.pod`,
+`perlrequick.pod`, `perlrepository.pod`, `perlre.pod`, `perlretut.pod`, and
+`perlrebackslash.pod` inventory. POD syntax examples are grouped by semantic
+family; documentation headings such as NAME, DESCRIPTION, BUGS, SEE ALSO,
+AUTHORS, and repository prose are intentionally excluded from capability
+claims. The review classified 565 inventory rows: 516 map to a capability
+family, 42 are intentional descriptive/tutorial exclusions, and seven record
+the source POD files; no rows were left unclassified.
+
+| Family | Status | Supported behavior and evidence | Boundary |
+|---|---|---|---|
+| Operations, interpolation, and state | ✅ | `qr//`, `m//`, `s///`, `split`, `tr///`, `y///`, interpolation, `m?PAT?`, `reset`, `/g`, `/c`, `pos`, `\G`, match offsets, `$1`, `$&`, `$'`, `$+`, `%+`, and `%-`; `RuntimeRegex.java`, `regex_g_pos.t`, `regex_c_pos.t`, `regex_once.t`, `regex_mark_control.t`. | Reuse and state are runtime-local; complete corpus parity remains a release gate. |
+| Modifiers and quoting | 🟡 | `/i`, `/m`, `/s`, `/p`, `/n`, `/x`, `/xx`, `/g`, `/c`, `/r`, `/e`, `/ee`, `(?^...)`, scoped option groups, `(?#...)`, `\Q...\E`, and case escapes; `RegexFlags.java`, `RegexQuoteMeta.java`, `regex_xx.t`, `regex_n.t`, `re_debug_pragma.t`. | `/d`, `/u`, `/l`, `/a`, and `/aa` provenance and folding differ by pattern/subject mode; unresolved portions are partial rather than a Java fallback. |
+| Atoms, quantifiers, and ordinary classes | 🟡 | Literals, `.`, anchors, alternation, captures/non-captures, greedy/lazy/possessive quantifiers, atomic groups, `\K`, POSIX classes, `\d`, `\w`, `\s`, `\h`, `\v`, `\R`, `\N`, and `\X`; `JoniRegexPattern.java`, `regex_possessive_quantifier.t`, `regex_atomic_group.t`, `joni_keep_progress.t`, `vertical_whitespace_escape.t`. | Forward/reverse literal and character-class fold expansion remains an open semantic gate; KEEP in lookaround and exact malformed-pattern wording are also partial. |
+| Escapes, numeric forms, and references | 🟡 | Control, octal, hexadecimal, named, absolute, relative, and named references are parsed by Joni; `numeric_escape_underscores.t`, `native_numeric_backreference_boundaries.t`, `native_escape_named_reference_diagnostics.t`, `joni_relative_group_call_diagnostics.t`. | Exact `re 'strict'` messages and values outside ordinary Unicode/signed scalar representation remain divergent. |
+| Unicode properties, names, and character-class algebra | 🟡 | Current-Perl generated properties, aliases, POSIX compatibility, `\p`/`\P`, `\N{name}`, multi-code-point names, `(?[...])`, and property-value wildcards execute in Joni; `UnicodeResolver.java`, `unicode_all_property.t`, `unicode_named_sequences.t`, `joni_named_character_sequences.t`, `unicode_property_value_wildcards.t`, `extended_more_character_class.t`. | Exact unknown/restricted-name and property diagnostics, plus remaining fold/provenance cases, are partial. Generated data refreshes from the latest upstream checkout rather than a pinned Perl SHA. |
+| Captures, names, branch reset, and backreferences | 🟡 | Perl and Python named captures, duplicate names, numbered/relative/named backreferences, `(?|...)`, `/n`, and capture publication; `branch_reset_capture_semantics.t`, `branch_reset_named_call.t`, `regex_duplicate_named_backreference.t`, `regex_named_capture.t`, `casefold_literal_backreference.t`, `joni_ascii_strict_backreference_folds.t`. | Case-insensitive backreference folding is covered for existing cases, but its full-fold/charset interaction shares the unresolved literal/class fold boundary; malformed-reference diagnostics remain partial. |
+| Lookarounds and alpha aliases | 🟡 | Positive/negative lookahead, fixed and bounded lookbehind, nested assertions, and `(*pla:...)`/`(*plb:...)`/`(*nla:...)`/`(*nlb:...)`; `lookbehind_native_acceptance.t`, `extended_unicode_lookbehind.t`, `alpha_assertion_native_routing.t`. | Variable-width limits, KEEP-in-lookaround, and exact failure positions remain partial. |
+| Calls, recursion, DEFINE, and conditions | ✅ | `(?R)`, `(?0)`, numbered/named/signed-relative calls, `(?(DEFINE)...)`, capture/assertion/recursion conditions, and branch-reset calls; `recursive_patterns.t`, `joni_whole_pattern_recursion.t`, `recursion_conditions.t`, `conditional_native_routing.t`. | Engine depth ceilings and remaining malformed-condition diagnostics are explicit limits. |
+| Code groups and dynamic programs | 🟡 | Parser-created `(?{...})`, `(*{...})`, callback conditions, and `(??{...})` execute as matcher-local Joni callouts with backtracking unwind; dynamic values may be strings or `qr//`; `executable_callbacks.t`, `dynamic_patterns.t`, `native_dynamic_pattern_contract.t`, `callback_exception_unwind.t`. | Raw runtime executable text requires `use re 'eval'`; untrusted interpolated callback text is not promoted to executable code. |
+| Control verbs and marks | ✅ | `(*ACCEPT)`, `(*FAIL)`/`(*F)`, `(*PRUNE)`, `(*SKIP)`, `(*THEN)`, `(*COMMIT)`, `(*MARK:NAME)`, `(*:NAME)`, `$REGMARK`, and `$REGERROR`; `perl_control_verbs.t`, `control_verb_paths.t`, `named_accept_fail_control_verbs.t`, `regex_mark_control.t`. | Optimizer behavior intentionally yields to matcher control flow; performance evidence is separate. |
+| Unicode boundaries and byte behavior | 🟡 | `\b{gcb}`, `\b{sb}`, `\b{wb}`, `\b{lb}`, `\B` forms, UTF-8 offset mapping, byte-mode identity mapping, and wide scalar encoding; `grapheme_boundary_rules.t`, `sentence_boundary_rules.t`, `word_boundary_rules.t`, `line_boundary_rules.t`, `wide_scalar_engine_semantics.t`. | Charset-mode folds and byte-backed class provenance are active partial work. |
+| Warnings, diagnostics, debug, and performance | 🟡 | Source-positioned compile errors, `use re 'strict'` policy, `use/no re 'debug'`, `debugcolor`, and warning inheritance; `RegexDiagnosticFormatter.java`, `frontend_regex_diagnostic_provenance.t`, `native_compile_diagnostics.t`, `re_debug_pragma.t`. | Remaining native lexer/parser diagnostic families and optimizer `speed*` behavior are active work; parser acceptance is not proof of exact Perl diagnostics. |
+| Custom engines and non-Perl interfaces | ❌ | `perlre` custom regex-engine hooks and host-defined engine protocols are not implemented. | PerlOnJava supports its bundled Joni engine only. |
+
+### Pending final integrated verification
+
+This first-pass matrix is pinned to integrated parent
+`5054f236ea8533c46b1068275bdbd703c3207ace`. A later pass may promote only rows
+whose required tests pass on the final immutable integration artifact:
+
+- `/d` byte-backed class provenance and its charset/fold interaction;
+- remaining native lexer/parser diagnostics and exact source-position wording;
+- optimizer-sensitive literal/fold search behavior, including the `speed*`
+ corpus.
+
+No result is predicted here; until that artifact exists, these boundaries remain
+partial regardless of progress in POJ3, POJ4, or POJ5 worktrees.
+
+The current source-level architecture is described in
+[`dev/implementation/regex.md`](../../dev/implementation/regex.md) and the
+runtime-neutral callback/packaging contract in
+[`docs/design/joni-callout-fork.md`](../design/joni-callout-fork.md). The Phase
+36 plan remains the authority for integration and release completion; active
+P3 `/d` provenance, P4 diagnostic, and P5 optimizer work is deliberately marked
+partial here until its commits and shared gates are integrated.
## Statements and Special Operators
diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
index 724d644097..f0ea3aa046 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
@@ -2,7 +2,6 @@
import org.perlonjava.backend.jvm.EmitterContext;
-import org.perlonjava.backend.jvm.EmitterMethodCreator;
import org.perlonjava.frontend.analysis.ConstantFoldingVisitor;
import org.perlonjava.frontend.analysis.FindDeclarationVisitor;
import org.perlonjava.frontend.analysis.RegexUsageDetector;
@@ -132,9 +131,6 @@ private void emitNormalLoopResult() {
// separately from the register table because closure analysis may first
// materialize a reference as a lexical before the body reaches `local`.
private final Set capturedOurVariableNames = new HashSet<>();
- // BEGIN support for named subroutine closures
- int currentSubroutineBeginId = 0; // BEGIN ID for current named subroutine (0 = not in named sub)
- Set currentSubroutineClosureVars = new HashSet<>(); // Variables captured from outer scope
// EmitterContext for strict checks and other compile-time options
private EmitterContext emitterContext;
// Register allocation
@@ -143,6 +139,9 @@ private void emitNormalLoopResult() {
private int maxRegisterEverUsed = 2; // Track highest register ever allocated
// True when this compiler was constructed for eval STRING (has parentRegistry)
private boolean isEvalString;
+ // Runtime regex interpolation can synthesize executable source through
+ // overload, so the containing CV must expose all of its live lexical cells.
+ private boolean tracksRuntimeRegexLexicals;
// True when compiling inside a defer block (control flow out of defer is prohibited)
private boolean isInDeferBlock;
// True when compiling inside a map/grep block (explicit return must use RETURN_NONLOCAL)
@@ -866,6 +865,14 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
// Store context for strict checks and other compile-time options
this.emitterContext = ctx;
+ if (node != null) {
+ VariableCollectorVisitor runtimeSourceCollector =
+ new VariableCollectorVisitor(new HashSet<>());
+ node.accept(runtimeSourceCollector);
+ tracksRuntimeRegexLexicals =
+ runtimeSourceCollector.requiresAllRuntimeLexicals();
+ }
+
// Detect closure variables if context is provided
if (ctx != null) {
detectClosureVariables(node, ctx);
@@ -994,6 +1001,7 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
// Set optimization flag - if no LOCAL_* or PUSH_LOCAL_VARIABLE opcodes were emitted,
// the interpreter can skip DynamicVariableManager.getLocalLevel/popToLocalLevel
code.usesLocalization = this.usesLocalization;
+ code.tracksRuntimeRegexLexicals = this.tracksRuntimeRegexLexicals;
// Attach the `our` registry so eval STRING can inherit caller's `our` aliases
code.ourVariableRegistry = ourVariableRegistry.isEmpty() ? null : ourVariableRegistry;
// Store goto label map for dynamic goto support (goto $variable)
@@ -1037,7 +1045,7 @@ private void detectClosureVariables(Node ast, EmitterContext ctx) {
Set used = new HashSet<>();
VariableCollectorVisitor collector = new VariableCollectorVisitor(used);
ast.accept(collector);
- if (!collector.hasEvalString()) {
+ if (!collector.requiresAllRuntimeLexicals()) {
usedVars = used;
}
}
@@ -1440,6 +1448,11 @@ public void visit(BlockNode node) {
// EmitBlock.emitPostBlockStrictOptions() in the JVM backend.
Object postBlockStrictOptions = node.getAnnotation("postBlockStrictOptions");
if (postBlockStrictOptions instanceof Integer hints) {
+ if (!node.getBooleanAnnotation("blockIsSubroutine")) {
+ Object postBlockWarningBits = node.getAnnotation("postBlockWarningBits");
+ emit(Opcodes.SET_CALL_SITE_WARNING_BITS);
+ emit(addToStringPool(postBlockWarningBits instanceof String bits ? bits : ""));
+ }
emit(Opcodes.SET_CALL_SITE_HINTS);
emit(hints);
}
@@ -1826,18 +1839,9 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) {
String varName = ((IdentifierNode) leftOp.operand).name;
String arrayVarName = "@" + varName;
- // Get the array register - check closure, lexical, then global
+ // Get the array register - check lexical, then global
int arrayReg;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(arrayVarName)
- && !isDynamicOurVariable(arrayVarName)) {
- arrayReg = allocateRegister();
- int nameIdx = addToStringPool(arrayVarName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- emitReg(arrayReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(arrayVarName) && !isOurVariable(arrayVarName)) {
+ if (hasVariable(arrayVarName) && !isOurVariable(arrayVarName)) {
arrayReg = getVariableRegister(arrayVarName);
} else {
// 'our' arrays (or undeclared globals) must be fetched from the global
@@ -1940,16 +1944,7 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) {
String varName = ((IdentifierNode) leftOp.operand).name;
String arrayVarName = "@" + varName;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(arrayVarName)
- && !isDynamicOurVariable(arrayVarName)) {
- arrayReg = allocateRegister();
- int nameIdx = addToStringPool(arrayVarName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- emitReg(arrayReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(arrayVarName) && !isDynamicOurVariable(arrayVarName)) {
+ if (hasVariable(arrayVarName) && !isDynamicOurVariable(arrayVarName)) {
arrayReg = getVariableRegister(arrayVarName);
} else {
arrayReg = allocateRegister();
@@ -2060,18 +2055,9 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) {
String varName = ((IdentifierNode) leftOp.operand).name;
String hashVarName = "%" + varName;
- // Get the hash register - check closure, lexical, then global
+ // Get the hash register - check lexical, then global
int hashReg;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(hashVarName)
- && !isDynamicOurVariable(hashVarName)) {
- hashReg = allocateRegister();
- int nameIdx = addToStringPool(hashVarName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- emitReg(hashReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(hashVarName) && !isOurVariable(hashVarName)) {
+ if (hasVariable(hashVarName) && !isOurVariable(hashVarName)) {
hashReg = getVariableRegister(hashVarName);
} else {
// 'our' hashes (or undeclared globals) must be fetched from the global
@@ -2187,16 +2173,7 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) {
String varName = ((IdentifierNode) leftOp.operand).name;
String hashVarName = "%" + varName;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(hashVarName)
- && !isDynamicOurVariable(hashVarName)) {
- hashReg = allocateRegister();
- int nameIdx = addToStringPool(hashVarName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- emitReg(hashReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(hashVarName) && !isDynamicOurVariable(hashVarName)) {
+ if (hasVariable(hashVarName) && !isDynamicOurVariable(hashVarName)) {
hashReg = getVariableRegister(hashVarName);
} else {
hashReg = allocateRegister();
@@ -2327,16 +2304,7 @@ void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) {
String varName = ((IdentifierNode) leftOp.operand).name;
String hashVarName = "%" + varName;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(hashVarName)
- && !isDynamicOurVariable(hashVarName)) {
- hashReg = allocateRegister();
- int nameIdx = addToStringPool(hashVarName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- emitReg(hashReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(hashVarName) && !isDynamicOurVariable(hashVarName)) {
+ if (hasVariable(hashVarName) && !isDynamicOurVariable(hashVarName)) {
hashReg = getVariableRegister(hashVarName);
} else {
hashReg = allocateRegister();
@@ -3025,6 +2993,7 @@ void compileVariableDeclaration(OperatorNode node, String op) {
}
default -> throwCompilerException("Unsupported variable type: " + sigil);
}
+ emitActiveLexicalBinding(reg, varName);
// Match JVM EmitVariable: MODIFY_*_ATTRIBUTES must run for : ATTR(...)
// declarations even when storage comes from RETRIEVE_BEGIN_* (closure
@@ -3090,6 +3059,7 @@ void compileVariableDeclaration(OperatorNode node, String op) {
}
default -> throwCompilerException("Unsupported variable type: " + sigil);
}
+ emitActiveLexicalBinding(reg, varName);
// Runtime attribute dispatch for state variables with attributes
emitVarAttrsIfNeeded(node, reg, sigil);
@@ -3442,6 +3412,7 @@ void compileVariableDeclaration(OperatorNode node, String op) {
default ->
throwCompilerException("Unsupported variable type in list declaration: " + sigil);
}
+ emitActiveLexicalBinding(reg, varName);
// A captured declaration-list slot is retrieved from the
// persistent definition-time cell, but attributes still
@@ -4602,20 +4573,7 @@ void compileVariableReference(OperatorNode node, String op) {
if (node.operand instanceof IdentifierNode) {
String varName = "$" + ((IdentifierNode) node.operand).name;
- // Check if this is a closure variable captured from outer scope via PersistentVariable
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars.contains(varName)
- && !isDynamicOurVariable(varName)) {
- // This is a closure variable - use RETRIEVE_BEGIN_SCALAR
- int rd = allocateOutputRegister();
- int nameIdx = addToStringPool(varName);
-
- emitWithToken(Opcodes.RETRIEVE_BEGIN_SCALAR, node.getIndex());
- emitReg(rd);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
-
- lastResultReg = rd;
- } else if (hasVariable(varName) && !isOurVariable(varName)) {
+ if (hasVariable(varName) && !isOurVariable(varName)) {
// Lexical variable (my/state) - use existing register
lastResultReg = getVariableRegister(varName);
} else if (hasVariable(varName) && isOurVariable(varName)) {
@@ -4735,19 +4693,8 @@ void compileVariableReference(OperatorNode node, String op) {
return;
}
- // Check if this is a closure variable captured from outer scope via PersistentVariable
int arrayReg;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars.contains(varName)
- && !isDynamicOurVariable(varName)) {
- // This is a closure variable - use RETRIEVE_BEGIN_ARRAY
- arrayReg = allocateRegister();
- int nameIdx = addToStringPool(varName);
-
- emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- emitReg(arrayReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(varName) && !isOurVariable(varName)) {
+ if (hasVariable(varName) && !isOurVariable(varName)) {
// Lexical array (my/state) - use existing register
arrayReg = getVariableRegister(varName);
} else if (hasVariable(varName) && isOurVariable(varName)) {
@@ -4852,16 +4799,7 @@ void compileVariableReference(OperatorNode node, String op) {
String varName = "%" + ((IdentifierNode) node.operand).name;
int hashReg;
- if (currentSubroutineBeginId != 0 && currentSubroutineClosureVars != null
- && currentSubroutineClosureVars.contains(varName)
- && !isDynamicOurVariable(varName)) {
- hashReg = allocateRegister();
- int nameIdx = addToStringPool(varName);
- emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- emitReg(hashReg);
- emit(nameIdx);
- emit(currentSubroutineBeginId);
- } else if (hasVariable(varName) && !isOurVariable(varName)) {
+ if (hasVariable(varName) && !isOurVariable(varName)) {
// Lexical hash (my/state) - use existing register
hashReg = getVariableRegister(varName);
} else if (hasVariable(varName) && isOurVariable(varName)) {
@@ -5230,6 +5168,16 @@ void emitLexicalAlias(int register, String variableName) {
emit(addToStringPool(variableName));
}
+ void emitActiveLexicalBinding(int register, String variableName) {
+ emit(Opcodes.BIND_ACTIVE_LEXICAL);
+ emitReg(register);
+ emit(addToStringPool(variableName));
+ }
+
+ boolean tracksRuntimeRegexLexicals() {
+ return tracksRuntimeRegexLexicals;
+ }
+
/**
* Apply Perl's context conversion to an array value. This is shared by
* named arrays and dereferenced arrays: scalar {@code @$ref} is the array
@@ -5361,7 +5309,7 @@ TreeMap collectVisiblePerlVariablesNarrowed(Node body) {
Set used = new HashSet<>();
VariableCollectorVisitor collector = new VariableCollectorVisitor(used);
body.accept(collector);
- if (collector.hasEvalString()) return all;
+ if (collector.requiresAllRuntimeLexicals()) return all;
TreeMap narrowed = new TreeMap<>();
for (Map.Entry e : all.entrySet()) {
if (used.contains(e.getValue())) {
@@ -5799,43 +5747,7 @@ private void visitNamedSubroutine(SubroutineNode node) {
closureCapturedVarNames.addAll(closureVarNames);
- int beginId = 0;
- if (!closureVarIndices.isEmpty()) {
- beginId = EmitterMethodCreator.classCounter.getAndIncrement();
-
- // Store each closure variable in PersistentVariable globals
- for (int i = 0; i < closureVarNames.size(); i++) {
- String varName = closureVarNames.get(i);
- int varReg = closureVarIndices.get(i);
-
- // Get the variable type from the sigil
- String sigil = varName.substring(0, 1);
- String bareVarName = varName.substring(1);
- String beginVarName = PersistentVariable.beginPackage(beginId) + "::" + bareVarName;
-
- // Store the variable value in PersistentVariable global
- int nameIdx = addToStringPool(beginVarName);
- switch (sigil) {
- case "$" -> {
- emit(Opcodes.STORE_GLOBAL_SCALAR);
- emit(nameIdx);
- emitReg(varReg);
- }
- case "@" -> {
- emit(Opcodes.STORE_GLOBAL_ARRAY);
- emit(nameIdx);
- emitReg(varReg);
- }
- case "%" -> {
- emit(Opcodes.STORE_GLOBAL_HASH);
- emit(nameIdx);
- emitReg(varReg);
- }
- }
- }
- }
-
- // Step 3: Compile the subroutine body with a packed registry for captured variables.
+ // Step 2: Compile the subroutine body with a packed registry for captured variables.
// The closure created at runtime packs capturedVars sequentially into registers 3..,
// so the compiled sub body must use the same packed register layout.
Map packedRegistry = new HashMap<>();
@@ -5863,16 +5775,12 @@ private void visitNamedSubroutine(SubroutineNode node) {
// Inherit pragma flags so BEGIN { $^H = ... } changes propagate into sub body
inheritPragmaFlags(subCompiler);
- // Set the BEGIN ID in the sub-compiler so it knows to use RETRIEVE_BEGIN opcodes
- subCompiler.currentSubroutineBeginId = beginId;
- subCompiler.currentSubroutineClosureVars = new HashSet<>(closureVarNames);
-
// Subroutine bodies should use RUNTIME context so the calling context
// (VOID/SCALAR/LIST) propagates correctly at runtime via register 2 (wantarray).
subCompiler.currentCallContext = RuntimeContextType.RUNTIME;
- // Step 4: Compile the subroutine body
- // Sub-compiler will use RETRIEVE_BEGIN opcodes for closure variables
+ // Step 3: Compile the subroutine body. Captured variables resolve directly
+ // through the packed registers instead of a copied synthetic global cell.
InterpretedCode subCode = subCompiler.compile(node.block);
subCode.futureAsyncAwaitSub = node.getBooleanAnnotation("futureAsyncAwaitSub");
subCode.futureAsyncAwaitFutureClass =
@@ -5884,7 +5792,7 @@ private void visitNamedSubroutine(SubroutineNode node) {
System.out.println(Disassemble.disassemble(subCode));
}
- // Step 5: Emit bytecode to create closure or simple code ref
+ // Step 4: Emit bytecode to create closure or simple code ref
int codeReg = allocateRegister();
if (closureVarIndices.isEmpty()) {
@@ -5906,7 +5814,7 @@ private void visitNamedSubroutine(SubroutineNode node) {
}
}
- // Step 6: Store in global namespace
+ // Step 5: Store in global namespace
String fullName = node.name;
if (!fullName.contains("::")) {
fullName = getCurrentPackage() + "::" + fullName; // Use getCurrentPackage() for proper package tracking
@@ -5917,7 +5825,7 @@ private void visitNamedSubroutine(SubroutineNode node) {
emit(nameIdx);
emitReg(codeReg);
- // Step 7: Register subroutine location for %DB::sub (only in debug mode)
+ // Step 6: Register subroutine location for %DB::sub (only in debug mode)
if (DebugState.isDebugMode() && errorUtil != null) {
int startLine = errorUtil.getLineNumber(node.getIndex());
// Use start line as end line for now (accurate end would require tracking block end)
diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
index 450849e571..018240bc9e 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
@@ -3,6 +3,7 @@
import java.util.BitSet;
import org.perlonjava.runtime.HintHashRegistry;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import org.perlonjava.runtime.WarningBitsRegistry;
import org.perlonjava.runtime.debugger.DebugHooks;
import org.perlonjava.runtime.operators.CompareOperators;
@@ -929,8 +930,6 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
if (element instanceof RuntimeScalarReadOnly
&& element != RuntimeScalarCache.scalarUndef) {
element = new ReadOnlyAlias(element);
- } else if (element instanceof ScalarSpecialVariable) {
- element = ensureMutableScalar(element);
}
registers[rd] = element;
GlobalVariable.aliasForeachGlobalVariable(name, element);
@@ -1099,6 +1098,7 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
int kindIdx = bytecode[pc++];
int packageIdx = bytecode[pc++];
int sourceIdx = bytecode[pc++];
+ boolean uninitializedWarningsEnabled = bytecode[pc++] != 0;
if (DEBUG_REGEX) {
RuntimeCode callbackCode =
(RuntimeCode) registers[codeReg].scalar().value;
@@ -1110,7 +1110,8 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
}
registers[rd] = org.perlonjava.runtime.regex.RuntimeRegexCallback.wrap(
registers[codeReg].scalar(), code.stringPool[kindIdx],
- code.stringPool[packageIdx], code.stringPool[sourceIdx]);
+ code.stringPool[packageIdx], code.stringPool[sourceIdx],
+ uninitializedWarningsEnabled);
}
case Opcodes.REGEX_TEMPLATE -> {
@@ -1318,16 +1319,13 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
// mutating opcodes would silently strip), but mutation
// attempts still throw "Modification of a read-only
// value". Fixes op/ref.t 232-234, op/for.t 130-134.
- // ScalarSpecialVariable is unboxed to a mutable copy
- // because surrounding interpreter paths can't propagate
- // its alias status (and Perl's $&/$1 differ from the
- // foreach-loop-alias case anyway).
+ // Keep ScalarSpecialVariable cells live. Foreach aliases
+ // to match variables such as $' must observe later matches,
+ // just like the JVM backend and Perl do.
RuntimeScalar elem = iterator.next();
if (elem instanceof RuntimeScalarReadOnly
&& elem != RuntimeScalarCache.scalarUndef) {
elem = new ReadOnlyAlias(elem);
- } else if (elem instanceof ScalarSpecialVariable) {
- elem = ensureMutableScalar(elem);
}
registers[rd] = elem;
pc = bodyTarget; // ABSOLUTE jump back to body start
@@ -2657,6 +2655,11 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
case Opcodes.SET_CALL_SITE_HINT_HASH ->
HintHashRegistry.setCallSiteHintHashId(bytecode[pc++]);
+ case Opcodes.SET_CALL_SITE_WARNING_BITS -> {
+ String warningBits = code.stringPool[bytecode[pc++]];
+ WarningBitsRegistry.setRuntimeWarningBits(warningBits);
+ }
+
// =================================================================
// DEBUGGER SUPPORT
// =================================================================
@@ -3401,6 +3404,12 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
int warningState = bytecode[pc++];
int warningBitsIndex = bytecode[pc++];
int quoteConstruction = bytecode[pc++];
+ int namedCharacterExpansionIndex = bytecode[pc++];
+ NamedCharacterExpansionMap namedCharacterExpansions =
+ namedCharacterExpansionIndex >= 0
+ ? (NamedCharacterExpansionMap)
+ code.constants[namedCharacterExpansionIndex]
+ : null;
RuntimeScalar flags = registers[flagsReg].scalar();
if (implicitU != 0) {
flags = RuntimeRegex.applyUnicodeStringsFeatureToModifiers(flags);
@@ -3408,7 +3417,9 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
RegexQuoteMeta.setCallSiteWarningState(warningState);
RegexQuoteMeta.setCallSiteWarningBits(warningBitsIndex >= 0
? code.stringPool[warningBitsIndex] : null);
- registers[rd] = RuntimeRegex.getQuotedRegex(registers[patternReg].scalar(), flags);
+ registers[rd] = RuntimeRegex.getQuotedRegex(
+ registers[patternReg].scalar(), flags,
+ namedCharacterExpansions);
if (quoteConstruction != 0) {
registers[rd] = quoteConstruction == 2
? RuntimeRegex.markSyntacticQuoteConstruction(registers[rd].scalar())
@@ -3425,6 +3436,12 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
int warningState = bytecode[pc++];
int warningBitsIndex = bytecode[pc++];
int quoteConstruction = bytecode[pc++];
+ int namedCharacterExpansionIndex = bytecode[pc++];
+ NamedCharacterExpansionMap namedCharacterExpansions =
+ namedCharacterExpansionIndex >= 0
+ ? (NamedCharacterExpansionMap)
+ code.constants[namedCharacterExpansionIndex]
+ : null;
RuntimeScalar flags = registers[flagsReg].scalar();
if (implicitU != 0) {
flags = RuntimeRegex.applyUnicodeStringsFeatureToModifiers(flags);
@@ -3432,7 +3449,9 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
RegexQuoteMeta.setCallSiteWarningState(warningState);
RegexQuoteMeta.setCallSiteWarningBits(warningBitsIndex >= 0
? code.stringPool[warningBitsIndex] : null);
- registers[rd] = RuntimeRegex.getQuotedRegex(registers[patternReg].scalar(), flags, callsiteId);
+ registers[rd] = RuntimeRegex.getQuotedRegex(
+ registers[patternReg].scalar(), flags, callsiteId,
+ namedCharacterExpansions);
if (quoteConstruction != 0) {
registers[rd] = quoteConstruction == 2
? RuntimeRegex.markSyntacticQuoteConstruction(registers[rd].scalar())
diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java
index 3bb4f49e3a..2165eed5ae 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/CompileAssignment.java
@@ -78,7 +78,7 @@ private static boolean handleLocalAssignment(BytecodeCompiler bc, BinaryOperator
// slot for the localized value without evaluating the index twice.
if (binOp.operator.equals("[")
&& !(binOp.left instanceof OperatorNode op && op.operator.equals("@"))) {
- int arrayReg = CompileExistsDelete.compileArrayForExistsDelete(bc, binOp, node.getIndex());
+ int arrayReg = CompileExistsDelete.compileArrayForExistsDelete(bc, binOp);
int indexReg = CompileExistsDelete.compileArrayIndex(bc, binOp);
int discardedReg = bc.allocateRegister();
bc.emit(Opcodes.ARRAY_DELETE_LOCAL);
@@ -501,6 +501,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(reg);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.emit(beginId);
+ bytecodeCompiler.emitActiveLexicalBinding(reg, varName);
// Now register contains a reference to the persistent RuntimeScalar
// Store the initializer value INTO that RuntimeScalar
@@ -540,6 +541,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(valueReg);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.emit(persistId);
+ bytecodeCompiler.emitActiveLexicalBinding(reg, varName);
bytecodeCompiler.registerVariable(varName, reg);
@@ -603,6 +605,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(arrayReg);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.emit(beginId);
+ bytecodeCompiler.emitActiveLexicalBinding(arrayReg, varName);
// Compile RHS (should evaluate to a list)
bytecodeCompiler.compileNode(node.right, -1, rhsContext);
@@ -678,6 +681,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.emit(beginId);
+ bytecodeCompiler.emitActiveLexicalBinding(hashReg, varName);
// Compile RHS (should evaluate to a list)
bytecodeCompiler.compileNode(node.right, -1, rhsContext);
@@ -824,6 +828,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emit(beginId);
}
}
+ bytecodeCompiler.emitActiveLexicalBinding(varReg, varName);
bytecodeCompiler.registerVariable(varName, varReg);
bytecodeCompiler.emit(Opcodes.REGISTER_MY_VAR);
bytecodeCompiler.emitReg(varReg);
@@ -1008,6 +1013,15 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
emitReleaseConsumedRhsTemp(bytecodeCompiler, node.right, valueReg, targetReg);
}
+ // ASSIGN_LEXICAL_SCALAR may replace the RuntimeScalar in
+ // the register after a temporary runtime-regex closure
+ // releases its capture. Refresh the live-cell registry so
+ // later executable runtime source captures the new cell,
+ // not the value that existed before an eval or callback.
+ if (bytecodeCompiler.tracksRuntimeRegexLexicals()) {
+ bytecodeCompiler.emitActiveLexicalBinding(targetReg, varName);
+ }
+
bytecodeCompiler.lastResultReg = targetReg;
} else {
// Global variable
@@ -1035,15 +1049,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
String varName = "@" + ((IdentifierNode) leftOp.operand).name;
int arrayReg;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(varName)) {
- arrayReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(varName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- bytecodeCompiler.emitReg(arrayReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(varName)) {
+ if (bytecodeCompiler.hasVariable(varName)) {
arrayReg = bytecodeCompiler.getVariableRegister(varName);
} else {
arrayReg = bytecodeCompiler.allocateRegister();
@@ -1078,15 +1084,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
String varName = "%" + ((IdentifierNode) leftOp.operand).name;
int hashReg;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(varName)) {
- hashReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(varName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- bytecodeCompiler.emitReg(hashReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(varName)) {
+ if (bytecodeCompiler.hasVariable(varName)) {
hashReg = bytecodeCompiler.getVariableRegister(varName);
} else {
hashReg = bytecodeCompiler.allocateRegister();
@@ -1480,15 +1478,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
if (arrayOp.operand instanceof IdentifierNode) {
String varName = "@" + ((IdentifierNode) arrayOp.operand).name;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(varName)) {
- arrayReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(varName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- bytecodeCompiler.emitReg(arrayReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(varName)) {
+ if (bytecodeCompiler.hasVariable(varName)) {
arrayReg = bytecodeCompiler.getVariableRegister(varName);
} else {
arrayReg = bytecodeCompiler.allocateRegister();
@@ -1577,16 +1567,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
String varName = ((IdentifierNode) arrayOp.operand).name;
String arrayVarName = "@" + varName;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(arrayVarName)
- && !bytecodeCompiler.isDynamicOurVariable(arrayVarName)) {
- arrayReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(arrayVarName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, node.getIndex());
- bytecodeCompiler.emitReg(arrayReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(arrayVarName)
+ if (bytecodeCompiler.hasVariable(arrayVarName)
&& !bytecodeCompiler.isOurVariable(arrayVarName)) {
arrayReg = bytecodeCompiler.getVariableRegister(arrayVarName);
} else {
@@ -1666,16 +1647,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
String varName = idNode.name;
String hashVarName = "%" + varName;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(hashVarName)
- && !bytecodeCompiler.isDynamicOurVariable(hashVarName)) {
- hashReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(hashVarName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- bytecodeCompiler.emitReg(hashReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(hashVarName)
+ if (bytecodeCompiler.hasVariable(hashVarName)
&& !bytecodeCompiler.isOurVariable(hashVarName)) {
hashReg = bytecodeCompiler.getVariableRegister(hashVarName);
} else {
@@ -1775,16 +1747,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
String varName = ((IdentifierNode) hashOp.operand).name;
String hashVarName = "%" + varName;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(hashVarName)
- && !bytecodeCompiler.isDynamicOurVariable(hashVarName)) {
- hashReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(hashVarName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, node.getIndex());
- bytecodeCompiler.emitReg(hashReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- } else if (bytecodeCompiler.hasVariable(hashVarName)
+ if (bytecodeCompiler.hasVariable(hashVarName)
&& !bytecodeCompiler.isOurVariable(hashVarName)) {
hashReg = bytecodeCompiler.getVariableRegister(hashVarName);
} else {
@@ -2102,16 +2065,6 @@ static int resolveArrayForDollarHash(BytecodeCompiler bytecodeCompiler, Operator
if (dollarHashOp.operand instanceof OperatorNode operandOp
&& operandOp.operator.equals("@") && operandOp.operand instanceof IdentifierNode idNode) {
String varName = "@" + idNode.name;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(varName)) {
- int arrayReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(varName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, dollarHashOp.getIndex());
- bytecodeCompiler.emitReg(arrayReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- return arrayReg;
- }
if (bytecodeCompiler.hasVariable(varName)) {
return bytecodeCompiler.getVariableRegister(varName);
}
@@ -2124,16 +2077,6 @@ static int resolveArrayForDollarHash(BytecodeCompiler bytecodeCompiler, Operator
return arrayReg;
} else if (dollarHashOp.operand instanceof IdentifierNode idNode) {
String varName = "@" + idNode.name;
- if (bytecodeCompiler.currentSubroutineBeginId != 0 && bytecodeCompiler.currentSubroutineClosureVars != null
- && bytecodeCompiler.currentSubroutineClosureVars.contains(varName)) {
- int arrayReg = bytecodeCompiler.allocateRegister();
- int nameIdx = bytecodeCompiler.addToStringPool(varName);
- bytecodeCompiler.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, dollarHashOp.getIndex());
- bytecodeCompiler.emitReg(arrayReg);
- bytecodeCompiler.emit(nameIdx);
- bytecodeCompiler.emit(bytecodeCompiler.currentSubroutineBeginId);
- return arrayReg;
- }
if (bytecodeCompiler.hasVariable(varName)) {
return bytecodeCompiler.getVariableRegister(varName);
}
diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java
index e7708e3a01..f94101ef0c 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperator.java
@@ -635,10 +635,19 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
// Create a copy of the operand list and add the left side (string)
ListNode boundList = new ListNode(new java.util.ArrayList<>(originalList.elements), originalList.tokenIndex);
+ boundList.handle = originalList.handle;
+ if (originalList.annotations != null) {
+ boundList.annotations = new java.util.HashMap<>(
+ originalList.annotations);
+ }
boundList.elements.add(node.left);
// Create a new OperatorNode with the modified operand list
OperatorNode boundOp = new OperatorNode(rightOp.operator, boundList, rightOp.tokenIndex);
+ boundOp.id = rightOp.id;
+ if (rightOp.annotations != null) {
+ boundOp.annotations = new java.util.HashMap<>(rightOp.annotations);
+ }
// For !~, we need to negate the result
if (node.operator.equals("!~")) {
diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java
index 728a3401d9..75df2c753a 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java
@@ -39,7 +39,7 @@ private static void visitExistsHash(BytecodeCompiler bc, OperatorNode node, Bina
}
private static void visitExistsArray(BytecodeCompiler bc, OperatorNode node, BinaryOperatorNode arrayAccess) {
- int arrayReg = compileArrayForExistsDelete(bc, arrayAccess, node.getIndex());
+ int arrayReg = compileArrayForExistsDelete(bc, arrayAccess);
int indexReg = compileArrayIndex(bc, arrayAccess);
int rd = bc.allocateOutputRegister();
bc.emit(Opcodes.ARRAY_EXISTS);
@@ -235,7 +235,7 @@ private static void visitDeleteArray(BytecodeCompiler bc, OperatorNode node, Bin
bc.lastResultReg = rd;
return;
}
- int arrayReg = compileArrayForExistsDelete(bc, arrayAccess, node.getIndex());
+ int arrayReg = compileArrayForExistsDelete(bc, arrayAccess);
int indexReg = compileArrayIndex(bc, arrayAccess);
int rd = bc.allocateOutputRegister();
bc.emit(Opcodes.ARRAY_DELETE);
@@ -347,7 +347,7 @@ private static void visitDeleteGeneric(BytecodeCompiler bc, Node arg) {
private static int resolveHashSliceTarget(BytecodeCompiler bc, OperatorNode node, OperatorNode leftOp, String error) {
if (leftOp.operand instanceof IdentifierNode id) {
- return loadHashVariable(bc, id.name, node.getIndex());
+ return loadHashVariable(bc, id.name);
}
if (leftOp.operand instanceof OperatorNode
|| leftOp.operand instanceof BlockNode
@@ -361,7 +361,7 @@ private static int resolveHashSliceTarget(BytecodeCompiler bc, OperatorNode node
private static int resolveArraySliceTarget(BytecodeCompiler bc, OperatorNode node, OperatorNode leftOp, String error) {
if (leftOp.operand instanceof IdentifierNode id) {
- return loadArrayVariable(bc, id.name, node.getIndex());
+ return loadArrayVariable(bc, id.name);
}
if (leftOp.operand instanceof OperatorNode
|| leftOp.operand instanceof BlockNode
@@ -373,18 +373,8 @@ private static int resolveArraySliceTarget(BytecodeCompiler bc, OperatorNode nod
return -1;
}
- private static int loadHashVariable(BytecodeCompiler bc, String name, int tokenIndex) {
+ private static int loadHashVariable(BytecodeCompiler bc, String name) {
String hashVarName = "%" + name;
- if (bc.currentSubroutineBeginId != 0 && bc.currentSubroutineClosureVars != null
- && bc.currentSubroutineClosureVars.contains(hashVarName)) {
- int hashReg = bc.allocateRegister();
- int nameIdx = bc.addToStringPool(hashVarName);
- bc.emitWithToken(Opcodes.RETRIEVE_BEGIN_HASH, tokenIndex);
- bc.emitReg(hashReg);
- bc.emit(nameIdx);
- bc.emit(bc.currentSubroutineBeginId);
- return hashReg;
- }
if (bc.hasVariable(hashVarName)) {
return bc.getVariableRegister(hashVarName);
}
@@ -397,18 +387,8 @@ private static int loadHashVariable(BytecodeCompiler bc, String name, int tokenI
return hashReg;
}
- private static int loadArrayVariable(BytecodeCompiler bc, String name, int tokenIndex) {
+ private static int loadArrayVariable(BytecodeCompiler bc, String name) {
String arrayVarName = "@" + name;
- if (bc.currentSubroutineBeginId != 0 && bc.currentSubroutineClosureVars != null
- && bc.currentSubroutineClosureVars.contains(arrayVarName)) {
- int arrayReg = bc.allocateRegister();
- int nameIdx = bc.addToStringPool(arrayVarName);
- bc.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, tokenIndex);
- bc.emitReg(arrayReg);
- bc.emit(nameIdx);
- bc.emit(bc.currentSubroutineBeginId);
- return arrayReg;
- }
if (bc.hasVariable(arrayVarName)) {
return bc.getVariableRegister(arrayVarName);
}
@@ -505,7 +485,7 @@ private static int compileHashKey(BytecodeCompiler bc, Node keySpec) {
}
}
- static int compileArrayForExistsDelete(BytecodeCompiler bc, BinaryOperatorNode arrayAccess, int tokenIndex) {
+ static int compileArrayForExistsDelete(BytecodeCompiler bc, BinaryOperatorNode arrayAccess) {
if (!(arrayAccess.left instanceof OperatorNode leftOp) || !leftOp.operator.equals("$")
|| !(leftOp.operand instanceof IdentifierNode)) {
bc.throwCompilerException("Array exists/delete requires simple array variable");
@@ -513,16 +493,7 @@ static int compileArrayForExistsDelete(BytecodeCompiler bc, BinaryOperatorNode a
}
String varName = ((IdentifierNode) leftOp.operand).name;
String arrayVarName = "@" + varName;
- if (bc.currentSubroutineBeginId != 0 && bc.currentSubroutineClosureVars != null
- && bc.currentSubroutineClosureVars.contains(arrayVarName)) {
- int arrayReg = bc.allocateRegister();
- int nameIdx = bc.addToStringPool(arrayVarName);
- bc.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, tokenIndex);
- bc.emitReg(arrayReg);
- bc.emit(nameIdx);
- bc.emit(bc.currentSubroutineBeginId);
- return arrayReg;
- } else if (bc.hasVariable(arrayVarName)) {
+ if (bc.hasVariable(arrayVarName)) {
return bc.getVariableRegister(arrayVarName);
} else {
int arrayReg = bc.allocateRegister();
@@ -623,7 +594,7 @@ private static void visitDeleteLocalArray(BytecodeCompiler bc, OperatorNode node
visitDeleteLocalArraySlice(bc, node, arrayAccess, leftOp);
return;
}
- int arrayReg = compileArrayForExistsDelete(bc, arrayAccess, node.getIndex());
+ int arrayReg = compileArrayForExistsDelete(bc, arrayAccess);
int indexReg = compileArrayIndex(bc, arrayAccess);
int rd = bc.allocateOutputRegister();
bc.emit(Opcodes.ARRAY_DELETE_LOCAL);
diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
index a902dffb35..5f45b0bb6b 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
@@ -2,6 +2,8 @@
import org.perlonjava.frontend.astnode.*;
import org.perlonjava.frontend.analysis.RegexLiteralAnalyzer;
+import org.perlonjava.frontend.parser.StringParser;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import org.perlonjava.runtime.operators.ScalarGlobOperator;
import org.perlonjava.runtime.regex.RuntimeRegex;
import org.perlonjava.runtime.runtimetypes.*;
@@ -98,16 +100,7 @@ private static int compileArrayForExistsDelete(BytecodeCompiler bc, BinaryOperat
}
String varName = ((IdentifierNode) leftOp.operand).name;
String arrayVarName = "@" + varName;
- if (bc.currentSubroutineBeginId != 0 && bc.currentSubroutineClosureVars != null
- && bc.currentSubroutineClosureVars.contains(arrayVarName)) {
- int arrayReg = bc.allocateRegister();
- int nameIdx = bc.addToStringPool(arrayVarName);
- bc.emitWithToken(Opcodes.RETRIEVE_BEGIN_ARRAY, tokenIndex);
- bc.emitReg(arrayReg);
- bc.emit(nameIdx);
- bc.emit(bc.currentSubroutineBeginId);
- return arrayReg;
- } else if (bc.hasVariable(arrayVarName)) {
+ if (bc.hasVariable(arrayVarName)) {
return bc.getVariableRegister(arrayVarName);
} else {
int arrayReg = bc.allocateRegister();
@@ -283,6 +276,7 @@ private static int loadDefaultUnderscore(BytecodeCompiler bc) {
}
private static int regexWarningState(OperatorNode node) {
+ if (Boolean.TRUE.equals(node.getAnnotation("regexWarningsSuppressed"))) return -1;
if (Boolean.TRUE.equals(node.getAnnotation("regexWarningsFatal"))) return 2;
return Boolean.TRUE.equals(node.getAnnotation("regexWarningsEnabled")) ? 1 : 0;
}
@@ -292,6 +286,18 @@ private static int regexWarningBitsIndex(BytecodeCompiler bc, OperatorNode node)
return bits instanceof String string ? bc.addToStringPool(string) : -1;
}
+ private static int namedCharacterExpansionIndex(
+ BytecodeCompiler bc, OperatorNode node, ListNode operand) {
+ Object expansions = node.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ if (expansions == null) {
+ expansions = operand.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ }
+ return expansions instanceof NamedCharacterExpansionMap map
+ ? bc.addToConstantPool(map) : -1;
+ }
+
private static int regexTargetNameIndex(BytecodeCompiler bc, Node node) {
if (node instanceof OperatorNode operator
&& operator.operator.equals("$")
@@ -328,6 +334,7 @@ private static void visitMatchRegex(BytecodeCompiler bc, OperatorNode node) {
bc.emit(regexWarningState(node));
bc.emit(regexWarningBitsIndex(bc, node));
bc.emit(0);
+ bc.emit(namedCharacterExpansionIndex(bc, node, args));
} else {
bc.emit(Opcodes.QUOTE_REGEX);
bc.emitReg(regexReg);
@@ -337,6 +344,7 @@ private static void visitMatchRegex(BytecodeCompiler bc, OperatorNode node) {
bc.emit(regexWarningState(node));
bc.emit(regexWarningBitsIndex(bc, node));
bc.emit(0);
+ bc.emit(namedCharacterExpansionIndex(bc, node, args));
}
int stringReg;
if (args.elements.size() > 2) {
@@ -853,6 +861,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
(String) node.getAnnotation("regexCallbackPackage")));
bytecodeCompiler.emit(bytecodeCompiler.addToStringPool(
(String) node.getAnnotation("regexCallbackSource")));
+ bytecodeCompiler.emit(Boolean.TRUE.equals(node.getAnnotation(
+ "regexCallbackUninitializedWarningsEnabled")) ? 1 : 0);
bytecodeCompiler.lastResultReg = rd;
}
case "regexTemplate" -> {
@@ -1119,7 +1129,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
if (operand.elements.size() < 2) {
bytecodeCompiler.throwCompilerException("quoteRegex requires pattern and flags");
}
- String literalPattern = RegexLiteralAnalyzer.constantString(operand.elements.get(0));
+ String literalPattern = node.getBooleanAnnotation("literalSyntaxValidated")
+ ? null : RegexLiteralAnalyzer.constantString(operand.elements.get(0));
if (literalPattern != null
&& operand.elements.get(1) instanceof StringNode literalFlags
&& !RuntimeRegex.requiresRuntimeUnicodePropertyResolution(literalPattern)) {
@@ -1129,7 +1140,19 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
modifiers += "u";
}
try {
- RuntimeRegex.validateLiteralSyntax(literalPattern, modifiers);
+ String diagnosticPattern = RegexLiteralAnalyzer.constantSourceString(
+ operand.elements.get(0));
+ StringParser.validateLiteralNamedCharacters(
+ operand, literalPattern, modifiers,
+ diagnosticPattern == null
+ ? literalPattern : diagnosticPattern);
+ Object expansions = operand.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ if (expansions != null) {
+ node.setAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS,
+ expansions);
+ }
} catch (PerlCompilerException exception) {
throw PerlCompilerException.withSourceLocation(
node.tokenIndex, exception.getMessage(),
@@ -1158,6 +1181,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emit(regexWarningState(node));
bytecodeCompiler.emit(regexWarningBitsIndex(bytecodeCompiler, node));
bytecodeCompiler.emit(node.getBooleanAnnotation("syntacticQuoteRegex") ? 2 : 1);
+ bytecodeCompiler.emit(namedCharacterExpansionIndex(
+ bytecodeCompiler, node, operand));
} else {
bytecodeCompiler.emit(Opcodes.QUOTE_REGEX);
bytecodeCompiler.emitReg(rd);
@@ -1167,6 +1192,8 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emit(regexWarningState(node));
bytecodeCompiler.emit(regexWarningBitsIndex(bytecodeCompiler, node));
bytecodeCompiler.emit(node.getBooleanAnnotation("syntacticQuoteRegex") ? 2 : 1);
+ bytecodeCompiler.emit(namedCharacterExpansionIndex(
+ bytecodeCompiler, node, operand));
}
bytecodeCompiler.lastResultReg = rd;
}
diff --git a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
index c48ab13583..df1a4c25ba 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
@@ -1260,6 +1260,7 @@ public static String disassemble(InterpretedCode interpretedCode) {
int callbackKindIdx = interpretedCode.bytecode[pc++];
int callbackPackageIdx = interpretedCode.bytecode[pc++];
int callbackSourceIdx = interpretedCode.bytecode[pc++];
+ int callbackUninitializedWarnings = interpretedCode.bytecode[pc++];
sb.append("REGEX_CALLBACK r").append(rd)
.append(" = callback(r").append(rs)
.append(", kind=")
@@ -1268,6 +1269,8 @@ public static String disassemble(InterpretedCode interpretedCode) {
.append(interpretedCode.stringPool[callbackPackageIdx])
.append(", source=")
.append(interpretedCode.stringPool[callbackSourceIdx])
+ .append(", uninitializedWarnings=")
+ .append(callbackUninitializedWarnings != 0)
.append(")\n");
break;
case Opcodes.REGEX_TEMPLATE:
@@ -1344,11 +1347,14 @@ public static String disassemble(InterpretedCode interpretedCode) {
int warningState = interpretedCode.bytecode[pc++];
int warningBitsIndex = interpretedCode.bytecode[pc++];
int quoteConstruction = interpretedCode.bytecode[pc++];
+ int namedCharacterExpansionIndex = interpretedCode.bytecode[pc++];
sb.append("QUOTE_REGEX r").append(rd).append(" = qr{r").append(patternReg)
.append("}r").append(flagsReg).append(" implicitU=").append(implicitU)
.append(" warningState=").append(warningState)
.append(" warningBits=").append(warningBitsIndex)
- .append(" quoteConstruction=").append(quoteConstruction).append("\n");
+ .append(" quoteConstruction=").append(quoteConstruction)
+ .append(" namedCharacters=")
+ .append(namedCharacterExpansionIndex).append("\n");
break;
case Opcodes.QUOTE_REGEX_O:
rd = interpretedCode.bytecode[pc++];
@@ -1359,12 +1365,15 @@ public static String disassemble(InterpretedCode interpretedCode) {
warningState = interpretedCode.bytecode[pc++];
warningBitsIndex = interpretedCode.bytecode[pc++];
quoteConstruction = interpretedCode.bytecode[pc++];
+ namedCharacterExpansionIndex = interpretedCode.bytecode[pc++];
sb.append("QUOTE_REGEX_O r").append(rd).append(" = qr{r").append(patternReg)
.append("}r").append(flagsReg).append(" callsite=").append(callsiteId)
.append(" implicitU=").append(implicitU)
.append(" warningState=").append(warningState)
.append(" warningBits=").append(warningBitsIndex)
- .append(" quoteConstruction=").append(quoteConstruction).append("\n");
+ .append(" quoteConstruction=").append(quoteConstruction)
+ .append(" namedCharacters=")
+ .append(namedCharacterExpansionIndex).append("\n");
break;
case Opcodes.ITERATOR_CREATE:
rd = interpretedCode.bytecode[pc++];
@@ -1847,6 +1856,10 @@ public static String disassemble(InterpretedCode interpretedCode) {
sb.append("SET_CALL_SITE_HINT_HASH id=")
.append(interpretedCode.bytecode[pc++]).append("\n");
break;
+ case Opcodes.SET_CALL_SITE_WARNING_BITS:
+ sb.append("SET_CALL_SITE_WARNING_BITS bits=")
+ .append(interpretedCode.bytecode[pc++]).append("\n");
+ break;
case Opcodes.PUSH_LABELED_BLOCK: {
int labelIdx = interpretedCode.bytecode[pc++];
int exitPc = interpretedCode.bytecode[pc++];
diff --git a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java
index 130106ec35..ff2066ceb9 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java
@@ -12,6 +12,7 @@
import org.perlonjava.frontend.parser.SpecialBlockParser;
import org.perlonjava.frontend.semantic.ScopedSymbolTable;
import org.perlonjava.runtime.HintHashRegistry;
+import org.perlonjava.runtime.regex.RegexQuoteMeta;
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.perlmodule.BHooksEndOfScope;
import org.perlonjava.runtime.perlmodule.Strict;
@@ -460,9 +461,12 @@ private static RuntimeList evalStringList(String perlCode,
parser.parsingEvalString = true;
Node ast;
BHooksEndOfScope.beginFileLoad(evalFileName);
+ String savedRegexWarningBits = RegexQuoteMeta.getParserWarningBits();
+ RegexQuoteMeta.setParserWarningBits(siteWarningBits);
try {
ast = parser.parse();
} finally {
+ RegexQuoteMeta.setParserWarningBits(savedRegexWarningBits);
BHooksEndOfScope.endFileLoad(evalFileName);
}
diff --git a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
index b49ceaaa95..ca722e8d73 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
@@ -492,6 +492,7 @@ public InterpretedCode withCapturedVars(RuntimeBase[] capturedVars) {
copy.inheritsSelfReference = this.inheritsSelfReference;
copy.isRegexCallbackPseudoBlock = this.isRegexCallbackPseudoBlock;
copy.isQuotedRegexCallback = this.isQuotedRegexCallback;
+ copy.tracksRuntimeRegexLexicals = this.tracksRuntimeRegexLexicals;
copy.attributesDispatchedAtCompileTime = this.attributesDispatchedAtCompileTime;
copy.deferredConstAttribute = this.deferredConstAttribute;
copy.cvStartFile = this.cvStartFile;
diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
index 7a8217ee8c..46d117ed1b 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
@@ -935,7 +935,7 @@ public class Opcodes {
/**
* Quote regex operator: rd = RuntimeRegex.getQuotedRegex(pattern_reg, flags_reg)
- * Format: QUOTE_REGEX rd pattern_reg flags_reg implicit_unicode_strings_u warning_state warning_bits_index quote_construction
+ * Format: QUOTE_REGEX rd pattern_reg flags_reg implicit_unicode_strings_u warning_state warning_bits_index quote_construction named_character_metadata_index
* quote_construction: 0 = match, 1 = scalar coercion, 2 = syntactic qr//.
*/
public static final short QUOTE_REGEX = 159;
@@ -1835,7 +1835,7 @@ public class Opcodes {
/**
* Quote regex with /o modifier support: rd = RuntimeRegex.getQuotedRegex(pattern_reg, flags_reg, callsite_id)
- * Format: QUOTE_REGEX_O rd pattern_reg flags_reg callsite_id implicit_unicode_strings_u warning_state warning_bits_index quote_construction
+ * Format: QUOTE_REGEX_O rd pattern_reg flags_reg callsite_id implicit_unicode_strings_u warning_state warning_bits_index quote_construction named_character_metadata_index
* quote_construction: 0 = match, 1 = scalar coercion, 2 = syntactic qr//.
*/
public static final short QUOTE_REGEX_O = 374;
@@ -2491,6 +2491,9 @@ public class Opcodes {
/** Restore lexical %^H after leaving a nested block. Format: SET_CALL_SITE_HINT_HASH id. */
public static final short SET_CALL_SITE_HINT_HASH = 527;
+ /** Restore lexical warning bits after leaving a nested block. Format: SET_CALL_SITE_WARNING_BITS bitsIdx. */
+ public static final short SET_CALL_SITE_WARNING_BITS = 528;
+
/** Return the mutable {@code $#array} cell. Format: ARRAY_LAST_INDEX_LVALUE rd arrayReg. */
public static final short ARRAY_LAST_INDEX_LVALUE = 518;
diff --git a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java
index 178720ae9b..61e5a89b87 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/SlowOpcodeHandler.java
@@ -315,6 +315,12 @@ public static int executeEvalString(
}
evalTrace("EVAL_STRING opcode enter rd=r" + rd + " strReg=r" + stringReg +
" ctx=" + evalCallContext + " evalSite=" + evalSiteIndex +
+ " warnUninitialized=" + (siteWarningBits != null
+ && org.perlonjava.runtime.runtimetypes.WarningFlags.isEnabledInBits(
+ siteWarningBits, "uninitialized")) +
+ " fatalUninitialized=" + (siteWarningBits != null
+ && org.perlonjava.runtime.runtimetypes.WarningFlags.isFatalInBits(
+ siteWarningBits, "uninitialized")) +
" codeType=" + codeScalar.type + " codeLen=" + codeScalar.toString().length() +
" src=" + (code != null ? code.sourceName : "null"));
diff --git a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java
index 10eee85b62..d7efeee859 100644
--- a/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java
+++ b/src/main/java/org/perlonjava/backend/bytecode/VariableCollectorVisitor.java
@@ -24,6 +24,7 @@ public class VariableCollectorVisitor implements Visitor {
private final Set declaredVariables;
private final Map declaredOurVariables;
private boolean hasEvalString = false;
+ private boolean requiresAllRuntimeLexicals = false;
private final Deque> localScopes = new ArrayDeque<>();
private int subroutineDepth = 0;
@@ -57,6 +58,16 @@ public boolean hasEvalString() {
return hasEvalString;
}
+ /**
+ * Returns true when runtime-generated source can reference any visible
+ * lexical, rather than only the variable nodes present in the static AST.
+ * This includes eval STRING and executable regex interpolation whose
+ * string overload can produce callback source at match time.
+ */
+ public boolean requiresAllRuntimeLexicals() {
+ return requiresAllRuntimeLexicals;
+ }
+
private boolean isDeclarationOperator(String op) {
return op.equals("my") || op.equals("state") || op.equals("our");
}
@@ -65,6 +76,14 @@ private boolean isVariableOperator(String op) {
return op.equals("$") || op.equals("@") || op.equals("%") || op.equals("&");
}
+ private boolean hasDynamicRegexPattern(OperatorNode node) {
+ if (!(node.operand instanceof ListNode operands)
+ || operands.elements == null || operands.elements.isEmpty()) {
+ return false;
+ }
+ return !(operands.elements.getFirst() instanceof StringNode);
+ }
+
private boolean isDeclaredLocal(String varName) {
for (Set scope : localScopes) {
if (scope.contains(varName)) return true;
@@ -162,6 +181,14 @@ public void visit(OperatorNode node) {
// at runtime, so we must capture all visible variables.
if (op.equals("eval") || op.equals("evalbytes")) {
hasEvalString = true;
+ requiresAllRuntimeLexicals = true;
+ } else if (op.equals("regexTemplate")) {
+ requiresAllRuntimeLexicals = true;
+ } else if ((op.equals("quoteRegex") || op.equals("matchRegex"))
+ && hasDynamicRegexPattern(node)) {
+ // A blessed interpolation can turn an otherwise ordinary dynamic
+ // pattern into executable callback source through dot overload.
+ requiresAllRuntimeLexicals = true;
}
// Check if this is a variable reference (sigil + identifier)
diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java b/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java
index 1359878a9d..82eadb78b4 100644
--- a/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java
+++ b/src/main/java/org/perlonjava/backend/jvm/EmitBlock.java
@@ -311,10 +311,11 @@ public static void emitBlock(EmitterVisitor emitterVisitor, BlockNode node) {
list.get(0) instanceof OperatorNode localOp && localOp.operator.equals("local") &&
list.get(1) instanceof For1Node forNode && forNode.needsArrayOfAlias) {
- // Pre-evaluate the For1Node's list to array of aliases before localizing $_
+ // Pre-evaluate the For1Node's list before localizing $_. Keep an
+ // actual array live; foreachAliasIterator snapshots other list
+ // expressions when the loop is emitted.
int tempArrayIndex = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
forNode.list.accept(emitterVisitor.with(RuntimeContextType.LIST));
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "getArrayOfAlias", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;", false);
mv.visitVarInsn(Opcodes.ASTORE, tempArrayIndex);
// Mark the For1Node to use the pre-evaluated array
diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java
index 0ea749e00e..89ff0f79b3 100644
--- a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java
+++ b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java
@@ -376,8 +376,11 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) {
mv.visitInsn(Opcodes.POP); // Discard the returned scalar
}
- // Get iterator from the pre-evaluated array
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeArray", "iterator", "()Ljava/util/Iterator;", false);
+ // Preserve live membership for an array while retaining snapshot
+ // iteration for non-array list expressions and tied arrays.
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "org/perlonjava/runtime/runtimetypes/RuntimeBase",
+ "foreachAliasIterator", "()Ljava/util/Iterator;", false);
mv.visitVarInsn(Opcodes.ASTORE, iteratorIndex);
} else if (isGlobalUnderscore) {
// Global $_ as loop variable: use pre-evaluated list (evaluated in enclosing scope)
@@ -410,10 +413,11 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) {
mv.visitVarInsn(Opcodes.ASTORE, iteratorIndex);
mv.visitJumpInsn(Opcodes.GOTO, afterIterLabel);
- // Non-range: preserve aliasing semantics by iterating an array-of-alias.
+ // Non-range: preserve aliases and live membership for an actual array.
mv.visitLabel(notRangeLabel);
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeBase", "getArrayOfAlias", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;", false);
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeArray", "iterator", "()Ljava/util/Iterator;", false);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "org/perlonjava/runtime/runtimetypes/RuntimeBase",
+ "foreachAliasIterator", "()Ljava/util/Iterator;", false);
mv.visitVarInsn(Opcodes.ASTORE, iteratorIndex);
mv.visitLabel(afterIterLabel);
diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java
index a3e4fb6b9a..0492004254 100644
--- a/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java
+++ b/src/main/java/org/perlonjava/backend/jvm/EmitRegex.java
@@ -8,6 +8,10 @@
import org.perlonjava.runtime.regex.RuntimeRegex;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
+import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
+import org.perlonjava.frontend.parser.StringParser;
+import org.perlonjava.runtime.NamedCharacterExpansion;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import java.util.ArrayList;
import java.util.HashMap;
@@ -25,9 +29,12 @@ static void handleRegexCallback(EmitterVisitor emitterVisitor, OperatorNode node
emitterVisitor.ctx.mv.visitLdcInsn((String) node.getAnnotation("regexCallbackKind"));
emitterVisitor.ctx.mv.visitLdcInsn((String) node.getAnnotation("regexCallbackPackage"));
emitterVisitor.ctx.mv.visitLdcInsn((String) node.getAnnotation("regexCallbackSource"));
+ emitterVisitor.ctx.mv.visitInsn(Boolean.TRUE.equals(node.getAnnotation(
+ "regexCallbackUninitializedWarningsEnabled"))
+ ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/regex/RuntimeRegexCallback", "wrap",
- "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
+ "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
}
@@ -64,7 +71,10 @@ private static void maybeApplyUnicodeStringsRegexModifiers(EmitterVisitor emitte
private static void emitRegexWarningState(EmitterVisitor emitterVisitor, OperatorNode node) {
boolean enabled = Boolean.TRUE.equals(node.getAnnotation("regexWarningsEnabled"));
boolean fatal = Boolean.TRUE.equals(node.getAnnotation("regexWarningsFatal"));
- emitterVisitor.ctx.mv.visitInsn(fatal ? Opcodes.ICONST_2
+ boolean suppressed = Boolean.TRUE.equals(
+ node.getAnnotation("regexWarningsSuppressed"));
+ emitterVisitor.ctx.mv.visitInsn(suppressed ? Opcodes.ICONST_M1
+ : fatal ? Opcodes.ICONST_2
: enabled ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/regex/RegexQuoteMeta",
@@ -80,6 +90,50 @@ private static void emitRegexWarningState(EmitterVisitor emitterVisitor, Operato
"setCallSiteWarningBits", "(Ljava/lang/String;)V", false);
}
+ /** Stack: pattern -> pattern carrying this literal's immutable lexical results. */
+ private static void attachNamedCharacterExpansions(
+ EmitterVisitor emitterVisitor, OperatorNode node, ListNode operand) {
+ Object annotated = node.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ if (annotated == null) {
+ annotated = operand.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ }
+ if (!(annotated instanceof NamedCharacterExpansionMap metadata)) return;
+
+ emitterVisitor.ctx.mv.visitLdcInsn(metadata.literalIdentity().value());
+ emitterVisitor.ctx.mv.visitLdcInsn(metadata.callableIdentity().value());
+ emitterVisitor.ctx.mv.visitLdcInsn(metadata.expansions().size() * 7);
+ emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
+ int index = 0;
+ for (var entry : metadata.expansions().entrySet()) {
+ NamedCharacterExpansionMap.Key key = entry.getKey();
+ NamedCharacterExpansion expansion = entry.getValue();
+ String[] fields = {
+ key.sourceSpelling(), key.sourceMode().name(),
+ expansion.sequence(), expansion.sourceMode().name(),
+ Boolean.toString(expansion.promotesUnicode()),
+ expansion.status().name(), expansion.diagnostic()
+ };
+ for (String field : fields) {
+ emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP);
+ emitterVisitor.ctx.mv.visitLdcInsn(index++);
+ if (field == null) emitterVisitor.ctx.mv.visitInsn(Opcodes.ACONST_NULL);
+ else emitterVisitor.ctx.mv.visitLdcInsn(field);
+ emitterVisitor.ctx.mv.visitInsn(Opcodes.AASTORE);
+ }
+ }
+ emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "org/perlonjava/runtime/NamedCharacterExpansionMap", "fromFlat",
+ "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)Lorg/perlonjava/runtime/NamedCharacterExpansionMap;",
+ false);
+ emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "org/perlonjava/runtime/regex/RuntimeRegex",
+ "attachNamedCharacterExpansions",
+ "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/NamedCharacterExpansionMap;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
+ false);
+ }
+
/**
* Handles the binding regex operation where a variable is bound to a regex operation.
* This method processes the binary operator node representing the binding operation.
@@ -99,6 +153,9 @@ static void handleBindRegex(EmitterVisitor emitterVisitor, BinaryOperatorNode no
// Do not mutate the original AST: create a local copy of the operator and its operand list.
ListNode boundListNode = new ListNode(new ArrayList<>(listNode.elements), listNode.tokenIndex);
boundListNode.handle = listNode.handle;
+ if (listNode.annotations != null) {
+ boundListNode.annotations = new HashMap<>(listNode.annotations);
+ }
boundListNode.elements.add(node.left);
OperatorNode boundRight = new OperatorNode(right.operator, boundListNode, right.tokenIndex);
@@ -311,6 +368,7 @@ static void handleQuoteRegex(EmitterVisitor emitterVisitor, OperatorNode node) {
// Process pattern and flags
operand.elements.get(0).accept(scalarVisitor); // Pattern
+ attachNamedCharacterExpansions(emitterVisitor, node, operand);
operand.elements.get(1).accept(scalarVisitor); // Flags
maybeApplyUnicodeStringsRegexModifiers(emitterVisitor);
emitRegexWarningState(emitterVisitor, node);
@@ -334,18 +392,29 @@ static void handleQuoteRegex(EmitterVisitor emitterVisitor, OperatorNode node) {
/** Validate non-interpolated qr// at CV compilation, as Perl does. */
private static void validateLiteralRegex(EmitterVisitor emitterVisitor,
OperatorNode node, ListNode operand) {
+ if (node.getBooleanAnnotation("literalSyntaxValidated")) return;
if (operand.elements.size() < 2
|| !(operand.elements.get(1) instanceof StringNode flags)) {
return;
}
String pattern = RegexLiteralAnalyzer.constantString(operand.elements.get(0));
if (pattern == null || RuntimeRegex.requiresRuntimeUnicodePropertyResolution(pattern)) return;
+ String diagnosticPattern = RegexLiteralAnalyzer.constantSourceString(
+ operand.elements.get(0));
String modifiers = flags.value;
if (unicodeStringsEnabled(emitterVisitor) && !modifiers.contains("u")) {
modifiers += "u";
}
try {
- RuntimeRegex.validateLiteralSyntax(pattern, modifiers);
+ StringParser.validateLiteralNamedCharacters(
+ operand, pattern, modifiers,
+ diagnosticPattern == null ? pattern : diagnosticPattern);
+ Object expansions = operand.getAnnotation(
+ StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ if (expansions != null) {
+ node.setAnnotation(StringParser.LEXICAL_NAMED_CHARACTER_EXPANSIONS,
+ expansions);
+ }
} catch (PerlCompilerException exception) {
throw PerlCompilerException.withSourceLocation(node.tokenIndex,
exception.getMessage(), emitterVisitor.ctx.errorUtil);
@@ -373,6 +442,7 @@ static void handleMatchRegex(EmitterVisitor emitterVisitor, OperatorNode node) {
// Process pattern and flags
operand.elements.get(0).accept(scalarVisitor); // Pattern
+ attachNamedCharacterExpansions(emitterVisitor, node, operand);
flagsNode.accept(scalarVisitor); // Flags
maybeApplyUnicodeStringsRegexModifiers(emitterVisitor);
emitRegexWarningState(emitterVisitor, node);
diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java
index 1250216780..2271ec5773 100644
--- a/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java
+++ b/src/main/java/org/perlonjava/backend/jvm/EmitSubroutine.java
@@ -108,9 +108,12 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) {
MethodVisitor mv = ctx.mv;
Set declaredLexicalNames = new LinkedHashSet<>();
+ boolean tracksRuntimeRegexLexicals = false;
if (node.block != null) {
- node.block.accept(new VariableCollectorVisitor(
- new HashSet<>(), declaredLexicalNames));
+ VariableCollectorVisitor metadataCollector = new VariableCollectorVisitor(
+ new HashSet<>(), declaredLexicalNames);
+ node.block.accept(metadataCollector);
+ tracksRuntimeRegexLexicals = metadataCollector.requiresAllRuntimeLexicals();
}
// Retrieve closure variable list (copy to avoid corrupting the cache)
@@ -145,7 +148,7 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) {
Set usedVars = new HashSet<>();
VariableCollectorVisitor collector = new VariableCollectorVisitor(usedVars);
node.block.accept(collector);
- if (!collector.hasEvalString()) {
+ if (!collector.requiresAllRuntimeLexicals()) {
int skip = EmitterMethodCreator.skipVariables;
int pos = 0;
Iterator> it =
@@ -696,6 +699,15 @@ public static void emitSubroutine(EmitterContext ctx, SubroutineNode node) {
+ "Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
+ if (tracksRuntimeRegexLexicals) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "org/perlonjava/runtime/runtimetypes/RuntimeCode",
+ "markRuntimeRegexLexicals",
+ "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)"
+ + "Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
+ false);
+ }
+
// 6. Clean up the stack if context is VOID
if (ctx.contextType == RuntimeContextType.VOID) {
mv.visitInsn(Opcodes.POP); // Remove the RuntimeScalar object from the stack
diff --git a/src/main/java/org/perlonjava/frontend/analysis/ConstantFoldingVisitor.java b/src/main/java/org/perlonjava/frontend/analysis/ConstantFoldingVisitor.java
index 0db3f3ff0f..ddf191584e 100644
--- a/src/main/java/org/perlonjava/frontend/analysis/ConstantFoldingVisitor.java
+++ b/src/main/java/org/perlonjava/frontend/analysis/ConstantFoldingVisitor.java
@@ -450,6 +450,9 @@ public void visit(OperatorNode node) {
if (foldedOperand != node.operand) {
OperatorNode newNode = new OperatorNode(node.operator, foldedOperand, node.tokenIndex);
newNode.id = node.id;
+ if (node.annotations != null) {
+ newNode.annotations = new java.util.HashMap<>(node.annotations);
+ }
result = newNode;
} else {
result = node;
@@ -524,7 +527,12 @@ public void visit(ListNode node) {
}
if (changed) {
- result = new ListNode(foldedElements, node.tokenIndex);
+ ListNode folded = new ListNode(foldedElements, node.tokenIndex);
+ folded.handle = node.handle;
+ if (node.annotations != null) {
+ folded.annotations = new java.util.HashMap<>(node.annotations);
+ }
+ result = folded;
} else {
result = node;
}
diff --git a/src/main/java/org/perlonjava/frontend/analysis/RegexLiteralAnalyzer.java b/src/main/java/org/perlonjava/frontend/analysis/RegexLiteralAnalyzer.java
index b0cce40e01..3e20852bda 100644
--- a/src/main/java/org/perlonjava/frontend/analysis/RegexLiteralAnalyzer.java
+++ b/src/main/java/org/perlonjava/frontend/analysis/RegexLiteralAnalyzer.java
@@ -36,4 +36,51 @@ public static String constantString(Node node) {
}
return null;
}
+
+ /**
+ * Return a syntax-equivalent literal source, or {@code null} when runtime
+ * interpolation is required. Parser-created callbacks are replaced by
+ * same-width neutral constructs so later diagnostic offsets remain in the
+ * original literal coordinate space.
+ */
+ public static String constantSyntaxString(Node node) {
+ return constantTemplateString(node, true);
+ }
+
+ /** Return the complete literal source, including parser-created callbacks. */
+ public static String constantSourceString(Node node) {
+ return constantTemplateString(node, false);
+ }
+
+ private static String constantTemplateString(Node node, boolean maskCallbacks) {
+ String constant = constantString(node);
+ if (constant != null) return constant;
+ if (node instanceof BinaryOperatorNode binary && ".".equals(binary.operator)) {
+ String left = constantTemplateString(binary.left, maskCallbacks);
+ String right = constantTemplateString(binary.right, maskCallbacks);
+ return left == null || right == null ? null : left + right;
+ }
+ if (node instanceof OperatorNode operator && "regexTemplate".equals(operator.operator)
+ && operator.operand instanceof ListNode parts) {
+ StringBuilder source = new StringBuilder();
+ for (Node part : parts.elements) {
+ String value = constantTemplateString(part, maskCallbacks);
+ if (value == null) return null;
+ source.append(value);
+ }
+ return source.toString();
+ }
+ if (node instanceof OperatorNode operator && "regexCallback".equals(operator.operator)) {
+ Object callbackSource = operator.getAnnotation("regexCallbackSource");
+ if (!(callbackSource instanceof String source)) return null;
+ if (!maskCallbacks) return source;
+ Object kind = operator.getAnnotation("regexCallbackKind");
+ String skeleton = "CONDITION".equals(kind) ? "?=)" : "(?:)";
+ if (source.length() < skeleton.length()) return null;
+ return skeleton.substring(0, skeleton.length() - 1)
+ + " ".repeat(source.length() - skeleton.length())
+ + skeleton.charAt(skeleton.length() - 1);
+ }
+ return null;
+ }
}
diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java
index 3828f30474..f737d38546 100644
--- a/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java
+++ b/src/main/java/org/perlonjava/frontend/parser/ParseBlock.java
@@ -123,6 +123,7 @@ public static BlockWithScope parseBlock(Parser parser, boolean exitScope) {
}
Integer postBlockStrictOptions = null;
+ String postBlockWarningBits = null;
// Run compile-time end-of-scope callbacks while this block is still
// the innermost parser scope. This must happen before returning to
@@ -134,6 +135,7 @@ public static BlockWithScope parseBlock(Parser parser, boolean exitScope) {
if (exitScope) {
parser.ctx.symbolTable.exitScope(scopeIndex);
postBlockStrictOptions = parser.ctx.symbolTable.getStrictOptions();
+ postBlockWarningBits = parser.ctx.symbolTable.getWarningBitsString();
}
// Create and return the block node with all parsed statements
@@ -141,6 +143,7 @@ public static BlockWithScope parseBlock(Parser parser, boolean exitScope) {
blockNode.labels = blockLabels; // Set the collected labels in the BlockNode
if (postBlockStrictOptions != null) {
blockNode.setAnnotation("postBlockStrictOptions", postBlockStrictOptions);
+ blockNode.setAnnotation("postBlockWarningBits", postBlockWarningBits);
}
return new BlockWithScope(blockNode, scopeIndex);
}
diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java b/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java
index b7eff890e7..780e0d2ae4 100644
--- a/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java
+++ b/src/main/java/org/perlonjava/frontend/parser/ParseInfix.java
@@ -195,10 +195,13 @@ public static Node parseInfixOperation(Parser parser, Node left, int precedence)
if ((operator.equals("=~") || operator.equals("!~"))
&& !isRegexOperator(right)) {
String flags = StringParser.addLexicalRegexContext(parser.ctx, "");
- right = new OperatorNode("quoteRegex",
- new ListNode(List.of(right,
- new StringNode(flags, right.getIndex())), right.getIndex()),
- right.getIndex());
+ ListNode regexOperand = new ListNode(new ArrayList<>(List.of(right,
+ new StringNode(flags, right.getIndex()))), right.getIndex());
+ if (right instanceof StringNode) {
+ StringParser.captureLexicalNamedCharacterTranslator(
+ regexOperand, right.getIndex(), parser.ctx);
+ }
+ right = new OperatorNode("quoteRegex", regexOperand, right.getIndex());
}
BinaryOperatorNode node = new BinaryOperatorNode(operator, left, right, parser.tokenIndex);
diff --git a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java
index d3fc5b6ea5..29d1b66f29 100644
--- a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java
@@ -259,7 +259,7 @@ static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block,
Set usedVars = new HashSet<>();
VariableCollectorVisitor collector = new VariableCollectorVisitor(usedVars);
block.accept(collector);
- boolean captureAllVisibleVariables = collector.hasEvalString();
+ boolean captureAllVisibleVariables = collector.requiresAllRuntimeLexicals();
for (SymbolTable.SymbolEntry entry : outerVars.values()) {
if (!entry.name().equals("@_") && !entry.decl().isEmpty()) {
// Skip lexical subs (entries starting with &) - they are stored as hidden variables
diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java
index ff00716971..e958e26295 100644
--- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java
@@ -1053,6 +1053,18 @@ public static Node parseUseDeclaration(Parser parser, LexerToken token) {
}
}
+ // Source decoding after use/no utf8 belongs to the
+ // enclosing parser scope. An interpreter-backed
+ // synthetic import frame restores its own pragma
+ // state on return, so retain the public hint here.
+ if ("utf8".equals(packageName)) {
+ if (isNoDeclaration) {
+ parser.ctx.symbolTable.disableStrictOption(Strict.HINT_UTF8);
+ } else {
+ parser.ctx.symbolTable.enableStrictOption(Strict.HINT_UTF8);
+ }
+ }
+
// Check if a source filter was installed during import()
// If so, we need to rejoin remaining tokens, apply the filter, and re-tokenize
if (FilterUtilCall.wasFilterInstalled()) {
diff --git a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java
index d351bfa197..7ee46f3c07 100644
--- a/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java
+++ b/src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java
@@ -9,6 +9,7 @@
import org.perlonjava.frontend.lexer.LexerTokenType;
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.perlmodule.Warnings;
+import org.perlonjava.runtime.regex.RegexMarkers;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
@@ -65,7 +66,7 @@ protected boolean regexCodeBlocksAreActive() {
// transformed before it becomes regex source. Perl therefore treats an
// executable-looking group there as runtime-generated source, not as a
// literal callback visible to the quote lexer.
- return caseModifiers.isEmpty();
+ return caseModifiers.isEmpty() && super.regexCodeBlocksAreActive();
}
/**
@@ -80,8 +81,15 @@ protected boolean regexCodeBlocksAreActive() {
* @param isRegex True if parsing regex pattern (affects interpolation)
* @param parseEscapes True to process escape sequences, false to preserve them
*/
- private StringDoubleQuoted(EmitterContext ctx, List tokens, Parser parser, int tokenIndex, boolean isRegex, boolean parseEscapes, boolean interpolateVariable, boolean isRegexReplacement, boolean isRegexQuoteConstruction) {
- super(ctx, tokens, parser, tokenIndex, isRegex, parseEscapes, interpolateVariable, isRegexReplacement, isRegexQuoteConstruction);
+ private StringDoubleQuoted(EmitterContext ctx, List tokens,
+ Parser parser, int tokenIndex, boolean isRegex,
+ boolean parseEscapes, boolean interpolateVariable,
+ boolean isRegexReplacement,
+ boolean isRegexQuoteConstruction,
+ boolean deferNamedCharacterDiagnosticsToRegex) {
+ super(ctx, tokens, parser, tokenIndex, isRegex, parseEscapes,
+ interpolateVariable, isRegexReplacement,
+ isRegexQuoteConstruction, deferNamedCharacterDiagnosticsToRegex);
}
/**
@@ -150,6 +158,34 @@ static Node parseDoubleQuotedString(EmitterContext ctx, StringParser.ParsedStrin
}
static Node parseDoubleQuotedString(EmitterContext ctx, StringParser.ParsedString rawStr, boolean parseEscapes, boolean interpolateVariable, boolean isRegexReplacement, List sharedHeredocNodes, Parser originalParser, boolean preprocessBracedBackslashQuotes, boolean isRegexQuoteConstruction) {
+ return parseDoubleQuotedString(ctx, rawStr, parseEscapes, interpolateVariable,
+ isRegexReplacement, sharedHeredocNodes, originalParser,
+ preprocessBracedBackslashQuotes, isRegexQuoteConstruction, false);
+ }
+
+ static Node parseDoubleQuotedString(EmitterContext ctx, StringParser.ParsedString rawStr,
+ boolean parseEscapes, boolean interpolateVariable,
+ boolean isRegexReplacement,
+ List sharedHeredocNodes,
+ Parser originalParser,
+ boolean preprocessBracedBackslashQuotes,
+ boolean isRegexQuoteConstruction,
+ boolean regexExtended) {
+ return parseDoubleQuotedString(ctx, rawStr, parseEscapes,
+ interpolateVariable, isRegexReplacement, sharedHeredocNodes,
+ originalParser, preprocessBracedBackslashQuotes,
+ isRegexQuoteConstruction, regexExtended, false);
+ }
+
+ static Node parseDoubleQuotedString(EmitterContext ctx, StringParser.ParsedString rawStr,
+ boolean parseEscapes, boolean interpolateVariable,
+ boolean isRegexReplacement,
+ List sharedHeredocNodes,
+ Parser originalParser,
+ boolean preprocessBracedBackslashQuotes,
+ boolean isRegexQuoteConstruction,
+ boolean regexExtended,
+ boolean deferNamedCharacterDiagnosticsToRegex) {
// Extract the first buffer (double-quoted strings don't have multiple parts like here-docs)
var input = rawStr.buffers.getFirst();
var tokenIndex = rawStr.next;
@@ -205,7 +241,11 @@ static Node parseDoubleQuotedString(EmitterContext ctx, StringParser.ParsedStrin
parser.baseLineNumber = rawStr.sourceLine;
// Create and run the double-quoted string parser with original token offset tracking
- var doubleQuotedParser = new StringDoubleQuoted(ctx, tokens, parser, tokenIndex, isRegex, parseEscapes, interpolateVariable, isRegexReplacement, isRegexQuoteConstruction);
+ var doubleQuotedParser = new StringDoubleQuoted(ctx, tokens, parser,
+ tokenIndex, isRegex, parseEscapes, interpolateVariable,
+ isRegexReplacement, isRegexQuoteConstruction,
+ deferNamedCharacterDiagnosticsToRegex);
+ doubleQuotedParser.setRegexExtended(regexExtended);
// Set up offset tracking and original string content for proper error reporting
doubleQuotedParser.setOriginalTokenOffset(tokenIndex);
@@ -486,6 +526,8 @@ private void parseDoubleQuotedEscapesRegex() {
// Pop and apply the most recent case modifier
if (!caseModifiers.isEmpty()) {
applyCaseModifier(caseModifiers.pop());
+ } else {
+ appendToCurrentSegment(RegexMarkers.LITERAL_USELESS_CASE_ESCAPE);
}
}
@@ -519,7 +561,7 @@ private void parseDoubleQuotedEscapesRegex() {
case "p", "P" -> preserveUnicodePropertyEscape(escape);
// Unknown escape - treat as literal character
- default -> appendToCurrentSegment("\\" + escape);
+ default -> appendLiteralToCurrentSegment("\\" + escape);
}
}
@@ -630,17 +672,7 @@ private void parseDoubleQuotedEscapes() {
// Control character: \cX
case "c" -> {
var controlChar = TokenUtils.consumeChar(parser);
- if (controlChar.isEmpty()) {
- throw new PerlCompilerException(parser.tokenIndex, "Missing control char name in \\c", parser.ctx.errorUtil);
- }
- var c = controlChar.charAt(0);
- var result = (c >= 'A' && c <= 'Z') ? String.valueOf((char) (c - 'A' + 1))
- : (c >= 'a' && c <= 'z') ? String.valueOf((char) (c - 'a' + 1))
- : c == '@' ? String.valueOf((char) 0)
- : (c >= '[' && c <= '_') ? String.valueOf((char) (c - '[' + 27))
- : c == '?' ? String.valueOf((char) 127)
- : String.valueOf(c);
- appendToCurrentSegment(result);
+ appendToCurrentSegment(controlCharacterValue(controlChar));
}
// Case modification end marker
diff --git a/src/main/java/org/perlonjava/frontend/parser/StringParser.java b/src/main/java/org/perlonjava/frontend/parser/StringParser.java
index 9706ddebc2..b7f07bb2d4 100644
--- a/src/main/java/org/perlonjava/frontend/parser/StringParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/StringParser.java
@@ -4,19 +4,25 @@
import org.perlonjava.backend.jvm.EmitterContext;
import org.perlonjava.frontend.astnode.*;
+import org.perlonjava.frontend.analysis.RegexLiteralAnalyzer;
import org.perlonjava.frontend.lexer.Lexer;
import org.perlonjava.frontend.lexer.LexerToken;
import org.perlonjava.frontend.lexer.LexerTokenType;
import org.perlonjava.runtime.operators.PerlUtfString;
+import org.perlonjava.runtime.HintHashRegistry;
+import org.perlonjava.runtime.NamedCharacterExpansion;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.GlobalVariable;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
import org.perlonjava.runtime.runtimetypes.RuntimeCode;
import org.perlonjava.runtime.regex.RuntimeRegex;
+import org.perlonjava.runtime.regex.RegexMarkers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
import static org.perlonjava.runtime.perlmodule.Strict.HINT_UTF8;
import static org.perlonjava.runtime.perlmodule.Strict.HINT_RE_ASCII;
@@ -40,6 +46,18 @@
*/
public class StringParser {
+ public static final String LEXICAL_NAMED_CHARACTER_TRANSLATOR =
+ "lexicalNamedCharacterTranslator";
+ public static final String LEXICAL_NAMED_CHARACTER_SOURCE_MODE =
+ "lexicalNamedCharacterSourceMode";
+ public static final String LEXICAL_NAMED_CHARACTER_EXPANSIONS =
+ "lexicalNamedCharacterExpansions";
+ private static final String LEXICAL_NAMED_CHARACTER_LITERAL_IDENTITY =
+ "lexicalNamedCharacterLiteralIdentity";
+ private static final String LEXICAL_NAMED_CHARACTER_CALLABLE_IDENTITY =
+ "lexicalNamedCharacterCallableIdentity";
+ private static final AtomicLong NEXT_LEXICAL_REGEX_IDENTITY = new AtomicLong();
+
/** Historical HINT_NEW_RE bit used by old regex interpolation parsing. */
private static final int HINT_NEW_RE = 0x00010000;
@@ -90,7 +108,7 @@ public static ParsedString parseRawStringWithDelimiter(EmitterContext ctx, List<
while (state != END_TOKEN) {
LexerToken currentToken = tokens.get(tokPos);
if (currentToken.type == LexerTokenType.EOF) {
- String errorMsg = endDelim == '/'
+ String errorMsg = isRegex
? "Search pattern not terminated"
: "Can't find string terminator "
+ (markerDelimiter != null ? "\".\"" : endDelim)
@@ -358,17 +376,19 @@ public static ParsedString parseRawStrings(Parser parser, EmitterContext ctx, Li
}
// fetch the last string: s/aaa/bbb/LAST
- String modifier = "";
- if (tokens.get(pos).type == LexerTokenType.IDENTIFIER) {
- modifier = tokens.get(pos).text;
- ast.next = pos + 1;
+ StringBuilder modifier = new StringBuilder();
+ while (tokens.get(pos).type == LexerTokenType.IDENTIFIER
+ || tokens.get(pos).type == LexerTokenType.NUMBER) {
+ modifier.append(tokens.get(pos).text);
+ pos++;
+ ast.next = pos;
}
ArrayList buffers = ast.buffers;
if (buffers == null) {
buffers = new ArrayList<>();
ast.buffers = buffers;
}
- buffers.add(modifier);
+ buffers.add(modifier.toString());
return ast;
}
@@ -378,6 +398,7 @@ static Node parseRegexString(EmitterContext ctx, ParsedString rawStr, Parser par
static Node parseRegexString(EmitterContext ctx, ParsedString rawStr, Parser parser, String modifiers, boolean isRegexQuoteConstruction) {
Node parsed;
+ boolean hasXModifier = modifiers != null && modifiers.contains("x");
if (rawStr.startDelim == '\'') {
// An apostrophe delimiter suppresses ordinary interpolation, but
@@ -387,11 +408,8 @@ static Node parseRegexString(EmitterContext ctx, ParsedString rawStr, Parser par
parsed = StringDoubleQuoted.parseDoubleQuotedString(
ctx, rawStr, false, false, true,
parser != null ? parser.getHeredocNodes() : null,
- null, true, isRegexQuoteConstruction);
+ null, true, isRegexQuoteConstruction, hasXModifier, true);
} else {
- // Check if /x modifier is present
- boolean hasXModifier = modifiers != null && modifiers.contains("x");
-
String patternStr = rawStr.buffers.getFirst();
if (hasXModifier && mayInterpolateRegex(patternStr)) {
// With /x modifier, strip comments before variable interpolation
@@ -410,7 +428,8 @@ static Node parseRegexString(EmitterContext ctx, ParsedString rawStr, Parser par
// interpolate variables, but ignore the escapes, keep `\$` if present
// Pass shared heredoc nodes to handle heredocs inside regex patterns
parsed = StringDoubleQuoted.parseDoubleQuotedString(ctx, rawStr, false, true, true,
- parser != null ? parser.getHeredocNodes() : null, null, true, isRegexQuoteConstruction);
+ parser != null ? parser.getHeredocNodes() : null, null, true,
+ isRegexQuoteConstruction, hasXModifier);
}
return parsed;
}
@@ -620,11 +639,14 @@ public static OperatorNode parseRegexReplace(EmitterContext ctx, ParsedString ra
elements.add(replace);
elements.add(modifiers);
ListNode list = new ListNode(elements, rawStr.index);
+ captureLexicalNamedCharacterTranslator(list, rawStr.index, ctx);
OperatorNode node = new OperatorNode(operator, list, rawStr.index);
node.setAnnotation("regexWarningsEnabled",
ctx.symbolTable != null && ctx.symbolTable.isWarningCategoryEnabled("regexp"));
node.setAnnotation("regexWarningsFatal",
ctx.symbolTable != null && ctx.symbolTable.isFatalWarningCategory("regexp"));
+ node.setAnnotation("regexWarningsSuppressed",
+ ctx.symbolTable != null && ctx.symbolTable.isWarningCategoryDisabled("regexp"));
node.setAnnotation("regexWarningBits",
ctx.symbolTable == null ? null : ctx.symbolTable.getWarningBitsString());
return node;
@@ -647,27 +669,119 @@ public static OperatorNode parseRegexMatch(EmitterContext ctx, String operator,
modStr = addLexicalRegexContext(ctx, modStr);
Node parsed = parseRegexString(ctx, rawStr, parser, modStr, isQuoteRegex);
+ List elements = new ArrayList<>();
+ elements.add(parsed);
+ ListNode list = new ListNode(elements, rawStr.index);
+ captureLexicalNamedCharacterTranslator(list, rawStr.index, ctx);
+ parsed = elements.getFirst();
+ boolean literalSyntaxValidated = false;
+ if (isQuoteRegex || list.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_TRANSLATOR) instanceof RuntimeScalar) {
+ String literalSyntax = RegexLiteralAnalyzer.constantSyntaxString(parsed);
+ String literalSource = RegexLiteralAnalyzer.constantSourceString(parsed);
+ if (literalSyntax != null
+ && literalSource != null
+ && !RuntimeRegex.requiresRuntimeUnicodePropertyResolution(literalSyntax)) {
+ String validationModifiers = modStr;
+ if (ctx.symbolTable != null
+ && ctx.symbolTable.isFeatureCategoryEnabled("unicode_strings")
+ && !validationModifiers.contains("u")) {
+ validationModifiers += "u";
+ }
+ try {
+ validateLiteralNamedCharacters(list, literalSyntax,
+ validationModifiers, literalSource);
+ literalSyntaxValidated = true;
+ } catch (PerlCompilerException exception) {
+ String message = exception.getMessage();
+ throw PerlCompilerException.withSourceLocation(
+ rawStr.index, message, ctx.errorUtil);
+ }
+ }
+ }
if (rawStr.startDelim == '?') {
// `m?PAT?` matches exactly once
// save the internal flag in the modifier string
modStr += '?';
}
Node modifiers = new StringNode(modStr, rawStr.index);
- List elements = new ArrayList<>();
- elements.add(parsed);
elements.add(modifiers);
- ListNode list = new ListNode(elements, rawStr.index);
OperatorNode node = new OperatorNode(operator, list, rawStr.index);
+ Object namedCharacterExpansions = list.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_EXPANSIONS);
+ if (namedCharacterExpansions != null) {
+ node.setAnnotation(LEXICAL_NAMED_CHARACTER_EXPANSIONS,
+ namedCharacterExpansions);
+ }
node.setAnnotation("syntacticQuoteRegex", isQuoteRegex);
+ node.setAnnotation("literalSyntaxValidated", literalSyntaxValidated);
node.setAnnotation("regexWarningsEnabled",
ctx.symbolTable != null && ctx.symbolTable.isWarningCategoryEnabled("regexp"));
node.setAnnotation("regexWarningsFatal",
ctx.symbolTable != null && ctx.symbolTable.isFatalWarningCategory("regexp"));
+ node.setAnnotation("regexWarningsSuppressed",
+ ctx.symbolTable != null && ctx.symbolTable.isWarningCategoryDisabled("regexp"));
node.setAnnotation("regexWarningBits",
ctx.symbolTable == null ? null : ctx.symbolTable.getWarningBitsString());
return node;
}
+ static void captureLexicalNamedCharacterTranslator(
+ ListNode operand, int sourceTokenIndex, EmitterContext ctx) {
+ RuntimeScalar translator = HintHashRegistry.getCompileTimeHint("charnames");
+ operand.setAnnotation(LEXICAL_NAMED_CHARACTER_SOURCE_MODE,
+ ctx.symbolTable != null
+ && ctx.symbolTable.isStrictOptionEnabled(HINT_UTF8)
+ ? NamedCharacterExpansion.SourceMode.UNICODE
+ : NamedCharacterExpansion.SourceMode.BYTE);
+ if (NamedCharacterExpansion.usesCustomTranslator(translator)) {
+ operand.setAnnotation(LEXICAL_NAMED_CHARACTER_TRANSLATOR,
+ new RuntimeScalar(translator));
+ Node pattern = operand.elements.get(0);
+ String identity = RegexMarkers.literalIdentity(
+ NEXT_LEXICAL_REGEX_IDENTITY.incrementAndGet());
+ operand.setAnnotation(LEXICAL_NAMED_CHARACTER_LITERAL_IDENTITY,
+ new NamedCharacterExpansionMap.LiteralIdentity(identity));
+ operand.setAnnotation(LEXICAL_NAMED_CHARACTER_CALLABLE_IDENTITY,
+ new NamedCharacterExpansionMap.CallableIdentity(translator.toString()));
+ if (pattern instanceof StringNode string) {
+ operand.elements.set(0, new StringNode(
+ string.value + identity, string.isVString,
+ string.forceByteString, string.tokenIndex));
+ } else {
+ operand.elements.set(0, new BinaryOperatorNode(".", pattern,
+ new StringNode(identity, sourceTokenIndex), sourceTokenIndex));
+ }
+ }
+ }
+
+ /** Validate a constant regex operand and retain any custom lexical results on its AST. */
+ public static void validateLiteralNamedCharacters(
+ ListNode operand, String pattern, String modifiers, String diagnosticPattern) {
+ Object capturedTranslator = operand.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_TRANSLATOR);
+ if (capturedTranslator instanceof RuntimeScalar translator) {
+ NamedCharacterExpansionMap expansions =
+ RuntimeRegex.validateLiteralSyntaxAndCapture(
+ pattern, modifiers, translator,
+ (NamedCharacterExpansion.SourceMode) operand.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_SOURCE_MODE),
+ (NamedCharacterExpansionMap.LiteralIdentity) operand.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_LITERAL_IDENTITY),
+ (NamedCharacterExpansionMap.CallableIdentity) operand.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_CALLABLE_IDENTITY),
+ diagnosticPattern);
+ operand.setAnnotation(LEXICAL_NAMED_CHARACTER_EXPANSIONS, expansions);
+ } else {
+ RuntimeRegex.validateLiteralSyntax(
+ pattern, modifiers,
+ HintHashRegistry.getCompileTimeHint("charnames"),
+ (NamedCharacterExpansion.SourceMode) operand.getAnnotation(
+ LEXICAL_NAMED_CHARACTER_SOURCE_MODE),
+ diagnosticPattern);
+ }
+ }
+
/**
* Carries lexical re-debug state through the existing flags operand. The
* private markers are stripped before public modifier validation and never
diff --git a/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java b/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java
index 7686c5479d..db0bce340d 100644
--- a/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/StringSegmentParser.java
@@ -9,9 +9,12 @@
import org.perlonjava.runtime.operators.PerlUtfString;
import org.perlonjava.runtime.HintHashRegistry;
import org.perlonjava.runtime.NamedCharacterExpansion;
+import org.perlonjava.runtime.regex.RegexMarkers;
+import org.perlonjava.runtime.regex.RegexQuoteMeta;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.PerlParserException;
import org.perlonjava.runtime.runtimetypes.ScalarUtils;
+import org.perlonjava.runtime.runtimetypes.WarningFlags;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -19,6 +22,7 @@
import static org.perlonjava.frontend.parser.ParseBlock.parseBlock;
import static org.perlonjava.frontend.parser.Variable.parseArrayHashAccess;
+import static org.perlonjava.runtime.perlmodule.Strict.HINT_RE_STRICT;
import static org.perlonjava.runtime.perlmodule.Strict.HINT_UTF8;
/**
@@ -77,6 +81,12 @@ public abstract class StringSegmentParser {
private boolean currentSegmentHasSourceNonAscii = false;
private boolean inRegexCharClass = false;
private boolean regexCharClassFirst = false;
+ private boolean regexCharClassEscape = false;
+ private boolean regexOutsideEscape = false;
+ private boolean inRegexComment = false;
+ private boolean regexCommentEscape = false;
+ private boolean inRegexLineComment = false;
+ private boolean regexExtended = false;
/**
* List of AST nodes representing string segments (literals and interpolated expressions)
*/
@@ -85,6 +95,7 @@ public abstract class StringSegmentParser {
protected boolean hasRuntimeInterpolation;
protected final boolean interpolateVariable;
protected final boolean parseEscapes;
+ protected final boolean deferNamedCharacterDiagnosticsToRegex;
/**
* Original token offset for mapping string positions back to source
*/
@@ -109,6 +120,17 @@ public StringSegmentParser(EmitterContext ctx, List tokens, Parser p
}
public StringSegmentParser(EmitterContext ctx, List tokens, Parser parser, int tokenIndex, boolean isRegex, boolean parseEscapes, boolean interpolateVariable, boolean isRegexReplacement, boolean isRegexQuoteConstruction) {
+ this(ctx, tokens, parser, tokenIndex, isRegex, parseEscapes,
+ interpolateVariable, isRegexReplacement,
+ isRegexQuoteConstruction, false);
+ }
+
+ public StringSegmentParser(EmitterContext ctx, List tokens,
+ Parser parser, int tokenIndex, boolean isRegex,
+ boolean parseEscapes, boolean interpolateVariable,
+ boolean isRegexReplacement,
+ boolean isRegexQuoteConstruction,
+ boolean deferNamedCharacterDiagnosticsToRegex) {
this.ctx = ctx;
this.tokens = tokens;
this.parser = parser;
@@ -120,6 +142,8 @@ public StringSegmentParser(EmitterContext ctx, List tokens, Parser p
this.interpolateVariable = interpolateVariable;
this.isRegexReplacement = isRegexReplacement;
this.isRegexQuoteConstruction = isRegexQuoteConstruction;
+ this.deferNamedCharacterDiagnosticsToRegex =
+ deferNamedCharacterDiagnosticsToRegex;
}
/**
@@ -158,23 +182,65 @@ protected boolean isInsideRegexCharClass() {
* Subclasses may suppress them while parsing a quoting region such as {@code \Q...\E}.
*/
protected boolean regexCodeBlocksAreActive() {
- return true;
+ return !inRegexCharClass && !inRegexComment && !inRegexLineComment;
+ }
+
+ void setRegexExtended(boolean regexExtended) {
+ this.regexExtended = regexExtended;
}
private void updateRegexCharClassState(char c) {
if (!isRegex) {
return;
}
- if (c == '[' && !inRegexCharClass) {
+ if (inRegexLineComment) {
+ if (c == '\n') {
+ inRegexLineComment = false;
+ }
+ return;
+ }
+ if (inRegexComment) {
+ if (regexCommentEscape) {
+ regexCommentEscape = false;
+ } else if (c == '\\') {
+ regexCommentEscape = true;
+ } else if (c == ')') {
+ inRegexComment = false;
+ }
+ return;
+ }
+ if (inRegexCharClass) {
+ if (regexCharClassEscape) {
+ regexCharClassEscape = false;
+ if (regexCharClassFirst) {
+ regexCharClassFirst = false;
+ }
+ } else if (c == '\\') {
+ regexCharClassEscape = true;
+ } else if (c == ']' && !regexCharClassFirst) {
+ inRegexCharClass = false;
+ } else if (regexCharClassFirst && c != '^') {
+ regexCharClassFirst = false;
+ }
+ } else if (regexOutsideEscape) {
+ regexOutsideEscape = false;
+ } else if (c == '\\') {
+ regexOutsideEscape = true;
+ } else if (c == '[') {
inRegexCharClass = true;
regexCharClassFirst = true;
- } else if (c == ']' && inRegexCharClass && !regexCharClassFirst) {
- inRegexCharClass = false;
- } else if (inRegexCharClass && regexCharClassFirst && c != '^') {
- regexCharClassFirst = false;
+ } else if (c == '#' && regexExtended) {
+ inRegexLineComment = true;
}
}
+ private boolean startsRegexComment() {
+ int currentPos = parser.tokenIndex;
+ return currentPos + 1 < parser.tokens.size()
+ && "?".equals(parser.tokens.get(currentPos).text)
+ && "#".equals(parser.tokens.get(currentPos + 1).text);
+ }
+
/**
* Adds a string segment node to the segments list.
*
@@ -798,6 +864,9 @@ public Node parse() {
* @return true if the token was handled specially, false if it should be treated as literal text
*/
protected boolean handleSpecialToken(String text) {
+ if (isRegex && (inRegexComment || inRegexLineComment)) {
+ return false;
+ }
return switch (text) {
case "\\" -> {
parseEscapeSequence();
@@ -811,6 +880,10 @@ protected boolean handleSpecialToken(String text) {
yield false;
}
case "(" -> {
+ if (isRegex && regexCodeBlocksAreActive() && startsRegexComment()) {
+ inRegexComment = true;
+ yield false;
+ }
// Check for (?{...}) and (??{...}) regex code blocks - only in regex context
if (isRegex && regexCodeBlocksAreActive() && isRegexCallbackCondition()) {
parseRegexCallbackCondition();
@@ -935,7 +1008,7 @@ private boolean isRegexRecursiveBlock() {
* real-world use cases, with pack.t and most Perl code using literal patterns.
*
* Future enhancement: To support interpolated patterns, this processing would need to be
- * moved to RegexPreprocessor.preProcessRegex() which sees the final pattern string regardless
+ * moved to native regex compilation, which sees the final pattern string regardless
* of how it was constructed.
*
* Only called when isRegex=true.
@@ -957,21 +1030,87 @@ private void parseRegexCodeBlock(boolean isRecursive) {
// Consume the "{" token
TokenUtils.consume(parser, LexerTokenType.OPERATOR, "{");
-
- // Parse the block content using the Block parser - this handles heredocs properly
- Node block = parseBlock(parser);
-
- // Consume the closing "}"
- TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}");
-
- // Consume the closing ")" that completes the (?{...}) construct
- TokenUtils.consume(parser, LexerTokenType.OPERATOR, ")");
+ int codeBodyStart = parser.tokenIndex;
+ Node block;
+ try {
+ // Parse the block content using the Block parser - this handles heredocs properly
+ block = parseBlock(parser);
+
+ // Consume the closing "}"
+ TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}");
+
+ // Consume the closing ")" that completes the (?{...}) construct
+ TokenUtils.consume(parser, LexerTokenType.OPERATOR, ")");
+ } catch (RuntimeException original) {
+ int closingBrace = findRegexCodeBlockClosingBrace(codeBodyStart);
+ if (closingBrace < 0) {
+ int failureIndex = Whitespace.skipWhitespace(
+ parser, parser.tokenIndex, parser.tokens);
+ if (failureIndex >= parser.tokens.size()
+ || parser.tokens.get(failureIndex).type == LexerTokenType.EOF) {
+ throw PerlCompilerException.withSourceLocation(
+ savedTokenIndex,
+ "Missing right curly or square bracket",
+ ctx.errorUtil);
+ }
+ // Perl reports an earlier expression syntax error before the
+ // structural EOF diagnostic when the callback parser stopped
+ // on a concrete bad token (for example `(?{(^{})`).
+ throw original;
+ }
+ int afterBrace = Whitespace.skipWhitespace(
+ parser, closingBrace + 1, parser.tokens);
+ if (afterBrace >= parser.tokens.size()
+ || !")".equals(parser.tokens.get(afterBrace).text)) {
+ throw PerlCompilerException.withSourceLocation(
+ savedTokenIndex,
+ "Sequence (?{...}) not terminated with ')'",
+ ctx.errorUtil);
+ }
+ throw original;
+ }
String kind = isRecursive ? "DYNAMIC" : "BLOCK";
segments.add(regexCallback(block, kind, savedTokenIndex, sourceLine,
"(" + regexSourceTokens(callbackSourceStart, parser.tokenIndex)));
}
+ /**
+ * Finds the structural brace that closes a Perl regex callback. Braces in
+ * ordinary quoted strings do not affect callback depth. This scanner is
+ * used only after the full Perl block parser rejects the source, so valid
+ * heredocs and quote-like operators continue to be owned by that parser.
+ */
+ private int findRegexCodeBlockClosingBrace(int codeBodyStart) {
+ int depth = 1;
+ String quote = null;
+ for (int i = codeBodyStart; i < parser.tokens.size(); i++) {
+ LexerToken token = parser.tokens.get(i);
+ if (token.type == LexerTokenType.EOF) {
+ return -1;
+ }
+ String text = token.text;
+ if ("\\".equals(text) && i + 1 < parser.tokens.size()) {
+ i++;
+ continue;
+ }
+ if (quote != null) {
+ if (quote.equals(text)) {
+ quote = null;
+ }
+ continue;
+ }
+ if ("'".equals(text) || "\"".equals(text)) {
+ quote = text;
+ } else if ("{".equals(text)) {
+ depth++;
+ } else if ("}".equals(text) && --depth == 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
private String regexSourceTokens(int start, int end) {
StringBuilder source = new StringBuilder();
for (int i = start; i < end && i < parser.tokens.size(); i++) {
@@ -1000,6 +1139,12 @@ private Node regexCallback(Node block, String kind, int index, int sourceLine,
callback.setAnnotation("regexCallbackPackage",
closure.getAnnotation("regexCallbackPackage"));
callback.setAnnotation("regexCallbackSource", source);
+ String callbackWarningBits = RegexQuoteMeta.getParserWarningBits();
+ if (callbackWarningBits == null) {
+ callbackWarningBits = parser.ctx.symbolTable.getWarningBitsString();
+ }
+ callback.setAnnotation("regexCallbackUninitializedWarningsEnabled",
+ WarningFlags.isEnabledInBits(callbackWarningBits, "uninitialized"));
hasExecutableRegexCallbacks = true;
return callback;
}
@@ -1265,29 +1410,38 @@ private boolean isNonInterpolatingCharacter(String text) {
/**
* Handles control character escape sequences like \cA, \cZ.
*
- * Control characters are represented as \c followed by a letter.
- * The letter is converted to its corresponding control character:
+ *
Control characters are represented as \c followed by one printable
+ * ASCII character. The operand is uppercased and XORed with {@code 0x40}:
*
* - \cA becomes ASCII 1 (Ctrl-A)
* - \cZ becomes ASCII 26 (Ctrl-Z)
- * - Both uppercase and lowercase letters are supported
+ * - \c# becomes {@code c}
+ * - \c? becomes DEL
*
*
- * If the character following \c is not a letter, it's used as-is.
+ * Perl reserves \c{ and reports a dedicated compile-time diagnostic.
*/
void handleControlCharacter() {
var controlChar = TokenUtils.consumeChar(parser);
+ appendToCurrentSegment(controlCharacterValue(controlChar));
+ }
+
+ protected String controlCharacterValue(String controlChar) {
if (controlChar.isEmpty()) {
throw new PerlCompilerException(parser.tokenIndex, "Missing control char name in \\c", parser.ctx.errorUtil);
}
var c = controlChar.charAt(0);
- var result = (c >= 'A' && c <= 'Z') ? String.valueOf((char) (c - 'A' + 1))
- : (c >= 'a' && c <= 'z') ? String.valueOf((char) (c - 'a' + 1))
- : c == '@' ? String.valueOf((char) 0)
- : (c >= '[' && c <= '_') ? String.valueOf((char) (c - '[' + 27))
- : c == '?' ? String.valueOf((char) 127)
- : String.valueOf(c);
- appendToCurrentSegment(result);
+ if (c == '{') {
+ throw new PerlCompilerException(parser.tokenIndex,
+ "Use \";\" instead of \"\\c{\"", parser.ctx.errorUtil);
+ }
+ if (c < 0x20 || c > 0x7e) {
+ return String.valueOf(c);
+ }
+ if (c >= 'a' && c <= 'z') {
+ c = (char) (c - ('a' - 'A'));
+ }
+ return String.valueOf((char) (c ^ 0x40));
}
/**
@@ -1499,6 +1653,11 @@ void handleUnicodeNameEscape() {
if ("}".equals(chr)) {
TokenUtils.consumeChar(parser); // consume '}'
var name = nameBuilder.toString();
+ if (isRegex && deferNamedCharacterDiagnosticsToRegex
+ && !name.regionMatches(true, 0, "U+", 0, 2)) {
+ appendToCurrentSegment("\\N{" + name + "}");
+ return;
+ }
if (isRegex && isPlainNonNewlineInterval(name)) {
// A brace immediately following plain \N can be its quantifier,
// not a named character. Leave both pieces for the regex lexer.
@@ -1518,14 +1677,15 @@ void handleUnicodeNameEscape() {
// any following capture groups disappear from the pattern.
boolean customTranslator = NamedCharacterExpansion.usesCustomTranslator(
HintHashRegistry.getCompileTimeHint("charnames"));
- if (!customTranslator || currentSegment.toString().endsWith("(?[")) {
+ if (!customTranslator) {
NamedCharacterExpansion expansion =
NamedCharacterExpansion.resolve(name, sourceMode);
- if (isIncompleteExtendedClassNamedSequence(expansion)) {
+ if (namedSequenceRequiresSingleCharacter(expansion)) {
throwNamedSequenceExtendedClassDiagnostic(expansion.sequence());
}
if (!expansion.resolved()) {
- throwNamedCharacterDiagnostic(expansion.diagnostic());
+ appendToCurrentSegment(RegexMarkers.literalDiagnostic(
+ namedCharacterDiagnostic(expansion.diagnostic())));
}
}
appendToCurrentSegment("\\N{" + name + "}");
@@ -1548,15 +1708,20 @@ void handleUnicodeNameEscape() {
}
private boolean isPlainNonNewlineInterval(String contents) {
- return contents.matches("(?:[0-9]+(?:,[0-9]*)?|,[0-9]+)");
+ return contents.replaceAll("\\s+", "")
+ .matches("(?:[0-9]+(?:,[0-9]*)?|,[0-9]+)");
}
private void throwNamedCharacterDiagnostic(String diagnostic) {
+ throw new PerlParserException(namedCharacterDiagnostic(diagnostic) + "\n");
+ }
+
+ private String namedCharacterDiagnostic(String diagnostic) {
int errorIndex = this.tokenIndex;
var location = ctx.errorUtil.getSourceLocationAccurate(errorIndex);
- throw new PerlParserException(diagnostic
+ return diagnostic
+ " at " + location.fileName() + " line " + location.lineNumber()
- + ", within " + (isRegex ? "pattern" : "string") + "\n");
+ + ", within " + (isRegex ? "pattern" : "string");
}
private void throwMissingNamedCharacterBraceDiagnostic() {
@@ -1569,10 +1734,89 @@ private void throwMissingNamedCharacterBraceDiagnostic() {
+ ", within " + (isRegex ? "pattern" : "string") + "\n");
}
- private boolean isIncompleteExtendedClassNamedSequence(
+ private boolean namedSequenceRequiresSingleCharacter(
NamedCharacterExpansion expansion) {
- return expansion.sequence().codePointCount(0, expansion.sequence().length()) > 1
- && currentSegment.toString().endsWith("(?[");
+ if (expansion.sequence().codePointCount(0, expansion.sequence().length()) <= 1) {
+ return false;
+ }
+ if (isInsideExtendedCharacterClass()) {
+ return true;
+ }
+ if (!ctx.symbolTable.isStrictOptionEnabled(HINT_RE_STRICT)) {
+ return false;
+ }
+ int classStart = activeOrdinaryCharacterClassStart();
+ if (classStart < 0) {
+ return false;
+ }
+ String classPrefix = currentSegment.substring(classStart + 1);
+ return classPrefix.startsWith("^")
+ || endsWithUnescapedHyphen(currentSegment)
+ || "-".equals(TokenUtils.peekChar(parser));
+ }
+
+ private boolean isInsideExtendedCharacterClass() {
+ return scanRegexClassContext().extendedDepth() > 0;
+ }
+
+ private int activeOrdinaryCharacterClassStart() {
+ return scanRegexClassContext().ordinaryClassStart();
+ }
+
+ private RegexClassContext scanRegexClassContext() {
+ String source = currentSegment.toString();
+ int extendedDepth = 0;
+ int ordinaryClassStart = -1;
+ boolean escaped = false;
+ for (int i = 0; i < source.length(); i++) {
+ char c = source.charAt(i);
+ if (escaped) {
+ escaped = false;
+ continue;
+ }
+ if (c == '\\') {
+ escaped = true;
+ continue;
+ }
+ if (ordinaryClassStart >= 0) {
+ if (c == ']') {
+ ordinaryClassStart = -1;
+ }
+ continue;
+ }
+ if (i + 2 < source.length()
+ && c == '(' && source.charAt(i + 1) == '?'
+ && source.charAt(i + 2) == '[') {
+ extendedDepth++;
+ i += 2;
+ continue;
+ }
+ if (extendedDepth > 0 && c == ']'
+ && i + 1 < source.length() && source.charAt(i + 1) == ')') {
+ extendedDepth--;
+ i++;
+ continue;
+ }
+ if (c == '[') {
+ ordinaryClassStart = i;
+ }
+ }
+ return new RegexClassContext(extendedDepth, ordinaryClassStart);
+ }
+
+ private boolean endsWithUnescapedHyphen(CharSequence source) {
+ int end = source.length() - 1;
+ if (end < 0 || source.charAt(end) != '-') {
+ return false;
+ }
+ int backslashes = 0;
+ for (int i = end - 1; i >= 0 && source.charAt(i) == '\\'; i--) {
+ backslashes++;
+ }
+ return (backslashes & 1) == 0;
+ }
+
+ private record RegexClassContext(int extendedDepth, int ordinaryClassStart) {
}
private String namedSequenceExtendedClassDiagnostic(String sequence) {
@@ -1583,7 +1827,20 @@ private String namedSequenceExtendedClassDiagnostic(String sequence) {
});
return "\\N{} here is restricted to one character in regex; "
+ "marked by <-- HERE in m/" + currentSegment
- + "\\N{U+" + canonical + " <-- HERE }/";
+ + "\\N{U+" + canonical + " <-- HERE }"
+ + remainingRegexSource() + "/";
+ }
+
+ private String remainingRegexSource() {
+ StringBuilder remaining = new StringBuilder();
+ for (int i = parser.tokenIndex; i < parser.tokens.size(); i++) {
+ LexerToken token = parser.tokens.get(i);
+ if (token.type == LexerTokenType.EOF) {
+ break;
+ }
+ remaining.append(token.text);
+ }
+ return remaining.toString();
}
private void throwNamedSequenceExtendedClassDiagnostic(String sequence) {
diff --git a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
index fd54f733ad..d48dcc126d 100644
--- a/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
+++ b/src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
@@ -1466,9 +1466,11 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
placeholder.ourVariableRegistry = declaredOurVarMap.isEmpty()
? null : declaredOurVarMap;
explicitlyUsedVars = usedVarSet;
- if (!collector.hasEvalString()) {
+ if (!collector.requiresAllRuntimeLexicals()) {
usedVars = usedVarSet;
}
+ placeholder.tracksRuntimeRegexLexicals =
+ collector.requiresAllRuntimeLexicals();
}
ArrayList classList = new ArrayList<>();
diff --git a/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java b/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java
index 7cae9c63fb..03e3eb29a6 100644
--- a/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java
+++ b/src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java
@@ -907,6 +907,10 @@ public void disableWarningCategory(String category) {
Integer bitPosition = warningBitPositions.get(category);
if (bitPosition != null) {
warningFlagsStack.peek().clear(bitPosition);
+ // A disabled category cannot remain fatal. Keeping the fatal bit
+ // made warning-mask serialization re-enable the category when an
+ // eval STRING inherited its caller's lexical warning state.
+ warningFatalStack.peek().clear(bitPosition);
// Mark as explicitly disabled (for proper $^W interaction)
warningDisabledStack.peek().set(bitPosition);
}
diff --git a/src/main/java/org/perlonjava/runtime/NamedCharacterExpansionMap.java b/src/main/java/org/perlonjava/runtime/NamedCharacterExpansionMap.java
new file mode 100644
index 0000000000..0cecfaa6fe
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/NamedCharacterExpansionMap.java
@@ -0,0 +1,68 @@
+package org.perlonjava.runtime;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Immutable lexical results for the named-character escapes in one regex literal.
+ *
+ * The parser attaches this value to the literal AST after executing a custom
+ * {@code %^H{charnames}} translator. Backends carry the same typed value into
+ * the compiled CV, allowing an ithread child to compile the literal without
+ * executing or sharing the parent's lexical callable.
+ */
+public record NamedCharacterExpansionMap(
+ LiteralIdentity literalIdentity,
+ CallableIdentity callableIdentity,
+ Map expansions) {
+
+ public record LiteralIdentity(String value) {}
+
+ public record CallableIdentity(String value) {}
+
+ public record Key(
+ String sourceSpelling,
+ NamedCharacterExpansion.SourceMode sourceMode) {}
+
+ public NamedCharacterExpansionMap {
+ expansions = Collections.unmodifiableMap(new LinkedHashMap<>(expansions));
+ }
+
+ public NamedCharacterExpansion get(
+ String sourceSpelling, NamedCharacterExpansion.SourceMode sourceMode) {
+ return expansions.get(new Key(sourceSpelling, sourceMode));
+ }
+
+ public boolean isEmpty() {
+ return expansions.isEmpty();
+ }
+
+ /** Lexical source mode captured before literal scalar materialization. */
+ public NamedCharacterExpansion.SourceMode sourceMode() {
+ return expansions.isEmpty() ? null
+ : expansions.keySet().iterator().next().sourceMode();
+ }
+
+ /** Rebuild a typed value from constants carried by generated JVM bytecode. */
+ public static NamedCharacterExpansionMap fromFlat(
+ String literalIdentity, String callableIdentity, String[] flat) {
+ if (flat.length % 7 != 0) {
+ throw new IllegalArgumentException("invalid named-character metadata");
+ }
+ Map entries = new LinkedHashMap<>();
+ for (int index = 0; index < flat.length; index += 7) {
+ NamedCharacterExpansion.SourceMode keyMode =
+ NamedCharacterExpansion.SourceMode.valueOf(flat[index + 1]);
+ entries.put(new Key(flat[index], keyMode), new NamedCharacterExpansion(
+ flat[index + 2],
+ NamedCharacterExpansion.SourceMode.valueOf(flat[index + 3]),
+ Boolean.parseBoolean(flat[index + 4]),
+ NamedCharacterExpansion.Status.valueOf(flat[index + 5]),
+ flat[index + 6]));
+ }
+ return new NamedCharacterExpansionMap(
+ new LiteralIdentity(literalIdentity),
+ new CallableIdentity(callableIdentity), entries);
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java
index a115a18f3c..e4bc1458b4 100644
--- a/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java
+++ b/src/main/java/org/perlonjava/runtime/operators/FileTestOperator.java
@@ -688,6 +688,13 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle)
yield scalarUndef;
}
getGlobalVariable("main::!").set(0); // Clear error
+ if (isPerlOnJavaExecutable(path)) {
+ // $^X names the logical interpreter executable. The
+ // development/package launcher may be a text script,
+ // but Perl's file-test contract observes the active
+ // interpreter as a binary executable.
+ yield getScalarBoolean(operator.equals("-B"));
+ }
yield isTextOrBinary(path, operator.equals("-T"));
}
case "-M", "-A", "-C" -> {
@@ -797,6 +804,17 @@ private static RuntimeScalar isTextOrBinary(Path path, boolean checkForText) thr
return analyzeTextBinary(buffer, bytesRead, checkForText);
}
+ private static boolean isPerlOnJavaExecutable(Path path) {
+ String executable = System.getenv("PERLONJAVA_EXECUTABLE");
+ if (executable == null || executable.isEmpty()) return false;
+ try {
+ Path executablePath = Path.of(executable);
+ return Files.exists(executablePath) && Files.isSameFile(path, executablePath);
+ } catch (IOException | InvalidPathException exception) {
+ return false;
+ }
+ }
+
/**
* Determines if a filehandle's content from the current position is text or binary.
* Used for -T/-B on filehandles where we need to read from the current position,
diff --git a/src/main/java/org/perlonjava/runtime/operators/PerlUtfString.java b/src/main/java/org/perlonjava/runtime/operators/PerlUtfString.java
index 47839cd661..3bf15104f0 100644
--- a/src/main/java/org/perlonjava/runtime/operators/PerlUtfString.java
+++ b/src/main/java/org/perlonjava/runtime/operators/PerlUtfString.java
@@ -147,6 +147,22 @@ public static int nextJavaBoundary(String s, int javaIndex) {
return readOnePerlLogical(s, javaIndex).nextJavaIndex();
}
+ /** Java UTF-16 index where the final Perl logical character begins. */
+ public static int lastLogicalCharacterStart(String s) {
+ if (s.isEmpty()) return 0;
+ PerlIndexMap indexMap = cachedIndexMap(s);
+ if (indexMap != null) {
+ return indexMap.javaBoundaries[indexMap.javaBoundaries.length - 2];
+ }
+ int previous = 0;
+ int current = 0;
+ while (current < s.length()) {
+ previous = current;
+ current = readOnePerlLogical(s, current).nextJavaIndex();
+ }
+ return previous;
+ }
+
public static int offsetByPerlCodePoints(String s, int startJava, int perlOffset) {
if (perlOffset <= 0) {
return startJava;
diff --git a/src/main/java/org/perlonjava/runtime/operators/StringOperators.java b/src/main/java/org/perlonjava/runtime/operators/StringOperators.java
index ded787298c..097a35b1a0 100644
--- a/src/main/java/org/perlonjava/runtime/operators/StringOperators.java
+++ b/src/main/java/org/perlonjava/runtime/operators/StringOperators.java
@@ -549,7 +549,11 @@ private static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSc
// enclosing call site was compiled, so retain a cheap undef-only
// runtime guard here as well. WarnDie performs the lexical category
// check and dispatches a localized __WARN__ handler when applicable.
- if (warnUninitialized
+ String callSiteWarningBits = WarningBitsRegistry.getCallSiteBits();
+ boolean callSiteWarnsUninitialized = callSiteWarningBits == null
+ || WarningFlags.areWarningsForcedOn()
+ || WarningFlags.isEnabledInBits(callSiteWarningBits, "uninitialized");
+ if (warnUninitialized && callSiteWarnsUninitialized
&& (!aResolved.getDefinedBoolean() || !bResolved.getDefinedBoolean())) {
WarnDie.warnWithCategory(
new RuntimeScalar("Use of uninitialized value in concatenation (.)"),
@@ -742,12 +746,9 @@ public static RuntimeScalar chopScalar(RuntimeScalar runtimeScalar) {
return new RuntimeScalar();
}
- // Handle Unicode properly by using code points instead of char units
- int lastCodePoint = str.codePointBefore(str.length());
- int lastCharSize = Character.charCount(lastCodePoint);
-
- String lastChar = str.substring(str.length() - lastCharSize);
- String remainingStr = str.substring(0, str.length() - lastCharSize);
+ int lastStart = PerlUtfString.lastLogicalCharacterStart(str);
+ String lastChar = str.substring(lastStart);
+ String remainingStr = str.substring(0, lastStart);
boolean wasByteString = runtimeScalar.type == RuntimeScalarType.BYTE_STRING;
runtimeScalar.set(remainingStr);
diff --git a/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java b/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java
index d89533a30f..2c7d531208 100644
--- a/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java
+++ b/src/main/java/org/perlonjava/runtime/operators/SystemOperator.java
@@ -45,59 +45,9 @@ public class SystemOperator {
private static final Pattern TAINTED_ENV_METACHARACTERS = Pattern.compile("[^A-Za-z0-9_./-]");
private static String decodeSubprocessOutput(byte[] bytes) {
- StringBuilder decoded = new StringBuilder(bytes.length);
- for (int i = 0; i < bytes.length; ) {
- int first = bytes[i] & 0xff;
- if (first < 0x80) {
- decoded.append((char) first);
- i++;
- continue;
- }
-
- int length;
- int codePoint;
- if (first >= 0xc2 && first <= 0xdf) {
- length = 2;
- codePoint = first & 0x1f;
- } else if (first >= 0xe0 && first <= 0xef) {
- length = 3;
- codePoint = first & 0x0f;
- } else if (first >= 0xf0 && first <= 0xf4) {
- length = 4;
- codePoint = first & 0x07;
- } else {
- decoded.append((char) first);
- i++;
- continue;
- }
-
- boolean valid = i + length <= bytes.length;
- for (int j = 1; valid && j < length; j++) {
- int continuation = bytes[i + j] & 0xff;
- valid = (continuation & 0xc0) == 0x80;
- if (valid) codePoint = (codePoint << 6) | (continuation & 0x3f);
- }
- if (valid && length == 3) {
- int second = bytes[i + 1] & 0xff;
- valid = (first != 0xe0 || second >= 0xa0)
- && (first != 0xed || second <= 0x9f);
- } else if (valid && length == 4) {
- int second = bytes[i + 1] & 0xff;
- valid = (first != 0xf0 || second >= 0x90)
- && (first != 0xf4 || second <= 0x8f);
- }
-
- if (valid) {
- decoded.appendCodePoint(codePoint);
- i += length;
- } else {
- // Preserve an invalid octet one-for-one instead of replacing
- // it with U+FFFD, so qx// remains binary-safe.
- decoded.append((char) first);
- i++;
- }
- }
- return decoded.toString();
+ // qx// has no implicit decoding layer: Perl returns subprocess stdout
+ // as an SvUTF8-off byte string even when the octets form valid UTF-8.
+ return new String(bytes, StandardCharsets.ISO_8859_1);
}
/**
@@ -1054,25 +1004,30 @@ private static RuntimeBase processOutput(String output, int ctx) {
int separatorLength = separator.length();
if (separatorLength == 0) {
- result.add(new RuntimeScalar(output).taintFromExternalInput());
+ result.add(subprocessOutputScalar(output));
} else {
while (index < output.length()) {
int nextIndex = output.indexOf(separator, index);
if (nextIndex == -1) {
- result.add(new RuntimeScalar(output.substring(index)).taintFromExternalInput());
+ result.add(subprocessOutputScalar(output.substring(index)));
break;
}
- result.add(new RuntimeScalar(output.substring(index, nextIndex + separatorLength))
- .taintFromExternalInput());
+ result.add(subprocessOutputScalar(
+ output.substring(index, nextIndex + separatorLength)));
index = nextIndex + separatorLength;
}
}
return list;
} else {
- return new RuntimeScalar(output).taintFromExternalInput();
+ return subprocessOutputScalar(output);
}
}
+ private static RuntimeScalar subprocessOutputScalar(String output) {
+ return new RuntimeScalar(output.getBytes(StandardCharsets.ISO_8859_1))
+ .taintFromExternalInput();
+ }
+
/**
* Executes a command and attempts to replace the current process.
* This implements Perl's exec() function.
diff --git a/src/main/java/org/perlonjava/runtime/operators/TieOperators.java b/src/main/java/org/perlonjava/runtime/operators/TieOperators.java
index 43782a17d2..923a1966e9 100644
--- a/src/main/java/org/perlonjava/runtime/operators/TieOperators.java
+++ b/src/main/java/org/perlonjava/runtime/operators/TieOperators.java
@@ -2,6 +2,7 @@
import org.perlonjava.runtime.mro.InheritanceResolver;
import org.perlonjava.runtime.runtimetypes.*;
+import org.perlonjava.runtime.perlmodule.TieHashNamedCapture;
import java.util.Arrays;
@@ -284,6 +285,10 @@ public static RuntimeScalar tied(int ctx, RuntimeBase... scalars) {
}
case HASHREFERENCE -> {
RuntimeHash hash = variable.hashDeref();
+ if (hash.elements instanceof HashSpecialVariable special
+ && special.captureMode() != null) {
+ return TieHashNamedCapture.tiedObject(special.captureMode());
+ }
if (hash.type == TIED_HASH && hash.elements instanceof TieHash) {
return ((TieHash) hash.elements).getSelf();
}
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Re.java b/src/main/java/org/perlonjava/runtime/perlmodule/Re.java
index 3ea47f5096..9e22438afb 100644
--- a/src/main/java/org/perlonjava/runtime/perlmodule/Re.java
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/Re.java
@@ -5,6 +5,7 @@
import org.perlonjava.runtime.runtimetypes.RuntimeArray;
import org.perlonjava.runtime.runtimetypes.RuntimeCode;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
+import org.perlonjava.runtime.runtimetypes.RuntimeHash;
import org.perlonjava.runtime.runtimetypes.RuntimeList;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
import org.perlonjava.runtime.runtimetypes.RuntimeScalarType;
@@ -26,6 +27,7 @@
* {@code use re 'debugcolor'} - Enables colorized lexical regex tracing
* {@code re::is_regexp($ref)} - Check if reference is a compiled regex
* {@code re::regexp_pattern($ref)} - Return pattern and modifiers from qr//
+ * {@code re::optimization($ref)} - Inspect optimizer-selected regex facts
*
*
* TODO: Features not yet implemented (see {@code perldoc re}):
@@ -77,6 +79,7 @@ public static void initialize() {
try {
re.registerMethod("is_regexp", "isRegexp", "$");
re.registerMethod("regexp_pattern", "regexpPattern", "$");
+ re.registerMethod("optimization", "optimization", "$");
re.registerMethod("import", "importRe", null);
re.registerMethod("unimport", "unimportRe", null);
} catch (NoSuchMethodException e) {
@@ -153,6 +156,66 @@ public static RuntimeList regexpPattern(RuntimeArray args, int ctx) {
}
}
+ /** Implements re::optimization($regex) using facts selected by Joni itself. */
+ public static RuntimeList optimization(RuntimeArray args, int ctx) {
+ if (args.size() != 1) {
+ throw new IllegalStateException("Bad number of arguments for optimization() method");
+ }
+
+ RuntimeScalar arg = args.get(0);
+ if (arg.type == RuntimeScalarType.REFERENCE && arg.value instanceof RuntimeScalar deref) {
+ arg = deref;
+ }
+ if (arg.type != RuntimeScalarType.REGEX) {
+ return new RuntimeList(RuntimeScalar.undef());
+ }
+
+ RuntimeRegex regex = (RuntimeRegex) arg.value;
+ org.joni.Regex.OptimizationInfo info = regex.getOptimizationInfo();
+ if (info == null) {
+ return new RuntimeList(RuntimeScalar.undef());
+ }
+
+ RuntimeHash result = new RuntimeHash();
+ result.put("minlen", new RuntimeScalar(info.minimumLength()));
+ result.put("minlenret", new RuntimeScalar(info.minimumLength()));
+ result.put("gofs", new RuntimeScalar(0));
+ result.put("noscan", perlBoolean(
+ info.beginBufferAnchored() || info.beginPositionAnchored()));
+ result.put("isall", perlBoolean(false));
+ result.put("skip", perlBoolean(false));
+ result.put("implicit", perlBoolean(
+ info.implicitSingleLineAnchor() || info.implicitMultiLineAnchor()));
+ result.put("anchor SBOL", perlBoolean(info.implicitMultiLineAnchor()));
+ result.put("anchor MBOL", perlBoolean(info.implicitSingleLineAnchor()));
+ result.put("anchor GPOS", perlBoolean(info.beginPositionAnchored()));
+ result.put("joni search", new RuntimeScalar(info.searchAlgorithm()));
+
+ String exact = info.exact();
+ if (exact == null) {
+ result.put("checking", new RuntimeScalar("none"));
+ } else {
+ boolean anchored = info.minimumOffset() == 0
+ && info.maximumOffset() != null && info.maximumOffset() == 0;
+ String kind = anchored ? "anchored" : "floating";
+ result.put(kind, new RuntimeScalar(exact));
+ result.put(kind + " min offset", new RuntimeScalar(info.minimumOffset()));
+ if (info.maximumOffset() != null) {
+ result.put(kind + " max offset", new RuntimeScalar(info.maximumOffset()));
+ }
+ result.put("checking", new RuntimeScalar(kind));
+ boolean isAll = anchored && info.minimumOffset() == 0
+ && info.exactReachEnd() && !info.hasCaptures()
+ && exact.codePointCount(0, exact.length()) == info.minimumLength();
+ result.put("isall", perlBoolean(isAll));
+ }
+ return new RuntimeList(result.createReferenceWithTrackedElements());
+ }
+
+ private static RuntimeScalar perlBoolean(boolean value) {
+ return new RuntimeScalar(value ? 1 : 0);
+ }
+
/**
* Handle `use re ...` import. Recognizes: 'strict', 'eval', '/a', '/u', '/aa'.
* Enables appropriate experimental warning categories so our regex preprocessor can emit them.
@@ -190,9 +253,9 @@ public static RuntimeList importRe(RuntimeArray args, int ctx) {
symbolTable.enableStrictOption(Strict.HINT_RE_EVAL);
} else if (opt.equalsIgnoreCase("taint")) {
symbolTable.enableStrictOption(Strict.HINT_RE_TAINT);
- } else if (opt.equals("debug")) {
+ } else if (opt.equalsIgnoreCase("debug")) {
symbolTable.enableStrictOption(Strict.HINT_RE_DEBUG);
- } else if (opt.equals("debugcolor")) {
+ } else if (opt.equalsIgnoreCase("debugcolor")) {
symbolTable.enableStrictOption(Strict.HINT_RE_DEBUG | Strict.HINT_RE_DEBUGCOLOR);
} else if (opt.equals("/a")) {
// use re '/a' - ASCII-restrict regex character classes
@@ -236,9 +299,9 @@ public static RuntimeList unimportRe(RuntimeArray args, int ctx) {
symbolTable.disableStrictOption(Strict.HINT_RE_EVAL);
} else if (opt.equalsIgnoreCase("taint")) {
symbolTable.disableStrictOption(Strict.HINT_RE_TAINT);
- } else if (opt.equals("debug")) {
+ } else if (opt.equalsIgnoreCase("debug")) {
symbolTable.disableStrictOption(Strict.HINT_RE_DEBUG | Strict.HINT_RE_DEBUGCOLOR);
- } else if (opt.equals("debugcolor")) {
+ } else if (opt.equalsIgnoreCase("debugcolor")) {
symbolTable.disableStrictOption(Strict.HINT_RE_DEBUGCOLOR);
} else if (opt.equals("/a") || opt.equals("/aa")) {
symbolTable.disableStrictOption(Strict.HINT_RE_ASCII | Strict.HINT_RE_ASCII_AA);
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/TieHashNamedCapture.java b/src/main/java/org/perlonjava/runtime/perlmodule/TieHashNamedCapture.java
new file mode 100644
index 0000000000..fa0c34c8a7
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/TieHashNamedCapture.java
@@ -0,0 +1,131 @@
+package org.perlonjava.runtime.perlmodule;
+
+import org.perlonjava.runtime.operators.ReferenceOperators;
+import org.perlonjava.runtime.runtimetypes.HashSpecialVariable;
+import org.perlonjava.runtime.runtimetypes.GlobalVariable;
+import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
+import org.perlonjava.runtime.runtimetypes.RuntimeArray;
+import org.perlonjava.runtime.runtimetypes.RuntimeHash;
+import org.perlonjava.runtime.runtimetypes.RuntimeList;
+import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
+import org.perlonjava.runtime.runtimetypes.RuntimeScalarType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Native implementation of Perl's built-in Tie::Hash::NamedCapture API. */
+public final class TieHashNamedCapture extends PerlModuleBase {
+ private static final int CAPTURE = 256;
+ private static final int CAPTURE_ALL = 512;
+
+ private TieHashNamedCapture() {
+ super("Tie::Hash::NamedCapture", false);
+ }
+
+ public static void initialize() {
+ TieHashNamedCapture module = new TieHashNamedCapture();
+ try {
+ module.registerMethod("FETCH", null);
+ module.registerMethod("STORE", null);
+ module.registerMethod("DELETE", null);
+ module.registerMethod("CLEAR", null);
+ module.registerMethod("EXISTS", null);
+ module.registerMethod("FIRSTKEY", null);
+ module.registerMethod("NEXTKEY", null);
+ module.registerMethod("SCALAR", null);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Cannot register Tie::Hash::NamedCapture", e);
+ }
+ }
+
+ public static RuntimeScalar tiedObject(HashSpecialVariable.Id mode) {
+ int value = mode == HashSpecialVariable.Id.CAPTURE_ALL ? CAPTURE_ALL : CAPTURE;
+ RuntimeScalar reference = new RuntimeScalar(value).createReference();
+ ReferenceOperators.bless(reference, new RuntimeScalar("Tie::Hash::NamedCapture"));
+ return reference;
+ }
+
+ private static HashSpecialVariable.Id mode(RuntimeScalar self) {
+ if (self == null || self.type == RuntimeScalarType.UNDEF) return null;
+ RuntimeScalar value = self;
+ if (value.type == RuntimeScalarType.REFERENCE) value = value.scalarDeref();
+ return value.getInt() == CAPTURE_ALL
+ ? HashSpecialVariable.Id.CAPTURE_ALL
+ : HashSpecialVariable.Id.CAPTURE;
+ }
+
+ private static RuntimeHash captureHash(HashSpecialVariable.Id mode) {
+ return GlobalVariable.getGlobalHash(
+ mode == HashSpecialVariable.Id.CAPTURE_ALL ? "main::-" : "main::+");
+ }
+
+ private static void arity(RuntimeArray args, int expected, String method, String signature) {
+ if (args.size() != expected) {
+ throw new PerlCompilerException(
+ "Usage: Tie::Hash::NamedCapture::" + method + "(" + signature + ")");
+ }
+ }
+
+ private static RuntimeList undef() {
+ return new RuntimeScalar().getList();
+ }
+
+ public static RuntimeList FETCH(RuntimeArray args, int ctx) {
+ arity(args, 2, "FETCH", "$key");
+ HashSpecialVariable.Id mode = mode(args.get(0));
+ if (mode == null) return undef();
+ return new RuntimeScalar(captureHash(mode).get(args.get(1))).getList();
+ }
+
+ public static RuntimeList STORE(RuntimeArray args, int ctx) {
+ arity(args, 3, "STORE", "$key, $value");
+ throw new PerlCompilerException("Modification of a read-only value attempted");
+ }
+
+ public static RuntimeList DELETE(RuntimeArray args, int ctx) {
+ arity(args, 2, "DELETE", "$key");
+ throw new PerlCompilerException("Modification of a read-only value attempted");
+ }
+
+ public static RuntimeList CLEAR(RuntimeArray args, int ctx) {
+ arity(args, 1, "CLEAR", "");
+ throw new PerlCompilerException("Modification of a read-only value attempted");
+ }
+
+ public static RuntimeList EXISTS(RuntimeArray args, int ctx) {
+ arity(args, 2, "EXISTS", "$key");
+ HashSpecialVariable.Id mode = mode(args.get(0));
+ if (mode == null) return undef();
+ return new RuntimeScalar(captureHash(mode).exists(args.get(1))).getList();
+ }
+
+ private static List keys(HashSpecialVariable.Id mode) {
+ return new ArrayList<>(captureHash(mode).elements.keySet());
+ }
+
+ public static RuntimeList FIRSTKEY(RuntimeArray args, int ctx) {
+ arity(args, 1, "FIRSTKEY", "");
+ HashSpecialVariable.Id mode = mode(args.get(0));
+ if (mode == null) return undef();
+ List keys = keys(mode);
+ return keys.isEmpty() ? undef() : new RuntimeScalar(keys.get(0)).getList();
+ }
+
+ public static RuntimeList NEXTKEY(RuntimeArray args, int ctx) {
+ arity(args, 2, "NEXTKEY", "$lastkey");
+ HashSpecialVariable.Id mode = mode(args.get(0));
+ if (mode == null) return undef();
+ List keys = keys(mode);
+ int index = keys.indexOf(args.get(1).toString()) + 1;
+ return index <= 0 || index >= keys.size()
+ ? undef()
+ : new RuntimeScalar(keys.get(index)).getList();
+ }
+
+ public static RuntimeList SCALAR(RuntimeArray args, int ctx) {
+ arity(args, 1, "SCALAR", "");
+ HashSpecialVariable.Id mode = mode(args.get(0));
+ if (mode == null) return undef();
+ return new RuntimeScalar(captureHash(mode).size()).getList();
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java b/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java
index 1a871fe9b7..9f6ce336a7 100644
--- a/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java
+++ b/src/main/java/org/perlonjava/runtime/perlmodule/Utf8.java
@@ -26,7 +26,10 @@ public class Utf8 extends PerlModuleBase {
* Initializes the module with the name "utf8".
*/
public Utf8() {
- super("utf8");
+ // Register core utf8:: functions eagerly without claiming that the
+ // pragma file was required. Perl leaves %INC untouched until an
+ // explicit require/use, and regex case folding must not load it.
+ super("utf8", false);
}
/**
diff --git a/src/main/java/org/perlonjava/runtime/regex/CaptureNameEncoder.java b/src/main/java/org/perlonjava/runtime/regex/CaptureNameEncoder.java
index 736a92c589..588c886ade 100644
--- a/src/main/java/org/perlonjava/runtime/regex/CaptureNameEncoder.java
+++ b/src/main/java/org/perlonjava/runtime/regex/CaptureNameEncoder.java
@@ -5,14 +5,14 @@
import org.perlonjava.runtime.runtimetypes.RuntimeScalarType;
/**
- * Utility class for encoding/decoding capture group names to work around Java regex limitations.
+ * Utility class for encoding and decoding internal capture-group metadata.
*
* Purpose
- * Java's regex engine has several limitations compared to Perl:
+ * The frontend and native matcher exchange metadata through capture names for:
*
- * - No support for executing code in regex ((?{...}) blocks)
- * - Named captures cannot contain underscores
- * - Duplicate capture group names not supported
+ * - constant code-block results used by the callback adapter
+ * - capture names containing underscores
+ * - duplicate capture names that need stable publication identities
*
*
* Solution: In-Place Encoding
@@ -51,21 +51,21 @@
* Self-contained: pattern carries its own metadata
* No static state accumulation
* Works with regex caching
- * Reusable pattern for multiple Java limitations
+ * Independent of the production matcher implementation
*
*/
public class CaptureNameEncoder {
/**
- * Maximum length for a Java capture group name.
- * Longer names may cause pattern compilation issues.
+ * Maximum length for an encoded capture group name.
+ * Longer names may cause native pattern compilation issues.
*/
public static final int MAX_CAPTURE_NAME_LENGTH = 200;
/**
* Marker appended to capture group names that appear more than once in the
* same pattern, e.g. {@code (?a)|(?b)}. Perl accepts duplicate names
- * in alternation; Java rejects them. RegexPreprocessor appends
+ * in alternation; the native compilation adapter appends
* "{@value #DUPLICATE_MARKER}" to the second and later occurrences, and
* {@link #decodeGroupName} strips the suffix back off so user code sees
* the original name.
@@ -78,7 +78,7 @@ public class CaptureNameEncoder {
/**
* Compiled pattern that matches the duplicate-name marker followed by its
- * counter at the end of a Java group name. Used by {@link #decodeGroupName}
+ * counter at the end of an encoded group name. Used by {@link #decodeGroupName}
* and {@link #stripDuplicateMarker} to recover the original Perl name.
*/
private static final java.util.regex.Pattern DUPLICATE_MARKER_PATTERN =
@@ -185,7 +185,7 @@ public static boolean isCodeBlockCapture(String captureName) {
// UNDERSCORE ENCODING:
//
- // Java regex doesn't allow underscores in group names (only [a-zA-Z][a-zA-Z0-9]*).
+ // The internal capture-name transport uses an alphanumeric alphabet.
// Perl allows \w+ (letters, digits, underscores) for group names.
//
// Encoding: Replace each underscore with "U95" (ASCII code 95 for '_')
@@ -193,16 +193,16 @@ public static boolean isCodeBlockCapture(String captureName) {
// (?<_>) → (?)
// (?<_foo>) → (?)
//
- // Names starting with underscore need a letter prefix for Java, so U95 works
+ // Names starting with underscore need a letter prefix, so U95 works
// since it starts with 'U'. To avoid ambiguity, literal "U95" sequences in
// names are escaped as "UU95" (the 'U' itself is escaped).
/**
- * Encodes a Perl capture group name for use in Java regex.
+ * Encodes a Perl capture group name for the internal matcher transport.
* Replaces underscores with "U95" and escapes literal "U95" sequences.
*
* @param perlName The original Perl capture group name
- * @return The encoded name safe for Java regex, or the original if no encoding needed
+ * @return The transport-safe name, or the original if no encoding is needed
*/
public static String encodeGroupName(String perlName) {
if (perlName == null || (!perlName.contains("_") && !perlName.contains("U95"))) {
@@ -216,20 +216,20 @@ public static String encodeGroupName(String perlName) {
}
/**
- * Decodes a Java regex capture group name back to the original Perl name.
+ * Decodes an internal capture group name back to the original Perl name.
* Reverses the encoding done by encodeGroupName, and also strips any
- * duplicate-name marker added by {@link RegexPreprocessor#handleNamedCapture}
+ * duplicate-name marker carried by the native capture-name adapter
* for patterns like {@code (?a)|(?b)}.
*
- * @param javaName The encoded Java group name
+ * @param javaName The encoded internal group name
* @return The original Perl capture group name
*/
public static String decodeGroupName(String javaName) {
if (javaName == null) {
return javaName;
}
- // First strip any duplicate-name marker (preprocessor adds this for
- // names that appear more than once in alternation branches).
+ // First strip any duplicate-name marker (the native compilation adapter
+ // adds this for names that appear more than once in alternation branches).
javaName = stripDuplicateMarker(javaName);
if (!javaName.contains("U95")) {
return javaName;
diff --git a/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java b/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java
deleted file mode 100644
index a409e810cd..0000000000
--- a/src/main/java/org/perlonjava/runtime/regex/ExtendedCharClass.java
+++ /dev/null
@@ -1,1367 +0,0 @@
-package org.perlonjava.runtime.regex;
-
-import com.ibm.icu.lang.UCharacter;
-
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * ExtendedCharClass handles Perl's Extended Bracketed Character Classes (?[...])
- *
- * Extended character classes allow set operations on character classes:
- * - Union: (?[ [a] + [b] ]) or (?[ [a] | [b] ])
- * - Intersection: (?[ [a-z] & [aeiou] ])
- * - Subtraction: (?[ [a-z] - [aeiou] ])
- * - Symmetric difference: (?[ [a-z] ^ [aeiou] ])
- * - Complement: (?[ ! [a-z] ])
- *
- * Features:
- * - Automatic /xx mode (whitespace ignored)
- * - Comments with # and (?#...)
- * - Nested expressions with parentheses
- * - Unicode properties (\p{...}, \pN)
- * - POSIX classes ([:word:], etc.)
- * - Escape sequences (\t, \cX, etc.)
- * - Regex interpolation (qr// patterns)
- *
- * The implementation tokenizes the content, parses it into an expression tree,
- * and transforms it into Java's character class syntax using intersection (&&)
- * and negation (^) operators.
- */
-public class ExtendedCharClass {
- // Thread-local flag to communicate case-insensitivity to nested processing
- private static final ThreadLocal caseInsensitive = ThreadLocal.withInitial(() -> false);
-
- /**
- * Handles Perl's Extended Bracketed Character Class (?[...])
- * Transforms it into Java-compatible character class syntax.
- *
- * @param s The regex string
- * @param offset Current position (at '(')
- * @param sb StringBuilder for output
- * @param regexFlags Current regex flags
- * @return Position after the closing ])
- */
- static int handleExtendedCharacterClass(String s, int offset, StringBuilder sb, RegexFlags regexFlags) {
- int start = offset + 3; // Skip past '(?['
-
- // First, check if this is an empty extended character class
- int i = start;
- while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
- i++;
- }
-
- if (i < s.length() && s.charAt(i) == ']' && i + 1 < s.length() && s.charAt(i + 1) == ')') {
- // This is an empty extended character class
- RegexPreprocessor.regexError(s, start, "Incomplete expression within '(?[ ])'");
- }
-
- // Now find the end
- int end;
- try {
- end = findExtendedClassEnd(s, start);
- } catch (IllegalStateException e) {
- // Convert validation errors to regex errors
- // This handles "Unexpected ]" and other structural errors
- RegexPreprocessor.regexError(s, offset, e.getMessage());
- return -1; // Never reached
- }
-
- if (end == -1) {
- // Extended class never properly closed - this is a syntax error
- RegexPreprocessor.regexError(s, offset, "Syntax error in (?[...])");
- }
-
- String content = s.substring(start, end);
-
- // System.err.println("DEBUG: ExtendedCharClass content: '" + content + "'");
-
- // Check for empty or whitespace-only content
- if (content.trim().isEmpty()) {
- RegexPreprocessor.regexError(s, start, "Incomplete expression within '(?[ ])'");
- }
-
- // try {
- // Parse and transform the extended character class
- // Set case-insensitivity flag for nested processing
- boolean isCaseInsensitive = regexFlags.isCaseInsensitive();
- caseInsensitive.set(isCaseInsensitive);
- try {
- String transformed = transformExtendedClass(content, s, start);
- // Wrap in (?-i:...) to disable Java's /i flag for the extended character class.
- // We handle case-folding manually for literals within the extended char class,
- // but /i should NOT affect Unicode properties like \p{Blk=ASCII}.
- // Wrapping ensures the character class uses only our manual case expansions.
- sb.append("(?-i:").append(transformed).append(")");
- } finally {
- caseInsensitive.set(false);
- }
-
- // Skip past the '])'
- return end + 1;
-// } catch (Exception e) {
-// RegexPreprocessor.regexError(s, start, e.getMessage());
-// return -1; // Never reached due to exception
-// }
- }
-
- /**
- * Find the end of the extended character class, handling nested brackets.
- *
- * This method tracks bracket depth to handle nested character classes like [a[b]c].
- * It also handles escape sequences, especially \c which consumes two characters.
- *
- * The extended character class ends when we find ]) at depth 0.
- *
- * @param s The regex string
- * @param start Position after the opening (?[
- * @return Position of the closing ], or -1 if not found
- */
- private static int findExtendedClassEnd(String s, int start) {
- int depth = 1; // Track bracket nesting depth
- int i = start;
- boolean inEscape = false;
-
- while (i < s.length() && depth > 0) {
- char c = s.charAt(i);
-
- if (inEscape) {
- inEscape = false;
- // Special case: \c consumes two characters (c and the control char)
- // This is critical for patterns like \c[ where [ is the control char
- // However, if we're inside nested structures (depth > 1) and \c is followed
- // by ] or ), those should close the structure, not be consumed by \c
- if (c == 'c' && i + 1 < s.length()) {
- char nextChar = s.charAt(i + 1);
- // At depth 1, \c] or \c) are valid (] closes extended class, ) is the outer paren)
- // At depth > 1, \c] or \c) leave \c incomplete - don't consume the ] or )
- if (depth > 1 && (nextChar == ']' || nextChar == ')')) {
- // Don't consume the ] or ) - let them be processed as structure
- i++; // Just skip the 'c'
- } else {
- i += 2; // Skip both 'c' and the next character
- }
- } else {
- i++;
- }
- continue;
- }
-
- // Check for nested (?[...])
- if (c == '(' && i + 2 < s.length() && s.charAt(i + 1) == '?' && s.charAt(i + 2) == '[') {
- // System.err.println("DEBUG: Found nested (?[ at position " + i);
- // We found a nested extended character class
- // We need to skip over it entirely
- int nestedEnd = findExtendedClassEnd(s, i + 3);
- if (nestedEnd == -1) {
- // The nested one is unterminated, but we should continue
- // The error will be caught when that nested one is processed
- return -1;
- }
- // Skip to after the "])'" of the nested extended class
- i = nestedEnd + 2; // +2 for "])"
- continue;
- }
-
- switch (c) {
- case '\\':
- inEscape = true;
- break;
- case '[':
- depth++;
- break;
- case ']':
- depth--;
- if (depth == 0) {
- // Check if this ends the extended character class
- int j = i + 1;
- while (j < s.length() && Character.isWhitespace(s.charAt(j))) {
- j++;
- }
- if (j < s.length() && s.charAt(j) == ')') {
- // System.err.println("DEBUG: Found end of extended class at position " + i);
- return i;
- }
- // Found ] at depth 0 but not followed by )
- // This is "Unexpected ]" not "Unterminated"
- throw new IllegalStateException("Unexpected ']' with no following ')' in (?[...");
- }
- break;
- }
- i++;
- }
-
- return -1;
- }
-
- /**
- * Transform the extended character class content into Java syntax
- */
- private static String transformExtendedClass(String content, String originalRegex, int contentStart) {
- // Before tokenizing, scan for nested regex constructs and process them
- // This allows us to catch errors in nested patterns
- scanForNestedConstructs(content, originalRegex, contentStart);
-
- // Tokenize the expression
- List tokens = tokenizeExtendedClass(content, originalRegex, contentStart);
-
- // Parse into expression tree
- ExprNode tree = parseExtendedClass(tokens, originalRegex, contentStart);
-
- // Transform to Java syntax
- return evaluateExtendedClass(tree);
- }
-
- private static void scanForNestedConstructs(String content, String originalRegex, int contentStart) {
- int i = 0;
- while (i < content.length()) {
- char c = content.charAt(i);
-
- // Look for (?...) constructs
- if (c == '(' && i + 1 < content.length() && content.charAt(i + 1) == '?') {
- // System.err.println("DEBUG: Found nested (? at position " + i + " in extended char class content");
-
- // Check what type of construct this is
- if (i + 2 < content.length()) {
- char nextChar = content.charAt(i + 2);
-
- if (nextChar == '[') {
- // Nested extended character class - process it recursively
- // System.err.println("DEBUG: Found nested (?[ at position " + i);
- // Call handleExtendedCharacterClass recursively
- StringBuilder dummySb = new StringBuilder();
- ExtendedCharClass.handleExtendedCharacterClass(originalRegex, contentStart + i, dummySb, RegexFlags.fromModifiers("", ""));
- } else if (nextChar == 'x' && i + 3 < content.length() && content.charAt(i + 3) == ':') {
- // (?x:...) construct - scan inside it
- // System.err.println("DEBUG: Found (?x: at position " + i);
- // Find the matching closing paren
- int closePos = findMatchingParen(content, i);
- if (closePos > i + 4) {
- String innerContent = content.substring(i + 4, closePos);
- scanForNestedConstructs(innerContent, originalRegex, contentStart + i + 4);
- }
- }
- }
- }
- i++;
- }
- }
-
- private static int findMatchingParen(String s, int start) {
- int depth = 1;
- int i = start + 1;
- while (i < s.length() && depth > 0) {
- char c = s.charAt(i);
- if (c == '(') depth++;
- else if (c == ')') depth--;
- i++;
- }
- return depth == 0 ? i - 1 : -1;
- }
-
- /**
- * Tokenize the extended character class content
- * Automatically handles whitespace (xx mode)
- */
- private static List tokenizeExtendedClass(String content, String originalRegex, int contentStart) {
- List tokens = new ArrayList<>();
- int i = 0;
-
- while (i < content.length()) {
- // Skip whitespace (automatic /xx mode)
- // In extended character classes, Pattern_White_Space includes NEL (U+0085)
- while (i < content.length()) {
- char ch = content.charAt(i);
- if (Character.isWhitespace(ch) || ch == '\u0085') { // NEL
- i++;
- } else {
- break;
- }
- }
-
- if (i >= content.length()) break;
-
- char c = content.charAt(i);
-
- // Handle comments (# to end of line)
- if (c == '#') {
- // Skip to end of line
- while (i < content.length() && content.charAt(i) != '\n') {
- i++;
- }
- continue;
- }
-
- int tokenStart = contentStart + i;
-
- switch (c) {
- case '&':
- case '+':
- case '|':
- case '-':
- case '^':
- tokens.add(new Token(TokenType.OPERATOR, String.valueOf(c), tokenStart));
- i++;
- break;
-
- case '!':
- tokens.add(new Token(TokenType.OPERATOR, "!", tokenStart));
- i++;
- break;
-
- case '(':
- // Check for inline comment (?#...)
- if (i + 2 < content.length() && content.charAt(i + 1) == '?' && content.charAt(i + 2) == '#') {
- // Skip to closing )
- int closePos = content.indexOf(')', i + 3);
- if (closePos != -1) {
- i = closePos + 1;
- } else {
- // Unterminated comment, skip to end
- i = content.length();
- }
- } else if (i + 6 < content.length() && content.charAt(i + 1) == '?') {
- // Check for interpolated regex pattern: (?FLAGS:(?[ ... ]))
- // When a qr// pattern containing (?[...]) is interpolated, it becomes:
- // (?^:(?[ ... ])) or (?^i:(?[ ... ])) etc.
- // We need to extract just the inner (?[ ... ]) part AND preserve any /i flag
-
- // Find the : after the flags (e.g., (?^: or (?^i:)
- int colonPos = i + 2;
- while (colonPos < content.length() && content.charAt(colonPos) != ':' && content.charAt(colonPos) != ')') {
- colonPos++;
- }
-
- // Extract and parse flags from this construct
- String outerFlags = content.substring(i + 2, colonPos);
- boolean hasInnerCaseInsensitive = outerFlags.contains("i");
-
- // Check if this is followed by (?[ or another (?FLAGS:
- if (colonPos < content.length() && content.charAt(colonPos) == ':' &&
- colonPos + 1 < content.length() && content.charAt(colonPos + 1) == '(') {
-
- int searchPos = colonPos + 1;
- // Skip nested (?FLAGS: groups to find the actual (?[, collecting all flags
- while (searchPos < content.length() && content.charAt(searchPos) == '(' &&
- searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?') {
- // Find the : after these flags
- int nextColon = searchPos + 2;
- while (nextColon < content.length() && content.charAt(nextColon) != ':' &&
- content.charAt(nextColon) != ')') {
- nextColon++;
- }
- // Collect flags from this nested construct too
- String nestedFlags = content.substring(searchPos + 2, nextColon);
- if (nestedFlags.contains("i")) {
- hasInnerCaseInsensitive = true;
- }
- if (nextColon < content.length() && content.charAt(nextColon) == ':') {
- if (nextColon + 3 < content.length() && content.charAt(nextColon + 1) == '(' &&
- content.charAt(nextColon + 2) == '?' && content.charAt(nextColon + 3) == '[') {
- // Found (?[!
- searchPos = nextColon + 1;
- break;
- }
- // Another (?FLAGS: group, continue searching
- searchPos = nextColon + 1;
- } else {
- break;
- }
- }
-
- // Check if we found (?[
- if (searchPos < content.length() && content.charAt(searchPos) == '(' &&
- searchPos + 2 < content.length() && content.charAt(searchPos + 1) == '?' &&
- content.charAt(searchPos + 2) == '[') {
- // Found (?FLAGS:...(?[ ... ])) - this is an interpolated extended char class
- int innerStart = searchPos; // Position of inner '(?['
- int innerEnd = findMatchingParen(content, innerStart);
- if (innerEnd != -1) {
- // Extract just the content between (?[ and ])
- String innerContent = content.substring(innerStart + 3, innerEnd - 1);
-
- // If the interpolated pattern has /i flag, process with case-insensitivity
- String processedContent;
- if (hasInnerCaseInsensitive) {
- // Temporarily enable case-insensitivity for this nested content
- boolean savedCaseSensitive = caseInsensitive.get();
- caseInsensitive.set(true);
- try {
- processedContent = transformExtendedClass(innerContent, content, innerStart + 3);
- } finally {
- caseInsensitive.set(savedCaseSensitive);
- }
- } else {
- processedContent = "(?[" + innerContent + "])";
- }
-
- // Add as a character class token (already processed if had /i)
- tokens.add(new Token(TokenType.CHAR_CLASS, processedContent, tokenStart));
- // Skip past the entire (?FLAGS:...(?[ ... ])) construct
- // We need to skip all the closing ) for the nested flag groups
- i = innerEnd + 1; // After the closing ) of (?[...)
- // Skip closing ) for each nested flag group
- while (i < content.length() && content.charAt(i) == ')') {
- i++;
- }
- } else {
- // Malformed, treat as regular paren
- tokens.add(new Token(TokenType.LPAREN, "(", tokenStart));
- i++;
- }
- } else {
- tokens.add(new Token(TokenType.LPAREN, "(", tokenStart));
- i++;
- }
- } else {
- tokens.add(new Token(TokenType.LPAREN, "(", tokenStart));
- i++;
- }
- } else {
- tokens.add(new Token(TokenType.LPAREN, "(", tokenStart));
- i++;
- }
- break;
-
- case ')':
- tokens.add(new Token(TokenType.RPAREN, ")", tokenStart));
- i++;
- break;
-
- case '[':
- // Parse a character class element
- int classEnd = findCharClassEnd(content, i);
- if (classEnd == -1) {
- RegexPreprocessor.regexError(originalRegex, tokenStart, "Unterminated character class");
- }
- String classContent = content.substring(i, classEnd + 1);
-
- // Validate character ranges in the class
- validateCharacterRanges(classContent, originalRegex, tokenStart);
-
- tokens.add(new Token(TokenType.CHAR_CLASS, classContent, tokenStart));
- i = classEnd + 1;
- break;
-
- case ':':
- // Check for POSIX class
- if (i + 1 < content.length() && content.charAt(i + 1) == ':') {
- int posixEnd = content.indexOf("::", i + 2);
- if (posixEnd != -1) {
- String posixClass = content.substring(i, posixEnd + 2);
- tokens.add(new Token(TokenType.CHAR_CLASS, posixClass, tokenStart));
- i = posixEnd + 2;
- break;
- }
- }
- // Fall through to error
-
- case '\\':
- // Parse escape sequence as a character class element
- int escapeEnd = parseEscapeInExtendedClass(content, i);
- String escapeSeq = content.substring(i, escapeEnd);
- // Wrap escape sequences in brackets to make them character class elements
- tokens.add(new Token(TokenType.CHAR_CLASS, "[" + escapeSeq + "]", tokenStart));
- i = escapeEnd;
- break;
-
- default:
- // Single character must be wrapped in brackets
- RegexPreprocessor.regexError(originalRegex, tokenStart,
- "Bare character '" + c + "' not allowed in (?[...]). Use [" + c + "] instead");
- }
- }
-
- tokens.add(new Token(TokenType.EOF, "", contentStart + content.length()));
-
- // Check if we have any meaningful tokens
- if (tokens.size() == 1 && tokens.get(0).type == TokenType.EOF) {
- RegexPreprocessor.regexError(originalRegex, contentStart, "Incomplete expression within '(?[ ])'");
- }
-
- return tokens;
- }
-
- private static void validateCharacterRanges(String charClass, String originalRegex, int classStart) {
- if (!charClass.startsWith("[") || !charClass.endsWith("]")) {
- return;
- }
-
- String content = charClass.substring(1, charClass.length() - 1);
- int i = 0;
- int lastChar = -1;
- boolean inEscape = false;
-
- while (i < content.length()) {
- char c = content.charAt(i);
-
- // Skip whitespace in extended character class
- if (Character.isWhitespace(c)) {
- i++;
- continue;
- }
-
- if (inEscape) {
- inEscape = false;
- lastChar = -1;
- i++;
- continue;
- }
-
- if (c == '\\') {
- inEscape = true;
- } else if (c == '-' && lastChar != -1 && i + 1 < content.length()) {
- char nextChar = content.charAt(i + 1);
- if (!Character.isWhitespace(nextChar) && nextChar != ']' && nextChar != '\\') {
- if (nextChar < lastChar) {
- RegexPreprocessor.regexError(originalRegex, classStart + i + 3,
- String.format("Invalid [] range \"%c-%c\" in regex", lastChar, nextChar));
- }
- // Valid range: skip over the end character and reset lastChar
- // so the next '-' will be treated as literal or start a new range
- i++; // Skip the range end character
- lastChar = -1;
- }
- } else if (c != '-' && c != '^' && c != '[' && c != ':') {
- lastChar = c;
- }
-
- i++;
- }
- }
-
- /**
- * Find the end of a character class [...]
- */
- private static int findCharClassEnd(String content, int start) {
- int i = start + 1; // Skip opening [
- boolean inEscape = false;
- boolean first = true;
- boolean afterCaret = false;
-
- while (i < content.length()) {
- char c = content.charAt(i);
-
- if (inEscape) {
- inEscape = false;
- first = false;
- afterCaret = false;
- i++;
- continue;
- }
-
- // In extended character classes, skip whitespace
- if (Character.isWhitespace(c)) {
- i++;
- continue;
- }
-
- if (c == '\\') {
- inEscape = true;
- first = false;
- afterCaret = false;
- } else if (c == ']') {
- // Special case: ] immediately after [ or [^ is a literal ]
- if (first || afterCaret) {
- // This is a literal ], not the closing bracket
- first = false;
- afterCaret = false;
- } else {
- // This closes the character class
- return i;
- }
- } else if (c == '^' && first) {
- afterCaret = true;
- first = false;
- } else {
- // Any non-whitespace character resets the flags
- first = false;
- afterCaret = false;
- }
-
- i++;
- }
-
- return -1;
- }
-
- /**
- * Parse an escape sequence in extended class
- */
- private static int parseEscapeInExtendedClass(String content, int start) {
- if (start + 1 >= content.length()) {
- return start + 1;
- }
-
- char next = content.charAt(start + 1);
-
- // Handle special escape sequences
- if (next == 'p' || next == 'P') {
- // Unicode property: \p{Name} or \pL (single letter)
- if (start + 2 < content.length() && content.charAt(start + 2) == '{') {
- int end = content.indexOf('}', start + 3);
- return end != -1 ? end + 1 : start + 2;
- }
- // Single-letter property like \pN, \pL
- return start + 3;
- } else if (next == 'N') {
- // Named character
- if (start + 2 < content.length() && content.charAt(start + 2) == '{') {
- int end = content.indexOf('}', start + 3);
- return end != -1 ? end + 1 : start + 2;
- }
- } else if (next == 'x') {
- // Hex escape
- if (start + 2 < content.length() && content.charAt(start + 2) == '{') {
- int end = content.indexOf('}', start + 3);
- return end != -1 ? end + 1 : start + 4; // \xHH
- }
- return start + 4; // \xHH
- } else if (next == 'o') {
- // Octal escape
- if (start + 2 < content.length() && content.charAt(start + 2) == '{') {
- int end = content.indexOf('}', start + 3);
- return end != -1 ? end + 1 : start + 2;
- }
- } else if (next == 'c') {
- // Control character: \cX consumes the backslash, 'c', and the next character
- // Check if we have a character after \c
- if (start + 2 < content.length()) {
- return start + 3;
- }
- // Incomplete \c at end of string
- return start + 2;
- }
-
- // Default: single character escape
- return start + 2;
- }
-
- /**
- * Parse tokens into expression tree
- * Precedence: ! > & > (+|-|^|)
- * Binary ops are left-associative, unary is right-associative
- */
- private static ExprNode parseExtendedClass(List tokens, String originalRegex, int contentStart) {
- return new ExtendedClassParser(tokens, originalRegex, contentStart).parse();
- }
-
- /**
- * Process a character class element from extended syntax.
- *
- * This handles:
- * - Nested extended character classes (?[...]) from interpolation
- * - POSIX classes ([:word:], [:digit:], etc.)
- * - Regular character classes ([a-z], [abc], etc.)
- * - Escape sequences (\d, \w, \s, etc.)
- *
- * @param charClass The character class string to process
- * @return Java-compatible character class syntax
- */
- private static String processCharacterClass(String charClass) {
- // Handle nested extended character class (?[...])
- // This occurs when a qr/(?[...])/ pattern is interpolated
- if (charClass.startsWith("(?[") && charClass.endsWith("])")) {
- // Recursively process the nested extended character class
- String nestedContent = charClass.substring(3, charClass.length() - 2);
- return transformExtendedClass(nestedContent, charClass, 0);
- }
-
- // Handle POSIX classes
- if (charClass.startsWith("::") && charClass.endsWith("::")) {
- // Transform ::word:: to [:word:]
- String posixClass = "[" + charClass.substring(1, charClass.length() - 1) + "]";
- String mapped = CharacterClassMapper.getMappedClass(posixClass);
- if (mapped != null) {
- return mapped;
- }
- } else if (charClass.startsWith("[:") && charClass.endsWith(":]") && charClass.length() > 4) {
- // Valid POSIX class must have at least one character between [: and :]
- // e.g., [:word:] is valid, but [:] is not
- String mapped = CharacterClassMapper.getMappedClass(charClass);
- if (mapped != null) {
- return mapped;
- }
- }
-
- // Handle bracketed content
- if (charClass.startsWith("[") && charClass.endsWith("]")) {
- String content = charClass.substring(1, charClass.length() - 1);
- StringBuilder result = new StringBuilder();
-
- // Process the content - in extended character classes, spaces are ignored (xx mode)
- int i = 0;
- int lastChar = -1; // Track last character for range validation
- boolean atStart = true; // Track if we're at the start (for ^ negation detection)
-
- while (i < content.length()) {
- char c = content.charAt(i);
-
- // Skip whitespace in extended character class (automatic /xx mode)
- if (Character.isWhitespace(c)) {
- i++;
- continue;
- }
-
- // Check for invalid range
- if (c == '-' && lastChar != -1 && i + 1 < content.length()) {
- char nextChar = content.charAt(i + 1);
- if (!Character.isWhitespace(nextChar) && nextChar != ']' && nextChar != '\\' && nextChar < lastChar) {
- throw new IllegalArgumentException(
- String.format("Invalid [] range \"%c-%c\"", lastChar, nextChar)
- );
- }
- }
-
- if (c == '\\') {
- // Handle escape sequences
- if (i + 1 < content.length()) {
- char next = content.charAt(i + 1);
-
- // Skip whitespace after backslash too
- if (Character.isWhitespace(next)) {
- // Literal whitespace needs to be escaped, but in extended mode it's ignored
- i += 2;
- continue;
- }
-
- if (next == 'c' && i + 2 < content.length()) {
- // Convert Perl's control-character escape before
- // Joni can mistake targets such as \\, [, or ] for
- // character-class syntax.
- int ctrl = content.codePointAt(i + 2);
- if (ctrl >= 'a' && ctrl <= 'z') {
- ctrl = ctrl - 'a' + 'A';
- }
- int value = (ctrl ^ 0x40) & 0xFF;
- result.append(String.format("\\x{%X}", value));
- i += 3;
- lastChar = value;
- continue;
- } else if (next == 'p' || next == 'P') {
- // Unicode property
- int start = i;
- if (i + 2 < content.length() && content.charAt(i + 2) == '{') {
- int end = content.indexOf('}', i + 3);
- if (end != -1) {
- String property = content.substring(i + 3, end);
- boolean negated = (next == 'P');
- String translated = UnicodeResolver.translateUnicodeProperty(
- property, negated);
-
- // Unwrap if necessary
- if (translated.startsWith("[") && translated.endsWith("]")) {
- result.append(translated, 1, translated.length() - 1);
- } else {
- result.append(translated);
- }
- i = end + 1;
- continue;
- }
- }
- } else if (next == 'N' && i + 2 < content.length() && content.charAt(i + 2) == '{') {
- // Named character
- int end = content.indexOf('}', i + 3);
- if (end != -1) {
- String name = content.substring(i + 3, end).trim();
- try {
- int codePoint = UnicodeResolver.getCodePointFromName(name);
- // Check if case-insensitive and needs case expansion
- if (caseInsensitive.get()) {
- // First check for special folds
- String expansion = expandCaseFoldInCharClass(codePoint);
- if (expansion != null) {
- result.append(expansion);
- i = end + 1;
- continue;
- }
- // Add upper/lower case variants
- int lower = UCharacter.toLowerCase(codePoint);
- int upper = UCharacter.toUpperCase(codePoint);
- if (lower != upper) {
- appendCharClassChar(result, codePoint);
- if (lower != codePoint) {
- appendCharClassChar(result, lower);
- }
- if (upper != codePoint) {
- appendCharClassChar(result, upper);
- }
- i = end + 1;
- continue;
- }
- }
- result.append(String.format("\\x{%X}", codePoint));
- i = end + 1;
- continue;
- } catch (IllegalArgumentException e) {
- // Let it fall through to be handled as regular escape
- }
- }
- } else if (next == 'x' && i + 2 < content.length() && content.charAt(i + 2) == '{') {
- // Hex escape \x{...}
- int end = content.indexOf('}', i + 3);
- if (end != -1) {
- String hex = content.substring(i + 3, end).trim();
- try {
- int codePoint = Integer.parseInt(hex, 16);
- // Check if case-insensitive and needs case expansion
- if (caseInsensitive.get()) {
- // First check for special folds
- String expansion = expandCaseFoldInCharClass(codePoint);
- if (expansion != null) {
- result.append(expansion);
- i = end + 1;
- continue;
- }
- // Add upper/lower case variants
- int lower = UCharacter.toLowerCase(codePoint);
- int upper = UCharacter.toUpperCase(codePoint);
- if (lower != upper) {
- appendCharClassChar(result, codePoint);
- if (lower != codePoint) {
- appendCharClassChar(result, lower);
- }
- if (upper != codePoint) {
- appendCharClassChar(result, upper);
- }
- i = end + 1;
- continue;
- }
- }
- result.append(String.format("\\x{%X}", codePoint));
- i = end + 1;
- continue;
- } catch (NumberFormatException e) {
- // Let it fall through to be handled as regular escape
- }
- }
- } else if ((next == 'b' || next == 'B') && i + 2 < content.length() && content.charAt(i + 2) == '{') {
- // Boundary assertions not allowed in character class
- RegexPreprocessor.regexError("", i, "Boundary assertion \\" + next + "{...} not allowed in character class");
- }
- }
-
- // Regular escape sequence
- result.append(c);
- if (i + 1 < content.length()) {
- result.append(content.charAt(i + 1));
- i += 2;
- } else {
- i++;
- }
- lastChar = -1; // Reset after escape
- } else if (c == '[' && i + 1 < content.length() && content.charAt(i + 1) == ':') {
- // POSIX class
- int end = content.indexOf(":]", i + 2);
- if (end != -1) {
- String posixClass = content.substring(i, end + 2);
- String mapped = CharacterClassMapper.getMappedClass(posixClass);
- if (mapped != null) {
- // Unwrap the mapped class
- if (mapped.startsWith("\\p{") && mapped.endsWith("}")) {
- result.append(mapped);
- } else if (mapped.startsWith("\\P{") && mapped.endsWith("}")) {
- result.append(mapped);
- } else {
- result.append(mapped);
- }
- i = end + 2;
- continue;
- }
- }
- result.append(c);
- i++;
- lastChar = -1; // Reset after POSIX class
- } else {
- // Regular character - handle case variants if case-insensitive
- int codePoint = content.codePointAt(i);
- int charLen = Character.charCount(codePoint);
-
- // Handle ^ at start as negation metacharacter (not a range start)
- if (codePoint == '^' && atStart) {
- result.append('^');
- i += charLen;
- atStart = false;
- // Note: don't set lastChar - ^ is a metacharacter, not a character in the class
- // Also, afterCaret means the next char is right after ^, so - should be literal
- continue;
- }
-
- // Check if - is at the start position (literal hyphen)
- // In [^-b], - is a literal because it's right after ^ (or at position 0)
- // atStart is true if we haven't seen any non-whitespace non-^ character yet
- boolean hyphenIsLiteral = (codePoint == '-' && (atStart || lastChar == -1));
-
- // After any non-whitespace, we're no longer at start
- atStart = false;
-
- // Check if this is the start of a range (followed by - and another char)
- // Skip whitespace when looking for the dash (extended char class syntax)
- boolean isRangeStart = false;
- int rangeEndCodePoint = -1;
- int rangeEndPos = -1;
- int dashPos = i + charLen;
- // Skip whitespace before dash
- while (dashPos < content.length() && Character.isWhitespace(content.charAt(dashPos))) {
- dashPos++;
- }
- if (dashPos < content.length() && content.charAt(dashPos) == '-') {
- int afterDashPos = dashPos + 1;
- // Skip whitespace after dash
- while (afterDashPos < content.length() && Character.isWhitespace(content.charAt(afterDashPos))) {
- afterDashPos++;
- }
- if (afterDashPos < content.length()) {
- char afterDash = content.charAt(afterDashPos);
- if (afterDash != ']' && afterDash != '-') {
- isRangeStart = true;
- rangeEndCodePoint = content.codePointAt(afterDashPos);
- rangeEndPos = afterDashPos;
- }
- }
- }
-
- if (caseInsensitive.get() && !isRangeStart) {
- // Not a range start - expand individual character
- // First check for special folds (KELVIN SIGN, etc.)
- String expansion = expandCaseFoldInCharClass(codePoint);
- if (expansion != null) {
- result.append(expansion);
- i += charLen;
- lastChar = -1; // Reset after expansion
- continue;
- }
- // For regular characters, add upper and lower case variants
- int lower = UCharacter.toLowerCase(codePoint);
- int upper = UCharacter.toUpperCase(codePoint);
- if (lower != upper) {
- // Has case variants - add both
- appendCharClassChar(result, codePoint);
- if (lower != codePoint) {
- appendCharClassChar(result, lower);
- }
- if (upper != codePoint) {
- appendCharClassChar(result, upper);
- }
- i += charLen;
- lastChar = -1; // Reset after expansion
- continue;
- }
- } else if (caseInsensitive.get() && isRangeStart) {
- // Handle range with case-insensitivity
- // Keep original range, then add case-folded range
- int rangeEndCharLen = Character.charCount(rangeEndCodePoint);
-
- // Append original range: start-end
- appendCharClassChar(result, codePoint);
- result.append('-');
- appendCharClassChar(result, rangeEndCodePoint);
-
- // Add case-folded range if different
- int startLower = UCharacter.toLowerCase(codePoint);
- int startUpper = UCharacter.toUpperCase(codePoint);
- int endLower = UCharacter.toLowerCase(rangeEndCodePoint);
- int endUpper = UCharacter.toUpperCase(rangeEndCodePoint);
-
- // Add uppercase range if the original was lowercase
- if (startLower == codePoint && startUpper != codePoint) {
- appendCharClassChar(result, startUpper);
- result.append('-');
- appendCharClassChar(result, endUpper);
- }
- // Add lowercase range if the original was uppercase
- else if (startUpper == codePoint && startLower != codePoint) {
- appendCharClassChar(result, startLower);
- result.append('-');
- appendCharClassChar(result, endLower);
- }
-
- // Add special Unicode case folds for characters in the range
- // Check if any character in the range (or its case-folded equivalent)
- // has a special reverse fold mapping
- for (Map.Entry entry : SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.entrySet()) {
- int baseChar = entry.getKey();
- int specialChar = entry.getValue();
- // Check if baseChar (or its upper/lower) falls within the range
- int baseLower = UCharacter.toLowerCase(baseChar);
- int baseUpper = UCharacter.toUpperCase(baseChar);
- int rangeLower = Math.min(codePoint, rangeEndCodePoint);
- int rangeUpper = Math.max(codePoint, rangeEndCodePoint);
- int rangeLowerFolded = Math.min(startLower, endLower);
- int rangeUpperFolded = Math.max(startUpper, endUpper);
-
- if ((baseChar >= rangeLower && baseChar <= rangeUpper) ||
- (baseLower >= rangeLowerFolded && baseLower <= rangeUpperFolded) ||
- (baseUpper >= rangeLower && baseUpper <= rangeUpper)) {
- // Add the special character
- appendCharClassChar(result, specialChar);
- }
- }
-
- // Skip past the entire range (including any whitespace)
- i = rangeEndPos + rangeEndCharLen;
- lastChar = -1;
- continue;
- }
-
- result.append(c);
- if (c != '-' && c != '^') {
- lastChar = c; // Remember this character
- }
- i++;
- }
- }
-
- return "[" + result + "]";
- }
-
- // Should not reach here
- return charClass;
- }
-
- /**
- * Unwrap outer brackets from a character class if present
- */
- private static String unwrapBrackets(String charClass) {
- if (charClass.startsWith("[") && charClass.endsWith("]")) {
- // But don't unwrap if it's a complex expression with operators
- String inner = charClass.substring(1, charClass.length() - 1);
- if (!inner.contains("&&")) {
- return inner;
- }
- }
- return charClass;
- }
-
- /**
- * Evaluate the expression tree to produce Java regex syntax
- */
- private static String evaluateExtendedClass(ExprNode tree) {
- return tree.evaluate();
- }
-
- // Token types for the parser
- private enum TokenType {
- CHAR_CLASS, OPERATOR, LPAREN, RPAREN, EOF
- }
-
- private static class Token {
- TokenType type;
- String value;
- int position;
-
- Token(TokenType type, String value, int position) {
- this.type = type;
- this.value = value;
- this.position = position;
- }
- }
-
- // Expression tree nodes
- private static abstract class ExprNode {
- abstract String evaluate();
- }
-
- private static class CharClassNode extends ExprNode {
- String content;
-
- CharClassNode(String content) {
- this.content = content;
- }
-
- @Override
- String evaluate() {
- // Process the character class content
- return processCharacterClass(content);
- }
- }
-
- /**
- * Binary operation node (union, intersection, subtraction, symmetric difference)
- *
- * Operators:
- * - + or | : Union (A or B)
- * - & : Intersection (A and B)
- * - - : Subtraction (A but not B)
- * - ^ : Symmetric difference (A or B but not both)
- */
- private static class BinaryOpNode extends ExprNode {
- String operator;
- ExprNode left;
- ExprNode right;
-
- BinaryOpNode(String operator, ExprNode left, ExprNode right) {
- this.operator = operator;
- this.left = left;
- this.right = right;
- }
-
- @Override
- String evaluate() {
- String leftEval = left.evaluate();
- String rightEval = right.evaluate();
-
- // Unwrap outer brackets if present
- leftEval = unwrapBrackets(leftEval);
- rightEval = unwrapBrackets(rightEval);
-
- switch (operator) {
- case "+":
- case "|":
- // Union: [A[B]]
- return "[" + leftEval + rightEval + "]";
-
- case "&":
- // Intersection: [A&&[B]]
- return "[" + leftEval + "&&[" + rightEval + "]]";
-
- case "-":
- // Subtraction: [A&&[^B]]
- return "[" + leftEval + "&&[^" + rightEval + "]]";
-
- case "^":
- // Symmetric difference: [[A&&[^B]][B&&[^A]]]
- return "[[" + leftEval + "&&[^" + rightEval + "]][" +
- rightEval + "&&[^" + leftEval + "]]]";
-
- default:
- throw new IllegalStateException("Unknown operator: " + operator);
- }
- }
- }
-
- /**
- * Unary operation node (complement)
- *
- * The ! operator complements a character class.
- * Important: Double negation is handled specially:
- * - ! [^A] becomes [A] (NOT NOT A = A)
- * - This prevents invalid Java regex like [^^A]
- */
- private static class UnaryOpNode extends ExprNode {
- String operator;
- ExprNode operand;
-
- UnaryOpNode(String operator, ExprNode operand) {
- this.operator = operator;
- this.operand = operand;
- }
-
- @Override
- String evaluate() {
- String operandEval = operand.evaluate();
-
- if (operator.equals("!")) {
- // Complement: [^A]
- operandEval = unwrapBrackets(operandEval);
-
- // Check if already negated (starts with ^)
- // This handles double negation: ! [^A] = [A]
- if (operandEval.startsWith("^")) {
- // Double negation: remove the ^
- return "[" + operandEval.substring(1) + "]";
- } else {
- return "[^" + operandEval + "]";
- }
- }
-
- throw new IllegalStateException("Unknown unary operator: " + operator);
- }
- }
-
- private static class ExtendedClassParser {
- private final List tokens;
- private final String originalRegex;
- private final int contentStart;
- private int current = 0;
-
- ExtendedClassParser(List tokens, String originalRegex, int contentStart) {
- this.tokens = tokens;
- this.originalRegex = originalRegex;
- this.contentStart = contentStart;
- }
-
- ExprNode parse() {
- ExprNode expr = parseExpression();
- if (!isAtEnd()) {
- error("Unexpected token: " + peek().value);
- }
- return expr;
- }
-
- private ExprNode parseExpression() {
- return parseUnion();
- }
-
- private ExprNode parseUnion() {
- ExprNode expr = parseIntersection();
-
- while (match("+", "|", "-", "^")) {
- String op = previous().value;
- ExprNode right = parseIntersection();
- expr = new BinaryOpNode(op, expr, right);
- }
-
- return expr;
- }
-
- private ExprNode parseIntersection() {
- ExprNode expr = parseUnary();
-
- while (match("&")) {
- String op = previous().value;
- ExprNode right = parseUnary();
- expr = new BinaryOpNode(op, expr, right);
- }
-
- return expr;
- }
-
- private ExprNode parseUnary() {
- if (match("!")) {
- ExprNode operand = parseUnary();
- return new UnaryOpNode("!", operand);
- }
-
- return parsePrimary();
- }
-
- private ExprNode parsePrimary() {
- if (match("(")) {
- ExprNode expr = parseExpression();
- consume(")", "Expected ')' after expression");
- return expr;
- }
-
- if (check(TokenType.CHAR_CLASS)) {
- return new CharClassNode(advance().value);
- }
-
- error("Expected character class or '('");
- return null; // Never reached
- }
-
- private boolean match(String... values) {
- for (String value : values) {
- if (check(value)) {
- advance();
- return true;
- }
- }
- return false;
- }
-
- private boolean check(String value) {
- return !isAtEnd() && peek().value.equals(value);
- }
-
- private boolean check(TokenType type) {
- return !isAtEnd() && peek().type == type;
- }
-
- private Token advance() {
- if (!isAtEnd()) current++;
- return previous();
- }
-
- private boolean isAtEnd() {
- return peek().type == TokenType.EOF;
- }
-
- private Token peek() {
- return tokens.get(current);
- }
-
- private Token previous() {
- return tokens.get(current - 1);
- }
-
- private void consume(String value, String message) {
- if (check(value)) {
- advance();
- return;
- }
- error(message);
- }
-
- private void error(String message) {
- Token token = peek();
- RegexPreprocessor.regexError(originalRegex, token.position, message);
- }
- }
-
- // Special characters with unusual case folding (same as in RegexPreprocessor)
- private static final Map SPECIAL_SINGLE_CHAR_FOLDS = Map.of(
- 0x00B5, 0x03BC, // MICRO SIGN -> GREEK SMALL LETTER MU
- 0x212A, 0x006B, // KELVIN SIGN -> k
- 0x212B, 0x00E5, // ANGSTROM SIGN -> å
- 0x017F, 0x0073 // LATIN SMALL LETTER LONG S -> s
- );
- private static final Map SPECIAL_SINGLE_CHAR_REVERSE_FOLDS = Map.of(
- 0x03BC, 0x00B5, // GREEK SMALL LETTER MU -> MICRO SIGN
- 0x006B, 0x212A, // k -> KELVIN SIGN
- 0x00E5, 0x212B, // å -> ANGSTROM SIGN
- 0x0073, 0x017F // s -> LATIN SMALL LETTER LONG S
- );
-
- /**
- * Expand a code point to include its case variants for case-insensitive matching.
- * Only expands special characters that have unusual case folding.
- * Returns null if no expansion is needed.
- */
- private static String expandCaseFoldInCharClass(int codePoint) {
- int folded = UCharacter.foldCase(codePoint, true);
-
- // Only expand if this is a known special fold character
- if (!SPECIAL_SINGLE_CHAR_FOLDS.containsKey(codePoint)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(folded)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(codePoint)) {
- return null;
- }
-
- LinkedHashSet variants = new LinkedHashSet<>();
- variants.add(codePoint);
- variants.add(folded);
-
- Integer reverse = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(folded);
- if (reverse != null) {
- variants.add(reverse);
- }
- Integer reverse2 = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(codePoint);
- if (reverse2 != null) {
- variants.add(reverse2);
- }
-
- // Include upper/lower variants of all participating code points
- LinkedHashSet expanded = new LinkedHashSet<>();
- for (Integer cp : variants) {
- expanded.add(cp);
- expanded.add(UCharacter.toLowerCase(cp));
- expanded.add(UCharacter.toUpperCase(cp));
- expanded.add(UCharacter.foldCase(cp, true));
- }
-
- StringBuilder sb = new StringBuilder();
- for (Integer cp : expanded) {
- // Escape special characters in char class
- if (cp == '\\' || cp == ']' || cp == '-' || cp == '^') {
- sb.append('\\');
- }
- sb.appendCodePoint(cp);
- }
- return sb.toString();
- }
-
- /**
- * Append a code point to a character class StringBuilder, escaping if necessary.
- */
- private static void appendCharClassChar(StringBuilder sb, int codePoint) {
- // Escape special characters in character class
- if (codePoint == '\\' || codePoint == ']' || codePoint == '-' || codePoint == '^' || codePoint == '[') {
- sb.append('\\');
- }
- sb.appendCodePoint(codePoint);
- }
-}
diff --git a/src/main/java/org/perlonjava/runtime/regex/JavaRegexMatcher.java b/src/main/java/org/perlonjava/runtime/regex/JavaRegexMatcher.java
deleted file mode 100644
index 915825eb36..0000000000
--- a/src/main/java/org/perlonjava/runtime/regex/JavaRegexMatcher.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.perlonjava.runtime.regex;
-
-import java.util.Map;
-import java.util.regex.Matcher;
-
-final class JavaRegexMatcher implements RegexMatcher {
- private final Matcher matcher;
- JavaRegexMatcher(Matcher matcher) {
- this.matcher = matcher;
- }
-
- Matcher unwrap() {
- return matcher;
- }
-
- @Override public boolean find() { return matcher.find(); }
- @Override public void region(int start, int end) { matcher.region(start, end); }
- @Override public void useAnchoringBounds(boolean enabled) { matcher.useAnchoringBounds(enabled); }
- @Override public void useTransparentBounds(boolean enabled) { matcher.useTransparentBounds(enabled); }
- @Override public int start() { return matcher.start(); }
- @Override public int end() { return matcher.end(); }
- @Override public int start(int index) { return matcher.start(index); }
- @Override public int end(int index) { return matcher.end(index); }
- @Override public int start(String name) { return matcher.start(name); }
- @Override public int end(String name) { return matcher.end(name); }
- @Override public String group(int index) {
- return matcher.group(index);
- }
- @Override public String group(String name) { return matcher.group(name); }
- @Override public int groupCount() { return matcher.groupCount(); }
- @Override public Map namedGroups() { return matcher.pattern().namedGroups(); }
- @Override public String patternDescription() { return matcher.pattern().pattern(); }
-}
diff --git a/src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java b/src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java
index 6c27d524dc..af5833c562 100644
--- a/src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java
+++ b/src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java
@@ -24,10 +24,17 @@
import static org.joni.constants.SyntaxProperties.OP2_OPTION_PERL;
import static org.joni.constants.SyntaxProperties.OP2_OPTION_RUBY;
import static org.joni.constants.SyntaxProperties.OP2_PLUS_POSSESSIVE_INTERVAL;
+import static org.joni.constants.SyntaxProperties.OP3_PERL_LITERAL_OPEN_IN_CC;
+import static org.joni.constants.SyntaxProperties.OP_ESC_C_CONTROL;
+import static org.joni.constants.SyntaxProperties.OP_POSIX_BRACKET;
+
+import org.joni.constants.internal.AnchorType;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
@@ -36,15 +43,17 @@
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.operators.PerlUtfString;
import org.perlonjava.runtime.NamedCharacterExpansion;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import org.perlonjava.runtime.runtimetypes.*;
-/**
- * Stack-based regex backend for Perl constructs that require matcher semantics
- * beyond the Java Pattern fast path.
- */
+/** Sole production adapter from Perl regex operations to the vendored Joni fork. */
final class JoniRegexPattern {
+ private static final String NAMED_SEQUENCE_CLASS_WARNING =
+ "Using just the first character returned by \\N{} in character class";
private static final Map INPUT_ENCODINGS = new WeakHashMap<>();
private static final Map BYTE_INPUT_ENCODINGS = new WeakHashMap<>();
+ private static final Map SUBJECT_INPUT_ENCODINGS =
+ Collections.synchronizedMap(new WeakHashMap<>());
private static final WideScalarCodec PERL_SCALAR_CODEC = new WideScalarCodec() {
@Override
public byte[] encode(long value, Encoding encoding) {
@@ -90,56 +99,65 @@ public Decoded decode(byte[] bytes, int p, int end, Encoding encoding) {
// explicitly in toJoniOptions(). Keep the richer Ruby parser surface used
// by callouts and control verbs while changing only that default policy.
private static final Syntax PERLONJAVA_SYNTAX = new Syntax(
- "PERLONJAVA", Syntax.RUBY.op,
+ "PERLONJAVA", Syntax.RUBY.op | OP_POSIX_BRACKET | OP_ESC_C_CONTROL,
(Syntax.RUBY.op2 & ~OP2_OPTION_RUBY) | OP2_OPTION_PERL
| OP2_PLUS_POSSESSIVE_INTERVAL | OP2_ESC_H_HORIZONTAL_WHITESPACE,
- Syntax.RUBY.op3,
+ Syntax.RUBY.op3 | OP3_PERL_LITERAL_OPEN_IN_CC,
Syntax.RUBY.behavior | ALLOW_MULTIPLEX_DEFINITION_NAME_CALL,
Syntax.RUBY.options & ~(Option.ASCII_RANGE
| Option.POSIX_BRACKET_ALL_RANGE | Option.WORD_BOUND_ALL_RANGE),
Syntax.RUBY.metaCharTable,
- JoniRegexPattern::resolveNamedCharacter,
- (bytes, p, end, encoding, inCharacterClass) ->
- resolveCharacterProperty(
- bytes, p, end, encoding, inCharacterClass, false),
+ null,
+ propertyResolver(new DeferredPropertyState()),
PERL_SCALAR_CODEC);
- private static int resolveNamedCharacter(byte[] bytes, int p, int end,
- Encoding encoding) {
- return UnicodeResolver.getCodePointFromName(new String(bytes, p, end - p,
- encoding == ISO8859_1Encoding.INSTANCE
- ? StandardCharsets.ISO_8859_1 : StandardCharsets.UTF_8));
- }
-
static final class NamedCharacterCache {
- private final Map expansions =
+ private final Map expansions =
new LinkedHashMap<>();
private final RuntimeScalar translator;
NamedCharacterCache() {
- this(null);
+ this((RuntimeScalar) null);
}
NamedCharacterCache(RuntimeScalar translator) {
this.translator = translator == null ? null : new RuntimeScalar(translator);
}
+ NamedCharacterCache(NamedCharacterExpansionMap preResolved) {
+ this.translator = null;
+ if (preResolved != null) expansions.putAll(preResolved.expansions());
+ }
+
NamedCharacterExpansion resolve(
String name, NamedCharacterExpansion.SourceMode sourceMode) {
- return expansions.computeIfAbsent(name,
+ return expansions.computeIfAbsent(new NamedCharacterExpansionMap.Key(name, sourceMode),
ignored -> translator == null
? NamedCharacterExpansion.resolve(name, sourceMode)
: NamedCharacterExpansion.resolve(name, translator, sourceMode));
}
+
+
+ NamedCharacterExpansionMap snapshot(
+ NamedCharacterExpansionMap.LiteralIdentity literalIdentity,
+ NamedCharacterExpansionMap.CallableIdentity callableIdentity) {
+ return new NamedCharacterExpansionMap(
+ literalIdentity, callableIdentity, expansions);
+ }
}
private static Syntax syntaxForNamedCharacters(
NamedCharacterCache cache, NamedCharacterExpansion.SourceMode sourceMode,
- boolean caseInsensitive) {
+ DeferredPropertyState deferredPropertyState) {
NamedCharacterResolver resolver = new NamedCharacterResolver() {
@Override
public int resolve(byte[] bytes, int p, int end, Encoding encoding) {
- return resolveNamedCharacter(bytes, p, end, encoding);
+ int[] sequence = resolveSequence(bytes, p, end, encoding);
+ if (sequence.length != 1) {
+ throw new IllegalArgumentException(
+ "named character resolver requires one code point");
+ }
+ return sequence[0];
}
@Override
@@ -147,6 +165,10 @@ public int[] resolveSequence(byte[] bytes, int p, int end, Encoding encoding) {
String name = new String(bytes, p, end - p,
encoding == ISO8859_1Encoding.INSTANCE
? StandardCharsets.ISO_8859_1 : StandardCharsets.UTF_8);
+ String trimmed = name.strip();
+ if (trimmed.regionMatches(true, 0, "U+", 0, 2)) {
+ name = trimmed;
+ }
NamedCharacterExpansion expansion = cache.resolve(name, sourceMode);
if (!expansion.resolved()) {
throw new IllegalArgumentException(expansion.diagnostic());
@@ -154,11 +176,7 @@ public int[] resolveSequence(byte[] bytes, int p, int end, Encoding encoding) {
return expansion.sequence().codePoints().toArray();
}
};
- CharacterPropertyResolver propertyResolver =
- (bytes, p, end, encoding, inCharacterClass) ->
- resolveCharacterProperty(
- bytes, p, end, encoding, inCharacterClass,
- caseInsensitive);
+ CharacterPropertyResolver propertyResolver = propertyResolver(deferredPropertyState);
return new Syntax(PERLONJAVA_SYNTAX.name, PERLONJAVA_SYNTAX.op,
PERLONJAVA_SYNTAX.op2, PERLONJAVA_SYNTAX.op3,
PERLONJAVA_SYNTAX.behavior, PERLONJAVA_SYNTAX.options,
@@ -167,6 +185,57 @@ public int[] resolveSequence(byte[] bytes, int p, int end, Encoding encoding) {
PERLONJAVA_SYNTAX.wideScalarCodec);
}
+ private static final class DeferredPropertyState {
+ private boolean deferred;
+ private boolean userDefined;
+ }
+
+ private static CharacterPropertyResolver propertyResolver(
+ DeferredPropertyState deferredPropertyState) {
+ return new CharacterPropertyResolver() {
+ @Override
+ public Result resolve(byte[] bytes, int p, int end, Encoding encoding,
+ boolean inCharacterClass) {
+ return resolve(bytes, p, end, encoding, inCharacterClass,
+ Option.NONE);
+ }
+
+ @Override
+ public Result resolve(byte[] bytes, int p, int end, Encoding encoding,
+ boolean inCharacterClass, int option) {
+ String property = new String(bytes, p, end - p,
+ encoding == ISO8859_1Encoding.INSTANCE
+ ? StandardCharsets.ISO_8859_1
+ : StandardCharsets.UTF_8).trim();
+ if (property.startsWith("^")) property = property.substring(1).trim();
+ boolean userDefined = UnicodeResolver.isUserDefinedPropertyName(property);
+ if (userDefined) deferredPropertyState.userDefined = true;
+
+ CharacterPropertyResolver.Result resolved = resolveCharacterProperty(
+ bytes, p, end, encoding, inCharacterClass,
+ Option.isIgnoreCase(option));
+ if (resolved != null) return resolved;
+
+ boolean caseInsensitive = Option.isIgnoreCase(option);
+ if (userDefined
+ && UnicodeResolver.mustDeferPotentialUserDefinedProperty(
+ property, caseInsensitive)) {
+ deferredPropertyState.deferred = true;
+ return new Result(new int[] {1, 0, 0x10ffff},
+ new long[] {1, 0, Long.MAX_VALUE}, false);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isScriptRun(byte[] bytes, int p, int end, Encoding encoding,
+ WideScalarCodec wideScalarCodec) {
+ return UnicodeResolver.isPerlScriptRun(
+ bytes, p, end, encoding, wideScalarCodec);
+ }
+ };
+ }
+
private static CharacterPropertyResolver.Result resolveCharacterProperty(
byte[] bytes, int p, int end, Encoding encoding,
boolean inCharacterClass, boolean caseInsensitive) {
@@ -179,11 +248,13 @@ private static CharacterPropertyResolver.Result resolveCharacterProperty(
private final Regex regex;
private final String sourcePattern;
+ private final String compatibilityPatternDescription;
private final Map namedGroups;
private final Map physicalNamedGroups;
private final RegexFlags flags;
private final boolean hasControlVerbState;
private final boolean hasDeferredUserDefinedUnicodeProperty;
+ private final boolean hasUserDefinedUnicodeProperty;
private final boolean byteMode;
private final List compileWarnings;
@@ -210,16 +281,52 @@ private static CharacterPropertyResolver.Result resolveCharacterProperty(
JoniRegexPattern(String perlPattern, RegexFlags flags, int trustedCalloutCount,
boolean forceAsciiClasses, boolean byteMode,
boolean byteBackedPattern, NamedCharacterCache namedCharacterCache) {
+ this(perlPattern, flags, trustedCalloutCount, forceAsciiClasses,
+ byteMode, byteBackedPattern, namedCharacterCache,
+ forceAsciiClasses || byteMode && byteBackedPattern
+ ? NamedCharacterExpansion.SourceMode.BYTE
+ : NamedCharacterExpansion.SourceMode.UNICODE,
+ false);
+ }
+
+ JoniRegexPattern(String perlPattern, RegexFlags flags, int trustedCalloutCount,
+ boolean forceAsciiClasses, boolean byteMode,
+ boolean byteBackedPattern, NamedCharacterCache namedCharacterCache,
+ boolean perlReStrict) {
+ this(perlPattern, flags, trustedCalloutCount, forceAsciiClasses,
+ byteMode, byteBackedPattern, namedCharacterCache,
+ forceAsciiClasses || byteMode && byteBackedPattern
+ ? NamedCharacterExpansion.SourceMode.BYTE
+ : NamedCharacterExpansion.SourceMode.UNICODE,
+ perlReStrict);
+ }
+
+ JoniRegexPattern(String perlPattern, RegexFlags flags, int trustedCalloutCount,
+ boolean forceAsciiClasses, boolean byteMode,
+ boolean byteBackedPattern, NamedCharacterCache namedCharacterCache,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode) {
+ this(perlPattern, flags, trustedCalloutCount, forceAsciiClasses,
+ byteMode, byteBackedPattern, namedCharacterCache,
+ namedCharacterSourceMode, false);
+ }
+
+ JoniRegexPattern(String perlPattern, RegexFlags flags, int trustedCalloutCount,
+ boolean forceAsciiClasses, boolean byteMode,
+ boolean byteBackedPattern, NamedCharacterCache namedCharacterCache,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode,
+ boolean perlReStrict) {
PerlSyntaxFeatures syntaxFeatures = analyzePerlSyntax(perlPattern, flags.isExtended());
if (syntaxFeatures.keepInLookaround()) {
throw new PerlCompilerException("\\K not permitted in lookahead/lookbehind in regex");
}
+ validateExtendedPropertyPolicy(perlPattern);
this.flags = flags;
this.byteMode = byteMode;
hasControlVerbState = hasControlVerbState(perlPattern);
- UserPropertyTranslation userProperties = translateUserDefinedProperties(perlPattern, flags);
- hasDeferredUserDefinedUnicodeProperty = userProperties.deferred();
- sourcePattern = translatePattern(userProperties.pattern(), flags, trustedCalloutCount);
+ sourcePattern = RuntimeRegexTemplate.materializeTrustedCallouts(
+ perlPattern, trustedCalloutCount);
+ compatibilityPatternDescription = legacyCompatibilityDescription(
+ sourcePattern, flags, trustedCalloutCount);
compileWarnings = new ArrayList<>();
java.nio.charset.Charset sourceCharset = byteMode && byteBackedPattern
? StandardCharsets.ISO_8859_1 : StandardCharsets.UTF_8;
@@ -234,8 +341,13 @@ public void warn(String message) {
public void warn(String message, int bytePosition) {
int bounded = Math.max(0, Math.min(bytePosition, bytes.length));
int characterOffset = new String(bytes, 0, bounded, sourceCharset).length();
+ WarningDisplay display = message.equals(NAMED_SEQUENCE_CLASS_WARNING)
+ ? canonicalNamedSequenceWarningDisplay(
+ sourcePattern, characterOffset,
+ namedCharacterCache, namedCharacterSourceMode)
+ : new WarningDisplay(sourcePattern, characterOffset);
compileWarnings.add(RegexDiagnosticFormatter.markedPerl(
- sourcePattern, characterOffset, message));
+ display.pattern(), display.offset(), message));
}
@Override
@@ -243,23 +355,64 @@ public boolean supportsPositions() {
return true;
}
};
- NamedCharacterExpansion.SourceMode namedCharacterSourceMode =
- forceAsciiClasses || byteMode && byteBackedPattern
- ? NamedCharacterExpansion.SourceMode.BYTE
- : NamedCharacterExpansion.SourceMode.UNICODE;
+ DeferredPropertyState deferredPropertyState = new DeferredPropertyState();
Syntax syntax = syntaxForNamedCharacters(
namedCharacterCache, namedCharacterSourceMode,
- flags.isCaseInsensitive());
- int options = toJoniOptions(flags, forceAsciiClasses);
+ deferredPropertyState);
+ int options = toJoniOptions(flags, forceAsciiClasses, perlReStrict);
if (byteMode && byteBackedPattern) options |= Option.PERL_BYTE_PATTERN;
regex = new Regex(bytes, 0, bytes.length, options,
byteMode ? ISO8859_1Encoding.INSTANCE : UTF8Encoding.INSTANCE,
syntax, warningCollector);
+ hasDeferredUserDefinedUnicodeProperty = deferredPropertyState.deferred;
+ hasUserDefinedUnicodeProperty = deferredPropertyState.userDefined;
NamedGroupMaps groupMaps = collectNamedGroups(regex);
namedGroups = groupMaps.logical();
physicalNamedGroups = groupMaps.physical();
}
+ private record WarningDisplay(String pattern, int offset) {}
+
+ /**
+ * Perl renders a named sequence as its canonical U+ list when a character
+ * class forces the sequence into a single-code-point context. Joni parses
+ * the source spelling so its warning byte position still refers to the
+ * original text; replace only the escape ending at that position and move
+ * the marker by the corresponding character delta.
+ */
+ private static WarningDisplay canonicalNamedSequenceWarningDisplay(
+ String pattern, int offset, NamedCharacterCache cache,
+ NamedCharacterExpansion.SourceMode sourceMode) {
+ int escapeStart = pattern.lastIndexOf("\\N{", Math.max(0, offset - 1));
+ if (escapeStart < 0) return new WarningDisplay(pattern, offset);
+ int nameStart = escapeStart + 3;
+ int close = pattern.indexOf('}', nameStart);
+ if (close < 0 || close + 1 != offset) {
+ return new WarningDisplay(pattern, offset);
+ }
+
+ String name = pattern.substring(nameStart, close);
+ NamedCharacterExpansion expansion = cache.resolve(name, sourceMode);
+ if (!expansion.resolved()
+ || expansion.sequence().codePointCount(
+ 0, expansion.sequence().length()) <= 1) {
+ return new WarningDisplay(pattern, offset);
+ }
+
+ StringBuilder canonical = new StringBuilder("\\N{U+");
+ boolean first = true;
+ for (int codePoint : expansion.sequence().codePoints().toArray()) {
+ if (!first) canonical.append('.');
+ canonical.append(Integer.toHexString(codePoint).toUpperCase());
+ first = false;
+ }
+ canonical.append('}');
+ String displayPattern = pattern.substring(0, escapeStart)
+ + canonical + pattern.substring(close + 1);
+ return new WarningDisplay(displayPattern,
+ escapeStart + canonical.length());
+ }
+
RegexMatcher matcher(String input, List callbacks) {
return matcher(input, callbacks, new RuntimeScalar(input));
}
@@ -270,8 +423,52 @@ RegexMatcher matcher(String input, List callbacks,
hasControlVerbState, byteMode, input, callbacks, subject);
}
+ Map namedGroups() {
+ return namedGroups;
+ }
+
record InputEncoding(byte[] bytes, int[] charToByte, int[] byteToChar) {}
+ private record SubjectInputEncodings(Object value, int type, boolean uncheckedOctets,
+ InputEncoding unicode, InputEncoding bytes) {}
+
+ static InputEncoding inputEncoding(String input, RuntimeScalar subject, boolean byteMode) {
+ boolean directImmutableString = subject != null
+ && (subject.type == RuntimeScalarType.STRING
+ || subject.type == RuntimeScalarType.BYTE_STRING)
+ && subject.value == input;
+ if (!directImmutableString) {
+ return byteMode ? byteInputEncoding(input) : inputEncoding(input);
+ }
+ Object value = subject.value;
+
+ synchronized (SUBJECT_INPUT_ENCODINGS) {
+ SubjectInputEncodings cached = SUBJECT_INPUT_ENCODINGS.get(subject);
+ if (cached != null && cached.value == value && cached.type == subject.type
+ && cached.uncheckedOctets == subject.utf8UncheckedOctets) {
+ InputEncoding encoding = byteMode ? cached.bytes : cached.unicode;
+ if (encoding != null) return encoding;
+ }
+
+ InputEncoding unicode = cached != null && cached.value == value
+ && cached.type == subject.type
+ && cached.uncheckedOctets == subject.utf8UncheckedOctets
+ ? cached.unicode : null;
+ InputEncoding bytes = cached != null && cached.value == value
+ && cached.type == subject.type
+ && cached.uncheckedOctets == subject.utf8UncheckedOctets
+ ? cached.bytes : null;
+ if (byteMode) {
+ bytes = buildByteInputEncoding(input);
+ } else {
+ unicode = buildInputEncoding(input);
+ }
+ SUBJECT_INPUT_ENCODINGS.put(subject, new SubjectInputEncodings(
+ value, subject.type, subject.utf8UncheckedOctets, unicode, bytes));
+ return byteMode ? bytes : unicode;
+ }
+ }
+
static InputEncoding inputEncoding(String input) {
synchronized (INPUT_ENCODINGS) {
return INPUT_ENCODINGS.computeIfAbsent(input, JoniRegexPattern::buildInputEncoding);
@@ -300,13 +497,24 @@ private static InputEncoding buildInputEncoding(String input) {
}
String patternDescription() {
- return sourcePattern;
+ return compatibilityPatternDescription;
}
Regex engineRegex() {
return regex;
}
+ String optimizerDebugDescription() {
+ int anchor = regex.getAnchor();
+ if ((anchor & AnchorType.ANYCHAR_STAR_ML) != 0) {
+ return "anchored(SBOL) implicit";
+ }
+ if ((anchor & AnchorType.ANYCHAR_STAR) != 0) {
+ return "anchored(MBOL) implicit";
+ }
+ return "";
+ }
+
boolean hasOnlyAuthoritativeWideCharacterClasses() {
return regex.hasOnlyAuthoritativeWideCharacterClasses();
}
@@ -315,141 +523,59 @@ boolean hasDeferredUserDefinedUnicodeProperty() {
return hasDeferredUserDefinedUnicodeProperty;
}
+ boolean hasUserDefinedUnicodeProperty() {
+ return hasUserDefinedUnicodeProperty;
+ }
+
List compileWarnings() {
return List.copyOf(compileWarnings);
}
- private record UserPropertyTranslation(String pattern, boolean deferred) {}
-
- private static UserPropertyTranslation translateUserDefinedProperties(
- String pattern, RegexFlags flags) {
- StringBuilder translated = new StringBuilder(pattern.length());
- boolean deferred = false;
- int extendedClassBracketDepth = 0;
- int standardClassBracketDepth = 0;
+ /** Retains only source policy that the runtime-neutral resolver cannot see. */
+ private static void validateExtendedPropertyPolicy(String pattern) {
+ int extendedDepth = 0;
for (int i = 0; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
- if (ch == '\\' && i + 1 < pattern.length() && pattern.charAt(i + 1) == '\\') {
- translated.append("\\\\");
- i++;
- continue;
- }
- if (extendedClassBracketDepth == 0 && pattern.startsWith("(?[", i)) {
- translated.append("(?[");
- extendedClassBracketDepth = 1;
- i += 2;
- continue;
- }
- if (extendedClassBracketDepth > 0 && ch == '[') {
- extendedClassBracketDepth++;
- translated.append(ch);
- continue;
- }
- if (extendedClassBracketDepth > 0 && ch == ']') {
- extendedClassBracketDepth--;
- translated.append(ch);
- continue;
- }
- if (extendedClassBracketDepth == 0 && ch == '[') {
- standardClassBracketDepth++;
- translated.append(ch);
- continue;
- }
- if (extendedClassBracketDepth == 0
- && standardClassBracketDepth > 0 && ch == ']') {
- standardClassBracketDepth--;
- translated.append(ch);
- continue;
- }
- if (ch != '\\' || i + 3 >= pattern.length()
- || (pattern.charAt(i + 1) != 'p' && pattern.charAt(i + 1) != 'P')
- || pattern.charAt(i + 2) != '{') {
- if (ch == '\\' && i + 1 < pattern.length()) {
- translated.append(pattern, i, i + 2);
- i++;
- } else {
- translated.append(ch);
- }
- continue;
- }
- int end = pattern.indexOf('}', i + 3);
- if (end < 0) {
- translated.append(ch);
- continue;
- }
- String property = pattern.substring(i + 3, end).trim();
- if (property.isEmpty()) {
- // Malformed Perl property syntax belongs to Joni's lexer. Do
- // not ask the adapter resolver to classify an empty name.
- translated.append(pattern, i, end + 1);
- i = end;
- continue;
- }
- String unnegated = property.startsWith("^")
- ? property.substring(1).trim() : property;
- boolean userDefined = UnicodeResolver.isUserDefinedPropertyName(unnegated);
- boolean scriptExtensions = unnegated.matches(
- "(?i)^(?:scx|script[-_ ]?extensions)\\s*(?:=|:(?!:)).*");
- boolean frontendProperty = unnegated.matches(
- "(?i)^(?:script|sc|block|blk|age|in|present[_ ]?in)\\s*(?:=|:(?!:)).*");
- boolean perlBuiltInAlias = UnicodeResolver.isPerlBuiltInPropertyAlias(unnegated);
- boolean preserveUserDefined = userDefined
- && UnicodeResolver.mustPreserveUserDefinedProperty(
- unnegated, flags.isCaseInsensitive());
- boolean joniResolvedProperty = UnicodeResolver.resolveJoniProperty(
- unnegated, extendedClassBracketDepth > 0
- || standardClassBracketDepth > 0,
- flags.isCaseInsensitive()) != null;
- if (joniResolvedProperty && (userDefined
- || (!userDefined || perlBuiltInAlias && !preserveUserDefined)
- && (frontendProperty || scriptExtensions
- || perlBuiltInAlias))) {
- translated.append(pattern, i, end + 1);
- i = end;
- continue;
- }
- if ((frontendProperty || scriptExtensions || perlBuiltInAlias)
- && extendedClassBracketDepth > 0) {
- translated.append(pattern, i, end + 1);
- i = end;
- continue;
- }
- if ((frontendProperty || scriptExtensions || perlBuiltInAlias)
- && standardClassBracketDepth > 0) {
- String propertyClass = UnicodeResolver.translateUnicodePropertyForCharClass(
- property, pattern.charAt(i + 1) == 'P');
- if (propertyClass.startsWith("[") && propertyClass.endsWith("]")) {
- translated.append(propertyClass, 1, propertyClass.length() - 1);
- } else {
- translated.append(propertyClass);
+ if (pattern.charAt(i) == '\\') {
+ if (i + 2 < pattern.length()
+ && (pattern.charAt(i + 1) == 'p'
+ || pattern.charAt(i + 1) == 'P')
+ && pattern.charAt(i + 2) == '{') {
+ int end = pattern.indexOf('}', i + 3);
+ if (end < 0) continue;
+ String property = pattern.substring(i + 3, end).trim();
+ if (property.startsWith("^")) {
+ property = property.substring(1).trim();
+ }
+ if (extendedDepth > 0
+ && UnicodeResolver.isPerlStringProperty(property)) {
+ throw new PerlCompilerException(RegexDiagnosticFormatter.markedPerl(
+ pattern, end + 1,
+ "Unicode string properties are not implemented in (?[...])"));
+ }
+ if (extendedDepth > 0
+ && UnicodeResolver.isUserDefinedPropertyName(property)
+ && (UnicodeResolver.mustDeferPotentialUserDefinedProperty(
+ property, false)
+ || UnicodeResolver.mustDeferPotentialUserDefinedProperty(
+ property, true))) {
+ throw new PerlCompilerException(
+ "Unknown user-defined property name \"" + property + "\"");
+ }
+ i = end;
+ continue;
}
- i = end;
- continue;
- }
- if (!userDefined && !scriptExtensions && !frontendProperty && !perlBuiltInAlias) {
- translated.append(pattern, i, end + 1);
- i = end;
+ if (i + 1 < pattern.length()) i++;
continue;
}
- try {
- String propertyClass = UnicodeResolver.translateUnicodeProperty(
- property, pattern.charAt(i + 1) == 'P', flags.isCaseInsensitive());
- translated.append("(?-i:")
- .append(normalizeGeneratedPropertyClassForJoni(propertyClass))
- .append(')');
- } catch (IllegalArgumentException error) {
- String message = error.getMessage();
- if (!userDefined || message != null
- && (message.contains("in expansion of")
- || message.startsWith("Can't find Unicode property definition"))) {
- throw error;
- }
- translated.append("[\\s\\S]");
- deferred = true;
+ if (extendedDepth == 0 && pattern.startsWith("(?[", i)) {
+ extendedDepth = 1;
+ i += 2;
+ } else if (extendedDepth > 0 && pattern.charAt(i) == '[') {
+ extendedDepth++;
+ } else if (extendedDepth > 0 && pattern.charAt(i) == ']') {
+ extendedDepth--;
}
- i = end;
}
- return new UserPropertyTranslation(translated.toString(), deferred);
}
/**
@@ -478,7 +604,8 @@ private static String normalizeGeneratedPropertyClassForJoni(String propertyClas
return normalized.toString();
}
- private static int toJoniOptions(RegexFlags flags, boolean forceAsciiClasses) {
+ private static int toJoniOptions(RegexFlags flags, boolean forceAsciiClasses,
+ boolean perlReStrict) {
int options = Option.NONE;
if (flags.isCaseInsensitive()) options |= Option.IGNORECASE;
if (flags.isExtended()) options |= Option.EXTEND;
@@ -487,7 +614,9 @@ private static int toJoniOptions(RegexFlags flags, boolean forceAsciiClasses) {
if (flags.isDotAll()) options |= Option.MULTILINE;
if (!flags.isMultiLine()) options |= Option.SINGLELINE;
if (flags.isAscii() || forceAsciiClasses) options |= Option.ASCII_RANGE;
+ if (flags.isAscii()) options |= Option.PERL_EXPLICIT_ASCII;
if (flags.isAsciiStrict()) options |= Option.PERL_ASCII_STRICT;
+ if (perlReStrict) options |= Option.PERL_RE_STRICT;
// Ruby/Oniguruma syntax implicitly makes unnamed groups non-capturing
// when a pattern also contains named groups. Perl keeps both kinds of
// captures numbered. Force that behavior unless /n explicitly disables
@@ -513,6 +642,7 @@ static boolean requiresJoniBackend(String pattern, RegexFlags flags) {
|| syntaxFeatures.asciiStrictPresent()
|| syntaxFeatures.keepPresent()
|| syntaxFeatures.lookbehindPresent()
+ || syntaxFeatures.nativeExtendedClassPresent()
|| syntaxFeatures.branchResetPresent()
|| syntaxFeatures.conditionalPresent()
|| syntaxFeatures.alphaAssertionPresent()
@@ -572,6 +702,7 @@ static boolean containsNamedCharacterEscape(String pattern) {
private record PerlSyntaxFeatures(boolean keepPresent,
boolean keepInLookaround,
boolean lookbehindPresent,
+ boolean nativeExtendedClassPresent,
boolean branchResetPresent,
boolean conditionalPresent,
boolean alphaAssertionPresent,
@@ -586,6 +717,7 @@ private static PerlSyntaxFeatures analyzePerlSyntax(String pattern, boolean exte
java.util.ArrayDeque groups = new java.util.ArrayDeque<>();
boolean keepPresent = false;
boolean lookbehindPresent = false;
+ boolean nativeExtendedClassPresent = false;
boolean branchResetPresent = false;
boolean conditionalPresent = false;
boolean alphaAssertionPresent = false;
@@ -603,7 +735,14 @@ private static PerlSyntaxFeatures analyzePerlSyntax(String pattern, boolean exte
}
if (extendedClassDepth > 0) {
if (ch == '\\' && i + 1 < pattern.length()) {
+ if (pattern.charAt(i + 1) == 'p' || pattern.charAt(i + 1) == 'P') {
+ nativeExtendedClassPresent = true;
+ }
i++;
+ } else if (ch == '[' && i + 1 < pattern.length()
+ && pattern.charAt(i + 1) == ':') {
+ nativeExtendedClassPresent = true;
+ extendedClassDepth++;
} else if (ch == '[') {
extendedClassDepth++;
} else if (ch == ']' && --extendedClassDepth == 0) {
@@ -653,7 +792,7 @@ private static PerlSyntaxFeatures analyzePerlSyntax(String pattern, boolean exte
} else if (escaped == 'K') {
keepPresent = true;
if (lookaroundDepth > 0) {
- return new PerlSyntaxFeatures(true, true, lookbehindPresent, branchResetPresent, conditionalPresent,
+ return new PerlSyntaxFeatures(true, true, lookbehindPresent, nativeExtendedClassPresent, branchResetPresent, conditionalPresent,
alphaAssertionPresent, asciiStrictPresent);
}
}
@@ -703,7 +842,11 @@ private static PerlSyntaxFeatures analyzePerlSyntax(String pattern, boolean exte
|| name.equals("negative_lookahead")
|| name.equals("nlb")
|| name.equals("negative_lookbehind")
- || name.equals("atomic");
+ || name.equals("atomic")
+ || name.equals("script_run")
+ || name.equals("sr")
+ || name.equals("atomic_script_run")
+ || name.equals("asr");
}
boolean lookaround = pattern.startsWith("(?=", i)
|| pattern.startsWith("(?!", i)
@@ -718,7 +861,7 @@ private static PerlSyntaxFeatures analyzePerlSyntax(String pattern, boolean exte
if (groups.pop()) lookaroundDepth--;
}
}
- return new PerlSyntaxFeatures(keepPresent, false, lookbehindPresent, branchResetPresent, conditionalPresent,
+ return new PerlSyntaxFeatures(keepPresent, false, lookbehindPresent, nativeExtendedClassPresent, branchResetPresent, conditionalPresent,
alphaAssertionPresent, asciiStrictPresent);
}
@@ -731,6 +874,103 @@ private static boolean hasControlVerbState(String pattern) {
|| pattern.contains("(*COMMIT");
}
+ /**
+ * Test-facing compatibility normalizer. Production patterns retain raw
+ * {@code \N{...}} source for Joni's resolver; this helper keeps its
+ * historical one-code-point assertion independent of that native path.
+ */
+ static String translatePattern(String pattern) {
+ RegexFlags flags = RegexFlags.fromModifiers("", pattern);
+ return legacyCompatibilityDescription(
+ translateCompatibilityPattern(pattern, flags), flags, 0);
+ }
+
+ /**
+ * Preserves the package-private description contract used by legacy unit
+ * tests and debug output. Production compilation, matching, warnings, and
+ * diagnostics use {@link #sourcePattern} unchanged, so this must never feed
+ * Joni or runtime regex reconstruction.
+ */
+ private static String legacyCompatibilityDescription(String pattern,
+ RegexFlags flags,
+ int trustedCalloutCount) {
+ StringBuilder description = new StringBuilder(pattern.length() + 8);
+ boolean escaped = false;
+ boolean inClass = false;
+ boolean inlineExtendedOption = hasInlineExtendedOption(pattern);
+ for (int i = 0; i < pattern.length(); i++) {
+ char ch = pattern.charAt(i);
+ if (escaped) {
+ description.append(ch);
+ escaped = false;
+ continue;
+ }
+ if (ch == '\\' && i + 3 < pattern.length()
+ && (pattern.charAt(i + 1) == 'p' || pattern.charAt(i + 1) == 'P')
+ && pattern.charAt(i + 2) == '{') {
+ int end = pattern.indexOf('}', i + 3);
+ if (end > i + 3) {
+ String property = pattern.substring(i + 3, end).trim();
+ if (isLegacyAgeWildcard(property)) {
+ String propertyClass = UnicodeResolver.translateUnicodeProperty(
+ property, pattern.charAt(i + 1) == 'P',
+ flags.isCaseInsensitive());
+ description.append("(?-i:")
+ .append(normalizeGeneratedPropertyClassForJoni(propertyClass))
+ .append(')');
+ i = end;
+ continue;
+ }
+ }
+ }
+ if (ch == '\\') {
+ description.append(ch);
+ escaped = true;
+ continue;
+ }
+ if (ch == '[') {
+ inClass = true;
+ description.append(ch);
+ continue;
+ }
+ if (ch == ']' && inClass) {
+ inClass = false;
+ description.append(ch);
+ continue;
+ }
+ if (!inClass && pattern.startsWith("(*:", i)) {
+ description.append("(*MARK:");
+ i += 2;
+ continue;
+ }
+ if (inClass && flags.isExtendedWhitespace() && !inlineExtendedOption
+ && Character.isWhitespace(ch)) {
+ continue;
+ }
+ if (!inClass && pattern.startsWith("(?)", i)) {
+ description.append("(?:)");
+ i += 2;
+ continue;
+ }
+ if (!inClass && pattern.startsWith("(?{", i)
+ && !isTrustedCallout(pattern, i, trustedCalloutCount)) {
+ int end = findCodeBlockEnd(pattern, i);
+ if (end >= 0) {
+ description.append("(?:)");
+ i = end;
+ continue;
+ }
+ }
+ description.append(ch);
+ }
+ return description.toString();
+ }
+
+ private static boolean isLegacyAgeWildcard(String property) {
+ return property.matches(
+ "(?is)^(?:age|in|present[-_ ]?in)\\s*(?:=|:(?!:))\\s*:\\\\A.*\\\\z:$");
+ }
+
private static boolean hasInlineExtendedOption(String pattern) {
boolean escaped = false;
boolean inClass = false;
@@ -765,24 +1005,12 @@ private static boolean hasInlineExtendedOption(String pattern) {
return false;
}
- static String translatePattern(String pattern) {
- return translatePattern(pattern, RegexFlags.fromModifiers("", pattern), 0, true);
- }
-
- private static String translatePattern(String pattern, RegexFlags flags,
- int trustedCalloutCount) {
- return translatePattern(pattern, flags, trustedCalloutCount, false);
- }
-
- private static String translatePattern(String pattern, RegexFlags flags,
- int trustedCalloutCount,
- boolean resolveNamedCharacters) {
+ private static String translateCompatibilityPattern(String pattern, RegexFlags flags) {
StringBuilder out = new StringBuilder(pattern.length() + 16);
boolean escaped = false;
boolean inClass = false;
boolean atClassStart = false;
boolean classAllowsLeadingClose = false;
- boolean inlineExtendedOption = hasInlineExtendedOption(pattern);
int posixClassDepth = 0;
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
@@ -796,7 +1024,7 @@ private static String translatePattern(String pattern, RegexFlags flags,
atClassStart = false;
classAllowsLeadingClose = false;
}
- if (resolveNamedCharacters && pattern.startsWith("\\N{", i)) {
+ if (pattern.startsWith("\\N{", i)) {
int end = pattern.indexOf('}', i + 3);
if (end > i + 3) {
int codePoint = UnicodeResolver.getCodePointFromName(
@@ -806,22 +1034,6 @@ private static String translatePattern(String pattern, RegexFlags flags,
continue;
}
}
- // In Perl, \g{name} is a backreference. Ruby/Oniguruma uses
- // \g for a subexpression call and \k for the
- // backreference, so passing the brace form through makes Joni
- // diagnose it as an invalid subexpression call. Numeric and
- // relative brace forms follow the same translation.
- if (!inClass && pattern.startsWith("\\g{", i)) {
- int end = pattern.indexOf('}', i + 3);
- if (end > i + 3) {
- String backreference = pattern.substring(i + 3, end);
- if (isValidPerlBraceBackreference(backreference)) {
- out.append("\\k<").append(backreference).append('>');
- i = end;
- continue;
- }
- }
- }
out.append(ch);
escaped = true;
continue;
@@ -831,19 +1043,19 @@ private static String translatePattern(String pattern, RegexFlags flags,
boolean posixClass = i + 1 < pattern.length()
&& (pattern.charAt(i + 1) == ':'
|| pattern.charAt(i + 1) == '.'
- || pattern.charAt(i + 1) == '=');
+ || pattern.charAt(i + 1) == '=')
+ && !(i + 2 < pattern.length() && pattern.charAt(i + 2) == ']');
if (posixClass) {
posixClassDepth++;
atClassStart = false;
classAllowsLeadingClose = false;
out.append(ch);
continue;
- } else {
- out.append("\\[");
- atClassStart = false;
- classAllowsLeadingClose = false;
- continue;
}
+ out.append(ch);
+ atClassStart = false;
+ classAllowsLeadingClose = false;
+ continue;
}
inClass = true;
atClassStart = true;
@@ -851,17 +1063,6 @@ private static String translatePattern(String pattern, RegexFlags flags,
out.append(ch);
continue;
}
- if (!inClass && pattern.startsWith("(*:", i)) {
- // Perl's abbreviated MARK form is (*:NAME). Joni accepts the
- // equivalent long spelling and publishes the mark normally.
- out.append("(*MARK:");
- i += 2;
- continue;
- }
- if (inClass && flags.isExtendedWhitespace() && !inlineExtendedOption
- && Character.isWhitespace(ch)) {
- continue;
- }
if (inClass && atClassStart && ch == '^') {
out.append(ch);
atClassStart = false;
@@ -888,20 +1089,6 @@ private static String translatePattern(String pattern, RegexFlags flags,
atClassStart = false;
classAllowsLeadingClose = false;
}
- if (!inClass && pattern.startsWith("(?{", i)
- && !isTrustedCallout(pattern, i, trustedCalloutCount)) {
- // A callback introduced as text by runtime interpolation has
- // no parser-created lexical closure to invoke. Preserve the
- // historical compatibility behavior for that unsupported
- // case: treat it as a zero-width no-op. Structured callbacks
- // remain match-time Joni callouts and are handled below.
- int end = findCodeBlockEnd(pattern, i);
- if (end >= 0) {
- out.append("(?:)");
- i = end;
- continue;
- }
- }
if (!inClass && pattern.startsWith("(?[", i)) {
int bracketDepth = 0;
boolean comment = false;
@@ -938,32 +1125,11 @@ private static String translatePattern(String pattern, RegexFlags flags,
i = Math.min(end, pattern.length() - 1);
continue;
}
- if (!inClass && pattern.startsWith("(?)", i)) {
- out.append("(?:)");
- i += 2;
- continue;
- }
out.append(ch);
}
return out.toString();
}
- private static boolean isValidPerlBraceBackreference(String content) {
- if (content.isEmpty()) return false;
- int start = content.charAt(0) == '-' ? 1 : 0;
- if (start == content.length()) return false;
- if (start == 1 || Character.isDigit(content.charAt(0))) {
- for (int i = start; i < content.length(); i++) {
- if (!Character.isDigit(content.charAt(i))) return false;
- }
- return true;
- }
- for (int i = 0; i < content.length(); i++) {
- if (Character.isWhitespace(content.charAt(i))) return false;
- }
- return true;
- }
-
private static void appendResolvedNamedCharacter(StringBuilder out, int codePoint,
RegexFlags flags) {
boolean extendedSyntax = flags.isExtended()
@@ -1016,25 +1182,6 @@ private static int findCodeBlockEnd(String pattern, int offset) {
return -1;
}
- /**
- * Joni's Ruby syntax does not understand Java's {@code &&} character-class
- * intersection. For an explicitly ASCII-bounded Perl extended class,
- * evaluate the already-translated Java class and emit the exact byte set.
- */
- private static void appendAsciiClassForJoni(StringBuilder out, String javaClass) {
- // Java requires literal closing/opening brackets to be escaped even in
- // the leading position accepted by Perl's bracket syntax.
- javaClass = javaClass.replace("[^][", "[^\\]\\[");
- java.util.regex.Pattern predicate = java.util.regex.Pattern.compile(javaClass);
- out.append('[');
- for (int value = 0; value < 128; value++) {
- if (predicate.matcher(Character.toString((char) value)).matches()) {
- out.append(String.format("\\x%02X", value));
- }
- }
- out.append(']');
- }
-
private record NamedGroupMaps(Map logical,
Map physical) {}
@@ -1098,8 +1245,7 @@ private static final class JoniRegexMatcher implements RegexMatcher {
this.input = input;
this.callbacks = callbacks;
this.subject = subject;
- InputEncoding encoding = byteMode
- ? byteInputEncoding(input) : inputEncoding(input);
+ InputEncoding encoding = inputEncoding(input, subject, byteMode);
this.bytes = encoding.bytes();
this.charToByte = encoding.charToByte();
this.byteToChar = encoding.byteToChar();
@@ -1207,6 +1353,7 @@ public String group(int index) {
int begin = index == 0 ? matcher.getBegin() : captures.getBeg(index);
int end = index == 0 ? matcher.getEnd() : captures.getEnd(index);
if (begin < 0 || end < 0) return null;
+ if (index == 0 && begin > end) return null;
return input.substring(toCharOffset(begin), toCharOffset(end));
}
@@ -1358,6 +1505,9 @@ static CaptureSnapshot of(MatchView match) {
private boolean executedNestedCallbackPattern;
private String failedNestedLastClosedCapture;
private String failedNestedLastParenMatch;
+ private final ArrayDeque callbackMutations =
+ new ArrayDeque<>();
+ private boolean preserveCallbackMutations;
PerlCalloutHandler(String input, int[] byteToChar, List callbacks,
RegexFlags outerFlags, boolean publishesControlVerbState,
@@ -1404,7 +1554,8 @@ public DynamicPatternResult executeDynamic(int id, MatchView match) {
}
Evaluation evaluation = evaluate(callback, match);
RuntimeScalar value = evaluation.result();
- if (value.type == RuntimeScalarType.UNDEF) {
+ if (value.type == RuntimeScalarType.UNDEF
+ && callback.uninitializedWarningsEnabled) {
WarnDie.warnWithCategory(
new RuntimeScalar("Use of uninitialized value"),
RuntimeScalarCache.scalarEmptyString, "uninitialized");
@@ -1440,7 +1591,10 @@ public DynamicPatternResult executeDynamic(int id, MatchView match) {
nestedCallbacks = runtimeRegex.executableCallbacks;
} else {
try {
- nestedPattern = new JoniRegexPattern(dynamicSource, outerFlags);
+ boolean byteBackedDynamic = value.type == RuntimeScalarType.BYTE_STRING;
+ boolean compileAsBytes = byteMode && byteBackedDynamic;
+ nestedPattern = new JoniRegexPattern(dynamicSource, outerFlags, 0,
+ compileAsBytes, compileAsBytes, byteBackedDynamic);
} catch (SyntaxException exception) {
String message = exception.getMessage();
if (message != null && (message.contains("premature end of char-class")
@@ -1491,6 +1645,9 @@ private Evaluation evaluate(RuntimeRegexCallback callback, MatchView match) {
}
}
CaptureSnapshot priorDynamicView = previousDynamicView;
+ if (callback.kind == RuntimeRegexCallback.Kind.BLOCK && parent == null) {
+ callbackMutations.addLast(RegexCallbackMutationSnapshot.capture(callback.code));
+ }
MatchView provisional = callback.kind == RuntimeRegexCallback.Kind.DYNAMIC
? dynamicCaptureView(match, priorDynamicView) : match;
publishProvisional(provisional);
@@ -1563,7 +1720,9 @@ public void unwind(Object value) {
@Override
public void complete(Object value) {
- restore((Token) value, true);
+ Token token = (Token) value;
+ if (token.block() && parent == null) preserveCallbackMutations = true;
+ restore(token, true);
}
@Override
@@ -1582,6 +1741,7 @@ public void finish(boolean matched) {
GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("R"))
.set(completedResult);
} else if (!matched) {
+ if (!preserveCallbackMutations) restoreCallbackMutations();
initialRegexState.restore();
if (hasFailedNestedCaptureState) {
state.lastClosedCapture = failedNestedLastClosedCapture;
@@ -1590,6 +1750,7 @@ public void finish(boolean matched) {
}
}
} finally {
+ callbackMutations.clear();
DynamicVariableManager.popToLocalLevel(initialLocalLevel);
}
}
@@ -1601,6 +1762,9 @@ private void recordFailedNestedCaptureState(String lastClosed, String lastParen)
}
void abort() {
+ // Perl retains ordinary assignments performed before a callback
+ // exception; only dynamic local() scope is unwound here.
+ callbackMutations.clear();
DynamicVariableManager.popToLocalLevel(initialLocalLevel);
}
@@ -1619,6 +1783,12 @@ private void restore(Token token, boolean completed) {
}
}
+ private void restoreCallbackMutations() {
+ while (!callbackMutations.isEmpty()) {
+ callbackMutations.removeLast().restore();
+ }
+ }
+
private static MatchView dynamicCaptureView(
MatchView current, CaptureSnapshot previous) {
CaptureSnapshot adjusted = CaptureSnapshot.of(current);
diff --git a/src/main/java/org/perlonjava/runtime/regex/PerlUnicodeScalarNameData.java b/src/main/java/org/perlonjava/runtime/regex/PerlUnicodeScalarNameData.java
new file mode 100644
index 0000000000..f60261cf9f
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/regex/PerlUnicodeScalarNameData.java
@@ -0,0 +1,152 @@
+/*
+ * Generated from the current imported Perl unicore/Name.pl.
+ * Do not edit manually; run dev/tools/generate_perl_unicode_scalar_name_data.pl.
+ *
+ * Name.pl is generated by Perl's mktables from the Unicode Character Database.
+ * Duplicate-name policy matches Perl's anchored table search: the first row wins.
+ * Distinct names for the same code point are all retained as Unicode aliases.
+ *
+ * Unicode data source copyright:
+ * © 2025 Unicode®, Inc.
+ * Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in
+ * the U.S. and other countries.
+ * For terms of use and license, see https://www.unicode.org/terms_of_use.html
+ */
+package org.perlonjava.runtime.regex;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+public final class PerlUnicodeScalarNameData {
+ public static final String SOURCE_SHA256 = "5ed5a90410345357b3bd3199d15790833d1e0d5b06b6dcf548507671bd78eacd";
+ public static final int ENTRY_COUNT = 35079;
+ public static final int DUPLICATE_NAME_COUNT = 0;
+
+ private static final String[] ENCODED = {
+ "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s4wFMRgBtIwFMhgBtYwFMxgBtowFNBgBt4wFNRgBuIwFNhgBuYwFNxgBuowFOBgBu4wFORcCvIwFMjAYAb2MBTEYAb6MBTIYAb+MBTMYAcCMBTQYAcGMBTUYAcKMBTYYAcOMBTcYAcSMBTgYAcWMBTkXAsaMBTMwBQvA9QdNSUNBTCBIRUFSVAIEk01DSE9SAgrRT0QgV0lUSCBET1QCCqLpB0dFUiBTWU1CT0wDAqBETEUFDp5TIFdJVEggUyBJTlNJREULCKRTVU5ERVJCQVIDB6DsB1JZIEZBQ0UDCqtCU1RST00gU0lHTgMLp+wHVUlTSEVEIEZBQ0UCAqVMS0gCAZzoB1QDBIiYB0VOTkEHCvbpByBXSVRIIEJBUlMDHsBSSUNMT0NLV0lTRSBDTE9TRUQgQ0lSQ0xFIEFSUk9XDw+zRE9OVE9VUiBJTlRFR1JBTA4ohOoHRE9XTldBUkRTIEFORCBVUFdBUkRTIE9QRU4gQ0lSQ0xFIEFSUk9XUw4T8k9HQVBQRUQgQ0lSQ0xFIEFSUk9XDguRVElOVEVHUkFUSU9ODhG6Q09QRU4gQ0lSQ0xFIEFSUk9XDhS2Q1RPUCBTRU1JQ0lSQ0xFIEFSUk9XDySNV1JJQU5HTEUtSEVBREVEIEJPVFRPTSBVLVNIQVBFRCBBUlJPVx4TjldMRUZUIFUtU0hBUEVEIEFSUk9XHhHvVk9QRU4gQ0lSQ0xFIEFSUk9XHhSMV1JJR0hUIFUtU0hBUEVEIEFSUk9XHhKPV1RPUCBVLVNIQVBFRCBBUlJPVwECnwFQQwIZ+kZMIEZVTkNUSU9OQUwgU1lNQk9MIEFMUEhBGwn2RiBVTkRFUkJBUhYNwEZCQUNLU0xBU0ggQkFSFhDJRkNJUkNMRSBCQUNLU0xBU0gdCeVGRElBRVJFU0lTHQO+RkpPVB0E30ZTVEFSHwO9RklMRR0I3EZVTkRFUkJBUhcI6kZPTU1BIEJBUhYN4kZERUwgRElBRVJFU0lTGgXSRlNUSUxFGgXrRlRJTERFGQjLRlRBIFNUSUxFHAjZRlVOREVSQkFSFw/aRklBTU9ORCBVTkRFUkJBUhcP8UZPV04gQ0FSRVQgVElMREUbCuZGU0hPRSBTVElMRRsIzkZUQUNLIEpPVCAIykZVTkRFUkJBUhoK1kZXQVJEUyBWQU5FFhD3RkVQU0lMT04gVU5ERVJCQVIWFulGR1JFQVRFUi1USEFOIERJQUVSRVNJUxYGtkZJLUJFQU0XA/NGT1RBGgn4RiBVTkRFUkJBUhYN5EZKT1QgRElBRVJFU0lTGgjbRlVOREVSQkFSFg/nRkxFRlQgU0hPRSBTVElMRRoKxUZXQVJEUyBWQU5FFgX1Rk9NRUdBGwn5RiBVTkRFUkJBUhYElUdRVUFEGgrCRiBCQUNLU0xBU0gbBrxGQ0lSQ0xFHATgRk9MT04bA9RGREVMHgLNRlRBHAa6RklBTU9ORB0EuUZWSURFHAnMRk9XTiBDQVJFVB8L10ZXQVJEUyBBUlJPVxsFuEZFUVVBTBsMxEZHUkVBVEVSLVRIQU4bA7tGSk9UGw/HRkxFRlRXQVJEUyBBUlJPVx0Hw0ZTUy1USEFOGwnvRk5PVCBFUVVBTBsI8EZRVUVTVElPThsQyEZSSUdIVFdBUkRTIEFSUk9XGwXBRlNMQVNIGwjTRlVQIENBUkVUHQvQRldBUkRTIEFSUk9XGAjeRk9URSBRVUFEHAjYRlVOREVSQkFSFgP0RlJITxcOxkZJR0hUV0FSRFMgVkFORRYS7kZTRU1JQ09MT04gVU5ERVJCQVIXCL9GTEFTSCBCQVIXCrdGUVVJU0ggUVVBRBcN40ZUQVIgRElBRVJFU0lTGAntRklMRSBUSUxERRYP6EZUSUxERSBESUFFUkVTSVMWDvJGVVAgQ0FSRVQgVElMREUZCN1GU0hPRSBKT1QZDuFGVEFDSyBESUFFUkVTSVMeA9VGSk9UHgfRRk9WRVJCQVIYCs9GV0FSRFMgVkFORRYF7EZaSUxERQIF5FdPTExPTgMHJ1NUUk9QSEUCCr2dB1BMRSBTWU1CT0wEF58BSUNBVElPTiBQUk9HUkFNIENPTU1BTkQDEdBEUk9BQ0hFUyBUSEUgTElNSVQFIsZEWElNQVRFTFkgQlVUIE5PVCBBQ1RVQUxMWSBFUVVBTCBUTw4R8FRFUVVBTCBPUiBFUVVBTCBUTxQCxURUTxYQ0kQgT1IgVEhFIElNQUdFIE9GAQfSTFFVQVJJVVMBGIcRUkFCSUMgQkFTRUxJTkUgUk9VTkQgRE9UCBTQnQRJQkxJQ0FMIEVORCBPRiBWRVJTRQcW/J0EQ09NQklOSU5HIEFMRUYgT1ZFUkxBWQoCjAxNQQgK5RFVUkxZIERBTU1BEgPoEVRBTg0F5BFGQVRIQRID5xFUQU4NBeYRS0FTUkESA+kRVEFOBwXPDERBTU1BDA74/AMgSVNPTEFURUQgRk9STQ0L+fwDTUVESUFMIEZPUk0NCP4RV0lUSCBET1QMA8wMVEFODw7y/AMgSVNPTEFURUQgRk9STQkMjQxURSBTRVBBUkFUT1IIEOsMRUNJTUFMIFNFUEFSQVRPUggT4hFJU1BVVEVEIEVORCBPRiBBWUFICBv7EU9VQkxFIFJJR0hUIEFSUk9XSEVBRCBBQk9WRSMJ/BEgV0lUSCBET1QOEvqdBFZFUlRJQ0FMIEJBUiBCRUxPVw0HnhFEIE1BRERBBxbrDUVNUFRZIENFTlRSRSBISUdIIFNUT1AUCOoNTE9XIFNUT1AICt0NTkQgT0YgQVlBSA4JnQxURVhUIE1BUksHBc4MRkFUSEEMDvb8AyBJU09MQVRFRCBGT1JNDQv3/ANNRURJQUwgRk9STQ0O9RFXSVRIIERPVCBBQk9WRRIE9BFSSU5HEgjeDFRXTyBET1RTDAPLDFRBTg8O8PwDIElTT0xBVEVEIEZPUk0IEO0MSVZFIFBPSU5URUQgU1RBUggOggxPT1ROT1RFIE1BUktFUggI1A1VTEwgU1RPUAcVnxFIQUxGIE1BRERBIE9WRVIgTUFEREEJCdQMTVpBIEFCT1ZFDQXVDEJFTE9XBw7XDElOVkVSVEVEIERBTU1BBwXQDEtBU1JBDA76/AMgSVNPTEFURUQgRk9STQ0L+/wDTUVESUFMIEZPUk0NDvYRV0lUSCBET1QgQkVMT1cMA80MVEFODw70/AMgSVNPTEFURUQgRk9STQcS0RFMQVJHRSBDSVJDTEUgQkVMT1cND84RUk9VTkQgRE9UIEFCT1ZFFwXPEUJFTE9XFxPSEUlOU0lERSBDSVJDTEUgQkVMT1cIE/cRRUZUIEFSUk9XSEVBRCBBQk9WRRYF+RFCRUxPVwkH1Q1UVEVSIEFFDwq7EUZSSUNBTiBGRUgWBL0RTk9PThYDvBFRQUYZFsQRIFdJVEggVEhSRUUgRE9UUyBBQk9WRQ8CuQxJThELyv0DIEZJTkFMIEZPUk0SDMv9A0lOSVRJQUwgRk9STRMMyf0DU09MQVRFRCBGT1JNEgvM/QNNRURJQUwgRk9STRIVoA1XSVRIIFRIUkVFIERPVFMgQUJPVkUiBbMRQkVMT1ciGN4OUE9JTlRJTkcgRE9XTldBUkRTIEFCT1ZFGA3dDldPIERPVFMgQUJPVkUgEN8OVkVSVElDQUxMWSBBQk9WRQ8DpwxMRUYSC479AyBGSU5BTCBGT1JNEw2N/QNJU09MQVRFRCBGT1JNEwfJDE1BS1NVUkEaC/D9AyBGSU5BTCBGT1JNGw3v/QNJU09MQVRFRCBGT1JNEwXxDFdBU0xBGAvR9gMgRklOQUwgRk9STRkN0PYDSVNPTEFURUQgRk9STRQf9RBJVEggQVRUQUNIRUQgQk9UVE9NIFJJR0hUIEtBU1JBMw79ECBBTkQgRE9UIEFCT1ZFOAmAEUxFRlQgUklORyEF8BBGQVRIQSEF9BBLQVNSQSEKghFMRUZUIEhBTVpBJgn4EFJPVU5EIERPVCELgRFSSUdIVCBIQU1aQScJ9xBST1VORCBET1QiDvYQT1VORCBET1QgQUJPVkUrBfkQQkVMT1chD/EQVE9QIFJJR0hUIEZBVEhBMA77ECBBTkQgRE9UIEFCT1ZFNQn+EExFRlQgUklORxgJ+hBET1QgQUJPVkUYJ/QORVhURU5ERUQgQVJBQklDLUlORElDIERJR0lUIFRIUkVFIEFCT1ZFNQjzDldPIEFCT1ZFGAujDEhBTVpBIEFCT1ZFIwuE/QMgRklOQUwgRk9STSQNg/0DSVNPTEFURUQgRk9STR4FpQxCRUxPVyMLiP0DIEZJTkFMIEZPUk0kDYf9A0lTT0xBVEVEIEZPUk0YEvMQTEVGVCBNSURETEUgU1RST0tFGAuiDE1BRERBIEFCT1ZFIwuC/QMgRklOQUwgRk9STSQNgf0DSVNPTEFURUQgRk9STRgT8hBSSUdIVCBNSURETEUgU1RST0tFKw78ECBBTkQgRE9UIEFCT1ZFMAn/EExFRlQgUklORxgQ8gxXQVZZIEhBTVpBIEFCT1ZFIwXzDEJFTE9XDgT7DEJFRUgSC9P2AyBGSU5BTCBGT1JNEwzU9gNJTklUSUFMIEZPUk0UDNL2A1NPTEFURUQgRk9STRML1fYDTUVESUFMIEZPUk0QAagMSBELkP0DIEZJTkFMIEZPUk0SDJH9A0lOSVRJQUwgRk9STRMMj/0DU09MQVRFRCBGT1JNEguS/QNNRURJQUwgRk9STRIj0Q5XSVRIIERPVCBCRUxPVyBBTkQgVEhSRUUgRE9UUyBBQk9WRRcLoRFIQU1aQSBBQk9WRRcW1Q5JTlZFUlRFRCBTTUFMTCBWIEJFTE9XFxC2EVNNQUxMIE1FRU0gQUJPVkUdAdYOVh4GoBEgQkVMT1cXHdAOVEhSRUUgRE9UUyBIT1JJWk9OVEFMTFkgQkVMT1ciFtIOUE9JTlRJTkcgVVBXQVJEUyBCRUxPVzgT0w4gQU5EIFRXTyBET1RTIEFCT1ZFGBvUDldPIERPVFMgQkVMT1cgQU5EIERPVCBBQk9WRRECgA1FSBML2/YDIEZJTkFMIEZPUk0UDNz2A0lOSVRJQUwgRk9STRUM2vYDU09MQVRFRCBGT1JNFAvd9gNNRURJQUwgRk9STQ4DtgxEQUQRC779AyBGSU5BTCBGT1JNEgy//QNJTklUSUFMIEZPUk0TDL39A1NPTEFURUQgRk9STRILwP0DTUVESUFMIEZPUk0SDvsNV0lUSCBET1QgQkVMT1cQA4wNSEFMEwuF9wMgRklOQUwgRk9STRQNhPcDSVNPTEFURUQgRk9STRABrwxMEQuq/QMgRklOQUwgRk9STRINqf0DSVNPTEFURUQgRk9STRIOig1XSVRIIERPVCBCRUxPVyAOiw0gQU5EIFNNQUxMIFRBSBcPkA1GT1VSIERPVFMgQUJPVkUXFtoOSU5WRVJURUQgU01BTEwgViBCRUxPVyAB7g1WFwSJDVJJTkcXGo8NVEhSRUUgRE9UUyBBQk9WRSBET1dOV0FSRFMiBa4RQkVMT1cYGMKdBFdPIERP",
+ "VFMgVkVSVElDQUxMWSBCRUxPVzAO2Q4gQU5EIFNNQUxMIFRBSA8FjQ1EQUhBTBQLg/cDIEZJTkFMIEZPUk0VDYL3A0lTT0xBVEVEIEZPUk0RAYgNTBILifcDIEZJTkFMIEZPUk0TDYj3A0lTT0xBVEVEIEZPUk0PCu4MT1RMRVNTIEJFSBYDoQ1GRUgWA+8MUUFGDwKODVVMEQuH9wMgRklOQUwgRk9STRINhvcDSVNPTEFURUQgRk9STQ8DhA1ZRUgSC/P2AyBGSU5BTCBGT1JNEwz09gNJTklUSUFMIEZPUk0UDPL2A1NPTEFURUQgRk9STRML9fYDTUVESUFMIEZPUk0OAdANRQ8L5fcDIEZJTkFMIEZPUk0QDOb3A0lOSVRJQUwgRk9STREM5PcDU09MQVRFRCBGT1JNEAvn9wNNRURJQUwgRk9STQ4JzA1GQVJTSSBZRUgXC/33AyBGSU5BTCBGT1JNGAz+9wNJTklUSUFMIEZPUk0ZDPz3A1NPTEFURUQgRk9STRgL//cDTUVESUFMIEZPUk0YK/cOV0lUSCBFWFRFTkRFRCBBUkFCSUMtSU5ESUMgRElHSVQgRk9VUiBCRUxPVzkL9g5USFJFRSBBQk9WRToI9Q5XTyBBQk9WRR0KvQxJTlZFUlRFRCBWHRC/DFRIUkVFIERPVFMgQUJPVkUeDb4MV08gRE9UUyBBQk9WRQ8CwQxFSBEL0v0DIEZJTkFMIEZPUk0SDNP9A0lOSVRJQUwgRk9STRMM0f0DU09MQVRFRCBGT1JNEgvU/QNNRURJQUwgRk9STRIOow1XSVRIIERPVCBCRUxPVyAVpBEgQU5EIFRIUkVFIERPVFMgQUJPVkUbC6INTU9WRUQgQkVMT1cXEKUNVEhSRUUgRE9UUyBCRUxPVyIW4Q5QT0lOVElORyBVUFdBUkRTIEJFTE9XGA3gDldPIERPVFMgQkVMT1cOA68NR0FGEQuT9wMgRklOQUwgRk9STRIMlPcDSU5JVElBTCBGT1JNEwyS9wNTT0xBVEVEIEZPUk0SC5X3A01FRElBTCBGT1JNEhSwEVdJVEggSU5WRVJURUQgU1RST0tFFwSwDVJJTkcXELQNVEhSRUUgRE9UUyBBQk9WRRgNsg1XTyBET1RTIEJFTE9XDwS6DEhBSU4TC879AyBGSU5BTCBGT1JNFAzP/QNJTklUSUFMIEZPUk0VDM39A1NPTEFURUQgRk9STRQL0P0DTUVESUFMIEZPUk0UDvwNV0lUSCBET1QgQkVMT1cZEMMRVEhSRUUgRE9UUyBBQk9WRQ8DyBFSQUYPA7MNVUVIEguX9wMgRklOQUwgRk9STRMMmPcDSU5JVElBTCBGT1JNFAyW9wNTT0xBVEVEIEZPUk0TC5n3A01FRElBTCBGT1JNDgOtDEhBSBELov0DIEZJTkFMIEZPUk0SDKP9A0lOSVRJQUwgRk9STRMMof0DU09MQVRFRCBGT1JNEguk/QNNRURJQUwgRk9STRIr/A5XSVRIIEVYVEVOREVEIEFSQUJJQy1JTkRJQyBESUdJVCBGT1VSIEJFTE9XFwuBDUhBTVpBIEFCT1ZFFxaKEUlOVkVSVEVEIFNNQUxMIFYgQkVMT1cXHfIOU01BTEwgQVJBQklDIExFVFRFUiBUQUggQUJPVkUwC+8OTkQgVFdPIERPVFMvBe4OQkVMT1cXEIUNVEhSRUUgRE9UUyBBQk9WRSIW2A5QT0lOVElORyBVUFdBUkRTIEJFTE9XGA3XDldPIERPVFMgQUJPVkUgDoINVkVSVElDQUwgQUJPVkUQA6EMTVpBEw6A/QMgSVNPTEFURUQgRk9STQ8CxwxFSBEMvg0gRE9BQ0hBU0hNRUUdC6v3AyBGSU5BTCBGT1JNHgys9wNJTklUSUFMIEZPUk0fDKr3A1NPTEFURUQgRk9STR4LrfcDTUVESUFMIEZPUk0SCur9A0ZJTkFMIEZPUk0SBMENR09BTBYLp/cDIEZJTkFMIEZPUk0XDKj3A0lOSVRJQUwgRk9STRgMpvcDU09MQVRFRCBGT1JNFwup9wNNRURJQUwgRk9STRcQwg1XSVRIIEhBTVpBIEFCT1ZFEgzr/QNJTklUSUFMIEZPUk0TDOn9A1NPTEFURUQgRk9STRIL7P0DTUVESUFMIEZPUk0SD/8NV0lUSCBJTlZFUlRFRCBWFwnADVlFSCBBQk9WRSALpfcDIEZJTkFMIEZPUk0hDaT3A0lTT0xBVEVEIEZPUk0PCfQMSUdIIEhBTVpBGAX1DCBBTEVGGQP2DFdBVxkD+AxZRUgOBKwMSkVFTRILnv0DIEZJTkFMIEZPUk0TDJ/9A0lOSVRJQUwgRk9STRQMnf0DU09MQVRFRCBGT1JNEwug/QNNRURJQUwgRk9STRMVxRFXSVRIIFRIUkVFIERPVFMgQUJPVkUjBcYRQkVMT1cZDaIRV08gRE9UUyBBQk9WRRABmA1IEQuL9wMgRklOQUwgRk9STRINivcDSVNPTEFURUQgRk9STQ4DwwxLQUYRC9r9AyBGSU5BTCBGT1JNEgzb/QNJTklUSUFMIEZPUk0TDNn9A1NPTEFURUQgRk9STRIL3P0DTUVESUFMIEZPUk0SDqwNV0lUSCBET1QgQUJPVkUbBbQRQkVMT1cXBKsNUklORxcQrg1USFJFRSBET1RTIEJFTE9XGA3/DldPIERPVFMgQUJPVkUgEMSdBFZFUlRJQ0FMTFkgQkVMT1cQCqAMU0hNSVJJIFlFSA8EqQ1FSEVIEwuP9wMgRklOQUwgRk9STRQMkPcDSU5JVElBTCBGT1JNFQyO9wNTT0xBVEVEIEZPUk0UC5H3A01FRElBTCBGT1JNFA7iDldJVEggRE9UIEFCT1ZFGQfCEVNNQUxMIFYZEOMOVEhSRUUgRE9UUyBBQk9WRSQFvAxCRUxPVyQW5A5QT0lOVElORyBVUFdBUkRTIEJFTE9XGg27DFdPIERPVFMgQUJPVkUiEI0RVkVSVElDQUxMWSBCRUxPVw8DrgxIQUgSC6b9AyBGSU5BTCBGT1JNEwyn/QNJTklUSUFMIEZPUk0UDKX9A1NPTEFURUQgRk9STRMLqP0DTUVESUFMIEZPUk0PCcUNSVJHSElaIE9FGAvh9wMgRklOQUwgRk9STRkN4PcDSVNPTEFURUQgRk9STRYCyQ1ZVRgL4/cDIEZJTkFMIEZPUk0ZDeL3A0lTT0xBVEVEIEZPUk0OA8QMTEFNEQve/QMgRklOQUwgRk9STRIM3/0DSU5JVElBTCBGT1JNEwzd/QNTT0xBVEVEIEZPUk0SC+D9A01FRElBTCBGT1JNEgjqDldJVEggQkFSFwm2DURPVCBBQk9WRRkIphFVQkxFIEJBUhcdxxFTTUFMTCBBUkFCSUMgTEVUVEVSIFRBSCBBQk9WRR0BtQ1WFxC3DVRIUkVFIERPVFMgQUJPVkUiBbgNQkVMT1cPB60RT1cgQUxFRg4EnAxNQVJLDwPFDEVFTRIL4v0DIEZJTkFMIEZPUk0TDOP9A0lOSVRJQUwgRk9STRQM4f0DU09MQVRFRCBGT1JNEwvk/QNNRURJQUwgRk9STRMO5Q5XSVRIIERPVCBBQk9WRRwF5g5CRUxPVxgQpxFUSFJFRSBET1RTIEFCT1ZFDgKtDU5HEAvU9wMgRklOQUwgRk9STREM1fcDSU5JVElBTCBGT1JNEgzT9wNTT0xBVEVEIEZPUk0RC9b3A01FRElBTCBGT1JNEAOxDU9FSBMLm/cDIEZJTkFMIEZPUk0UDJz3A0lOSVRJQUwgRk9STRUMmvcDU09MQVRFRCBGT1JNFAud9wNNRURJQUwgRk9STQ8DxgxPT04SC+b9AyBGSU5BTCBGT1JNEwa6DUdIVU5OQRkLn/cDIEZJTkFMIEZPUk0aDZ73A0lTT0xBVEVEIEZPUk0TDOf9A0lOSVRJQUwgRk9STRQM5f0DU09MQVRFRCBGT1JNEwvo/QNNRURJQUwgRk9STRMOuQ1XSVRIIERPVCBCRUxPVxgQiRFJTlZFUlRFRCBTTUFMTCBWGAS8DVJJTkccBo8RIEFCT1ZFGAnoDlNNQUxMIFRBSB4B6Q5WGBC9DVRIUkVFIERPVFMgQUJPVkUZDecOV08gRE9UUyBCRUxPVw8Dgw1ZRUgSC/f2AyBGSU5BTCBGT1JNEwz49gNJTklUSUFMIEZPUk0UDPb2A1NPTEFURUQgRk9STRML+fYDTUVESUFMIEZPUk0OAsYNT0UQC9r3AyBGSU5BTCBGT1JNEQ3Z9wNJU09MQVRFRCBGT1JNDgP+DFBFSBEL1/YDIEZJTkFMIEZPUk0SDNj2A0lOSVRJQUwgRk9STRMM1vYDU09MQVRFRCBGT1JNEgvZ9gNNRURJQUwgRk9STRIVtxFXSVRIIFNNQUxMIE1FRU0gQUJPVkUdAb4RVhECpg1FSBML7/YDIEZJTkFMIEZPUk0UDPD2A0lOSVRJQUwgRk9STRUM7vYDU09MQVRFRCBGT1JNFAvx9gNNRURJQUwgRk9STQ4DwgxRQUYRC9b9AyBGSU5BTCBGT1JNEgzX/QNJTklUSUFMIEZPUk0TDNX9A1NPTEFURUQgRk9STRIL2P0DTUVESUFMIEZPUk0SDqcNV0lUSCBET1QgQUJPVkUbBaURQkVMT1cgErURIEFORCBOTyBET1RTIEFCT1ZFFxCoDVRIUkVFIERPVFMgQUJPVkUOA7EMUkVIEQuu/QMgRklOQUwgRk9STRINrf0DSVNPTEFURUQgRk9STRIOlA1XSVRIIERPVCBCRUxPVyAOlg0gQU5EIERPVCBBQk9WRRcPmQ1GT1VSIERPVFMgQUJPVkUXC+wOSEFNWkEgQUJPVkUXCu8NSU5WRVJURUQgVhcEqhFMT09QFwSTDVJJTkcXJPEOU01BTEwgQVJBQklDIExFVFRFUiBUQUggQU5EIFRXTyBET1RTHQq5EU5PT04gQUJPVkUdAZINVh4GlQ0gQkVMT1cYBdsOVFJPS0UXDpcNVFdPIERPVFMgQUJPVkUgEOsOVkVSVElDQUxMWSBBQk9WRQ8Euw1OT09OEwuh9wMgRklOQUwgRk9STRQMovcDSU5JVElBTCBGT1JNFQyg9wNTT0xBVEVEIEZPUk0UC6P3A01FRElBTCBGT1JNDwusEU9ISU5HWUEgWUVIDwORDVJFSBILjfcDIEZJTkFMIEZPUk0TDYz3A0lTT0xBVEVEIEZPUk0OA7UMU0FEEQu6/QMgRklOQUwgRk9STRIMu/0DSU5JVElBTCBGT1JNEwy5/QNTT0xBVEVEIEZPUk0SC7z9A01FRElBTCBGT1JNEhWeDVdJVEggVEhSRUUgRE9UUyBBQk9WRSIFrxFCRUxPVxgNnQ1XTyBET1RTIEJFTE9XDwOzDEVFThILsv0DIEZJTkFMIEZPUk0TDLP9A0lOSVRJQUwgRk9STRQMsf0DU09MQVRFRCBGT1JNEwu0/QNNRURJQUwgRk9STRMcmg1XSVRIIERPVCBCRUxPVyBBTkQgRE9UIEFCT1ZFGCb9DkVYVEVOREVEIEFSQUJJQy1JTkRJQyBESUdJVCBGT1VSIEFCT1ZFGA/cDkZPVVIgRE9UUyBBQk9WRRgK/g5JTlZFUlRFRCBWGCTwDlNNQUxMIEFSQUJJQyBMRVRURVIgVEFIIEFORCBUV08gRE9UUxgQmw1USFJFRSBET1RTIEJFTE9XKBWcDSBBTkQgVEhSRUUgRE9UUyBBQk9WRRkY7Q5XTyBET1RTIFZFUlRJQ0FMTFkgQUJPVkUPBLQMSEVFThMLtv0DIEZJTkFMIEZPUk0UDLf9A0lOSVRJQUwgRk9STRUMtf0DU09MQVRFRCBGT1JNFAu4/QNNRURJQUwgRk9STRQO+g1XSVRIIERPVCBCRUxPVw8LsRFUUkFJR0hUIFdBVw8P8AxVUEVSU0NSSVBUIEFMRUYPCKoNV0FTSCBLQUYOA7cMVEFIEQvC/QMgRklOQUwgRk9STRIMw/0DSU5JVElBTCBGT1JNEwzB/QNTT0xBVEVEIEZPUk0SC8T9A01FRElBTCBGT1JNEg6LEVdJVEggRE9UIEJFTE9XFxCfDVRIUkVFIERPVFMgQUJPVkUiBYwRQkVMT1cYDaMRV08gRE9UUyBBQk9WRSAQw50EVkVSVElDQUxMWSBCRUxPVw8Ehg1DSEVIEwv79gMgRklOQUwgRk9STRQM/PYDSU5JVElBTCBGT1JNFQz69gNTT0xBVEVEIEZPUk0UC/32A01FRElBTCBGT1JNFA6/DVdJVEggRE9UIEFCT1ZFGQfBEVNNQUxMIFYTAocNRUgVC//2AyBGSU5BTCBGT1JNFgyA9wNJTklUSUFMIEZPUk0XDP72A1NPTEFURUQgRk9STRYLgfcDTUVESUFMIEZPUk0PAqoMRUgRC5b9AyBGSU5BTCBGT1JNEgyX/QNJTklUSUFMIEZPUk0TDJX9A1NPTEFURUQgRk9STRIHqQxNQVJCVVRBGQuU/QMgRklOQUwgRk9STRoEww1HT0FMGg2T/QNJU09MQVRFRCBGT1JNEwqY/QNFRElBTCBGT1JNEgn8DFdJVEggUklORxcPuBFTTUFMTCBURUggQUJPVkUdAb8RVhca/QxUSFJFRSBET1RTIEFCT1ZFIERPV05XQVJEUxEC/wxFSBML4/YDIEZJTkFMIEZPUk0UDOT2A0lOSVRJQUwgRk9STRUM4vYDU09MQVRFRCBGT1JNFAvl9gNNRURJQUwgRk9STQ8DsAxIQUwSC6z9AyBGSU5BTCBGT1JNEw2r/QNJU09MQVRFRCBGT1JNEAKrDEVIEgua/QMgRklOQUwgRk9STRMMm/0DSU5JVElBTCBGT1JNFAyZ/QNTT0xBVEVEIEZPUk0TC5z9A01FRElBTCBGT1JNEAfGnQRJTiBOT09OEwOGEVlFSA8D+QxURUgSC+f2AyBGSU5BTCBGT1JNEwzo9gNJTklUSUFMIEZPUk0UDOb2A1NPTEFURUQgRk9STRML6fYDTUVESUFMIEZPUk0TDMARV0lUSCBTTUFMTCBWEgL6DEVIFAvf9gMgRklOQUwgRk9STRUM4PYDSU5JVElBTCBGT1JNFgze9gNTT0xBVEVEIEZPUk0VC+H2A01FRElBTCBGT1JNDgHHDVUPC9j3AyBGSU5BTCBGT1JNEA3X9wNJU09MQVRFRCBGT1JNEBD3DFdJVEggSEFNWkEgQUJPVkUgDt33AyBJU09MQVRFRCBGT1JNDy7o9wNJR0hVUiBLQVpBS0ggS0lSR0hJWiBBTEVGIE1BS1NVUkEgSU5JVElBTCBGT1JNMQvp9wNNRURJQUwgRk9STQ4Cyw1WRRAL3/cDIEZJTkFMIEZPUk0RDd73A0lTT0xBVEVEIEZPUk0QAaQNSBEL6/YDIEZJTkFMIEZPUk0SDOz2A0lOSVRJQUwgRk9STRMM6vYDU09MQVRFRCBGT1JNEgvt9gNNRURJQUwgRk9STQ4DyAxXQVcRC+79AyBGSU5BTCBGT1JNEg3t/QNJU09MQVRFRCBGT1JNEg7PDVdJVEggRE9UIEFCT1ZFGwarEVdJVEhJThcn+Q5FWFRFTkRFRCBBUkFCSUMtSU5ESUMgRElHSVQgVEhSRUUgQUJPVkU0CPgOV08gQUJPVkUXC6QMSEFNWkEgQUJPVkUiC4b9AyBGSU5BTCBGT1JNIw2F/QNJU09MQVRFRCBGT1JNFwTEDVJJTkcXDsoNVFdPIERPVFMgQUJPVkUOA8oMWUVIEQfSDSBCQVJSRUUYC6/3AyBGSU5BTCBGT1JNGQ2u9wNJU09MQVRFRCBGT1JNGSz7DldJVEggRVhURU5ERUQgQVJBQklDLUlORElDIERJR0lUIFRIUkVFIEFCT1ZFOwj6DldPIEFCT1ZFHgvTDUhBTVpBIEFCT1ZFKQux9wMgRklOQUwgRk9STSoNsPcDSVNPTEFURUQgRk9S",
+ "TRIK8v0DRklOQUwgRk9STRIM8/0DSU5JVElBTCBGT1JNEwzx/QNTT0xBVEVEIEZPUk0SC/T9A01FRElBTCBGT1JNEhTHnQRXSVRIIEZPVVIgRE9UUyBCRUxPVxcLpgxIQU1aQSBBQk9WRSILiv0DIEZJTkFMIEZPUk0jDIv9A0lOSVRJQUwgRk9STSQMif0DU09MQVRFRCBGT1JNIwuM/QNNRURJQUwgRk9STRcHzg1TTUFMTCBWFwTNDVRBSUwYD9ENSFJFRSBET1RTIEJFTE9XGBupEVdPIERPVFMgQkVMT1cgQU5EIERPVCBBQk9WRSoLqBFIQU1aQSBBQk9WRSoQuhFTTUFMTCBOT09OIEFCT1ZFDwHIDVUQC9z3AyBGSU5BTCBGT1JNEQ3b9wNJU09MQVRFRCBGT1JNDgO4DFpBSBELxv0DIEZJTkFMIEZPUk0SDMf9A0lOSVRJQUwgRk9STRMMxf0DU09MQVRFRCBGT1JNEgvI/QNNRURJQUwgRk9STRACsgxJThILsP0DIEZJTkFMIEZPUk0TDa/9A0lTT0xBVEVEIEZPUk0TFbIRV0lUSCBJTlZFUlRFRCBWIEFCT1ZFCCiT+gNJR0FUVVJFIEFJTiBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJg33+QNJU09MQVRFRCBGT1JNGRG6+QNKRUVNIElOSVRJQUwgRk9STR8MqfgDU09MQVRFRCBGT1JNHhT1+gNXSVRIIE1FRU0gRklOQUwgRk9STSgMxPsDSU5JVElBTCBGT1JNGRG7+QNNRUVNIElOSVRJQUwgRk9STR8MqvgDU09MQVRFRCBGT1JNHhz4+gNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNIw/2+gNNRUVNIEZJTkFMIEZPUk0oDPf6A0lOSVRJQUwgRk9STSMOtvsDWUVIIEZJTkFMIEZPUk0ZDpT6A1lFSCBGSU5BTCBGT1JNHQ34+QNJU09MQVRFRCBGT1JNESHR9wNKSkFMIEFMTEFBSFUgRkFSQUpBSFUgQVNILVNIQVJFRUYeHM37A1RBQUFMQUEgRkFSQUpBSFUgQVNILVNIQVJFRUYREvP7A0tCQVIgSVNPTEFURUQgRk9STREQzfoDTEFZSEFBIEFTLVNBTEFBTSAN0Z0EVFUgV0FTLVNBTEFBTRUP9/sDRSBJU09MQVRFRCBGT1JNFQvS9wNJIEFSLVJBSE1BSBgIx/oDUy1TQUxBQU0fDcr6A1RVIFdBUy1TQUxBQU0WC8j6A00gQVMtU0FMQUFNIA3SnQRUVSBXQVMtU0FMQUFNFwzJ+gNBQSBBUy1TQUxBQU0iDdOdBFRVIFdBUy1TQUxBQU0SK5D5A0VGIE1BS1NVUkEgV0lUSCBTVVBFUlNDUklQVCBBTEVGIEZJTkFMIEZPUk0zDd34A0lTT0xBVEVEIEZPUk0VGLz6A1dJVEggRkFUSEFUQU4gRklOQUwgRk9STSMNvfoDSVNPTEFURUQgRk9STRIR8vsDTEFIIElTT0xBVEVEIEZPUk0RC//7A1paQSBXQSBKQUxMECDu+ANCRUggV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSYNifgDSVNPTEFURUQgRk9STRkQnfkDSEFIIElOSVRJQUwgRk9STR4MhvgDU09MQVRFRCBGT1JNHRPC+wNXSVRIIFlFSCBGSU5BTCBGT1JNGg+g+QNFSCBJTklUSUFMIEZPUk0dC+L5A01FRElBTCBGT1JNGRGc+QNKRUVNIElOSVRJQUwgRk9STR8MhfgDU09MQVRFRCBGT1JNGRGe+QNLSEFIIElOSVRJQUwgRk9STR8Mh/gDU09MQVRFRCBGT1JNHhOe+wNXSVRIIFlFSCBGSU5BTCBGT1JNGQ/s+ANNRUVNIEZJTkFMIEZPUk0eDJ/5A0lOSVRJQUwgRk9STR8MiPgDU09MQVRFRCBGT1JNHgvh+QNNRURJQUwgRk9STRkP7fgDTk9PTiBGSU5BTCBGT1JNGQ7q+ANSRUggRklOQUwgRk9STRkO7/gDWUVIIEZJTkFMIEZPUk0dDYr4A0lTT0xBVEVEIEZPUk0ZD+v4A1pBSU4gRklOQUwgRk9STREc/fsDSVNNSUxMQUggQVItUkFITUFOIEFSLVJBSEVFTRATxPcDREFBTUFUIEJBUkFLQUFUVUhVTRIeo/oDRCBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJg2H+gNJU09MQVRFRCBGT1JNGRC1+QNIQUggSU5JVElBTCBGT1JNHgyj+ANTT0xBVEVEIEZPUk0dHO76A1dJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0iDqv7A1lFSCBGSU5BTCBGT1JNGRG0+QNKRUVNIElOSVRJQUwgRk9STR8MovgDU09MQVRFRCBGT1JNGRG2+QNLSEFIIElOSVRJQUwgRk9STR8MpPgDU09MQVRFRCBGT1JNHhTv+gNXSVRIIE1FRU0gRklOQUwgRk9STSgM8PoDSU5JVElBTCBGT1JNGRG3+QNNRUVNIElOSVRJQUwgRk9STR8MpfgDU09MQVRFRCBGT1JNGQ6s+gNSRUggRklOQUwgRk9STR0NkPoDSVNPTEFURUQgRk9STRkOpPoDWUVIIEZJTkFMIEZPUk0dDYj6A0lTT0xBVEVEIEZPUk0QIPz4A0ZFSCBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJg2x+ANJU09MQVRFRCBGT1JNGRC/+QNIQUggSU5JVElBTCBGT1JNHgyu+ANTT0xBVEVEIEZPUk0ZEb75A0pFRU0gSU5JVElBTCBGT1JNHwyt+ANTT0xBVEVEIEZPUk0ZEcD5A0tIQUggSU5JVElBTCBGT1JNHwyv+ANTT0xBVEVEIEZPUk0eFPz6A1dJVEggTUVFTSBGSU5BTCBGT1JNKAz9+gNJTklUSUFMIEZPUk0ZEcH5A01FRU0gSU5JVElBTCBGT1JNHwyw+ANTT0xBVEVEIEZPUk0eE8H7A1dJVEggWUVIIEZJTkFMIEZPUk0ZDv34A1lFSCBGSU5BTCBGT1JNHQ2y+ANJU09MQVRFRCBGT1JNECKV+gNHSEFJTiBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNKA35+QNJU09MQVRFRCBGT1JNGxG8+QNKRUVNIElOSVRJQUwgRk9STSEMq/gDU09MQVRFRCBGT1JNGxG9+QNNRUVNIElOSVRJQUwgRk9STSEMrPgDU09MQVRFRCBGT1JNIBz7+gNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJQ/5+gNNRUVNIEZJTkFMIEZPUk0lDvr6A1lFSCBGSU5BTCBGT1JNGw6W+gNZRUggRklOQUwgRk9STR8N+vkDSVNPTEFURUQgRk9STRAPzPcDSEFGSVpBSFUgQUxMQUFIHwnN9wNVIFRBQUFMQUEYEc73A00gQUxMQUFIVSBUQUFBTEFBGRLP9wNBQSBBTExBQUhVIFRBQUFMQUESHpv6A0ggV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSYN//kDSVNPTEFURUQgRk9STRkRqfkDSkVFTSBJTklUSUFMIEZPUk0fDJf4A1NPTEFURUQgRk9STR4Tv/sDV0lUSCBZRUggRklOQUwgRk9STRkRqvkDTUVFTSBJTklUSUFMIEZPUk0fDJj4A1NPTEFURUQgRk9STR4c2/oDV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSMO2voDWUVIIEZJTkFMIEZPUk0ZDpz6A1lFSCBGSU5BTCBGT1JNHQ2A+gNJU09MQVRFRCBGT1JNESLT+ANFSCBXSVRIIEFMRUYgTUFLU1VSQSBJU09MQVRFRCBGT1JNGRHX+QNKRUVNIElOSVRJQUwgRk9STR8M0fgDU09MQVRFRCBGT1JNGRHY+QNNRUVNIElOSVRJQUwgRk9STR8M0vgDU09MQVRFRCBGT1JNHhaT+wNXSVRIIEpFRU0gSU5JVElBTCBGT1JNIxGU+wNNRUVNIElOSVRJQUwgRk9STRkd2fkDU1VQRVJTQ1JJUFQgQUxFRiBJTklUSUFMIEZPUk0ZEdT4A1lFSCBJU09MQVRFRCBGT1JNEA3D9wNKQUxMQSBXQS1BTEFBFQr7+wNKQUxBTE9VSE9VESCd+gNFRU0gV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STScNgfoDSVNPTEFURUQgRk9STRoQp/kDSEFIIElOSVRJQUwgRk9STR8MlfgDU09MQVRFRCBGT1JNHhym+wNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNIw6++wNZRUggRklOQUwgRk9STRoRqPkDTUVFTSBJTklUSUFMIEZPUk0gDJb4A1NPTEFURUQgRk9STR8cp/sDV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSQO2PoDSEFIIEZJTkFMIEZPUk0oDNn6A0lOSVRJQUwgRk9STSQOpfsDWUVIIEZJTkFMIEZPUk0aDp76A1lFSCBGSU5BTCBGT1JNHg2C+gNJU09MQVRFRCBGT1JNEBiA+QNLQUYgV0lUSCBBTEVGIEZJTkFMIEZPUk0eDbf4A0lTT0xBVEVEIEZPUk0eEoP5A01BS1NVUkEgRklOQUwgRk9STSYNvfgDSVNPTEFURUQgRk9STRkQxfkDSEFIIElOSVRJQUwgRk9STR4MufgDU09MQVRFRCBGT1JNGRHE+QNKRUVNIElOSVRJQUwgRk9STR8MuPgDU09MQVRFRCBGT1JNGRHG+QNLSEFIIElOSVRJQUwgRk9STR8MuvgDU09MQVRFRCBGT1JNGQ6B+QNMQU0gRklOQUwgRk9STR0Mx/kDSU5JVElBTCBGT1JNHgy7+ANTT0xBVEVEIEZPUk0dC+v5A01FRElBTCBGT1JNGQ+C+QNNRUVNIEZJTkFMIEZPUk0eDMj5A0lOSVRJQUwgRk9STR8MvPgDU09MQVRFRCBGT1JNHgvs+QNNRURJQUwgRk9STR4Uu/sDV0lUSCBNRUVNIEZJTkFMIEZPUk0oDMP7A0lOSVRJQUwgRk9STSMOt/sDWUVIIEZJTkFMIEZPUk0ZDoT5A1lFSCBGSU5BTCBGT1JNHQ2++ANJU09MQVRFRCBGT1JNEhTO+wNSUkFNQSBBTExBQUhVIFdBSkhBSBEgn/oDSEFIIFdJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0nDYP6A0lTT0xBVEVEIEZPUk0aEZr4A0hBSCBJU09MQVRFRCBGT1JNGhGr+QNKRUVNIElOSVRJQUwgRk9STSAMmfgDU09MQVRFRCBGT1JNGhGs+QNNRUVNIElOSVRJQUwgRk9STSAMm/gDU09MQVRFRCBGT1JNGg6g+gNZRUggRklOQUwgRk9STR4NhPoDSVNPTEFURUQgRk9STRAY/P0DTEFNIFdJVEggQUxFRiBGSU5BTCBGT1JNHg37/QNJU09MQVRFRCBGT1JNHhKG+QNNQUtTVVJBIEZJTkFMIEZPUk0mDcP4A0lTT0xBVEVEIEZPUk0eG/j9A1dJVEggSEFNWkEgQUJPVkUgRklOQUwgRk9STS8N9/0DSVNPTEFURUQgRk9STSkQ+v0DQkVMT1cgRklOQUwgRk9STS8N+f0DSVNPTEFURUQgRk9STSMW9v0DTUFEREEgQUJPVkUgRklOQUwgRk9STS8N9f0DSVNPTEFURUQgRk9STRkQyvkDSEFIIElOSVRJQUwgRk9STR4MwPgDU09MQVRFRCBGT1JNHRyC+wNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNIg+A+wNNRUVNIEZJTkFMIEZPUk0nDLX7A0lOSVRJQUwgRk9STSIOgfsDWUVIIEZJTkFMIEZPUk0aD835A0VIIElOSVRJQUwgRk9STRkRyfkDSkVFTSBJTklUSUFMIEZPUk0fDL/4A1NPTEFURUQgRk9STR4UhPsDV0lUSCBKRUVNIEZJTkFMIEZPUk0oDIP7A0lOSVRJQUwgRk9STSMPvPsDTUVFTSBGSU5BTCBGT1JNKAy6+wNJTklUSUFMIEZPUk0jDqz7A1lFSCBGSU5BTCBGT1JNGRHL+QNLSEFIIElOSVRJQUwgRk9STR8MwfgDU09MQVRFRCBGT1JNHhSF+wNXSVRIIE1FRU0gRklOQUwgRk9STSgMhvsDSU5JVElBTCBGT1JNGQ+F+QNNRUVNIEZJTkFMIEZPUk0eDMz5A0lOSVRJQUwgRk9STR8MwvgDU09MQVRFRCBGT1JNHgvt+QNNRURJQUwgRk9STR4Th/sDV0lUSCBIQUggRklOQUwgRk9STScMiPsDSU5JVElBTCBGT1JNIw6t+wNZRUggRklOQUwgRk9STRkOh/kDWUVIIEZJTkFMIEZPUk0dDcT4A0lTT0xBVEVEIEZPUk0QGYj5A01FRU0gV0lUSCBBTEVGIEZJTkFMIEZPUk0fFcn4A01BS1NVUkEgSVNPTEFURUQgRk9STRoQz/kDSEFIIElOSVRJQUwgRk9STR8MxvgDU09MQVRFRCBGT1JNHhaJ+wNXSVRIIEpFRU0gSU5JVElBTCBGT1JNIxGK+wNNRUVNIElOSVRJQUwgRk9STSMOi/sDWUVIIEZJTkFMIEZPUk0aEc75A0pFRU0gSU5JVElBTCBGT1JNIAzF+ANTT0xBVEVEIEZPUk0fFYz7A1dJVEggSEFIIElOSVRJQUwgRk9STSQRkvsDS0hBSCBJTklUSUFMIEZPUk0kEY37A01FRU0gSU5JVElBTCBGT1JNJA7A+wNZRUggRklOQUwgRk9STRoR0PkDS0hBSCBJTklUSUFMIEZPUk0gDMf4A1NPTEFURUQgRk9STR8WjvsDV0lUSCBKRUVNIElOSVRJQUwgRk9STSQRj/sDTUVFTSBJTklUSUFMIEZPUk0kDrn7A1lFSCBGSU5BTCBGT1JNGg+J+QNNRUVNIEZJTkFMIEZPUk0fDNH5A0lOSVRJQUwgRk9STSAMyPgDU09MQVRFRCBGT1JNHxOx+wNXSVRIIFlFSCBGSU5BTCBGT1JNGhHK+ANZRUggSVNPTEFURUQgRk9STREV9PsDT0hBTU1BRCBJU09MQVRFRCBGT1JNEBjYnQROQVdXQVJBIEFMTEFBSFUgTUFSUUFEQUgRII75A09PTiBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJw3P+ANJU09MQVRFRCBGT1JNGhDT+QNIQUggSU5JVElBTCBGT1JNHwzM+ANTT0xBVEVEIEZPUk0eHJb7A1dJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0jEZX7A01FRU0gSU5JVElBTCBGT1JNIw6z+wNZRUggRklOQUwgRk9STRsP1vkDRUggSU5JVElBTCBGT1JNHgvv+QNNRURJQUwgRk9STRoR0vkDSkVFTSBJTklUSUFMIEZPUk0gDMv4A1NPTEFURUQgRk9STR8cmfsDV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSQOvfsDSEFIIEZJTkFMIEZPUk0oDLj7A0lOSVRJQUwgRk9STSQPl/sDTUVFTSBGSU5BTCBGT1JNKQyY+wNJTklUSUFMIEZPUk0kDsf7A1lFSCBGSU5BTCBGT1JNGhHU+QNLSEFIIElOSVRJQUwgRk9STSAMzfgDU09MQVRFRCBGT1JNGg+M+QNNRUVNIEZJTkFMIEZPUk0fDNX5A0lOSVRJQUwgRk9STSAMzvgDU09MQVRFRCBGT1JNHwvu+QNNRURJQUwgRk9STR8cm/sDV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSQOmvsDWUVIIEZJTkFMIEZPUk0aD435A05PT04gRklOQUwgRk9STRoOivkDUkVI",
+ "IEZJTkFMIEZPUk0aDo/5A1lFSCBGSU5BTCBGT1JNHg3Q+ANJU09MQVRFRCBGT1JNGg+L+QNaQUlOIEZJTkFMIEZPUk0QFtSdBFFBRERBU0EgQUxMQUFIVSBTSVJSQUgSHv74A0YgV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSYNtfgDSVNPTEFURUQgRk9STRkQwvkDSEFIIElOSVRJQUwgRk9STR4Ms/gDU09MQVRFRCBGT1JNGRHD+QNNRUVNIElOSVRJQUwgRk9STR8MtPgDU09MQVRFRCBGT1JNHhP++gNXSVRIIEhBSCBGSU5BTCBGT1JNJwy0+wNJTklUSUFMIEZPUk0jD//6A01FRU0gRklOQUwgRk9STSMOsvsDWUVIIEZJTkFMIEZPUk0ZDv/4A1lFSCBGSU5BTCBGT1JNHQ22+ANJU09MQVRFRCBGT1JNEirx+wNMQSBVU0VEIEFTIEtPUkFOSUMgU1RPUCBTSUdOIElTT0xBVEVEIEZPUk0RDcv6A1VERElTQSBTSVJSQUgcBNWdBFVIVU0gAtadBEFBFwzXnQRUIEFTUkFBUlVIVU0QEMH6A1JBREkgQUxMQUFIVSBBTkggAsL6A0FBIALD+gNVTSICxPoDQUEhA8X6A05OQR0LyfsDVEFBQUxBQSBBTkgoAsr7A0FBKALL9wNVTSoCy/sDQUESDcD6A0hJTUFIVSBBTExBQUgfCMj7AyBUQUFBTEFBGAjP+gNNIEFMTEFBSCAJyPcDVSBUQUFBTEFBGQnJ9wNBQSBBTExBQUgiCcr3A1UgVEFBQUxBQRMSkPsDTUFUVSBBTExBQUhJIEFMQVlIJQKR+wNBQSUCxvcDSU0nAsf3A0FBIA3F9wNUQUFBTEFBIEFMQVlIEhL2+wNTT1VMIElTT0xBVEVEIEZPUk0RJtz4A0VIIFdJVEggU1VQRVJTQ1JJUFQgQUxFRiBJU09MQVRFRCBGT1JNECCh+gNTQUQgV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSYNhfoDSVNPTEFURUQgRk9STRkQsfkDSEFIIElOSVRJQUwgRk9STR4MoPgDU09MQVRFRCBGT1JNHRPk+gNXSVRIIEhBSCBGSU5BTCBGT1JNJgzl+gNJTklUSUFMIEZPUk0iDqn7A1lFSCBGSU5BTCBGT1JNGRGy+QNLSEFIIElOSVRJQUwgRk9STRkRs/kDTUVFTSBJTklUSUFMIEZPUk0fDKH4A1NPTEFURUQgRk9STR4U5voDV0lUSCBNRUVNIEZJTkFMIEZPUk0oDMX7A0lOSVRJQUwgRk9STRkOq/oDUkVIIEZJTkFMIEZPUk0dDY/6A0lTT0xBVEVEIEZPUk0ZDqL6A1lFSCBGSU5BTCBGT1JNHQ2G+gNJU09MQVRFRCBGT1JNEg/P+wNMQUFNVUhVIEFMQVlOQUEUD/X7A00gSVNPTEFURUQgRk9STRMQ+fsDTEEgSVNPTEFURUQgRk9STRYn8PsDVVNFRCBBUyBLT1JBTklDIFNUT1AgU0lHTiBJU09MQVRFRCBGT1JNFRbG+gNMTEFBSFUgQUxBWUhJIFdBLUFBTElIHBjQ9wNUQUFBTEFBIEFMQVlISSBXQS1TQUxMQU0YE/r7A0hPVSBBTEFZSEUgV0FTQUxMQU0ZIsz7A1UgQUxBWUhJIFdBLUFMQUEgQUFMSUhFRSBXQS1TQUxMQU0kEcz6A0FBTElIRUUgV0EtU0FMTEFNESCX+gNFRU4gV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STScN+/kDSVNPTEFURUQgRk9STRoQrvkDSEFIIElOSVRJQUwgRk9STR8MnfgDU09MQVRFRCBGT1JNHgu1+gNNRURJQUwgRk9STR4W3PoDV0lUSCBKRUVNIElOSVRJQUwgRk9STRsPsfoDRUggSU5JVElBTCBGT1JNHgvo+QNNRURJQUwgRk9STRoRrfkDSkVFTSBJTklUSUFMIEZPUk0gDJz4A1NPTEFURUQgRk9STR8LtPoDTUVESUFMIEZPUk0fHN76A1dJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0kEN36A0hBSCBJTklUSUFMIEZPUk0aEa/5A0tIQUggSU5JVElBTCBGT1JNIAye+ANTT0xBVEVEIEZPUk0fC7b6A01FRElBTCBGT1JNHxyo+wNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJA7G+wNZRUggRklOQUwgRk9STRoRsPkDTUVFTSBJTklUSUFMIEZPUk0gDJ/4A1NPTEFURUQgRk9STR8L5/kDTUVESUFMIEZPUk0fE9/6A1dJVEggSEFIIEZJTkFMIEZPUk0oDOD6A0lOSVRJQUwgRk9STSQR4foDSkVFTSBJTklUSUFMIEZPUk0kD+L6A01FRU0gRklOQUwgRk9STSkM4/oDSU5JVElBTCBGT1JNGg6q+gNSRUggRklOQUwgRk9STR4NjvoDSVNPTEFURUQgRk9STRoOmPoDWUVIIEZJTkFMIEZPUk0eDfz5A0lTT0xBVEVEIEZPUk0RHuH4A0hBRERBIFdJVEggREFNTUEgSVNPTEFURUQgRk9STSIL8/kDTUVESUFMIEZPUk0hEd74A1RBTiBJU09MQVRFRCBGT1JNHBPg+ANGQVRIQSBJU09MQVRFRCBGT1JNIgvy+QNNRURJQUwgRk9STRwT4vgDS0FTUkEgSVNPTEFURUQgRk9STSIL9PkDTUVESUFMIEZPUk0hEd/4A1RBTiBJU09MQVRFRCBGT1JNHB7j+ANTVVBFUlNDUklQVCBBTEVGIElTT0xBVEVEIEZPUk0SIJn6A0VFTiBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNKA39+QNJU09MQVRFRCBGT1JNGw6m+gNIQUggRklOQUwgRk9STR8MrvoDSU5JVElBTCBGT1JNIAyK+gNTT0xBVEVEIEZPUk0fC7j6A01FRElBTCBGT1JNHxTn+gNXSVRIIE1FRU0gRklOQUwgRk9STSkM6PoDSU5JVElBTCBGT1JNJA6q+wNZRUggRklOQUwgRk9STRwPsvoDRUggSU5JVElBTCBGT1JNHwvq+QNNRURJQUwgRk9STRsPpfoDSkVFTSBGSU5BTCBGT1JNIAyt+gNJTklUSUFMIEZPUk0hDIn6A1NPTEFURUQgRk9STSALt/oDTUVESUFMIEZPUk0gE+n6A1dJVEggWUVIIEZJTkFMIEZPUk0bD6f6A0tIQUggRklOQUwgRk9STSAMr/oDSU5JVElBTCBGT1JNIQyL+gNTT0xBVEVEIEZPUk0gC7n6A01FRElBTCBGT1JNGw+o+gNNRUVNIEZJTkFMIEZPUk0gDLD6A0lOSVRJQUwgRk9STSEMjPoDU09MQVRFRCBGT1JNIAvp+QNNRURJQUwgRk9STSAU6voDV0lUSCBLSEFIIEZJTkFMIEZPUk0qDOv6A0lOSVRJQUwgRk9STSUP7PoDTUVFTSBGSU5BTCBGT1JNKgzt+gNJTklUSUFMIEZPUk0bDqn6A1JFSCBGSU5BTCBGT1JNHw2N+gNJU09MQVRFRCBGT1JNGw6a+gNZRUggRklOQUwgRk9STR8N/vkDSVNPTEFURUQgRk9STREU/vsDVUJIQUFOQUhVIFdBIFRBQUFMQUEQFM76A1RBQkFBUkFLQSBXQS1UQUFBTEFBEh6R+gNIIFdJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0mDfX5A0lTT0xBVEVEIEZPUk0ZELj5A0hBSCBJTklUSUFMIEZPUk0eDKb4A1NPTEFURUQgRk9STRkRs/oDTUVFTSBJTklUSUFMIEZPUk0fDKf4A1NPTEFURUQgRk9STR4LuvoDTUVESUFMIEZPUk0eE/H6A1dJVEggSEFIIEZJTkFMIEZPUk0nDPL6A0lOSVRJQUwgRk9STSMR8/oDTUVFTSBJTklUSUFMIEZPUk0jDvT6A1lFSCBGSU5BTCBGT1JNGQ6S+gNZRUggRklOQUwgRk9STR0N9vkDSVNPTEFURUQgRk9STREf9PgDRUggV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSYNj/gDSVNPTEFURUQgRk9STRkQovkDSEFIIElOSVRJQUwgRk9STR4MjPgDU09MQVRFRCBGT1JNHRTR+gNXSVRIIEpFRU0gRklOQUwgRk9STScM0voDSU5JVElBTCBGT1JNIhHT+gNNRUVNIElOSVRJQUwgRk9STRoPpfkDRUggSU5JVElBTCBGT1JNHQvk+QNNRURJQUwgRk9STRkRofkDSkVFTSBJTklUSUFMIEZPUk0fDIv4A1NPTEFURUQgRk9STR4coPsDV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STSMR0PoDTUVFTSBJTklUSUFMIEZPUk0jDp/7A1lFSCBGSU5BTCBGT1JNGRGj+QNLSEFIIElOSVRJQUwgRk9STR8MjfgDU09MQVRFRCBGT1JNHhyi+wNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNIxHU+gNNRUVNIElOSVRJQUwgRk9STSMOofsDWUVIIEZJTkFMIEZPUk0ZD/L4A01FRU0gRklOQUwgRk9STR4MpPkDSU5JVElBTCBGT1JNHwyO+ANTT0xBVEVEIEZPUk0eC+P5A01FRElBTCBGT1JNHhyk+wNXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNIxDW+gNIQUggSU5JVElBTCBGT1JNIxHV+gNKRUVNIElOSVRJQUwgRk9STSMR1/oDS0hBSCBJTklUSUFMIEZPUk0jDqP7A1lFSCBGSU5BTCBGT1JNGQ/z+ANOT09OIEZJTkFMIEZPUk0ZDvD4A1JFSCBGSU5BTCBGT1JNGQ71+ANZRUggRklOQUwgRk9STR0NkPgDSVNPTEFURUQgRk9STRkP8fgDWkFJTiBGSU5BTCBGT1JNESfb+ANIQUwgV0lUSCBTVVBFUlNDUklQVCBBTEVGIElTT0xBVEVEIEZPUk0SH/r4A0VIIFdJVEggQUxFRiBNQUtTVVJBIEZJTkFMIEZPUk0nDZP4A0lTT0xBVEVEIEZPUk0aD+b5A0hFSCBNRURJQUwgRk9STRoSkfgDSkVFTSBJU09MQVRFRCBGT1JNGg/4+ANNRUVNIEZJTkFMIEZPUk0fDKb5A0lOSVRJQUwgRk9STSAMkvgDU09MQVRFRCBGT1JNHwvl+QNNRURJQUwgRk9STRoP+fgDTk9PTiBGSU5BTCBGT1JNGg72+ANSRUggRklOQUwgRk9STRoO+/gDWUVIIEZJTkFMIEZPUk0eDZT4A0lTT0xBVEVEIEZPUk0aD/f4A1pBSU4gRklOQUwgRk9STRBA+vcDVUlHSFVSIEtJUkdISVogWUVIIFdJVEggSEFNWkEgQUJPVkUgV0lUSCBBTEVGIE1BS1NVUkEgRklOQUwgRk9STUYM+/cDSU5JVElBTCBGT1JNRwz59wNTT0xBVEVEIEZPUk0QFvj7A1dBU0FMTEFNIElTT0xBVEVEIEZPUk0QIJX5A1lFSCBXSVRIIEFMRUYgTUFLU1VSQSBGSU5BTCBGT1JNJg3Z+ANJU09MQVRFRCBGT1JNGRDb+QNIQUggSU5JVElBTCBGT1JNHgzW+ANTT0xBVEVEIEZPUk0dE677A1dJVEggWUVIIEZJTkFMIEZPUk0bHO33A01aQSBBQk9WRSBXSVRIIEFFIEZJTkFMIEZPUk0tDez3A0lTT0xBVEVEIEZPUk0rDuv3A0xFRiBGSU5BTCBGT1JNLw3q9wNJU09MQVRFRCBGT1JNLxLo+ANNQUtTVVJBIEZJTkFMIEZPUk03DYP4A0lTT0xBVEVEIEZPUk0qDPf3A0UgRklOQUwgRk9STSwM+PcDSU5JVElBTCBGT1JNLQz29wNTT0xBVEVEIEZPUk0qEJj5A0hBSCBJTklUSUFMIEZPUk0vDIH4A1NPTEFURUQgRk9STSsPm/kDRUggSU5JVElBTCBGT1JNLgvg+QNNRURJQUwgRk9STSoRl/kDSkVFTSBJTklUSUFMIEZPUk0wDID4A1NPTEFURUQgRk9STSoRmfkDS0hBSCBJTklUSUFMIEZPUk0qD+b4A01FRU0gRklOQUwgRk9STS8MmvkDSU5JVElBTCBGT1JNMAyC+ANTT0xBVEVEIEZPUk0vC9/5A01FRElBTCBGT1JNKg/n+ANOT09OIEZJTkFMIEZPUk0qDfP3A09FIEZJTkFMIEZPUk0tDfL3A0lTT0xBVEVEIEZPUk0qDuT4A1JFSCBGSU5BTCBGT1JNKgzx9wNVIEZJTkFMIEZPUk0sDfD3A0lTT0xBVEVEIEZPUk0qDu/3A1dBVyBGSU5BTCBGT1JNLg3u9wNJU09MQVRFRCBGT1JNKg7p+ANZRUggRklOQUwgRk9STS4NhPgDSVNPTEFURUQgRk9STSsM9fcDVSBGSU5BTCBGT1JNLQ309wNJU09MQVRFRCBGT1JNKg/l+ANaQUlOIEZJTkFMIEZPUk0aD975A0VIIElOSVRJQUwgRk9STR0L8fkDTUVESUFMIEZPUk0ZEdr5A0pFRU0gSU5JVElBTCBGT1JNHwzV+ANTT0xBVEVEIEZPUk0eE6/7A1dJVEggWUVIIEZJTkFMIEZPUk0ZEdz5A0tIQUggSU5JVElBTCBGT1JNHwzX+ANTT0xBVEVEIEZPUk0ZD5P5A01FRU0gRklOQUwgRk9STR4M3fkDSU5JVElBTCBGT1JNHwzY+ANTT0xBVEVEIEZPUk0eC/D5A01FRElBTCBGT1JNHhSc+wNXSVRIIE1FRU0gRklOQUwgRk9STSgMnfsDSU5JVElBTCBGT1JNIw6w+wNZRUggRklOQUwgRk9STRkPlPkDTk9PTiBGSU5BTCBGT1JNGQ6R+QNSRUggRklOQUwgRk9STRkOlvkDWUVIIEZJTkFMIEZPUk0dDdr4A0lTT0xBVEVEIEZPUk0ZD5L5A1pBSU4gRklOQUwgRk9STRAaufkDWkFIIFdJVEggTUVFTSBJTklUSUFMIEZPUk0fDKj4A1NPTEFURUQgRk9STR4Lu/oDTUVESUFMIEZPUk0HDJwRTUFEREEgV0FBSklCDAfTDEggQUJPVkUJDtgMUksgTk9PTiBHSFVOTkEMFP8RU0lERVdBWVMgTk9PTiBHSFVOTkEJDo/cB1RIRU1BVElDQUwgQUlOFQOA3AdMRUYUA4HcB0JFSBQDmdwHREFEFgGD3AdMFQqc3AdPVExFU1MgQkVIHAOe3AdGRUgcBJ3cB05PT04cA5/cB1FBRhYPr90HVUJMRS1TVFJVQ0sgQUlOIgOh3QdCRUgiA7ndB0RBRCQBo90HTCIDsN0HRkVIIgW73QdHSEFJTiIDp90HSEFIIgSi3QdKRUVNIgS33QdLSEFIIgOr3QdMQU0iBKzdB01FRU0iBK3dB05PT04iA7LdB1FBRiIDs90HUkVIIgOx3QdTQUQjA67dB0VFTiMEtN0HSEVFTiIDqN0HVEFIIwK13QdFSCMDuN0HSEFMJAK23QdFSCIDpd0HV0FXIgOp3QdZRUgiA7rdB1pBSCQCpt0HSU4UA5DcB0ZFSBQFm9wHR0hBSU4UA4fcB0hBSBQLr9wHSU5JVElBTCBBSU4cA6HcB0JFSBwDudwHREFEHAOw3AdGRUgcBbvcB0dIQUlOHAOn3AdIQUgdAqTcB0VIHASi3AdKRUVNHAOq3AdLQUYdA7fcB0hBSBwDq9wHTEFNHASs3AdNRUVNHASt3AdOT09OHAOy3AdRQUYcA7HcB1NBRB0DrtwHRUVOHQS03AdIRUVOHAO13AdURUgdA7bcB0hFSBwDqdwHWUVIFASC3AdKRUVNFAOK3AdLQUYVA5fcB0hBSBQDi9wHTEFNFQmP3QdPT1BFRCBBSU4cA4DdB0xF",
+ "RhsDgd0HQkVIGwOZ3QdEQUQdAYPdB0wbA5DdB0ZFSBsFm90HR0hBSU4bA4fdB0hBSBwChN0HRUgbBILdB0pFRU0bBJfdB0tIQUgbA4vdB0xBTRsEjN0HTUVFTRsEjd0HTk9PThsDkt0HUUFGGwOT3QdSRUgbA5HdB1NBRBwDjt0HRUVOHASU3QdIRUVOGwOI3QdUQUgcApXdB0VIHAOY3QdIQUwdApbdB0VIGwOF3QdXQVcbA4ndB1lFSBsDmt0HWkFIHQKG3QdJThQEjNwHTUVFTRQEjdwHTk9PThQV8d0HT1BFUkFUT1IgSEFIIFdJVEggREFMHRrw3QdNRUVNIFdJVEggSEFIIFdJVEggVEFUV0VFTBQDktwHUUFGFAOT3AdSRUgUA5HcB1NBRBUDjtwHRUVOFQSU3AdIRUVOFQzv3AdUUkVUQ0hFRCBBSU4eA+HcB0JFSB4D+dwHREFEHwr83AdPVExFU1MgQkVIJgP+3AdGRUgeA/DcB0ZFSB4F+9wHR0hBSU4eA+fcB0hBSB8C5NwHRUgeBOLcB0pFRU0eA+rcB0tBRh8D99wHSEFIHgTs3AdNRUVNHgTt3AdOT09OHgPy3AdRQUYeA/HcB1NBRB8D7twHRUVOHwT03AdIRUVOHgPo3AdUQUgfAvXcB0VIHwP23AdIRUgeA+ncB1lFSB4D+twHWkFIFAOI3AdUQUgWCM/cB0lMRUQgQUlOGwPZ3AdEQUQcC93cB09UTEVTUyBOT09OIwPf3AdRQUYbBdvcB0dIQUlOGwPH3AdIQUgbBMLcB0pFRU0bBNfcB0tIQUgbA8vcB0xBTRsEzdwHTk9PThsD0twHUUFGGwPR3AdTQUQcA87cB0VFThwE1NwHSEVFThsDydwHWUVIFQKV3AdFSBUDmNwHSEFMFgKW3AdFSBQDhdwHV0FXFAOJ3AdZRUgUA5rcB1pBSBYChtwHSU4HEYUMTlVNQkVSIE1BUksgQUJPVkUOBIAMU0lHTgcN8RFPUEVOIERBTU1BVEFODAjwEUZBVEhBVEFODAjyEUtBU1JBVEFOBwWXEVBFUEVUCQrqDFJDRU5UIFNJR04IEZERSUFTVFJFIE1BUksgQUJPVkUIDukNTEFDRSBPRiBTQUpEQUgIEI4MT0VUSUMgVkVSU0UgU0lHTgkOkBFVTkQgTUFSSyBBQk9WRQcNnwxRVUVTVElPTiBNQVJLBxCIEVJBSVNFRCBST1VORCBET1QJAYgMWQgN3QxFVkVSU0VEIERBTU1BCBT4EUlHSFQgQVJST1dIRUFEIEFCT1ZFHAn9ESBXSVRIIERPVBcF+hFCRUxPVwgj7A1PVU5ERUQgSElHSCBTVE9QIFdJVEggRklMTEVEIENFTlRSRQcJmwxTRU1JQ09MT04IBdEMSEFEREENDvz8AyBJU09MQVRFRCBGT1JNDgv9/ANNRURJQUwgRk9STQgTkQxJR04gQUxBWUhFIEFTU0FMTEFNDAWPDE1JU1JBDBGTDFJBREkgQUxMQUhPVSBBTkhVDhCSDEhNQVRVTExBSCBBTEFZSEUMBYMMU0FGSEEOGpAMTExBTExBSE9VIEFMQVlIRSBXQVNTQUxMQU0OBIQMTVZBVA4DgQxOQUgND/0NSU5ESEkgQU1QRVJTQU5EExD+DVBPU1RQT1NJVElPTiBNRU4MCZQMVEFLSEFMTFVTCAqZDE1BTEwgREFNTUENCckRRkFSU0kgWUVIDwOYDFRIQQ0I1hFISUdIIEFJThIU4Q1ET1RMRVNTIEhFQUQgT0YgS0hBSBIJyhFGQVJTSSBZRUgTDuART09UTk9URSBNQVJLRVISBNoNSkVFTRII2Q1MQU0gQUxFRhMelgxJR0FUVVJFIEFMRUYgV0lUSCBMQU0gV0lUSCBZRUglCpYMWUVIIEJBUlJFRRse1w1RQUYgV0lUSCBMQU0gV0lUSCBBTEVGIE1BS1NVUkEbHtYNU0FEIFdJVEggTEFNIFdJVEggQUxFRiBNQUtTVVJBEgXkDU1BRERBExDYDUVFTSBJTklUSUFMIEZPUk0YDOINU09MQVRFRCBGT1JNEgToDU5PT04WC9gRIFdJVEggS0FTUkESA9cRUUFGEgzfDVJPVU5ERUQgWkVSTxID1RFTQUQTA9wNRUVOEwnhEUlHTiBTQUZIQRIDlQxUQUgTCdsNSFJFRSBET1RTEhjgDVVQUklHSFQgUkVDVEFOR1VMQVIgWkVSTxID8xFXQVcTCpgRT1JEIEFMLUpVWhgG3BFOLU5JU0YYBdQRUi1SVUIYB9sRUy1TQUpEQRgL2hFUSC1USEFMQVRIQRcD3hFRSUYXA8wRU0FIGQPdEUtUQRcF3xFXQVFGQRID5w1ZRUgVG8sRIEJBUlJFRSBXSVRIIFRXTyBET1RTIEJFTE9XEgPNEVpBSBQClwxJTg0FmgxLQVNSQQ0I7Q1MT1cgTUVFTREE+50ETk9PThUL2REgV0lUSCBLQVNSQREE4w1TRUVOEQPTEVdBVxIKmhFPUkQgSU1BQUxBFwaZEVNITUFBTRYF/50ETUFEREEWBP6dBFFBU1IWBf2dBFNBS1RBFgebEVRBU0hFRUwNA+UNV0FXDQPmDVlFSBAbxZ0EIEJBUlJFRSBXSVRIIFRXTyBET1RTIEJFTE9XCBPeDVRBUlQgT0YgUlVCIEVMIEhJWkIIDdYMVUJTQ1JJUFQgQUxFRgkD0gxLVU4MBtARIEJFTE9XDQ3+/ANJU09MQVRFRCBGT1JNDQv//ANNRURJQUwgRk9STQkYnRFQRVJTQ1JJUFQgQUxFRiBNT0tIQVNTQVMID7L3A1lNQk9MIERPVCBBQk9WRRIFs/cDQkVMT1cQF7z3A1VCTEUgVkVSVElDQUwgQkFSIEJFTE9XDg+69wNGT1VSIERPVFMgQUJPVkUYBbv3A0JFTE9XDgS/9wNSSU5HDg/A9wNTTUFMTCBUQUggQUJPVkUYBcH3A0JFTE9XDhC29wNUSFJFRSBET1RTIEFCT1ZFGQW39wNCRUxPVxkYuPcDUE9JTlRJTkcgRE9XTldBUkRTIEFCT1ZFLAW59wNCRUxPVw8NtPcDV08gRE9UUyBBQk9WRRcFtfcDQkVMT1cXEL33A1ZFUlRJQ0FMTFkgQUJPVkUiBb73A0JFTE9XDgvC9wNXQVNMQSBBQk9WRQcN8/wDVEFJTCBGUkFHTUVOVAkFwAxUV0VFTA4U8fwDIFdJVEggRkFUSEFUQU4gQUJPVkUUEIMRT1ZFUlNUUlVDSyBIQU1aQR8DhBFXQVcUDoURVFdPIERPVFMgQkVMT1cIEuwMSE9VU0FORFMgU0VQQVJBVE9SCA7sEU9ORSBMT09QIEFCT1ZFEQXvEUJFTE9XDA3qEU9ORSBET1QgQUJPVkUUBe0RQkVMT1cMDusRVFdPIERPVFMgQUJPVkUVBe4RQkVMT1cIGp4MUklQTEUgRE9UIFBVTkNUVUFUSU9OIE1BUksIEeMRVVJORUQgREFNTUEgQkVMT1cHDY4RVkVSVElDQUwgVEFJTAgT3AxPV0VMIFNJR04gRE9UIEJFTE9XEhbbDElOVkVSVEVEIFNNQUxMIFYgQUJPVkUS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B1ZFUkFHRSBCT1gBDsG4BEhBSUtTVUtJIERBTkRBCwrYuARJR0lUIEVJR0hUEATVuARGSVZFEQPUuARPVVIQBNm4BE5JTkUQA9G4BE9ORRAF17gEU0VWRU4RAta4BElYEAXTuARUSFJFRREC0rgEV08QBNC4BFpFUk8LC8K4BE9VQkxFIERBTkRBCgzEuARHQVAgRklMTEVSLTEVAcW4BDIKEuy4BEhVTkRSRURTIFVOSVQgTUFSSwoIgLgETEVUVEVSIEESAYG4BEESAYu4BEkSAY24BFURAqS4BEJBEgKluARIQRECk7gEQ0ESApS4BEhBEQKfuAREQRICmrgEREETApu4BEhBEgKguARIQREBirgERRECkLgER0ESApG4BEhBEQKuuARIQREBgrgESRIBg7gESREClbgESkESApa4BEhBEQKOuARLQRICj7gESEERAqm4BExBEQKmuARNQRECobgETkESApK4BEdBEgKcuAROQRICl7gEWUERAYy4BE8RAqK4BFBBEgKjuARIQRECqLgEUkERAq24BFNBEgKruARIQRICrLgEU0ERAp24BFRBEgKeuARIQRICmLgEVEETApm4BEhBEQGEuARVEgGFuARVEQKquARWQRIIiLgET0NBTElDIEwZAYa4BFIaAYe4BFIRAqe4BFlBCgzhuAROVU1CRVIgRUlHSFQWAeq4BFkRBee4BEZJRlRZEwLeuARWRRIE5rgET1JUWRMC3bgEVVIRBOK4BE5JTkUVAuu4BFRZEQPauARPTkURBeC4BFNFVkVOFgLpuARUWRIC37gESVgUAui4BFRZEQPjuARURU4SBeW4BEhJUlRZEwPcuARSRUUSBeS4BFdFTlRZEwHbuARPCg29uARTSUdOIEFOVVNWQVJBEAfAuARWQUdSQUhBDwu8uARDQU5EUkFCSU5EVQ8Gv7gEVklSQU1BEQW+uARTQVJHQQoNr7gEVk9XRUwgU0lHTiBBQRYBubgESRYBu7gEVRUBuLgERRUBsLgESRYBsbgESRUBurgETxUBsrgEVRYBs7gEVRUJtrgEVk9DQUxJQyBMHQG0uARSHgG1uARSCg7DuARXT1JEIFNFUEFSQVRPUgEGsu0HSUNZQ0xFBgO07QdJU1QCEflTRyBSRVZFUlNFIFNPTElEVVMEB/hTU09MSURVUwIE2egHS0lOSQII4vMHTExFRCBDQVAEBbHnB0lBUkRTAgyjTE9IQVpBUkQgU0lHTgICpugHUkQDCoLnB1RIREFZIENBS0UCA6zzB1NPTgIKv0FUQ09JTiBTSUdOAwfm9QdJTkcgTElQAQvTU0xBQ0sgQk9XVElFBhGsTkNFTlRSRSBXSElURSBTVEFSBwrX9AdIRVNTIEFMRklMDAbdTEJJU0hPUBIXkvQHIFJPVEFURUQgTklORVRZIERFR1JFRVMbG7z0B1RXTyBIVU5EUkVEIFNFVkVOVFkgREVHUkVFUwwKyfQHRVFVSUhPUFBFUhYXzPQHIFJPVEFURUQgTklORVRZIERFR1JFRVMMBNb0B0ZFUloMBNpMS0lORxAXj/QHIFJPVEFURUQgTklORVRZIERFR1JFRVMZG7n0B1RXTyBIVU5EUkVEIFNFVkVOVFkgREVHUkVFUw0F3kxOSUdIVBIbh/QHIFJPVEFURUQgRk9SVFktRklWRSBERUdSRUVTGw6T9AdOSU5FVFkgREVHUkVFUxsfnPQHT05FIEhVTkRSRUQgVEhJUlRZLUZJVkUgREVHUkVFUxsdxvQHVEhSRUUgSFVORFJFRCBGSUZURUVOIERFR1JFRVMcGr30B1dPIEhVTkRSRUQgU0VWRU5UWSBERUdSRUVTJxOx9AdUV0VOVFktRklWRSBERUdSRUVTEgfT9ActQklTSE9QEwXR9AdRVUVFThME0vQHUk9PSwwE30xQQVdOEBeU9AcgUk9UQVRFRCBOSU5FVFkgREVHUkVFUxkbvvQHVFdPIEhVTkRSRUQgU0VWRU5UWSBERUdSRUVTDAXbTFFVRUVOEReQ9AcgUk9UQVRFRCBOSU5FVFkgREVHUkVFUxobuvQHVFdPIEhVTkRSRUQgU0VWRU5UWSBERUdSRUVTDATcTFJPT0sQF5H0ByBST1RBVEVEIE5JTkVUWSBERUdSRUVTGRu79AdUV08gSFVORFJFRCBTRVZFTlRZIERFR1JFRVMMDaf0B1RVUk5FRCBCSVNIT1ATBKT0B0tJTkcUBaj0B05JR0hUEwSp9AdQQVdOEwWl9AdRVUVFThMEpvQHUk9PSwcFz0tJUkNMRQwL+kcgRk9SIFJFQ09SRA0P7VNXSVRIIERPV04gQVJST1cSDolNVFdPIFdISVRFIERPVFMSD4hNV0hJVEUgRE9UIFJJR0hUBwjjTExVQiBTVUlUBw7oTVJPU1MgT04gU0hJRUxEByOoV1VSVkVEIERPV05XQVJEUyBBTkQgTEVGVFdBUkRTIEFSUk9XGxCpV1JJR0hUV0FSRFMgQVJST1cNHa5XTEVGVFdBUkRTIEFORCBET1dOV0FSRFMgQVJST1cbDaxXVVBXQVJEUyBBUlJPVw0er1dSSUdIVFdBUkRTIEFORCBET1dOV0FSRFMgQVJST1ccDa1XVVBXQVJEUyBBUlJPVw0bqldVUFdBUkRTIEFORCBMRUZUV0FSRFMgQVJST1cZEKtXUklHSFRXQVJEUyBBUlJPVwYHxktESUFNT05EDQjBVyBDRU5UUkVEDg3WTk1JTlVTIFdISVRFIFgOCN5XT04gQ1JPU1MOBOZMU1VJVA4P6lNXSVRIIERPV04gQVJST1cHG6PrB09XTiBQT0lOVElORyBCQUNLSEFORCBJTkRFWAoZ7EctUE9JTlRJTkcgRE9VQkxFIFRSSUFOR0xFFBiD7wdJU09TQ0VMRVMgUklHSFQgVFJJQU5HTEUUDr5LU01BTEwgVFJJQU5HTEUUCLxLVFJJQU5HTEUKG59XV0FSRFMgRVFVSUxBVEVSQUwgQVJST1dIRUFEBwzDTVJBVUdIVFMgS0lORw8Dwk1NQU4IBaLmB09QTEVUBgSRTUZMQUcIBr9OT1JFVFRFBwW/6wdPTERFUggPpk5VUiBQT0lOVEVEIFNUQVIGFqrrB0hBUkQgU0hFTEwgRkxPUFBZIERJU0sHBKTrB0VBUlQLBeVMIFNVSVQIBaJWWEFHT04HEaxWT1JJWk9OVEFMIEVMTElQU0UIB9dTVVJHTEFTUwYMpFZMQVJHRSBDSVJDTEUSG+uYByBNSU5VUyBMRUZUIFFVQVJURVIgU0VDVElPThoU7pgHT1dFUiBRVUFSVEVSIFNFQ1RJT04ZFe2YB1JJR0hUIFFVQVJURVIgU0VDVElPThkV7JgHVVBQRVIgUVVBUlRFUiBTRUNUSU9ODAabVlNRVUFSRQcO2E1FRlQgTEFORSBNRVJHRQsXnOsHUE9JTlRJTkcgQkFDS0hBTkQgSU5ERVgUBZpMSU5ERVgKGepHLVBPSU5USU5HIERPVUJMRSBUUklBTkdMRSMS7kcgV0lUSCBWRVJUSUNBTCBCQVIUGIDvB0lTT1NDRUxFUyBSSUdIVCBUUklBTkdMRRQHxEtQT0lOVEVSFA7CS1NNQUxMIFRSSUFOR0xFFAjAS1RSSUFOR0xFCgzMQFdBUkRTIEJVTExFVBAVnFdFUVVJTEFURVJBTCBBUlJPV0hFQUQHEuNLT1dFUiBMRUZUIFRSSUFOR0xFDA7iS1JJR0hUIFRSSUFOR0xFCAXrU1pFTkdFBg6lVk1FRElVTSBESUFNT05EDhX3R09XTi1QT0lOVElORyBUUklBTkdMRSMIxlcgQ0VOVFJFRA0W9EdMRUZULVBPSU5USU5HIFRSSUFOR0xFIwjHVyBDRU5UUkVEDganVk9aRU5HRQ0X9UdSSUdIVC1QT0lOVElORyBUUklBTkdMRSQIyFcgQ0VOVFJFRA0Nme8HU01BTEwgRElBTU9ORBMHn+8HTE9aRU5HRRMG/ktTUVVBUkUOBfxLUVVBUkUNFPZHVVAtUE9JTlRJTkcgVFJJQU5HTEUhCMVXIENFTlRSRUQHCrhNT09OIExJTElUSAYM75gHTkVVVFJBTCBGQUNFBwKSTklCBgfEV09DVEFHT04GDbBLUEFSQUxMRUxPR1JBTQcG8ucHRU5OQU5UCQWfVlRBR09OBwaI6wdVU0hQSU4GFtNOUVVFU1RJT04gTUFSSyBPUk5BTUVOVAYJrEtSRUNUQU5HTEUHHJ3rB0lHSFQgUE9JTlRJTkcgQkFDS0hBTkQgSU5ERVgVBZtMSU5ERVgMDrOdB1RSSUFOR0xFIENBUkVUCxnpRy1QT0lOVElORyBET1VCTEUgVFJJQU5HTEUkEu1HIFdJVEgg",
+ "VkVSVElDQUwgQkFSFRiC7wdJU09TQ0VMRVMgUklHSFQgVFJJQU5HTEUVCNNWUEVOVEFHT04WBrpLT0lOVEVSFQ64S1NNQUxMIFRSSUFOR0xFFQi2S1RSSUFOR0xFHRnvRyBXSVRIIERPVUJMRSBWRVJUSUNBTCBCQVILC6FPV0FSRFMgQVJST1cWBKRPSEVBRBEGzUBCVUxMRVQRFZ5XRVFVSUxBVEVSQUwgQVJST1dIRUFEBwb25wdPU0VUVEUGD4BOU0FGRVRZIFNDSVNTT1JTBweCTkNJU1NPUlMHCpdMSE9HSSBQSUVDRQcT8eoHS1VMTCBBTkQgQ1JPU1NCT05FUwcUhO8HTElHSFRMWSBTTUFMTCBDSVJDTEUVBo3vB1NRVUFSRQcMqVZNQUxMIERJQU1PTkQMB6pWTE9aRU5HRQwGqktTUVVBUkUNA9FWVEFSDBPN9wdVUC1QT0lOVElORyBDSEVWUk9OCAq7TElMSU5HIEZBQ0UHBsdNTk9XTUFOBwngTFBBREUgU1VJVAcFoEtRVUFSRQwHsuoHIEJVVFRPTg0HwFdDRU5UUkVEDQj5R0ZPUiBTVE9QBwOFTFRBUgcMgExVTiBXSVRIIFJBWVMGCY5MVEVMRVBIT05FBwuX7wdJTlkgRElBTU9ORAsHne8HTE9aRU5HRQsGjO8HU1FVQVJFBxL/6gdPVUNIVE9ORSBURUxFUEhPTkUHBN9NUlVDSwcX1k1XTy1XQVkgTEVGVCBXQVkgVFJBRkZJQwYa+0xVTklWRVJTQUwgUkVDWUNMSU5HIFNZTUJPTAcZousHUCBQT0lOVElORyBCQUNLSEFORCBJTkRFWAgZ60ctUE9JTlRJTkcgRE9VQkxFIFRSSUFOR0xFEhiB7wdJU09TQ0VMRVMgUklHSFQgVFJJQU5HTEUSDrRLU01BTEwgVFJJQU5HTEUSCLJLVFJJQU5HTEUIEeRLUEVSIExFRlQgVFJJQU5HTEUMDuVLUklHSFQgVFJJQU5HTEUIG51XV0FSRFMgRVFVSUxBVEVSQUwgQVJST1dIRUFEBhCuVlZFUlRJQ0FMIEVMTElQU0UPCa5LUkVDVEFOR0xFCQ+Y7wdZIFNNQUxMIERJQU1PTkQRB57vB0xPWkVOR0URBp1WU1FVQVJFBRu2Ty1GRUFUSEVSRUQgTk9SVEggRUFTVCBBUlJP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DAihRklOVEVHUkFMDBDbXExFRlQgUEFSRU5USEVTSVMQDd2YBy1GQUNJTkcgUk9CT1QZDe2bB1VOTkVSIEZSQU1FLTElAe+bBzIMEdxcUklHSFQgUEFSRU5USEVTSVMRDd+YBy1GQUNJTkcgUk9CT1QaDembB1VOTkVSIEZSQU1FLTEmAeubBzIMD+ebB1NUQU5ESU5HIFBFUlNPTgch6vcHSlVTVElGSUVEIFVQUEVSIEhBTEYgQkxBQ0sgQ0lSQ0xFHAzi9wdXSElURSBDSVJDTEUHC55GTEVGVCBDT1JORVINA41GUk9QDAykXEhBTEYgQlJBQ0tFVAwq7fcHSlVTVElGSUVEIFVQUEVSIFJJR0hUIFFVQVJURVIgQkxBQ0sgQ0lSQ0xFBwvdR1BBUkVOVEhFU0lTBwyfRlJJR0hUIENPUk5FUg4DjEZST1ANDKVcSEFMRiBCUkFDS0VUDSnu9wdKVVNUSUZJRUQgVVBQRVIgTEVGVCBRVUFSVEVSIEJMQUNLIENJUkNMRQcOtUdTUVVBUkUgQlJBQ0tFVBUYtkcgT1ZFUiBUT1AgU1FVQVJFIEJSQUNLRVQHFuFHVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVAIFkOkHVVFVRVQHC5XnByBPRiBGTE9XRVJTAgv55wdXIEFORCBBUlJPVwMMz+oHTCBPRiBIWUdJRUlBBQrj8gdXSVRIIFNQT09OBAOz5wdJTkcDA8hFVElFBhXRUyBXSVRIIExFRlQgSEFMRiBCTEFDSwwQ0lNSSUdIVCBIQUxGIEJMQUNLAkWJnAdYIERSQVdJTkdTIERPVUJMRSBESUFHT05BTCBMT1dFUiBMRUZUIFRPIE1JRERMRSBDRU5UUkUgVE8gTE9XRVIgUklHSFQdGaCYB1VQUEVSIExFRlQgVE8gTE9XRVIgUklHSFQrHIqcB01JRERMRSBDRU5UUkUgVE8gVVBQRVIgUklHSFQjE5+YB1JJR0hUIFRPIExPV0VSIExFRlQVEuZKT1dOIEFORCBIT1JJWk9OVEFMHQTXSkxFRlQdBdRKUklHSFQUCtBKSE9SSVpPTlRBTBQR6UpVUCBBTkQgSE9SSVpPTlRBTBsE3UpMRUZUGwXaSlJJR0hUFAjRSlZFUlRJQ0FMHA/sSiBBTkQgSE9SSVpPTlRBTCEE40pMRUZUIQXgSlJJR0hUDx/lSldOIERPVUJMRSBBTkQgSE9SSVpPTlRBTCBTSU5HTEUdC9ZKTEVGVCBTSU5HTEUdDNNKUklHSFQgU0lOR0xFEhqwSkhFQVZZIEFORCBIT1JJWk9OVEFMIExJR0hUHAqSSkxFRlQgTElHSFQhCKdKVVAgTElHSFQcC45KUklHSFQgTElHSFQiCJ9KVVAgTElHSFQcE8FKVVAgSE9SSVpPTlRBTCBMSUdIVBIar0pMSUdIVCBBTkQgSE9SSVpPTlRBTCBIRUFWWRwKkUpMRUZUIEhFQVZZIQipSlVQIEhFQVZZHAuNSlJJR0hUIEhFQVZZIgihSlVQIEhFQVZZHBPHSlVQIEhPUklaT05UQUwgSEVBVlkSHORKU0lOR0xFIEFORCBIT1JJWk9OVEFMIERPVUJMRR0L1UpMRUZUIERPVUJMRR0M0kpSSUdIVCBET1VCTEUNHM1KSEVBVlkgRE9VQkxFIERBU0ggSE9SSVpPTlRBTB8Iz0pWRVJUSUNBTBUC+0pXThcPs0ogQU5EIEhPUklaT05UQUwcBJNKTEVGVBwFj0pSSUdIVBMKgUpIT1JJWk9OVEFMEwT4SkxFRlQXEP5KIEFORCBMSUdIVCBSSUdIVBMZiUpRVUFEUlVQTEUgREFTSCBIT1JJWk9OVEFMIgiLSlZFUlRJQ0FMEwX6SlJJR0hUExaFSlRSSVBMRSBEQVNIIEhPUklaT05UQUwfCIdKVkVSVElDQUwTAvlKVVAVD7tKIEFORCBIT1JJWk9OVEFMGgSbSkxFRlQbCf9KSUdIVCBET1dOGgWXSlJJR0hUEwiDSlZFUlRJQ0FMGw/LSiBBTkQgSE9SSVpPTlRBTCAEq0pMRUZUIAWjSlJJR0hUDSLFSkxFRlQgRE9XTiBIRUFWWSBBTkQgUklHSFQgVVAgTElHSFQSGq1KSEVBVlkgQU5EIFJJR0hUIERPV04gTElHSFQiCLVKVVAgTElHSFQiDr1KVkVSVElDQUwgTElHSFQSGrJKTElHSFQgQU5EIFJJR0hUIERPV04gSEVBVlkiCLpKVVAgSEVBVlkiDspKVkVSVElDQUwgSEVBVlkSHcNKVVAgSEVBVlkgQU5EIFJJR0hUIERPV04gTElHSFQOFu5KSUdIVCBBUkMgRE9XTiBBTkQgTEVGVCAF7UpSSUdIVBcL70pVUCBBTkQgTEVGVB4F8EpSSUdIVBMVnpgHQk9UVE9NIEFORCBMT1dFUiBMRUZUEw7zSkRJQUdPTkFMIENST1NTHAeu9wdESUFNT05EHCra9wdMT1dFUiBMRUZUIFRPIE1JRERMRSBDRU5UUkUgVE8gTE9XRVIgUklHSFQqG973B1VQUEVSIENFTlRSRSBUTyBMT1dFUiBSSUdIVBwbovcHTUlERExFIExFRlQgVE8gTE9XRVIgQ0VOVFJFNxCm9wcgVE8gTUlERExFIFJJR0hUMQXT9wdSSUdIVCscp/cHVVBQRVIgQ0VOVFJFIFRPIE1JRERMRSBSSUdIVEcQrPcHIFRPIExPV0VSIENFTlRSRSMVo/cHUklHSFQgVE8gTE9XRVIgQ0VOVFJFMgTQ9wdMRUZULCut9wdVUFBFUiBDRU5UUkUgVE8gTUlERExFIExFRlQgVE8gTE9XRVIgQ0VOVFJFHBrX9wdVUFBFUiBDRU5UUkUgVE8gTE9XRVIgTEVGVDIF1fcHUklHSFQsC6D3B01JRERMRSBMRUZUNyGo9wcgQU5EIE1JRERMRSBSSUdIVCBUTyBMT1dFUiBDRU5UUkU4D6T3B1RPIExPV0VSIENFTlRSRUcQq/cHIFRPIE1JRERMRSBSSUdIVDMFofcHUklHSFQ4IKn3ByBBTkQgTUlERExFIExFRlQgVE8gTE9XRVIgQ0VOVFJFOQ+l9wdUTyBMT1dFUiBDRU5UUkVID6r3ByBUTyBNSURETEUgTEVGVCIU1PcHTEVGVCBUTyBMT1dFUiBDRU5UUkU2D9z3",
+ "ByBUTyBVUFBFUiBSSUdIVDAF8kpSSUdIVCob2/cHTUlERExFIENFTlRSRSBUTyBMT1dFUiBMRUZUOwvY9wdVUFBFUiBSSUdIVDEF0vcHUklHSFQ2Dt/3ByBUTyBMT1dFUiBMRUZUIhXW9wdSSUdIVCBUTyBMT1dFUiBDRU5UUkUxBPFKTEVGVCsc2fcHTUlERExFIENFTlRSRSBUTyBMT1dFUiBSSUdIVDIE0fcHTEVGVDYP3fcHIFRPIExPV0VSIFJJR0hUFBXMSk9VQkxFIERBU0ggSE9SSVpPTlRBTB8IzkpWRVJUSUNBTBUC90pXThcPrEogQU5EIEhPUklaT05UQUwcBJBKTEVGVBwFjEpSSUdIVBMKgEpIT1JJWk9OVEFMHRCcmAcgQU5EIExPV0VSIFJJR0hUIgubmAdVUFBFUiBSSUdIVB4Ur/cHV0lUSCBWRVJUSUNBTCBTVFJPS0UTBPRKTEVGVBcQ/EogQU5EIEhFQVZZIFJJR0hUExmISlFVQURSVVBMRSBEQVNIIEhPUklaT05UQUwiCIpKVkVSVElDQUwTBfZKUklHSFQTEp2YB1RPUCBBTkQgVVBQRVIgTEVGVBQVhEpSSVBMRSBEQVNIIEhPUklaT05UQUwfCIZKVkVSVElDQUwTAvVKVVAVD/1KIEFORCBIRUFWWSBET1dOGwm0Sk9SSVpPTlRBTBoEmEpMRUZUGgWUSlJJR0hUEwiCSlZFUlRJQ0FMGxCZnAcgQU5EIEJPVFRPTSBMRUZUJwWXnAdSSUdIVCAKvEpIT1JJWk9OVEFMIASkSkxFRlQgBZxKUklHSFQgCJicB1RPUCBMRUZUJAWWnAdSSUdIVA0ixkpSSUdIVCBET1dOIEhFQVZZIEFORCBMRUZUIFVQIExJR0hUExmuSkhFQVZZIEFORCBMRUZUIERPV04gTElHSFQiCLZKVVAgTElHSFQiDr5KVkVSVElDQUwgTElHSFQTGbFKTElHSFQgQU5EIExFRlQgRE9XTiBIRUFWWSIIuUpVUCBIRUFWWSIOyUpWRVJUSUNBTCBIRUFWWRMcxEpVUCBIRUFWWSBBTkQgTEVGVCBET1dOIExJR0hUDR/oSlVQIERPVUJMRSBBTkQgSE9SSVpPTlRBTCBTSU5HTEUbC9xKTEVGVCBTSU5HTEUbDNlKUklHSFQgU0lOR0xFEB/ASkhFQVZZIEFORCBET1dOIEhPUklaT05UQUwgTElHSFQaELhKSE9SSVpPTlRBTCBMSUdIVBoPpkpMRUZUIERPV04gTElHSFQfBZpKTElHSFQaEJ5KUklHSFQgRE9XTiBMSUdIVCAFlkpMSUdIVBAfyEpMSUdIVCBBTkQgRE9XTiBIT1JJWk9OVEFMIEhFQVZZGhC3SkhPUklaT05UQUwgSEVBVlkaD6pKTEVGVCBET1dOIEhFQVZZHwWZSkhFQVZZGhCiSlJJR0hUIERPV04gSEVBVlkgBZVKSEVBVlkQHOdKU0lOR0xFIEFORCBIT1JJWk9OVEFMIERPVUJMRRsL20pMRUZUIERPVUJMRRsM2EpSSUdIVCBET1VCTEUNJetKVkVSVElDQUwgRE9VQkxFIEFORCBIT1JJWk9OVEFMIFNJTkdMRSEL4kpMRUZUIFNJTkdMRSEM30pSSUdIVCBTSU5HTEUWGsJKSEVBVlkgQU5EIEhPUklaT05UQUwgTElHSFQgCqhKTEVGVCBMSUdIVCALoEpSSUdIVCBMSUdIVBYav0pMSUdIVCBBTkQgSE9SSVpPTlRBTCBIRUFWWSAKpUpMRUZUIEhFQVZZIAudSlJJR0hUIEhFQVZZFhzqSlNJTkdMRSBBTkQgSE9SSVpPTlRBTCBET1VCTEUhC+FKTEVGVCBET1VCTEUhDN5KUklHSFQgRE9VQkxFAwnK8gdJTkcgR0xPVkUCAeboB1kDCMntB1MgU1lNQk9MAQKCAVBIAQvHoARSQUhNSSBEQU5EQQgK7qAESUdJVCBFSUdIVA0E66AERklWRQ4D6qAET1VSDQTvoAROSU5FDQPnoARPTkUNBe2gBFNFVkVODgLsoARJWA0F6aAEVEhSRUUOAuigBFdPDQTmoARaRVJPCAvIoARPVUJMRSBEQU5EQQcIhaAETEVUVEVSIEEPAYagBEEPAZCgBEkPAZKgBFUOAqmgBEJBDwKqoARIQQ4CmKAEQ0EPApmgBEhBDgKkoAREQQ8Cn6AEREEQAqCgBEhBDwKloARIQQ4Bj6AERQ4ClaAER0EPApagBEhBDgKzoARIQQ4Bh6AESQ8BiKAESQ4CmqAESkEPApugBEhBDgKToARLQQ8ClKAESEEOAq6gBExBDwK0oARMQQ4Cq6AETUEOAqagBE5BDwKXoARHQQ8CoaAETkEPApygBFlBDgGRoARPDwz1oARMRCBUQU1JTCBMTEEaArWgBExBGAS3oAROTk5BGAO2oARSUkEYB/GgBFNIT1JUIEUeAfKgBE8OAqegBFBBDwKooARIQQ4CraAEUkEOArKgBFNBDwKwoARIQQ8CsaAEU0EOAqKgBFRBDwKjoARIQQ8CnaAEVEEQAp6gBEhBDgGJoARVDwGKoARVDgKvoARWQQ8IjaAET0NBTElDIEwXAY6gBEwWAYugBFIXAYygBFIOAqygBFlBBwzZoAROVU1CRVIgRUlHSFQTAeKgBFkOBd+gBEZJRlRZEALWoARWRQ8E3qAET1JUWRAC1aAEVVIOBv+gBEpPSU5FUg4E2qAETklORRIC46AEVFkOA9KgBE9ORREI5KAEIEhVTkRSRUQSCOWgBFRIT1VTQU5EDgXYoARTRVZFThMC4aAEVFkPAtegBElYEQLgoARUWQ4D26AEVEVODwXdoARISVJUWRAD1KAEUkVFDwXcoARXRU5UWRAB06AETwcYzKAEUFVOQ1RVQVRJT04gQ1JFU0NFTlQgQkFSEwPJoARET1QVCMqgBFVCTEUgRE9UEwTLoARMSU5FFATNoARPVFVTBw2BoARTSUdOIEFOVVNWQVJBDAuAoARDQU5EUkFCSU5EVQwLg6AESklIVkFNVUxJWUEMEPCgBE9MRCBUQU1JTCBWSVJBTUEMC4SgBFVQQURITUFOSVlBDAeCoARWSVNBUkdBBwbGoARWSVJBTUEIDLigBE9XRUwgU0lHTiBBQRMBw6AESRMBxaAEVRIOuaAEQkhBVFRJUFJPTFUgQUESAcKgBEUSAbqgBEkTAbugBEkSAcSgBE8TEPOgBExEIFRBTUlMIFNIT1JUIEUiAfSgBE8SAbygBFUTAb2gBFUSCcCgBFZPQ0FMSUMgTBsBwaAETBoBvqAEUhsBv6AEUgMSgFBJTExFIFBBVFRFUk4gQkxBTksQBoFQRE9UUy0xFgGDUDIXAYdQMxgBj1A0GQGfUDUaAb9QNhsB/1A3HAH/UTgbAb9ROBoB31A3GwHfUTgaAZ9ROBkBr1A2GgHvUDcbAe9ROBoBr1E4GQHPUDcaAc9ROBkBj1E4GAGXUDUZAbdQNhoB91A3GwH3UTgaAbdROBkB11A3GgHXUTgZAZdROBgBp1A2GQHnUDcaAedROBkBp1E4GAHHUDcZAcdROBgBh1E4FwGLUDQYAZtQNRkBu1A2GgH7UDcbAftROBoBu1E4GQHbUDcaAdtROBkBm1E4GAGrUDYZAetQNxoB61E4GQGrUTgYActQNxkBy1E4GAGLUTgXAZNQNRgBs1A2GQHzUDcaAfNROBkBs1E4GAHTUDcZAdNROBgBk1E4FwGjUDYYAeNQNxkB41E4GAGjUTgXAcNQNxgBw1E4FwGDUTgWAYVQMxcBjVA0GAGdUDUZAb1QNhoB/VA3GwH9UTgaAb1ROBkB3VA3GgHdUTgZAZ1ROBgBrVA2GQHtUDcaAe1ROBkBrVE4GAHNUDcZAc1ROBgBjVE4FwGVUDUYAbVQNhkB9VA3GgH1UTgZAbVROBgB1VA3GQHVUTgYAZVROBcBpVA2GAHlUDcZAeVROBgBpVE4FwHFUDcYAcVROBcBhVE4FgGJUDQXAZlQNRgBuVA2GQH5UDcaAflROBkBuVE4GAHZUDcZAdlROBgBmVE4FwGpUDYYAelQNxkB6VE4GAGpUTgXAclQNxgByVE4FwGJUTgWAZFQNRcBsVA2GAHxUDcZAfFROBgBsVE4FwHRUDcYAdFROBcBkVE4FgGhUDYXAeFQNxgB4VE4FwGhUTgWAcFQNxcBwVE4FgGBUTgVAYJQMhYBhlAzFwGOUDQYAZ5QNRkBvlA2GgH+UDcbAf5ROBoBvlE4GQHeUDcaAd5ROBkBnlE4GAGuUDYZAe5QNxoB7lE4GQGuUTgYAc5QNxkBzlE4GAGOUTgXAZZQNRgBtlA2GQH2UDcaAfZROBkBtlE4GAHWUDcZAdZROBgBllE4FwGmUDYYAeZQNxkB5lE4GAGmUTgXAcZQNxgBxlE4FwGGUTgWAYpQNBcBmlA1GAG6UDYZAfpQNxoB+lE4GQG6UTgYAdpQNxkB2lE4GAGaUTgXAapQNhgB6lA3GQHqUTgYAapROBcBylA3GAHKUTgXAYpROBYBklA1FwGyUDYYAfJQNxkB8lE4GAGyUTgXAdJQNxgB0lE4FwGSUTgWAaJQNhcB4lA3GAHiUTgXAaJROBYBwlA3FwHCUTgWAYJROBUBhFAzFgGMUDQXAZxQNRgBvFA2GQH8UDcaAfxROBkBvFE4GAHcUDcZAdxROBgBnFE4FwGsUDYYAexQNxkB7FE4GAGsUTgXAcxQNxgBzFE4FwGMUTgWAZRQNRcBtFA2GAH0UDcZAfRROBgBtFE4FwHUUDcYAdRROBcBlFE4FgGkUDYXAeRQNxgB5FE4FwGkUTgWAcRQNxcBxFE4FgGEUTgVAYhQNBYBmFA1FwG4UDYYAfhQNxkB+FE4GAG4UTgXAdhQNxgB2FE4FwGYUTgWAahQNhcB6FA3GAHoUTgXAahROBYByFA3FwHIUTgWAYhROBUBkFA1FgGwUDYXAfBQNxgB8FE4FwGwUTgWAdBQNxcB0FE4FgGQUTgVAaBQNhYB4FA3FwHgUTgWAaBROBUBwFA3FgHAUTgVAYBROAQB4PMHTgID3uYHRUFEBBCCAUsgUEVSTUlUVEVEIEhFUkUECrHyB1NULUZFRURJTkcDAtgFVkUCA/HzB0lDSwMM8OgHREUgV0lUSCBWRUlMBAuJ5gdHRSBBVCBOSUdIVAMGvOkHRUZDQVNFBQHy9AdTAgbm8gdPQ0NPTEkDB6YBS0VOIEJBUgcbi0dDSVJDTEUgV0lUSCBOT1JUSFdFU1QgQVJST1cHBZTpB0hFQVJUAwL58wdPTQMIjvIHV04gSEVBUlQBAQhTAQnL8wdVQkJMRSBURUEGAef1B1MCBKP1B0NLRVQCAZvoB0cDFJ80SU5FU0UgRU5EIE9GIFNFQ1RJT04JCJU0TEVUVEVSIEEQAoU0QkEQAow0Q0EQAok0REEQAoE0R0EQApY0SEEQAo00SkEQAoA0S0EQApI0TEEQAoY0TUERAoc0UEEQAoo0TkERAoI0R0ESAoM0S0ERAos0UkERAo40WUESAo80Q0EQAoQ0UEEQApE0UkEQApQ0U0EQAog0VEEQApM0VkEQApA0WUEJB540UEFMTEFXQQkNmzRWT1dFTCBTSUdOIEFFFAGZNEUUAZc0SRQBmjRPFAGYNFUCDMAuSElEIExFVFRFUiBBDQLKLkJBDQLHLkRBDQLELkdBDQLRLkhBDQHBLkkNAsMuS0ENAs4uTEENAssuTUENAsguTkEOAsUuR0ENAskuUEENAs0uUkENAtAuU0ENAsYuVEENAcIuVQ0Czy5XQQ0CzC5ZQQYM0i5WT1dFTCBTSUdOIEkRAdMuVQIT1+cHSUxESU5HIENPTlNUUlVDVElPTgIEokBMTEVUBgmZRCBPUEVSQVRPUgQE6+oHSE9STggR7OoHIFdJVEggU09VTkQgV0FWRVMEBM5LU0VZRQIFr+YHUlJJVE8CAYztB1MDBY/tByBTVE9QAw/k6AdUIElOIFNJTEhPVUVUVEUED+XoB1MgSU4gU0lMSE9VRVRURQIEyPMHVFRFUgYDi/MHRkxZAQ7//QNZVEUgT1JERVIgTUFSSwIhnKEHWkFOVElORSBNVVNJQ0FMIFNZTUJPTCBBR09HSSBBUkdJIgWboQdPVEVSSR8Fn6EHR09SR0kjBaChB09URVJJHwSeoQdNRVNJIQSdoQdUUklBHwmaoQdQT0xJIEFSR0kkBaGhB0dPUkdJGguooAdOQVRSSUNISVNNQRsN4qAHVElLRU5PS1lMSVNNQSEC3KAHTUEaEI2gB1BFU08gRUtGT05JVElLT04gBrOgB1hPIE5FTxsChaEHTEkbD6KgB09ERVJNQSBBUkNIQUlPTiIDhKEHTkVPHBXSoAdTVFJPRk9JIFNZTkRFU01PUyBORU8kEK2hB1RFTE9VUyBJQ0hJTUFUT1MiAZGgB1MjBpKgByBESVBMSSQD0aAHTkVPHAWjoAdUSEVNQRoEl6EHUkdPTh0J86AHU1lOVEhFVE9OGwjsoQdLVElLTyBESSEC66EHR0EhAu2hB0tFIQLvoQdOSSEC6aEHUEEhA+qhB1ZPVSEC7qEHWk8ZB9agB0NIQU1JTEkfApigB09OGw+7oAdPUkVWTUEgQVJDSEFJT04iA+egB05FTxsKyaEHUk9BIEtMSVRPTh8GyqEHU1BBVEhJHwXIoQdaWUdPUxkGgaAHREFTRUlBGgaZoQdJQVJHT04cEduhB1NUT0xJIEFQTEkgTUVHQUxJKATaoQdJS1JJIgXcoQdESVBMSSIH3aEHVEhFU0VPUxsW0KEHRVNJUyBBUExJIERZTyBET0RFS0FUQSAV0qEHRElHUkFNTU9TIEVYIERPREVLQVRBIBzRoQdNT05PR1JBTU1PUyBURVNTRVJBIERPREVLQVRBIA3PoQdURVRBUlRJTU9SSU9OIRfToQdSSUdSQU1NT1MgT0tUTyBET0RFS0FUQSMIzqEHVElNT1JJT04bCuehB0ZUT0dHT1MgT1UbBpKhB0dPUkdPTiEblKEHIFBBUkVTVElHTUVOT04gQVJJU1RFUkEgQU5POQSToQdLQVRPMAWVoQdERVhJQRwI5qEHUkFNTUEgR0cbA4ahB1BMSR4JgKEHIEFSQ0hBSU9OGgKvoAdZTxkK5KAHRUtTVFJFUFRPThoG1aAHTEFGUk9OGhW2oQdOQVJYSVMgS0FJIEZUSE9SQSBWT1UbB/ugB0RPRk9OT04aB+igB1BFR0VSTUEaE/SgB1RFUk9OIEFSR09TWU5USEVUT04gC+CgB1BBUkFLQUxFU01BGg6OoAdYTyBFS0ZPTklUSUtPThkSsKEHRkFORVJPU0lTIERJRk9OSUFTIwqvoQdNT05PRk9OSUFTIwuuoQdURVRSQUZPTklBUxoaxaEHSFRPUkEgU0tMSVJPTiBDSFJPTUEgVkFTSVMaDrSgB1RIT1JBIEFSQ0hBSU9OKA+5oQcgREVZVEVST1UgSUNIT1UgDL2hB0RJQVRPTklLSSBESSoCv6EHS0UqBLuhB05BTkErBcKhB0kgQU5PLQTBoQdLQVRPKgK6oQdQQSoCwKEHWk8gFMyhB0VOQVJNT05JT1MgQU5USUZPTklBIBbLoQdJIFlGRVNJUyBURVRBUlRJTU9SSU9OIBfDoQdNQUxBS09OIENIUk9NQSBESUZPTklBUy8KxKEHTU9OT0ZPTklBUyAKvKEHTkFPUyBJQ0hPUyEFx6EHRU5BTk8gFcahB1NLTElST04gQ0hST01BIFNZTkFGSS8FxaEHVkFTSVMoC76hB0RJQVRPTk9OIERJGQ3YoQdHRU5JS0kgRElFU0lTIAbZoQdZRkVTSVMaDpagB09SR09OIEFSQ0hBSU9OIAePoQdORU8gQU5PJAT1oQdLQVRPIBaQoQdQQVJFU1RJR01FTk9OIEFSSVNURVJBLgWRoQdERVhJQR4J8qAHU1lOVEhFVE9O",
+ "HBC0oQdUSE1JS09OIE4gQVBMT1VOJge1oQdESVBMT1VOGgvFoAdST05USElTTUFUQRkHv6AHSUNIQURJThoJmKEHTUlESUFSR09OHAW3oQdGT05PTh0FtaAHVEhPUkEhArihB09OGhasoQdTQUtJQSBURUxPVVMgSUNISU1BVE9TGwuaoAdPTiBBUkNIQUlPTh4DxqAHTkVPGQ+3oAdLQVRBVkEgVFJPTUlLT04fA/qgB1NNQRwFh6AHSElTVEkaD5ugB0VOVElNQSBBUkNIQUlPTiEHz6AHTkVPIEFOTyUE86EHS0FUTyUE8aEHTUVTTyACkKAHVEEiCZygByBBUkNIQUlPTiMHzqAHTkVPIEFOTycE8qEHS0FUTycE8KEHTUVTTxoFpKAHTEFTTUEfBP+gByBBTk8gBPShB0tBVE8aB7qgB09OVEVWTUEhBcKgByBBTExPGwWJoQdST05JUxsGyqAHVUZJU01BGguCoQdSQVRJTUEgQUxMTyIHgaEHUkNIQUlPTiEDg6EHTkVPIAKyoAdUQR8JzKAHT0tPVUZJU01BIAjUoAdZUE9SUk9PThsGjKAHRU1BU1RJGgbhoAdZTElTTUEZEouhB0xFSU1NQSBEWU8gQ0hST05PTiAMiqEHRU5PUyBDSFJPTk9VIA+OoQdJTUlTRU9TIENIUk9OT1UgEI2hB1RFU1NBUk9OIENIUk9OT04hDIyhB1JJT04gQ0hST05PThsDrqAHTU9JGgbdoAdZR0lTTUEZHKWhB01BUlRZUklBIEFMTEkgREVZVEVST1MgSUNIT1MnDKOhB1BST1RPUyBJQ0hPUyIOpKEHREVZVEVST1MgSUNIT1MiDaqhB0xFR0VUT1MgSUNIT1MiDauhB1BMQUdJT1MgSUNIT1MqDrOhB1RFVEFSVE9TIElDSE9TIwuioQdST1RPUyBJQ0hPUycLsqEHVkFSWVMgSUNIT1MiDqihB1RFVEFSVE9TIElDSE9TKw2poQdMRUdFVE9TIElDSE9TIwinoQdSSUZPTklBUyUJpqEHVE9TIElDSE9TIguxoQdWQVJZUyBJQ0hPUxoK16AHSUtST04gSVNPThkEwKAHTkFOQRkPlaAHT0xJR09OIEFSQ0hBSU9OIAPHoAdORU8aBdugB01BTE9OGgqEoAdYRUlBIERJUExJHwuDoAdFS0ZPTklUSUtP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QUwgQkFSIEFORCBUT1AgSEFMRiBESVZJREVEIEJZIFZFUlRJQ0FMIEJBUgcm1UtXSVRIIEFMTCBCVVQgVVBQRVIgTEVGVCBRVUFEUkFOVCBCTEFDSwwOtVNIT1JJWk9OVEFMIEJBUgwP0EtMRUZUIEhBTEYgQkxBQ0sND9JLT1dFUiBIQUxGIEJMQUNLDBDRS1JJR0hUIEhBTEYgQkxBQ0sMGcJTU01BTEwgQ0lSQ0xFIFRPIFRIRSBSSUdIVA0Nu1NVUEVSSU1QT1NFRCBYDCPDU1RXTyBIT1JJWk9OVEFMIFNUUk9LRVMgVE8gVEhFIFJJR0hUDBDTS1VQUEVSIEhBTEYgQkxBQ0sSFNRLUklHSFQgUVVBRFJBTlQgQkxBQ0sMDc1LVkVSVElDQUwgRklMTAYVjuIHRCBBTlRJQ0xPQ0tXSVNFIEFSUk9XFRa8Uy1ST1RBVEVEIERJVklTSU9OIFNJR04JEJtFU1RFUklTSyBPUEVSQVRPUggGv1dCT0xEIFgJBb9TVUxMRVQIGe7iB0MgV0lUSCBPVkVSTEFJRCBCQUNLU0xBU0gJAe3iB0MJAa3iB0QJC4LyB1JPU1MgRk9STUVFFA+A8gcgV0lUSCBGT1VSIERPVFMaCIHyB1RXTyBET1RTDgbA6gdQT01NRUUNCdJNSU5HIExBTkVTCASdRURBU0gJCudISUdJVCBFSUdIVA4E5EhGSVZFDwPjSE9VUg4E6EhOSU5FDgPgSE9ORQ4F5khTRVZFTg8C5UhJWA4F4khUSFJFRQ8C4UhXTw4E6klaRVJPCgu4VFZJU0lPTiBTSUdOEgSYRUxBU0gJIo/iB09MTEFSIFNJR04gV0lUSCBPVkVSTEFJRCBCQUNLU0xBU0gKCplFVCBPUEVSQVRPUggGnEVFUVVBTFMIDMFTR1JFQVRFUi1USEFOCA7pZEhBTkdVTCBDSElFVUNIFgL3ZCBBEAToZElFVUMUAvZkIEEPBe1kSElFVUgUAvtkIEEPBedkSUVVTkcUAvVkIEEVAf5kVQ8H6mRLSElFVUtIFgL4ZCBBEAXgZElZRU9LFQLuZCBBDwXkZE1JRVVNFALyZCBBDwXhZE5JRVVOFALvZCBBDwfsZFBISUVVUEgWAvpkIEEQBOVkSUVVUBQC82QgQQ8F42RSSUVVTBQC8WQgQQ8E5mRTSU9TEwL0ZCBBDwfrZFRISUVVVEgWAvlkIEEQBeJkSUtFVVQVAvBkIEEJG7JPRUFWWSBXSElURSBSSUdIVFdBUkRTIEFSUk9XCRiJR09SSVpPTlRBTCBCQVIgV0lUSCBOT1RDSAkL7+IHVU1BTiBGSUdVUkUIENHkB0lERU9HUkFQSCBBQ0NFUFQTCNDkB0RWQU5UQUdFEwevZUxMSUFOQ0UTCJ9lVFRFTlRJT04SBqVlQ0VOVFJFEw2XZU9OR1JBVFVMQVRJT04UAqJlUFkUBaNlUlJFQ1QSBY9lRUFSVEgTBIdlSUdIVBMJrWVOVEVSUFJJU0UTCJ1lWENFTExFTlQSBptlRkVNQUxFEwiWZUlOQU5DSUFMFAKLZVJFFAKEZVZFEwODZU9VUhIEkmVIQVZFEwOkZUlHSBIEoGVJVEVNEgzFZEtJTkRFUkdBUlRFThMDx2RPVE8SBZhlTEFCT1ITA6dlRUZUEwKmZU9XEgSaZU1BTEUTB6llRURJQ0lORRQDjmVUQUwTA4plT09OEgSUZU5BTUUTBLBlSUdIVBQCiGVORRIDgGVPTkUSBZ5lUFJJTlQSCMRkUVVFU1RJT04SCKplUkVMSUdJT04UBq5lU09VUkNFFQGhZVQTBKhlSUdIVBIGxmRTQ0hPT0wTBZllRUNSRVQUA4ZlVkVOEwKFZUlYEwaTZU9DSUVUWRMGlWVQRUNJQUwTBJFlVE9DSxQDq2VVRFkTB5xlVUlUQUJMRRQBkGVOFAesZVBFUlZJU0USA4llVEVOEwSCZUhSRUUTAoFlV08SBYxlV0FURVIT",
+ "A41lT09ECRHI7QdORk9STUFUSU9OIFNPVVJDRQkcq+IHVEFMSUMgTEFUSU4gQ0FQSVRBTCBMRVRURVIgQyQBrOIHUggK0GVLQVRBS0FOQSBBEQHTZUURAullSEESAexlRRIB6mVJEgHtZU8SAetlVREB0WVJEQLVZUtBEgHYZUUSAdZlSRIB2WVPEgHXZVURAu5lTUESAfFlRRIB72VJEgHyZU8SAfBlVREC5GVOQRIB52VFEgHlZUkSAehlTxIB5mVVEQHUZU8RAvZlUkESAfllRRIB92VJEgH6ZU8SAfhlVREC2mVTQRIB3WVFEgHbZUkSAd5lTxIB3GVVEQLfZVRBEgHiZUUSAeBlSRIB42VPEgHhZVURAdJlVREC+2VXQRIB/WVFEgH8ZUkSAf5lTxEC82VZQRIB9WVPEgH0ZVUJFvxkT1JFQU4gQ0hBUkFDVEVSIENIQU1LTxkF/WRKVUVVSQgWtklMQVRJTiBDQVBJVEFMIExFVFRFUiBBHQG3SUIdAbhJQx0BuUlEHQG6SUUdAbtJRh0BvElHHQG9SUgdAb5JSR0Bv0lKHQHASUsdAcFJTB0BwklNHQHDSU4dAcRJTx0BxUlQHQHGSVEdAcdJUh0ByElTHQHJSVQdAcpJVR0By0lWHQHMSVcdAc1JWB0BzklZHQHPSVoODtBJU01BTEwgTEVUVEVSIEEbAdFJQhsB0klDGwHTSUQbAdRJRRsB1UlGGwHWSUcbAddJSBsB2ElJGwHZSUobAdpJSxsB20lMGwHcSU0bAd1JThsB3klPGwHfSVAbAeBJURsB4UlSGwHiSVMbAeNJVBsB5ElVGwHlSVYbAeZJVxsB50lYGwHoSVkbAelJWgkIwFNFU1MtVEhBTggFlkVNSU5VUwkptlRVTFRJUExJQ0FUSU9OIFNJR04gV0lUSCBDSVJDVU1GTEVYIEFDQ0VOVAgP8UhOVU1CRVIgRUlHSFRFRU4UEc9kWSBPTiBCTEFDSyBTUVVBUkUQBepITEVWRU4PB+5IRklGVEVFThMBv2VZFBDMZCBPTiBCTEFDSyBTUVVBUkUQBLVlT1JUWRQGvWUgRUlHSFQVBLplRklWRRYDuWVPVVIVBL5lTklORRUPy2RPTiBCTEFDSyBTUVVBUkUXAbZlRRUFvGVTRVZFThYCu2VJWBUFuGVUSFJFRRYCt2VXTxEG7UhVUlRFRU4PCPJITklORVRFRU4PCfBIU0VWRU5URUVOFRHOZFkgT04gQkxBQ0sgU1FVQVJFEAbvSElYVEVFThMRzWRZIE9OIEJMQUNLIFNRVUFSRQ8D6UhURU4SEMhkIE9OIEJMQUNLIFNRVUFSRRAH7EhISVJURUVOFAHaZFkVBrNlIEVJR0hUFgTfZEZJVkUXA95kT1VSFgS0ZU5JTkUWD8pkT04gQkxBQ0sgU1FVQVJFGAHbZEUWBbJlU0VWRU4XArFlSVgWBd1kVEhSRUUXAtxkV08QBetIV0VMVkUSA/NITlRZFQbYZCBFSUdIVBYE1WRGSVZFFwPUZE9VUhYE2WROSU5FFg/JZE9OIEJMQUNLIFNRVUFSRRgB0WRFFgXXZFNFVkVOFwLWZElYFgXTZFRIUkVFFwLSZFdPCB7CTk9QRU4gQ0VOVFJFIEVJR0hUIFBPSU5URUQgU1RBUggIt1NQQVJBTExFTAkMuVNFUlBFTkRJQ1VMQVIJA5VFTFVTCQq2YE9TVEFMIE1BUksID7hTUkVWRVJTRSBTT0xJRFVTCQyaRUlORyBPUEVSQVRPUggG1+8HU1FVQVJFCAWXRVRJTUVTCQfV7wdSSUFOR0xFEAWKRyBET1dOCAy2U1ZFUlRJQ0FMIEJBUggMvlNXSElURSBCVUxMRVQOBKpOU1RBUgkBruIHWggBvldYCA+N4gdaRVJPIFdJVEggU0xBU0gEEJBUVUxBVElPTiBGVU5DVElPTgUMXk1GTEVYIEFDQ0VOVAUGqucHUyBURU5UAgfZ5wdUWVNDQVBFCQiG5gcgQVQgRFVTSwERrl1KSyBSQURJQ0FMIEJBTUJPTw0D2F1MVUUNDKpdT0xUIE9GIENMT1RIDgLjXU5FDgGGXVgNCLpdUlVTSCBPTkUSA7tdVFdPDBHmXUMtU0lNUExJRklFRCBCSVJEGQTLXUNBUlQZBvBdRFJBR09OGQPgXUVBVBoD7F1WRU4ZBOVdRklTSBoC3F1MWRoD6l1ST0cZBNRdR0FURRoD0F1PTEQZBeJdSE9SU0UZBNpdTEVBRhoD011PTkcZBOddU0FMVBoCxV1FRRoEyV1IRUxMGgOwXUlMSxoFyF1QRUVDSBkO2V1UQU5ORUQgTEVBVEhFUhoE7l1PT1RIGgXzXVVSVExFGQTbXVdJTkQNBJtdSE9LRQ0Dz11JVFkOBqBdVklMSUFODQSBXUxJRkYOBcJdT1RIRVMNAqddT1cMBZ5dREVBVEgNCYpdSVZJTkFUSU9ODQKoXU9HDAfdXUVBVCBPTkUQBd9dVEhSRUURAt5dV08NArhdV0UNAqtdWUUMBKNdRklSRQ0Dyl1PT1QMBeRdR0hPU1QNCL5dUkFTUyBPTkUSBcBdVEhSRUUTAr9dV08MBJhdSEFORA0D4V1FQUQPBpZdUlQgT05FEgOXXVRXTw0Dx11PUk4ME+9dSi1TSU1QTElGSUVEIERSQUdPThkE611FVkVOGQXtXVRPT1RIGgXyXVVSVExFDQOpXUFERQwJiF1LTklGRSBPTkUSA4ldVFdPDAmRXUxBTUUgRk9VUhEDjl1PTkURBZBdVEhSRUUSAo9dV08NB9FdT05HIE9ORRED0l1UV08MBLxdTUVBVA4CtV1TSA0DnV1PT04OBL1dUlRBUg4En11USEVSDgfVXVVORCBPTkUSA9ZdVFdPDAi0XU5FVCBGT1VSEAOxXU9ORRAFs11USFJFRRECsl1XTwwDuV1PTEQMB6RdUEFXIE9ORRADpV1UV08NBYVdRVJTT04MBNddUkFJTg4Bt11NDgGZXVANBYBdRVBFQVQMBItdU0VBTA4Igl1DT05EIE9ORRMFhF1USFJFRRQCg11XTw0Etl1IRUVQDQOvXUlMSw4Ypl1NUExJRklFRCBIQUxGIFRSRUUgVFJVTksYA8ZdT1JOFwTMXVdBTEsYBOhdSEVBVBcG6V1ZRUxMT1cNCIxdTUFMTCBPTkUSA41dVFdPDQSSXU5BS0UOB5RdT1VUIE9ORRIDlV1UV08NCaxdUElSSVQgT05FEwOtXVRXTw0CnF1VTgwFh11UQUJMRQ0Fk11IUkVBRA0EwV1JR0VSDQXxXVVSVExFDAjNXVdBTEsgT05FEQPOXVRXTw4HoV1URVIgT05FEgOiXVRXTw0Hw11FU1QgT05FEQPEXVRXTwQKw2NTVFJPS0UgQlhHCwHUY0QLAdBjSAwB1mNHDAHHY1ANAsxjV0cMAuRjWEcNAuBjV0cMAdVjWg0BxmNHDQHKY1QNAc1jVw4ByGNHDQHFY1oOActjUA4BzmNaDwHhY0cLAc9jTgsB0mNQDAHbY0QMAeJjRwwB3GNaCwHjY1ELAdFjUwwB2mNHDAHTY1AMAdljVAwBxGNXDQHfY0cNAdhjWgwB12NaDQHlY1ANAsljV0cNAd5jWgsBwGNUDAHdY04LAsFjV0cLAsJjWEcBFYHrB0xBTVNIRUxMIE1PQklMRSBQSE9ORQMKrOcHUFBFUiBCT0FSRAUOz+gHSU5HIEhBTkRTIFNJR04DD9vnB1NTSUNBTCBCVUlMRElORwIRmkdFQVIgU0NSRUVOIFNZTUJPTAIQ++YHSU5LSU5HIEJFRVIgTVVHUwkHwvIHR0xBU1NFUwMGy+kHUEJPQVJEAhXX6gdPQ0sgRkFDRSBFSUdIVCBPQ0xPQ0sQB+PqBy1USElSVFkMDNrqB0xFVkVOIE9DTE9DSxEH5uoHLVRISVJUWQsL1OoHRklWRSBPQ0xPQ0sPB+DqBy1USElSVFkMCtPqB09VUiBPQ0xPQ0sPB9/qBy1USElSVFkLC9jqB05JTkUgT0NMT0NLDwfk6gctVEhJUlRZCwrQ6gdPTkUgT0NMT0NLDgfc6gctVEhJUlRZCwzW6gdTRVZFTiBPQ0xPQ0sQB+LqBy1USElSVFkMCdXqB0lYIE9DTE9DSw4H4eoHLVRISVJUWQsK2eoHVEVOIE9DTE9DSw4H5eoHLVRISVJUWQwL0uoHSFJFRSBPQ0xPQ0sQB97qBy1USElSVFkMDNvqB1dFTFZFIE9DTE9DSxEH5+oHLVRISVJUWQ0I0eoHTyBPQ0xPQ0sOB93qBy1USElSVFkFGMFSV0lTRSBDTE9TRUQgQ0lSQ0xFIEFSUk9XCw+yRE9OVE9VUiBJTlRFR1JBTAoog+oHRE9XTldBUkRTIEFORCBVUFdBUkRTIE9QRU4gQ0lSQ0xFIEFSUk9XUwoT809HQVBQRUQgQ0lSQ0xFIEFSUk9XCgixRElOVEVHUkFMChG7Q09QRU4gQ0lSQ0xFIEFSUk9XCiDY6wdSSUdIVCBBTkQgTEVGVCBTRU1JQ0lSQ0xFIEFSUk9XUw8mgeoHV0FSRFMgQU5EIExFRlRXQVJEUyBPUEVOIENJUkNMRSBBUlJPV1M1GYLqByBXSVRIIENJUkNMRUQgT05FIE9WRVJMQVkKFLdDVE9QIFNFTUlDSVJDTEUgQVJST1cLIO5WUklBTkdMRS1IRUFERUQgT1BFTiBDSVJDTEUgQVJST1cDBdBAU0UgVVAFBtXpB0QgQk9PSwcYzVRJTlRFUlNFQ1RJT04gV0lUSCBTRVJJRlMHDZDqB0xPQ0sgV0lUSCBLRVkHGerpB01BSUxCT1ggV0lUSCBMT1dFUkVEIEZMQUcUC+vpB1JBSVNFRCBGTEFHBwbPVVNVQlNFVA0M0VUgT1IgRVFVQUwgVE8JBtBVUEVSU0VUDwzSVSBPUiBFUVVBTCBUTwcIguYHVU1CUkVMTEEIEMxUTklPTiBXSVRIIFNFUklGUxgS0FQgQU5EIFNNQVNIIFBST0RVQ1QDAoFMVUQFD6nmByBXSVRIIExJR0hUTklORwsEp+YHUkFJTgsEqOYHU05PVwsHquYHVE9STkFETwMHofIHV04gRkFDRQED5fMHT0FUAgez9QdDS1JPQUNIBAr45gdUQUlMIEdMQVNTAwTl8gdPTlVUAgSwTUZGSU4CApn1B0lOAg6l6QdMTElTSU9OIFNZTUJPTAMCOk9OBQfURCBFUVVBTFMGBKFBU0lHTgIUgQZNQklOSU5HIEFDVVRFIEFDQ0VOVBYGlwYgQkVMT1cQCcEGVE9ORSBNQVJLDwzJOy1HUkFWRS1BQ1VURRAGxztNQUNST04QDdM1VkVSVElDQUwtTElORQsUzAZMTU9TVCBFUVVBTCBUTyBBQk9WRRoF/TtCRUxPVwsN50FOTlVJVFkgU1lNQk9MDBfUQVRJQ0xPQ0tXSVNFIEFSUk9XIEFCT1ZFGAzaQVJJTkcgT1ZFUkxBWQsN8EFTVEVSSVNLIEFCT1ZFEwXZBkJFTE9XCgu7pgRCSU5EVSBCRUxPVwsEhgZSRVZFDwauBiBCRUxPVw8HyzstTUFDUk9ODArGBklER0UgQUJPVkURBaoGQkVMT1cKC5AGQ0FORFJBQklORFUMA4wGUk9ODwasBiBCRUxPVwsGpwZFRElMTEELEIIGSVJDVU1GTEVYIEFDQ0VOVBsGrQYgQkVMT1cLFNVBTE9DS1dJU0UgQVJST1cgQUJPVkUUDNlBUklORyBPVkVSTEFZCwqTBk9NTUEgQUJPVkUVBpUGIFJJR0hUEAWmBkJFTE9XDA+m/ANOSk9JTklORyBNQUNST04bBq38AyBCRUxPVwsWhQlZUklMTElDIERBU0lBIFBORVVNQVRBExXxzAJIVU5EUkVEIE1JTExJT05TIFNJR04bDogJVEhPVVNBTkRTIFNJR04TBvzMAktBVllLQRMI9ltMRVRURVIgQRoC4FtCRRsG/ltJRyBZVVMaA/FbQ0hFGgLjW0RFGwT4W0pFUlYaAp7NAkVGGwHnW0wbAehbTRsB6VtOGwHsW1IbAe1bUxwD9VstVEUaBPRbRklUQRoD4ltHSEUaAu9bSEEcB/jMAlJEIFNJR04aAfXMAkkbAfdbRRsJ/FtPVElGSUVEIEEjB/9bQklHIFlVUyMBn80CRRoC5ltLQRoK/VtMSVRUTEUgWVVTGgz5W01PTk9HUkFQSCBVSxoB6ltPGwT7zAJNRUdBGgLrW1BFGgPyW1NIQRwD81tDSEEbCPrMAk9GVCBTSUdOGgLuW1RFGwLwW1NFGgH3zAJVGwv0zAJLUkFJTklBTiBJRRoC4VtWRRoD+ltZQVQbA/nMAkVSVRsB9swCSRsB+1tVGgLlW1pFGwLkW0hFEw2JCU1JTExJT05TIFNJR04TDoQJUEFMQVRBTElaQVRJT04VBf3MAllFUk9LFAeHCU9LUllUSUUUDYYJU0lMSSBQTkVVTUFUQRMlj8EHU01BTEwgTEVUVEVSIEJZRUxPUlVTU0lBTi1VS1JBSU5JQU4gSRMR8MwCVEVOIE1JTExJT05TIFNJR04UFfLMAkhPVVNBTkQgTUlMTElPTlMgU0lHThQEgwlJVExPGAqu/AMgTEVGVCBIQUxGGQqv/ANSSUdIVCBIQUxGEwXvzAJWWk1FVAoN+ztERUxFVElPTiBNQVJLDBTo0QJWQU5BR0FSSSBESUdJVCBFSUdIVBsE5dECRklWRRwD5NECT1VSGwTp0QJOSU5FGwPh0QJPTkUbBefRAlNFVkVOHALm0QJJWBsF49ECVEhSRUUcAuLRAldPGwTg0QJaRVJPFQjq0QJMRVRURVIgQRwC7NECS0EcAu3RAk5BHALu0QJQQRwC79ECUkEcAevRAlUcAvDRAlZJFQ3x0QJTSUdOIEFWQUdSQUhBCwiIBklBRVJFU0lTEwakBiBCRUxPVxQU3DVXSVRIIFJBSVNFRCBMRUZUIERPVBMFsTUtUklORwsIhwZPVCBBQk9WRRMF+DsgTEVGVBQF2AZSSUdIVA4FowZCRUxPVxMF+jsgTEVGVA0P3TUtQU5ELVJJTkcgQkVMT1cNEME7VEVEIEFDVVRFIEFDQ0VOVBEMwDtHUkFWRSBBQ0NFTlQMEYsGVUJMRSBBQ1VURSBBQ0NFTlQSCec1UkNIIEFCT1ZFFgXmNUJFTE9XEQXdBkJSRVZFFgbcBiBCRUxPVxEFzzVDQVJPThIPzTtJUkNVTUZMRVggQUJPVkURDI8GR1JBVkUgQUNDRU5UEQ7hBklOVkVSVEVEIEJSRVZFHwb8OyBCRUxPVxEIswZMT1cgTElORREG3gZNQUNST04XBt8GIEJFTE9XEQ+4NU9QRU4gTUFSSyBCRUxPVxIHvwZWRVJMSU5FERG8NVBBUkVOVEhFU0VTIEFCT1ZFEg7JNUxVUyBTSUdOIEFCT1ZFGwXKNUJFTE9XERbrNVJJR0hUV0FSRFMgQVJST1cgQUJPVkUiBeIGQkVMT1cTCNoGTkcgQkVMT1cRBeAGVElMREUWCqL8AyBMRUZUIEhBTEYXCqP8A1JJR0hUIEhBTEYRE44GVkVSVElDQUwgTElORSBBQk9WRR8FyAZCRUxPVxoO5kFTVFJPS0UgT1ZFUkxBWRATsDVEIENJUkNVTUZMRVggQUNDRU5UDA3bNVdOIFRBQ0sgQUJPVkUUBZ4GQkVMT1cOC7M1V0FSRFMgQVJST1cKEN1BRU5DTE9TSU5HIENJUkNMRRoK4EEgQkFDS1NMQVNIFAffQURJQU1PTkQUBuNBS0VZQ0FQFAbiQVNDUkVFThUF3kFRVUFSRRQY5EFVUFdBUkQgUE9JTlRJTkcgVFJJQU5HTEULEOg1UVVBTFMgU0lHTiBBQk9WRRYFxwZCRUxPVwoH0gZGRVJNQVRBCwjaNUxBVCBTSUdOCw7cQU9VUiBET1RTIEFCT1ZFChWAwAdHTEFHT0xJVElDIExFVFRFUiBBWlUcB6jA",
+ "B0JJRyBZVVMdA4HAB1VLWRwGncAHQ0hSSVZJHAaMwAdESkVSVkkdBITAB09CUk8cBKrAB0ZJVEEdBJfAB1JJVFUcB4PAB0dMQUdPTEkcBJjAB0hFUlUcAYvAB0kdC4rAB05JVElBTCBJWkhFHQ6pwAdPVEFURUQgQklHIFlVUyQJp8AHU01BTEwgWVVTHQOJwAdaSEUcBI3AB0tBS08cB47AB0xKVURJSkUcB4/AB01ZU0xJVEUcBZDAB05BU0hJHAORwAdPTlUcBpLAB1BPS09KSRwFk8AHUklUU0kcA57AB1NIQR4Cm8AHVEEdBJTAB0xPVk8dCKTAB01BTEwgWVVTHAOcwAdUU0kdBZXAB1ZSSURPHAOWwAdVS1UcBILAB1ZFREUcBKHAB1lBVEkdA6DAB0VSSR8Bn8AHVR4DhcAHU1RVHQGmwAdPHQGjwAdVHAaIwAdaRU1MSkEdBobAB0hJVkVURQsR66YEUkFOVEhBIERJR0lUIEZJVkUZA+qmBE9VUhgD56YET05FGAPspgRTSVgYBemmBFRIUkVFGQLopgRXTxgE5qYEWkVSTxII8KYETEVUVEVSIEEZAvGmBEtBGQLypgROQRkC9KYEUEEZAvOmBFZJDQzPBlBIRU1FIEpPSU5FUg0JgAZWRSBBQ0NFTlQWBpYGIEJFTE9XEAnABlRPTkUgTUFSSw8MyDstQUNVVEUtR1JBVkUQBsU7TUFDUk9OEA3RNVZFUlRJQ0FMLUxJTkUME8QGRUVLIERJQUxZVElLQSBUT05PUxAHwwZLT1JPTklTEBHEpAdNVVNJQ0FMIFBFTlRBU0VNRRgJw6QHVEVUUkFTRU1FGQbCpAdSSVNFTUUQC8IGUEVSSVNQT01FTkkQDcUGWVBPR0VHUkFNTUVOSQoQywZIT01PVEhFVElDIEFCT1ZFDAiJBk9LIEFCT1ZFDAKbBlJOCgiyNUlORklOSVRZDAyRBlZFUlRFRCBCUkVWRRgGrwYgQkVMT1cVCuM1SURHRSBBQk9WRRoFugZCRUxPVxMRxzVET1VCTEUgQVJDSCBBQk9WRR8FqwZCRUxPVwsH0DtTIEJFTE9XCiiaYUtBVEFLQU5BLUhJUkFHQU5BIFNFTUktVk9JQ0VEIFNPVU5EIE1BUkscEZlhVk9JQ0VEIFNPVU5EIE1BUksMD/c7VllLQSBBQk9WRSBMRUZUFwX2O1JJR0hUChzbO0xBVElOIExFVFRFUiBTTUFMTCBDQVBJVEFMIEclAd47TCUB3ztNJQHhO04lAeI7UhAO4wZTTUFMTCBMRVRURVIgQR4P8jsgV0lUSCBESUFFUkVTSVMeAdQ7RR4E5ztMUEhBHgHVO08eAdY7Vh0B6DtCHgPpO0VUQR0B6AZDHgjXOyBDRURJTExBHQHpBkQdAeQGRR4C7ztTSB4C2TtUSB0B6ztGHhXTO0xBVFRFTkVEIE9QRU4gQSBBQk9WRR0B2jtHHQHqBkgdAeUGSR4I2DtOU1VMQVIgRCUBzDVHJQHNNVIlAc41VB0B3DtLHQHdO0weGew7IFdJVEggRE9VQkxFIE1JRERMRSBUSUxERR4F5TtPTkcgUx0B6wZNHQHgO04dAeYGTx4P8zsgV0lUSCBESUFFUkVTSVMkG+07TElHSFQgQ0VOVFJBTElaQVRJT04gU1RST0tFHQHuO1AdAewGUh4GyjsgQkVMT1cfB+M7Uk9UVU5EQR0B5DtTHgTqO0NIV0EdAe0GVB4NwDVVUk5FRCBXIEJFTE9XHQHnBlUeD/Q7IFdJVEggRElBRVJFU0lTJBvwO0xJR0hUIENFTlRSQUxJWkFUSU9OIFNUUk9LRR0B7gZWHQHxO1ceBr81IEJFTE9XHQHvBlgdAeY7WgsPmgZFRlQgQU5HTEUgQUJPVkUVBckGQkVMT1cVDek1Q0VOVFJFRCBBQk9WRRAK1kFSUk9XIEFCT1ZFFQXuQUJFTE9XFAr+O0hFQUQgQUJPVkUZBdQGQkVMT1cPD9EGSEFMRiBSSU5HIEFCT1ZFGQWcBkJFTE9XEQvQQVJQT09OIEFCT1ZFDxbBNVBBUkVOVEhFU0lTIEFCT1ZFIExFRlQbCsM1QkVMT1cgTEVGVA8R4UFSSUdIVCBBUlJPVyBBQk9WRRsFzQZCRUxPVw8K4DVUQUNLIEFCT1ZFFAWYBkJFTE9XDhPqQVdBUkRTIEFSUk9XIE9WRVJMQVkUG+1BSEFSUE9PTiBXSVRIIEJBUkIgRE9XTldBUkRTCxGg/ANJR0FUVVJFIExFRlQgSEFMRhwGp/wDIEJFTE9XEwqh/ANSSUdIVCBIQUxGHQao/AMgQkVMT1cNHrk1SFQgQ0VOVFJBTElaQVRJT04gU1RST0tFIEJFTE9XCxrrQU9ORyBET1VCTEUgU09MSURVUyBPVkVSTEFZDw+4BlNPTElEVVMgT1ZFUkxBWRANtgZUUk9LRSBPVkVSTEFZDxXSQVZFUlRJQ0FMIExJTkUgT1ZFUkxBWQwGsgZXIExJTkUKBoQGTUFDUk9OEAaxBiBCRUxPVxEJpPwDTEVGVCBIQUxGGgar/AMgQkVMT1cRCqX8A1JJR0hUIEhBTEYbBqz8AyBCRUxPVxAGxDstQUNVVEUWBtg1LUdSQVZFEQXMO0JSRVZFEQXGO0dSQVZFEQ3VNVZFUlRJQ0FMLUxJTkULD+I1SU5VUyBTSUdOIEFCT1ZFFQWgBkJFTE9XCg/KBk5PVCBUSUxERSBBQk9WRQsQxjVVTUJFUiBTSUdOIEFCT1ZFCgaoBk9HT05FSxAGzjsgQUJPVkULE/aGBExEIFBFUk1JQyBMRVRURVIgQU4cA/eGBERPSRwF+YYETkVOT0UcA/qGBFNJSRwE+IYEWkFUQQsOtzVQRU4gTUFSSyBCRUxPVwsHhQZWRVJMSU5FChahBlBBTEFUQUxJWkVEIEhPT0sgQkVMT1cMD7s1UkVOVEhFU0VTIEFCT1ZFFgW9NUJFTE9XFge+NU9WRVJMQVkLDsg1TFVTIFNJR04gQUJPVkUUBZ8GQkVMT1cKFKIGUkVUUk9GTEVYIEhPT0sgQkVMT1cMFeVBVkVSU0UgU09MSURVUyBPVkVSTEFZEQ2UBkQgQ09NTUEgQUJPVkULENdBSUdIVCBBUlJPVyBBQk9WRRYF70FCRUxPVxUK0AZIRUFEIEFCT1ZFGxf/O05EIERPV04gQVJST1dIRUFEIEJFTE9XHhLWBlVQIEFSUk9XSEVBRCBCRUxPVxoF1QZCRUxPVxAP1wZIQUxGIFJJTkcgQUJPVkUaBbkGQkVMT1cSC9FBUlBPT04gQUJPVkUQF8I1UEFSRU5USEVTSVMgQUJPVkUgUklHSFQcC8Q1QkVMT1cgUklHSFQQCuE1VEFDSyBBQk9WRRUFmQZCRUxPVw8h7EFXQVJEUyBIQVJQT09OIFdJVEggQkFSQiBET1dOV0FSRFMMCIoGTkcgQUJPVkUPBaUGQkVMT1cPB9hBT1ZFUkxBWQoN5TVTRUFHVUxMIEFCT1ZFEgW8BkJFTE9XCwnZNUhBUlAgU0lHTgwTtwZPUlQgU09MSURVUyBPVkVSTEFZEQ21BlRST0tFIE9WRVJMQVkQFdNBVkVSVElDQUwgTElORSBPVkVSTEFZCwrCO05BS0UgQkVMT1cLC+Q1UVVBUkUgQUJPVkURBbsGQkVMT1cSDcU1UkFDS0VUUyBBQk9WRQshujVUUk9ORyBDRU5UUkFMSVpBVElPTiBTVFJPS0UgQkVMT1cLDsM7VVNQRU5TSU9OIE1BUksKENtBVEhSRUUgRE9UUyBBQk9WRQsEgwZJTERFDwawBiBCRUxPVxAPqfwDTEVGVCBIQUxGIEJFTE9XEAe0Bk9WRVJMQVkQEKr8A1JJR0hUIEhBTEYgQkVMT1cLEss1UklQTEUgQUNVVEUgQUNDRU5UEQO0NURPVBEI6EFVTkRFUkRPVAsRkgZVUk5FRCBDT01NQSBBQk9WRQoN9TtVUCBUQUNLIEFCT1ZFEgWdBkJFTE9XDBHqNVdBUkRTIEFSUk9XIEFCT1ZFGAXOBkJFTE9XCwfRO1IgQUJPVkULB9I7UyBBQk9WRQoTjQZWRVJUSUNBTCBMSU5FIEFCT1ZFGAWpBkJFTE9XEwW+BlRJTERFEgvQNS1MSU5FLUFDVVRFHQbWNS1HUkFWRRgF0jVHUkFWRR0G1zUtQUNVVEUYBtQ1TUFDUk9OChHpQVdJREUgQlJJREdFIEFCT1ZFDxX5O0lOVkVSVEVEIEJSSURHRSBCRUxPVwwPtjVHR0xZIExJTkUgQkVMT1cKB70GWCBBQk9WRQwF0wZCRUxPVwsItTUtWCBCRUxPVwoM2wZaSUdaQUcgQUJPVkURBc87QkVMT1cDAoRMRVQDAixNQQQJQEVSQ0lBTCBBVAsK0kBNSU5VUyBTSUdOAwTt8wdQQVNTBAaBRExFTUVOVAQOhEdPU0lUSU9OIFNZTUJPTAQH3OsHUkVTU0lPTgILiucHTkZFVFRJIEJBTEwEC5bsB09VTkRFRCBGQUNFBAmV7AdVU0VEIEZBQ0UDFe1UR1JVRU5UIFdJVEggRE9UIEFCT1ZFAwqyRklDQUwgVEFQRVIDCIxMSlVOQ1RJT04DDqftB1NUUlVDVElPTiBTSUdODQb36AdXT1JLRVIDD4tEVEFJTlMgQVMgTUVNQkVSDA+zRU5PUk1BTCBTVUJHUk9VUBsMtUUgT1IgRVFVQUwgVE8JG/pFV0lUSCBMT05HIEhPUklaT05UQUwgU1RST0tFDgf9RU9WRVJCQVIOKPtFVkVS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RVhURU5ERUQgQkhBTEUeCoW2BCBXSVRIIEhPT0sQEPESSElHSCBTUEFDSU5HIERPVBAUgBJJTlZFUlRFRCBDQU5EUkFCSU5EVRAFibYETUlORFUQBbwSTlVLVEEQCPjRAlBVU0hQSUtBEBiItgRSRVZFUlNFRCBOSU5FLUxJS0UgQkhBTEUQB/zRAlNJRERIQU0REvLRAlBBQ0lORyBDQU5EUkFCSU5EVRAGzRJWSVJBTUESBYMSU0FSR0EQF4a2BFdFU1RFUk4gRklWRS1MSUtFIEJIQUxFGA+HtgROSU5FLUxJS0UgQkhBTEUME9ISVFJFU1MgU0lHTiBBTlVEQVRUQRcG0RJVREFUVEELDb4SVk9XRUwgU0lHTiBBQRcByBJJFwHMElUXAc8SVxcB/9ECWRYIxRJDQU5EUkEgRR0G1RJMT05HIEUdAckSTxYBxxJFFgG/EkkXAcASSRYByxJPFwG6EkUXArsST0UWD84SUFJJU0hUSEFNQVRSQSBFFgfGElNIT1JUIEUcAcoSTxYBwRJVFwHWEkUXAcISVRgB1xJFFgniElZPQ0FMSUMgTB8B4xJMHgHDElIfAcQSUgMQFElDRSBDT05UUk9MIEZPVVIPAxFPTkUPBpABU1RSSU5HDwUTVEhSRUUQAhJXTwEIqAFJQUVSRVNJUwMVw5gHR09OQUwgQ1JPU1NIQVRDSCBGSUxMAwqARk1FVEVSIFNJR04EDMRFT05EIE9QRVJBVE9SCBeg6QdTSEFQRSBXSVRIIEEgRE9UIElOU0lERQgGnO8HVEFSR0VUCBaZVldJVEggQk9UVE9NIEhBTEYgQkxBQ0sND5ZWTEVGVCBIQUxGIEJMQUNLDRCXVlJJR0hUIEhBTEYgQkxBQ0sNDphWVE9QIEhBTEYgQkxBQ0sCCIBNRSBGQUNFLTEJAYFNMgkBgk0zCQGDTTQJAYRNNQkBhU02Aw7y7QdTRUwgTE9DT01PVElWRQIQz0RGRkVSRU5DRSBCRVRXRUVOAgk4R0lUIEVJR0hUCwaJ4gcgQ09NTUEMCY9JRlVMTCBTVE9QBgQ1RklWRQoGhuIHIENPTU1BCwmMSUZVTEwgU1RPUAcDNE9VUgoGheIHIENPTU1BCwmLSUZVTEwgU1RPUAYEOU5JTkUKBoriByBDT01NQQsJkElGVUxMIFNUT1AGAzFPTkUJBoLiByBDT01NQQoJiElGVUxMIFNUT1AGBTdTRVZFTgsGiOIHIENPTU1BDAmOSUZVTEwgU1RPUAcCNklYCQaH4gcgQ09NTUEKCY1JRlVMTCBTVE9QBgUzVEhSRUULBoTiByBDT01NQQwJiklGVUxMIFNUT1AHAjJXTwkGg+IHIENPTU1BCgmJSUZVTEwgU1RPUAYEMFpFUk8KBoHiByBDT01NQQsJgOIHRlVMTCBTVE9QAw2FpgdSQU0gRk9SIEVBUlRIEAmDpgdMWSBIRUFWRU4UBISmB1VNQU4LDIxNR1JFQVRFUiBZQU5HFAKPTUlOCw6BpgdIRUFWRU5MWSBFQVJUSAwKgqYHVU1BTiBFQVJUSAsLjk1MRVNTRVIgWUFORxMCjU1JTgIOsUZNRU5TSU9OIE9SSUdJTgIkh09OR0JBVCBDSVJDTEVEIFNBTlMtU0VSSUYgRElHSVQgRUlHSFQhBIRPRklWRSIDg09PVVIhBIhPTklORSEDgE9PTkUhBYZPU0VWRU4iAoVPSVghBYJPVEhSRUUiAoFPV08hBIviB1pFUk8bColPTlVNQkVSIFRFTggc/U5ORUdBVElWRSBDSVJDTEVEIERJR0lUIEVJR0hUHwT6TkZJVkUgA/lOT1VSHwT+Tk5JTkUfA/ZOT05FHwX8TlNFVkVOIAL7TklYHwX4TlRIUkVFIAL3TldPGQr/Tk5VTUJFUiBURU4ZFpFPU0FOUy1TRVJJRiBESUdJVCBFSUdIVCoEjk9GSVZFKwONT09VUioEkk9OSU5FKgOKT09ORSoFkE9TRVZFTisCj09JWCoFjE9USFJFRSsCi09XTyoEjOIHWkVSTyQKk09OVU1CRVIgVEVOAhyTR1JFQ1QgQ1VSUkVOVCBTWU1CT0wgRk9STSBUV08HA6/nB0hJVAIKzU1TQUJMRUQgQ0FSBBql7AdQUE9JTlRFRCBCVVQgUkVMSUVWRUQgRkFDRQ0EnuwHRkFDRQMbgkdDT05USU5VT1VTIFVOREVSTElORSBTWU1CT0wDC/jyB0dVSVNFRCBGQUNFAwvq9QdUT1JURUQgRkFDRQIIg2BUVE8gTUFSSwIV2LIEVkVTIEFLVVJVIERJR0lUIEVJR0hUEgTVsgRGSVZFEwPUsgRPVVISBNmyBE5JTkUSA9GyBE9ORRIF17IEU0VWRU4TAtayBElYEgXTsgRU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RRYC9klXTw8K/klOVU1CRVIgVEVOCAr0VE9MT04gRVFVQUwICb9PVVJMWSBMT09QBwahQERBR0dFUggI6lVPV04gVEFDSwcQvEBFWENMQU1BVElPTiBNQVJLBx6fQEhJR0gtUkVWRVJTRUQtOSBRVU9UQVRJT04gTUFSSwgFwFxZUEhFTgcIrERJTlRFR1JBTAsI0kVSU0VDVElPTgcdlVNMRUZUIEFSQyBHUkVBVEVSLVRIQU4gQlJBQ0tFVAgK01RPR0lDQUwgQU5EDwLUVE9SCQaXQFcgTElORQoRnkAtOSBRVU9UQVRJT04gTUFSSwsZwlxSRVZFUlNFRC05IFFVT1RBVElPTiBNQVJLBxOiVU5FU1RFRCBHUkVBVEVSLVRIQU4OCaFVTEVTUy1USEFOFw6jVSBXSVRIIFVOREVSQkFSBw6XXE9CTElRVUUgSFlQSEVOBwT6U1BMVVMIB7tVUkVDRURFUwkDs0BJTUUMD55gIFFVT1RBVElPTiBNQVJLBw3HQFFVRVNUSU9OIE1BUksHG5ZTUklHSFQgQVJDIExFU1MtVEhBTiBCUkFDS0VUBxD9VVNPTElEVVMgT1BFUkFUT1IIEs5UUVVBUkUgSU5URVJTRUNUSU9ODgXPVFVOSU9OCAzJXFRBQ0tFRCBDT01NQQkN7FVST0tFIE5PVCBTSUdOCAXQRVVCU0VUCQa8VUNDRUVEUwkG0UVQRVJTRVQJDcRcU1BFTlNJT04gTUFSSwcF00VVTklPTggG61VQIFRBQ0sHDPhHVkVSVElDQUwgQkFSExblVSBET1VCTEUgTEVGVCBUVVJOU1RJTEUbD6tFUklHSFQgVFVSTlNUSUxFFA7jVUxFRlQgVFVSTlNUSUxFEASWQExJTkUHDcz8A1dBVlkgT1ZFUkxJTkUGD99TLUVOREVEIE1VTFRJTUFQBx2aVUxJTkUgRVFVQUwgVE8gT1IgR1JFQVRFUi1USEFOGAmZVUxFU1MtVEhBTgwgnFVTTEFOVEVEIEVRVUFMIFRPIE9SIEdSRUFURVItVEhBTiAJm1VMRVNTLVRIQU4UGPpVR1JFQVRFUi1USEFOIE9SIEVRVUFMIFRPFBX5VUxFU1MtVEhBTiBPUiBFUVVBTCBUTwcQgkJTVFJVQ0sgQ0FQSVRBTCBDFgW+QkdBTU1BFgGNQkgWAZVCThYBmUJQFwG/QkkWAZpCURYBnUJSFgGkQloOEMVCSVRBTElDIENBUElUQUwgRBUHxkJTTUFMTCBEGwHHQkUbAchCSRsByUJKDg/AQk4tQVJZIFNVTU1BVElPTg4LvUJTTUFMTCBHQU1NQRQCvEJQSQYNok1EIEZFTUFMRSBTSUdOCAmjTU1BTEUgU0lHTggG/FdTWU1CT0wDBenmB0dITlVUAgvK6gdWRSBPRiBQRUFDRQIMhEZXTiBBUlJPV0hFQUQFCf9SRklTSCBUQUlMBRfxRVJJR0hUIERJQUdPTkFMIEVMTElQU0lTBQSkRVRBQ0sJEvFVIFdJVEggQ0lSQ0xFIEJFTE9XBCT3mActRkFDSU5HIFNOQUtFIEhFQUQgV0lUSCBDTE9TRUQgTU9VVEgcCvOYB09QRU4gTU9VVEgFEfibB1BPSU5USU5HIEFJUlBMQU5FDwrjmAdUT01JQyBCT01CDgv7mAdFTkVSR1kgV0FWRQ4E/JsHRlJPRw4KmpkHUkFDSU5HIENBUg8Lu+oHRUQgVFJJQU5HTEUPBOiYB0lGTEUPCtmYB09DS0VUIFNISVAOEr3qB1NNQUxMIFJFRCBUUklBTkdMRQ8LhJgHVElDSyBGSUdVUkUOHehTVFJJQU5HTEUgV0lUSCBMRUZUIEhBTEYgQkxBQ0scEOlTUklHSFQgSEFMRiBCTEFDSwQQiPIHV0FSRCBGQUNJTkcgSE9PSxQJivIHIFdJVEggRE9UEAyJ8gdOT1RDSEVEIEhPT0scCYvyByBXSVRIIERPVAgIlFxTIEFOQ09SQQsEk0NSUk9XDxu39wcgQU5EIFJJR0hUIE9ORSBFSUdIVEggQkxPQ0sQCKdDRlJPTSBCQVIQGvVDTEVGVFdBUkRTIE9GIFVQV0FSRFMgQVJST1cQBpNSVE8gQkFSEBW1Q1dJVEggQ09STkVSIExFRlRXQVJEUxUN30NET1VCTEUgU1RST0tFFRWX8AdFUVVJTEFURVJBTCBBUlJPV0hFQUQVEYhSSE9SSVpPTlRBTCBTVFJPS0UVGIvwB0xBUkdFIFRSSUFOR0xFIEFSUk9XSEVBRBUZh/AHTUVESVVNIFRSSUFOR0xFIEFSUk9XSEVBRBUMm/EHTk9UQ0hFRCBUQUlMFRuT8AdTTUFMTCBFUVVJTEFURVJBTCBBUlJPV0hFQUQbEoPwB1RSSUFOR0xFIEFSUk9XSEVBRBUNskNUSVAgTEVGVFdBUkRTGQqzQ1JJR0hUV0FSRFMKC4dWQkxBQ0sgQVJST1cVB7PxByBUTyBCQVIQE4tXQ0lSQ0xFRCBXSElURSBBUlJPVwoQv/AHQ09NUFJFU1NFRCBBUlJPVwoM40NEQVNIRUQgQVJST1cLC9NDT1VCTEUgQVJST1cKEbfwB0ZJTkdFUi1QT1NUIEFSUk9XCj/lUkhBUlBPT04gV0lUSCBCQVJCIExFRlQgQkVTSURFIERPV05XQVJEUyBIQVJQT09OIFdJVEggQkFSQiBSSUdIVCgf71JVUFdBUkRTIEhBUlBPT04gV0lUSCBCQVJCIFJJR0hUIQjhUkZST00gQkFSIQbZUlRPIEJBUiAFw0NXQVJEUxwO3VJSSUdIVCBGUk9NIEJBUiIG1VJUTyBCQVIhBcJDV0FSRFMLCsfwB0VBVlkgQVJST1cQEMPwB0NPTVBSRVNTRUQgQVJST1cKDcpDUEFJUkVEIEFSUk9XUwoP8U9RVUFEUlVQTEUgQVJST1cKBu/sB1JPQ0tFVAoQ0/AHU0FOUy1TRVJJRiBBUlJPVwsMu/AHUVVBUkVEIEFSUk9XChKT8QdUUklBTkdMRSBBUlJPV0hFQUQSDeNWLUhFQURFRCBBUlJPVx8rg1cgTEVGVFdBUkRTIE9GIFVQV0FSRFMgVFJJQU5HTEUtSEVBREVEIEFSUk9XIAbzVlRPIEJBUiAPq/AHV0lUSCBCT0xEIFNIQUZUJRj9VkRPVUJMRSBIT1JJWk9OVEFMIFNUUk9LRSULr/AHSEVBVlkgU0hBRlQlEqBXTE9ORyBUSVAgTEVGVFdBUkRTLgqhV1JJR0hUV0FSRFMlDKfwB01FRElVTSBTSEFGVCUMo/AHTkFSUk9XIFNIQUZUJRCz8AdWRVJZIEhFQVZZIFNIQUZUGgztVkRBU0hFRCBBUlJPVxoNh1dQQUlSRUQgQVJST1dTGgzNVlpJR1pBRyBBUlJPVw0Ji1JQTEUgQVJST1cLD6FDV08gSEVBREVEIEFSUk9XDSbvVy1IRUFERUQgQVJST1cgV0lUSCBUUklBTkdMRSBBUlJPV0hFQURTCgvpQ1dISVRFIEFSUk9XFRqX8QcgV0lUSElOIFRSSUFOR0xFIEFSUk9XSEVBRAoMr0NaSUdaQUcgQVJST1cBC69B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IERBU0gDBIBAUVVBRAMFgkBTUEFDRQIRlwFEIE9GIEdVQVJERUQgQVJFQQcECkxJTkUHBhlNRURJVU0HBY5EUFJPT0YKC5cBVEVDVEVEIEFSRUEHDYcBU0VMRUNURUQgQVJFQQcEA1RFWFQICwRSQU5TTUlTU0lPThMGFyBCTE9DSwQamuoHV0lUSCBMRUZUV0FSRFMgQVJST1cgQUJPVkUCAQVRAwQFVUlSWQIKhkdURVIgU1lNQk9MAgaJTlZFTE9QRQgb6ekHIFdJVEggRE9XTldBUkRTIEFSUk9XIEFCT1ZFDgmE6wdMSUdIVE5JTkcBAgpPTAIBGU0CAQRUAQKXAVBBARTVRVFVQUwgQU5EIFBBUkFMTEVMIFRPBhDdRFRPIEJZIERFRklOSVRJT04JD91FT1IgR1JFQVRFUi1USEFODAncRUxFU1MtVEhBTgwI3kVQUkVDRURFUwwI30VTVUNDRUVEUwUH1URTIENPTE9OBwQ9U0lHTgsWwFYgQUJPVkUgTEVGVFdBUkRTIEFSUk9XEgnxVFBMVVMgU0lHThIQ8VJSSUdIVFdBUkRTIEFSUk9XEg7zVFRJTERFIE9QRVJBVE9SDRPjU05EIFNMQU5URUQgUEFSQUxMRUwgEeRTIFdJVEggVElMREUgQUJPVkUMEK5VV0lUSCBCVU1QWSBBQk9WRREJ5lRET1QgQkVMT1cRDpZXSU5GSU5JVFkgQUJPVkUaBflXQkVMT1cRIfdUVFdPIERPVFMgQUJPVkUgQU5EIFRXTyBET1RTIEJFTE9XBw3uVFdJVEggQVNURVJJU0sDC9pESUFOR1VMQVIgVE8ECc1EVkFMRU5UIFRPCxT4VFdJVEggRk9VUiBET1RTIEFCT1ZFARCrRlJBU0UgVE8gVEhFIExFRlQNBaZGUklHSFQCC/BXSVMgRk9STSBPTkUKA/FXVFdPAhfzU1JPUi1CQVJSRUQgQkxBQ0sgQ0lSQ0xFEwfxU0RJQU1PTkQTBu9TU1FVQVJFDQzyU1dISVRFIENJUkNMRRMH8FNESUFNT05EEwbuU1NRVUFSRQEChwFTQQIBG0MDAxtBUEUCDq5CVElNQVRFRCBTWU1CT0wIAdlEUwECF1RCAgzlJkhJT1BJQyBDT0xPTgso3SZNQklOSU5HIEdFTUlOQVRJT04gQU5EIFZPV0VMIExFTkdU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AYYlTxQBhyVBEwGBJVUTAoglV0EVAYslQRQBjSVFFQGMJUUUAYolSRMC0FtZQRUB01tBFAHVW0UVAdRbRRQB0ltJFAHWW08UAdFbVRIC6CVZQRQB6yVBEwHtJUUUAewlRRMB6iVJEwHuJU8UAe8lQRMB6SVVEgLYJVpBFAHbJUETAd0lRRQB3CVFEwLgJUhBFQHjJUEUAeUlRRUB5CVFFAHiJUkUAeYlTxQB4SVVFALnJVdBEwHaJUkTAd4lTxQBi1tBEwHZJVUTAt8lV0ETArBbWkEVAbNbQRQBtVtFFQG0W0UUAbJbSRQBtltPFAGxW1UJEZYnVE9OQUwgTUFSSyBDSElSRVQUBZEnREVSRVQZBpgnLUhJREVUFQSUJ0lGQVQUBZcnSElERVQUBZUnS0VOQVQVA5knVVJUFAaSJ1JJS1JJSxQMkydTSE9SVCBSSUtSSUsUBZAnWUlaRVQJCeEmV09SRFNQQUNFAgEDWAENh0JVTEVSIENPTlNUQU5UAgXInQdOT01JQQIHrEFSTyBTSUdOBA6gQS1DVVJSRU5DWSBTSUdOBAvw5wdQRUFOIENBU1RMRQkL5OcHUE9TVCBPRkZJQ0UBDbLmB1ZFUkdSRUVOIFRSRUUBBblEWENFU1MDDSFMQU1BVElPTiBNQVJLDA3JQFFVRVNUSU9OIE1BUksCEoKcB1BMT1NJT04gQVQgSE9SSVpPTgoH/ZsHRlJBTUUtMRAB/psHMhAB/5sHMwMQkewHUkVTU0lPTkxFU1MgRkFDRQIf+A1URU5ERUQgQVJBQklD",
+ "LUlORElDIERJR0lUIEVJR0hUHAT1DUZJVkUdA/QNT1VSHAT5DU5JTkUcA/ENT05FHAX3DVNFVkVOHQL2DUlYHAXzDVRIUkVFHQLyDVdPHATwDVpFUk8DE/3oB1JBVEVSUkVTVFJJQUwgQUxJRU4EILTvB0VNRUxZIEhFQVZZIEZJVkUgU1BPS0VEIEFTVEVSSVNLEAun7wdHUkVFSyBDUk9TUxAHru8HU0FMVElSRRESuu8HSVggU1BPS0VEIEFTVEVSSVNLEAyJ7wdXSElURSBDSVJDTEUWBpPvB1NRVUFSRQECwegHWUUDB9PoB0dMQVNTRVMDAcDoB1MAF/nyB0ZBQ0UgSE9MRElORyBCQUNLIFRFQVJTBQeG6QdNQVNTQUdFBQSm8gdQQUxNBRiL7AdTQVZPVVJJTkcgREVMSUNJT1VTIEZPT0QGELHsB0NSRUFNSU5HIElOIEZFQVIFD5jsB1RIUk9XSU5HIEEgS0lTUwUU6fUHV0lUSCBCQUdTIFVOREVSIEVZRVMKCpPsB0NPTEQgU1dFQVQMCKDyB1dCT1kgSEFUCg7k9QdESUFHT05BTCBNT1VUSAobq/IHRklOR0VSIENPVkVSSU5HIENMT1NFRCBMSVBTCgyV8gdIRUFELUJBTkRBR0UKD6TsB0xPT0sgT0YgVFJJVU1QSAoMt+wHTUVESUNBTCBNQVNLCwbQ8wdPTk9DTEUKD8XsB05PIEdPT0QgR0VTVFVSRQoKxuwHT0sgR0VTVFVSRQsRqPIHTkUgRVlFQlJPVyBSQUlTRUQLHOL1B1BFTiBFWUVTIEFORCBIQU5EIE9WRVIgTU9VVEgPBa7sB01PVVRIFA+w7AcgQU5EIENPTEQgU1dFQVQVCK7yB1ZPTUlUSU5HChjz8gdQQVJUWSBIT1JOIEFORCBQQVJUWSBIQVQLCuP1B0VFS0lORyBFWUULDPryB0xFQURJTkcgRVlFUwoMxOwHUk9MTElORyBFWUVTChCb7AdTVFVDSy1PVVQgVE9OR1VFGhid7AcgQU5EIFRJR0hUTFktQ0xPU0VEIEVZRVMfC5zsB1dJTktJTkcgRVlFCgyC7AdURUFSUyBPRiBKT1kLCpLyB0hFUk1PTUVURVIKGvTyB1VORVZFTiBFWUVTIEFORCBXQVZZIE1PVVRICQm27AdPVVQgTU9VVEgDC7tCU0lNSUxFIFNJR04DBO3nB1RPUlkCA9rzB0lSWQIFxvMHTEFGRUwDCMLmB0xFTiBMRUFGBCavUklORyBESUFHT05BTCBDUk9TU0lORyBOT1JUSCBFQVNUIEFSUk9XGg+sUlJJU0lORyBESUFHT05BTBEf3k1JTiBXSElURSBDSVJDTEUgSU4gQkxBQ0sgU1FVQVJFAgTq6AdNSUxZAgqrTFJTSSBTWU1CT0wCDoXnB1RIRVIgQ0hSSVNUTUFTAga36wdYIElDT04EB+DpB01BQ0hJTkUBC6jsB0VBUkZVTCBGQUNFAwS29QdUSEVSAgnATE1BTEUgU0lHTgMXqgFJTklORSBPUkRJTkFMIElORElDQVRPUgIEuvIHTkNFUgIKoecHUlJJUyBXSEVFTAQB9E1ZAQEMRgEa0ecHSUVMRCBIT0NLRVkgU1RJQ0sgQU5EIEJBTEwCCe/1B0dIVCBDTE9VRAMIkkBVUkUgREFTSAcFh0BTUEFDRQIKxOsHTEUgQ0FCSU5FVAUGwekHRk9MREVSBQkcU0VQQVJBVE9SAwie5wdNIEZSQU1FUwUJ/ekHUFJPSkVDVE9SAgnG9QdOR0VSUFJJTlQDEY1USVRFIFBBUlQgSU5URUdSQUwCAqXqB1JFBAeS7QcgRU5HSU5FBgvv8wdYVElOR1VJU0hFUgQH6PMHQ1JBQ0tFUgQNh+cHV09SSyBTUEFSS0xFUggBhucHUwMOx/IHU1QgUExBQ0UgTUVEQUwGDL1MUVVBUlRFUiBNT09OEgeT5gcgU1lNQk9MEwmb5gdXSVRIIEZBQ0UGDuhAU1RST05HIElTT0xBVEUCAp/oB1NIBBfl5gcgQ0FLRSBXSVRIIFNXSVJMIERFU0lHTgQDyUtFWUUEEaPnB0lORyBQT0xFIEFORCBGSVNIAw3K6AdURUQgSEFORCBTSUdOAgutXFZFIERPVCBNQVJLCQvZQFBVTkNUVUFUSU9OAQvzTUxBRyBJTiBIT0xFAwWp8wdNSU5HTwMKlpkHUFBJTkcgQklSRAMG//IHVCBTSE9FBAXT9QdCUkVBRAQE5UdORVNTAgqcTUVVUi1ERS1MSVMDCqrpB1hFRCBCSUNFUFMCCb7pB09QUFkgRElTSwMCwp0HUkEFB+ZOTCBIRUFSVAMDmE1XRVIGDrTnByBQTEFZSU5HIENBUkRTCA/VQFVOQ1RVQVRJT04gTUFSSwIKs+wHVVNIRUQgRkFDRQMCiPUHVEUCAbD1B1kDCM/yB0lORyBESVNDBwiF6wdFTlZFTE9QRQcG+O0HU0FVQ0VSDQf7mQcgU1lNQk9MDgrKmAdXSVRIIEJFQU1TEgnLmAdPVVQgQkVBTVMBAqvmB09HAwKB5gdHWQIEwOsHTERFUgQMrfUHSU5HIEhBTkQgRkFOAgTV9QdORFVFAgK28wdPVAQG4+gHUFJJTlRTAgWARFIgQUxMAwOpRUNFUwML9OYHSyBBTkQgS05JRkUOC/3mByBXSVRIIFBMQVRFBA6QXEVEIFBBUkFHUkFQSE9TBAPcVUlORwMGDE0gRkVFRAkFDCAoRkYpAwTMnQdUVU5BBgjg8gdFIENPT0tJRQIG8k1VTlRBSU4DGaNOUiBCQUxMT09OLVNQT0tFRCBBU1RFUklTSwUUpU5DTFVCLVNQT0tFRCBBU1RFUklTSwYjlFdPUk5FUiBBUlJPV1MgQ0lSQ0xJTkcgQU5USUNMT0NLV0lTRQUI20BET1QgTUFSSwkL2EBQVU5DVFVBVElPTgULwOYHTEVBRiBDTE9WRVIFEsbvB1BPSU5URUQgQkxBQ0sgU1RBUgUYok5URUFSRFJPUC1TUE9LRUQgQVNURVJJU0sEDYVALVBFUi1FTSBTUEFDRQQHnERUSCBST09UAgaK8wdYIEZBQ0UBFd9CUkFDVElPTiBOVU1FUkFUT1IgT05FCQXEQFNMQVNIAwu6nQdHSUxFIFNZTUJPTAMMvusHTUUgV0lUSCBBTiBYCwe86wdQSUNUVVJFCwW96wdUSUxFUwIL9vIHRUVaSU5HIEZBQ0UDDqNBTkNIIEZSQU5DIFNJR04JA9/mB0lFUwIK5OYHSUVEIFNIUklNUAIHuOgHT0cgRkFDRQMUpegHTlQtRkFDSU5HIEJBQlkgQ0hJQ0sGJqxPVElMVEVEIFNIQURPV0VEIFdISVRFIFJJR0hUV0FSRFMgQVJST1cDAqJGV04FGKbsB0lORyBGQUNFIFdJVEggT1BFTiBNT1VUSAEBHFMCAehASQEI/U1VRUwgUFVNUAIIiEtMTCBCTE9DSwULleYHTU9PTiBTWU1CT0wKCZ3mB1dJVEggRkFDRQUK109PVVRFUiBKT0lOBQQuU1RPUAQPhv4DV0lEVEggQU1QRVJTQU5ECwmH/gNQ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EALjIVVOEAPVIVZJThAC8yFXRRAD7iFYQU4QAvchWU4QA9YhWkVOEQPfIUhBUgkbvTlNVEFWUlVMSSBDQVBJVEFMIExFVFRFUiBBRU4iAro5SU4iAZA5TiEDkTlCQU4hA6o5Q0FOIgOtOUhBUiMCqTlJTiICrDlJTCEDkzlET04hBbg5RUxJRkkiAZQ5TiECtjlGSSEDkjlHQU4iA6Y5SEFOIQOwOUhBRSMBtDlSJAa+OUQgU0lHTiIBsTlFIgKyOUlFIgK1OU9FIQKYOUlOIQSvOUpIQU4iAqs5SUwhA5k5S0FOIgOlOUhBUiELvzlMQUJJQUwgU0lHTiMBmjlTIQObOU1BTiEDnDlOQVIhAp05T04hA545UEFSIgOkOUhBUiEDpzlRQVIhA6A5UkFFIQOhOVNBTiIDqDlISU4hA5c5VEFOIwGiOVIiCbk5VVJORUQgR0FOIQKjOVVOIQOVOVZJTiECszlXRSEDrjlYQU4hArc5WU4hA5Y5WkVOIgOfOUhBUgkT+yFQQVJBR1JBUEggU0VQQVJBVE9SCRCtWlNNQUxMIExFVFRFUiBBRU4XAYBaThYDgVpCQU4WA5paQ0FOFwOdWkhBUhgCmVpJThcCnFpJTBYDg1pET04WAoRaRU4WA4JaR0FOFwOWWkhBThYDoFpIQUUYAaRaUhcBoVpFFwKiWklFFwKlWk9FFgKIWklOFgSfWkpIQU4XAptaSUwWA4laS0FOFwOVWkhBUhYDilpMQVMWA4taTUFOFgOMWk5BUhYCjVpPThYDjlpQQVIXA5RaSEFS",
+ "FgOXWlFBUhYDkFpSQUUWA5FaU0FOFwOYWkhJThYDh1pUQU4YAZJaUhYCk1pVThYDhVpWSU4WAqNaV0UWA55aWEFOFgKnWllOFgOGWlpFThcDj1pIQVICD7BBUk1BTiBQRU5OWSBTSUdOAgeTYFRBIE1BUksBBPvoB0hPU1QBC7dCSU1FTCBTWU1CT0wCCdr1B05HRVIgUk9PVAIKkvMHUkFGRkUgRkFDRQMB5+gHTAQIyu0HUyBTWU1CT0wBHIBYTEFHT0xJVElDIENBUElUQUwgTEVUVEVSIEFaVRoHqFhCSUcgWVVTGwOBWFVLWRoOr1hDQVVEQVRFIENIUklWSRsFnVhIUklWSRoGjFhESkVSVkkbBIRYT0JSTxsEh1haRUxPGgSqWEZJVEEbBJdYUklUVRoHg1hHTEFHT0xJGgSYWEhFUlUaAYtYSRsLilhOSVRJQUwgSVpIRRsOqVhPVEFURUQgQklHIFlVUyIJp1hTTUFMTCBZVVMbA4lYWkhFHQSrWElUU0EaBI1YS0FLTxoQrlhMQVRJTkFURSBNWVNMSVRFGwaOWEpVRElKRRoHj1hNWVNMSVRFGgWQWE5BU0hJGgORWE9OVRsCmVhUVRoCmlhQRRsFklhPS09KSRoFk1hSSVRTSRoDnlhTSEEcAptYVEEeA6xYUElDGwSUWExPVk8bCKRYTUFMTCBZVVMjCqVYIFdJVEggVEFJTBsJolhQSURFUlkgSEEaDK1YVFJPS1VUQVNUSSBBGwKcWFNJGwWVWFZSSURPGgOWWFVLVRoEglhWRURFGgShWFlBVEkbA6BYRVJJHQGfWFUcA4VYU1RVGwGmWE8bAaNYVRoGiFhaRU1MSkEbBoZYSElWRVRFCxCwWFNNQUxMIExFVFRFUiBBWlUYB9hYQklHIFlVUxkDsVhVS1kYDt9YQ0FVREFURSBDSFJJVkkZBc1YSFJJVkkYBrxYREpFUlZJGQS0WE9CUk8ZBLdYWkVMTxgE2lhGSVRBGQTHWFJJVFUYB7NYR0xBR09MSRgEyFhIRVJVGAG7WEkZC7pYTklUSUFMIElaSEUZDtlYT1RBVEVEIEJJRyBZVVMgCddYU01BTEwgWVVTGQO5WFpIRRsE21hJVFNBGAS9WEtBS08YEN5YTEFUSU5BVEUgTVlTTElURRkGvlhKVURJSkUYB79YTVlTTElURRgFwFhOQVNISRgDwVhPTlUZAslYVFUYAspYUEUZBcJYT0tPSkkYBcNYUklUU0kYA85YU0hBGgLLWFRBHAPcWFBJQxkExFhMT1ZPGQjUWE1BTEwgWVVTIQrVWCBXSVRIIFRBSUwZCdJYUElERVJZIEhBGAzdWFRST0tVVEFTVEkgQRkCzFhTSRkFxVhWUklETxgDxlhVS1UYBLJYVkVERRgE0VhZQVRJGQPQWEVSSRsBz1hVGgO1WFNUVRkB1lhPGQHTWFUYBrhYWkVNTEpBGQa2WEhJVkVURQMK2/IHU1MgT0YgTUlMSwIK5lNFSUNIIFNUQVJLAhKQ5gdPQkUgV0lUSCBNRVJJRElBTlMDA+TzB1ZFUwMJn+YHV0lORyBTVEFSAQfF8gdPQUwgTkVUAwGQ6AdUAgX98gdHR0xFUwIEzOcHTEZFUgIG/e4HTkdHT05HAgO/9QdPU0UCBY3zB1JJTExBAhCwhgRUSElDIExFVFRFUiBBSFNBDwW0hgRJSFZVUw4HsYYEQkFJUktBTg4Es4YEREFHUw4DuYYERUlTDgXGhgRGQUlIVQ4EsoYER0lCQQ4Et4YESEFHTA8EyIYEV0FJUg4Fx4YESUdHV1MPA7aGBFVKQQ4DvoYESkVSDgW6hgRLVVNNQQ4Fu4YETEFHVVMOBbyGBE1BTk5BDga9hgROQVVUSFMPC8qGBElORSBIVU5EUkVEEgLBhgRUWQ4FyYYET1RIQUwOCMCGBFBBSVJUSFJBDgi1hgRRQUlSVEhSQQ4FwoYEUkFJREEOBcOGBFNBVUlMDgXEhgRURUlXUw8FuIYESElVVEgOBL+GBFVSVVMOBcWGBFdJTkpBAQ2T5wdSQURVQVRJT04gQ0FQAxPXpgROVEhBIEFVIExFTkdUSCBNQVJLCAiFpgRMRVRURVIgQRABhqYEQRABkKYESRABlKYEVQ8CrKYEQkEQAq2mBEhBDwKapgRDQRACm6YESEEPAqamBERBEAKhpgREQRECoqYESEEQAqemBEhBDwKPpgRFRQ8Cl6YER0EQApimBEhBDwK5pgRIQQ8Bh6YESRABiKYESQ8CnKYESkEQAp2mBEhBDwKVpgRLQRAClqYESEEPArKmBExBEAKzpgRMQQ8CrqYETUEPAqimBE5BEAKZpgRHQRACo6YETkEQAp6mBFlBDwKTpgRPTw8CqqYEUEEQAqumBEhBDwKwpgRSQQ8CuKYEU0EQAramBEhBEAK3pgRTQQ8CpKYEVEEQAqWmBEhBEAKfpgRUQRECoKYESEEPAYmmBFUQAYqmBFUPArWmBFZBEA3epgRFRElDIEFOVVNWQVJBFQ/fpgRET1VCTEUgQU5VU1ZBUkEQCIymBE9DQUxJQyBMGAHhpgRMFwGLpgRSGAHgpgRSDwKvpgRZQQgC0KYET00IDYKmBFNJR04gQU5VU1ZBUkEOB72mBFZBR1JBSEENC4GmBENBTkRSQUJJTkRVDheApgRPTUJJTklORyBBTlVTVkFSQSBBQk9WRQ0FvKYETlVLVEENBd2mBFBMVVRBDQbNpgRWSVJBTUEPBYOmBFNBUkdBCA2+pgRWT1dFTCBTSUdOIEFBFAHIpgRJFAHMpgRVEwLHpgRFRRMBv6YESRQBwKYESRMCy6YET08TAcGmBFUUAcKmBFUTCeKmBFZPQ0FMSUMgTBwB46YETBsBw6YEUhwBxKYEUgMDx+YHUEVTAwlgVkUgQUNDRU5UAjKMVUVBVEVSLVRIQU4gQUJPVkUgRE9VQkxFLUxJTkUgRVFVQUwgQUJPVkUgTEVTUy1USEFOEyGSVUxFU1MtVEhBTiBBQk9WRSBET1VCTEUtTElORSBFUVVBTBMQ+FJSSUdIVFdBUkRTIEFSUk9XExeQVVNJTUlMQVIgQUJPVkUgTEVTUy1USEFOGwiOVU9SIEVRVUFMFDCUVUxBTlRFRCBFUVVBTCBBQk9WRSBMRVNTLVRIQU4gQUJPVkUgU0xBTlRFRCBFUVVBTA4SilVORCBOT1QgQVBQUk9YSU1BVEURGIhVU0lOR0xFLUxJTkUgTk9UIEVRVUFMIFRPDRClVUJFU0lERSBMRVNTLVRIQU4OD+lEVVQgTk9UIEVRVUFMIFRPGArnRUlWQUxFTlQgVE8ND6dVQ0xPU0VEIEJZIENVUlZFHBSpVSBBQk9WRSBTTEFOVEVEIEVRVUFMDRXbRUVRVUFMIFRPIE9SIExFU1MtVEhBTg0OhlVPUiBBUFBST1hJTUFURRAI5URFUVVBTCBUTxMK80RJVkFMRU5UIFRPEAn3RExFU1MtVEhBThAQ/lRTTEFOVEVEIEVRVUFMIFRPIA+CVSBXSVRIIERPVCBBQk9WRS8FhFUgTEVGVCoGgFVJTlNJREUODOdEVkVSIEVRVUFMIFRPERGkVUxBUFBJTkcgTEVTUy1USEFODQQ+U0lHTg0S+lRXSVRIIENJUkNMRSBJTlNJREUSA9dFRE9UEhP8VFFVRVNUSU9OIE1BUksgQUJPVkUDGcSCBEVLIEFDUk9QSE9OSUMgQVRUSUMgRklGVFkcCNGCBCBTVEFURVJTHQfKggRUQUxFTlRTHgfHggRIT1VTQU5EJQjWggQgU1RBVEVSUxkCw4IEVkUbCMWCBCBIVU5EUkVEIwjTggQgU1RBVEVSUyQHzIIEVEFMRU5UUxwHz4IEU1RBVEVSUxwHyIIEVEFMRU5UUx0HxoIESE9VU0FORCQIzoIEIFRBTEVOVFMXC8KCBE9ORSBEUkFDSE1BGwTBggRIQUxGHA7SggRVTkRSRUQgU1RBVEVSUyMHy4IEVEFMRU5UUxsHwIIEUVVBUlRFUhsQ1IIEVEhPVVNBTkQgU1RBVEVSUyQHzYIEVEFMRU5UUxcI14IEVEVOIE1OQVMbB9CCBFNUQVRFUlMbB8mCBFRBTEVOVFMcD9WCBEhPVVNBTkQgU1RBVEVSUxEW74IEQ0FSWVNUSUFOIEZJVkUgSFVORFJFRBIU3YIEWVJFTkFJQyBUV08gRFJBQ0hNQVMREfOCBERFTFBISUMgRklWRSBNTkFTERfsggRFUElEQVVSRUFOIEZJVkUgSFVORFJFRBwD24IEVFdPHwneggQgRFJBQ0hNQVMRFNiCBEhFUkFFVU0gT05FIFBMRVRIUk9OFA3oggRNSU9OSUFOIEZJRlRZHAPaggRPTkUcA+KCBFRFThEN44IETUVTU0VOSUFOIFRFThET8IIETkFYSUFOIEZJVkUgSFVORFJFRBET9IIEU1RSQVRJQU4gRklGVFkgTU5BUxEO6YIEVEhFU1BJQU4gRklGVFkcCu6CBFZFIEhVTkRSRUQfCPKCBFRIT1VTQU5EGgPZggRPTkUdCOqCBCBIVU5EUkVEHgjxggRUSE9VU0FORBoD5IIEVEVOGwXlggRISVJUWRwL64IEUkVFIEhVTkRSRUQbAtyCBFdPEg/mggRST0VaRU5JQU4gRklGVFkhD+eCBCBBTFRFUk5BVEUgRk9STR4C34IEVkUgCO2CBCBIVU5EUkVEHAPgggRURU4fD+GCBCBBTFRFUk5BVEUgRk9STQcJhwdOTyBURUxFSUEHCoeDBFJPVVJBIFNJR04ICYaDBFRBQkUgU0lHTgYL0AdCRVRBIFNZTUJPTAYi/gdDQVBJVEFMIERPVFRFRCBMVU5BVEUgU0lHTUEgU1lNQk9MDgrPB0tBSSBTWU1CT0wODJEHTEVUVEVSIEFMUEhBGguJPiBXSVRIIERBU0lBJQmNPiBBTkQgT1hJQS4TjT8gQU5EIFBST1NHRUdSQU1NRU5JKguPPlBFUklTUE9NRU5JNROPPyBBTkQgUFJPU0dFR1JBTU1FTkkrDYk/Uk9TR0VHUkFNTUVOSSoFiz5WQVJJQS8Tiz8gQU5EIFBST1NHRUdSQU1NRU5JIAa5P01BQ1JPTiAEuz9PWElBIA68P1BST1NHRUdSQU1NRU5JIQSIPlNJTEklCYw+IEFORCBPWElBLhOMPyBBTkQgUFJPU0dFR1JBTU1FTkkqC44+UEVSSVNQT01FTkk1E44/IEFORCBQUk9TR0VHUkFNTUVOSSsNiD9ST1NHRUdSQU1NRU5JKgWKPlZBUklBLxOKPyBBTkQgUFJPU0dFR1JBTU1FTkkgBYYHVE9OT1MgBbo/VkFSSUEhBbg/UkFDSFkWDPIGUkNIQUlDIFNBTVBJFQSSB0JFVEEVA6cHQ0hJFQWUB0RFTFRBFQeVB0VQU0lMT04cC5k+IFdJVEggREFTSUEnCZ0+IEFORCBPWElBLAWbPlZBUklBIgTJP09YSUEiBZg+UFNJTEknCZw+IEFORCBPWElBLAWaPlZBUklBIgWIB1RPTk9TIgXIP1ZBUklBFgKXB1RBGAupPiBXSVRIIERBU0lBIwmtPiBBTkQgT1hJQSwTnT8gQU5EIFBST1NHRUdSQU1NRU5JKAuvPlBFUklTUE9NRU5JMxOfPyBBTkQgUFJPU0dFR1JBTU1FTkkpDZk/Uk9TR0VHUkFNTUVOSSgFqz5WQVJJQS0Tmz8gQU5EIFBST1NHRUdSQU1NRU5JHgTLP09YSUEeDsw/UFJPU0dFR1JBTU1FTkkfBKg+U0lMSSMJrD4gQU5EIE9YSUEsE5w/IEFORCBQUk9TR0VHUkFNTUVOSSgLrj5QRVJJU1BPTUVOSTMTnj8gQU5EIFBST1NHRUdSQU1NRU5JKQ2YP1JPU0dFR1JBTU1FTkkoBao+VkFSSUEtE5o/IEFORCBQUk9TR0VHUkFNTUVOSR4FiQdUT05PUx4Fyj9WQVJJQRUFkwdHQU1NQRUE8AZIRVRBFQSZB0lPVEEZC7k+IFdJVEggREFTSUEkCb0+IEFORCBPWElBKQu/PlBFUklTUE9NRU5JKQW7PlZBUklBIAiqB0lBTFlUSUtBHwbZP01BQ1JPTh8E2z9PWElBHwW4PlBTSUxJJAm8PiBBTkQgT1hJQSkLvj5QRVJJU1BPTUVOSSkFuj5WQVJJQR8FigdUT05PUx8F2j9WQVJJQSAF2D9SQUNIWRUFmgdLQVBQQRUFmwdMQU1EQRUCnAdNVRUCnQdOVRUFqQdPTUVHQRoL6T4gV0lUSCBEQVNJQSUJ7T4gQU5EIE9YSUEuE60/IEFORCBQUk9TR0VHUkFNTUVOSSoL7z5QRVJJU1BPTUVOSTUTrz8gQU5EIFBST1NHRUdSQU1NRU5JKw2pP1JPU0dFR1JBTU1FTkkqBes+VkFSSUEvE6s/IEFORCBQUk9TR0VHUkFNTUVOSSAE+z9PWElBIA78P1BST1NHRUdSQU1NRU5JIQToPlNJTEklCew+IEFORCBPWElBLhOsPyBBTkQgUFJPU0dFR1JBTU1FTkkqC+4+UEVSSVNQT01FTkk1E64/IEFORCBQUk9TR0VHUkFNTUVOSSsNqD9ST1NHRUdSQU1NRU5JKgXqPlZBUklBLxOqPyBBTkQgUFJPU0dFR1JBTU1FTkkgBY8HVE9OT1MgBfo/VkFSSUEXBZ8HSUNST04cC8k+IFdJVEggREFTSUEnCc0+IEFORCBPWElBLAXLPlZBUklBIgT5P09YSUEiBcg+UFNJTEknCcw+IEFORCBPWElBLAXKPlZBUklBIgWMB1RPTk9TIgX4P1ZBUklBFRL2BlBBTVBIWUxJQU4gRElHQU1NQRYCpgdISRYBoAdJFgKoB1NJFQOhB1JITxgL7D8gV0lUSCBEQVNJQRUD+gdTQU4WAvcHSE8WBKMHSUdNQRUDpAdUQVUWBJgHSEVUQRUHpQdVUFNJTE9OHAvZPiBXSVRIIERBU0lBJwndPiBBTkQgT1hJQSwL3z5QRVJJU1BPTUVOSSwF2z5WQVJJQSMIqwdJQUxZVElLQSIG6T9NQUNST04iBOs/T1hJQSIFjgdUT05PUyIF6j9WQVJJQSMF6D9SQUNIWRUCngdYSRUD/wZZT1QVBJYHWkVUQQ8S+QdVTkFURSBTSUdNQSBTWU1CT0wOI/8HUkVWRVJTRUQgRE9UVEVEIExVTkFURSBTSUdNQSBTWU1CT0wXE/0HTFVOQVRFIFNJR01BIFNZTUJPTA4M9AdUSEVUQSBTWU1CT0wGBf4/REFTSUELCd4/IEFORCBPWElBEAvfP1BFUklTUE9NRU5JEAXdP1ZBUklBBxHuP0lBTFlUSUtBIEFORCBPWElBFAvBP1BFUklTUE9NRU5JFAXtP1ZBUklBEAWFB1RPTk9TBwv7ggRSQUNITUEgU0lHTgYPgIMERklWRSBPQk9MUyBTSUdOBw7/ggRPVVIgT0JPTFMgU0lHTgYLiIMER1JBTU1BIFNJR04GDo2DBElORElDVElPTiBTSUdOCBydpAdTVFJVTUVOVEFMIE5PVEFUSU9OIFNZTUJPTC0xJAGjpAcxJAGkpAcyJAGlpAczJAGmpAc0JAGnpAc3JAGopAc4JAGppAc5IwGepAcyJAGqpAczJAGrpAc0JAGspAc1JAGtpAc2JAGupAc3JAGvpAc5IwKwpAczMCQBsaQHMiQBsqQHNiQBs6QHNyQBtKQHOCQBtaQHOSMBn6QHNCQBtqQHMCQBt6QHMiQBuKQHMyQBuaQHNSQBuqQHNyQBu6QHOCQBvKQHOSMBoKQHNSQBvaQHMCQBvqQHMSQBv6QHMiQBwKQHMyQBwaQHNCMBoaQHNyMBoqQHOAYK1wdLQUkgU1lNQk9MCArwB1BQQSBTWU1CT0wHBr0/T1JPTklTBxCCgwRZQVRIT1MgQkFTRSBTSUdOBhTYB0xFVFRFUiBBUkNIQUlDIEtPUFBBDQfcB0RJR0FNTUEN",
+ "Bd4HS09QUEENBeAHU0FNUEkOEqY6TUFMTCBDQVBJVEFMIEdBTU1BGwWnOkxBTURBGwXl1gJPTUVHQRsCqDpQSRwCqjpTSRsDqTpSSE8OBdoHVElHTUENA/MHWU9UBwmDgwRJVFJBIFNJR04HEfUGT1dFUiBOVU1FUkFMIFNJR04HFPUHVU5BVEUgRVBTSUxPTiBTWU1CT0wNDPIHU0lHTUEgU1lNQk9MBg2BgwRNRVRSRVRFUyBTSUdOBw3FpAdVU0lDQUwgTEVJTU1BBgz0Bk5VTUVSQUwgU0lHTgYJ/IIET0JPTCBTSUdOBwz1ggRORSBIQUxGIFNJR04TD/aCBCBBTFRFUk5BVEUgRk9STQoMi4MEUVVBUlRFUiBTSUdOBwqEgwRVTktJQSBTSUdOBwP9P1hJQQYLwD9QRVJJU1BPTUVOSQcJ1QdISSBTWU1CT0wHCNYHSSBTWU1CT0wHDb4/Uk9TR0VHUkFNTUVOSQcEvz9TSUxJCwnOPyBBTkQgT1hJQRALzz9QRVJJU1BPTUVOSRAFzT9WQVJJQQYN/gZRVUVTVElPTiBNQVJLBh72B1JFVkVSU0VEIExVTkFURSBFUFNJTE9OIFNZTUJPTAcJ8QdITyBTWU1CT0wKEvwHV0lUSCBTVFJPS0UgU1lNQk9MBg2MgwRTSU5VU09JRCBTSUdOBx/8Bk1BTEwgRE9UVEVEIExVTkFURSBTSUdNQSBTWU1CT0wMDLEHTEVUVEVSIEFMUEhBGAuBPiBXSVRIIERBU0lBIwmFPiBBTkQgT1hJQSwShT8gQU5EIFlQT0dFR1JBTU1FTkkoC4c+UEVSSVNQT01FTkkzEoc/IEFORCBZUE9HRUdSQU1NRU5JKAWDPlZBUklBLRKDPyBBTkQgWVBPR0VHUkFNTUVOSSgNgT9ZUE9HRUdSQU1NRU5JHgaxP01BQ1JPTh4E8T5PWElBIhK0PyBBTkQgWVBPR0VHUkFNTUVOSR4Ltj9QRVJJU1BPTUVOSSkStz8gQU5EIFlQT0dFR1JBTU1FTkkfBIA+U0lMSSMJhD4gQU5EIE9YSUEsEoQ/IEFORCBZUE9HRUdSQU1NRU5JKAuGPlBFUklTUE9NRU5JMxKGPyBBTkQgWVBPR0VHUkFNTUVOSSgFgj5WQVJJQS0Sgj8gQU5EIFlQT0dFR1JBTU1FTkkoDYA/WVBPR0VHUkFNTUVOSR4FrAdUT05PUx4F8D5WQVJJQSMSsj8gQU5EIFlQT0dFR1JBTU1FTkkfBbA/UkFDSFkeDbM/WVBPR0VHUkFNTUVOSRQM2QdSQ0hBSUMgS09QUEEbBfMGU0FNUEkTBLIHQkVUQRMDxwdDSEkTBbQHREVMVEEUBt0HSUdBTU1BEwe1B0VQU0lMT04aC5E+IFdJVEggREFTSUElCZU+IEFORCBPWElBKgWTPlZBUklBIATzPk9YSUEgBZA+UFNJTEklCZQ+IEFORCBPWElBKgWSPlZBUklBIAWtB1RPTk9TIAXyPlZBUklBFAK3B1RBFguhPiBXSVRIIERBU0lBIQmlPiBBTkQgT1hJQSoSlT8gQU5EIFlQT0dFR1JBTU1FTkkmC6c+UEVSSVNQT01FTkkxEpc/IEFORCBZUE9HRUdSQU1NRU5JJgWjPlZBUklBKxKTPyBBTkQgWVBPR0VHUkFNTUVOSSYNkT9ZUE9HRUdSQU1NRU5JHAT1Pk9YSUEgEsQ/IEFORCBZUE9HRUdSQU1NRU5JHAvGP1BFUklTUE9NRU5JJxLHPyBBTkQgWVBPR0VHUkFNTUVOSR0EoD5TSUxJIQmkPiBBTkQgT1hJQSoSlD8gQU5EIFlQT0dFR1JBTU1FTkkmC6Y+UEVSSVNQT01FTkkxEpY/IEFORCBZUE9HRUdSQU1NRU5JJgWiPlZBUklBKxKSPyBBTkQgWVBPR0VHUkFNTUVOSSYNkD9ZUE9HRUdSQU1NRU5JHAWuB1RPTk9THAX0PlZBUklBIRLCPyBBTkQgWVBPR0VHUkFNTUVOSRwNwz9ZUE9HRUdSQU1NRU5JEwvCB0ZJTkFMIFNJR01BEwWzB0dBTU1BEwTxBkhFVEETBLkHSU9UQRcLsT4gV0lUSCBEQVNJQSIJtT4gQU5EIE9YSUEnC7c+UEVSSVNQT01FTkknBbM+VkFSSUEeCMoHSUFMWVRJS0EmCdM/IEFORCBPWElBKwvXP1BFUklTUE9NRU5JKwWQB1RPTk9TKwXSP1ZBUklBHQbRP01BQ1JPTh0E9z5PWElBHQvWP1BFUklTUE9NRU5JHgSwPlNJTEkiCbQ+IEFORCBPWElBJwu2PlBFUklTUE9NRU5JJwWyPlZBUklBHQWvB1RPTk9THQX2PlZBUklBHgXQP1JBQ0hZEwW6B0tBUFBBFATfB09QUEETBbsHTEFNREETArwHTVUTAr0HTlUTBckHT01FR0EYC+E+IFdJVEggREFTSUEjCeU+IEFORCBPWElBLBKlPyBBTkQgWVBPR0VHUkFNTUVOSSgL5z5QRVJJU1BPTUVOSTMSpz8gQU5EIFlQT0dFR1JBTU1FTkkoBeM+VkFSSUEtEqM/IEFORCBZUE9HRUdSQU1NRU5JKA2hP1lQT0dFR1JBTU1FTkkeBP0+T1hJQSIS9D8gQU5EIFlQT0dFR1JBTU1FTkkeC/Y/UEVSSVNQT01FTkkpEvc/IEFORCBZUE9HRUdSQU1NRU5JHwTgPlNJTEkjCeQ+IEFORCBPWElBLBKkPyBBTkQgWVBPR0VHUkFNTUVOSSgL5j5QRVJJU1BPTUVOSTMSpj8gQU5EIFlQT0dFR1JBTU1FTkkoBeI+VkFSSUEtEqI/IEFORCBZUE9HRUdSQU1NRU5JKA2gP1lQT0dFR1JBTU1FTkkeBc4HVE9OT1MeBfw+VkFSSUEjEvI/IEFORCBZUE9HRUdSQU1NRU5JHg3zP1lQT0dFR1JBTU1FTkkVBb8HSUNST04aC8E+IFdJVEggREFTSUElCcU+IEFORCBPWElBKgXDPlZBUklBIAT5Pk9YSUEgBcA+UFNJTEklCcQ+IEFORCBPWElBKgXCPlZBUklBIAXMB1RPTk9TIAX4PlZBUklBExL3BlBBTVBIWUxJQU4gRElHQU1NQRQCxgdISRQBwAdJFALIB1NJEwPBB1JITxYL5T8gV0lUSCBEQVNJQRwF5D9QU0lMSRMF4QdTQU1QSRUB+wdOFAL4B0hPFATDB0lHTUEUBdsHVElHTUETA8QHVEFVFAS4B0hFVEETB8UHVVBTSUxPThoL0T4gV0lUSCBEQVNJQSUJ1T4gQU5EIE9YSUEqC9c+UEVSSVNQT01FTkkqBdM+VkFSSUEhCMsHSUFMWVRJS0EpCeM/IEFORCBPWElBLgvnP1BFUklTUE9NRU5JLgWwB1RPTk9TLgXiP1ZBUklBIAbhP01BQ1JPTiAE+z5PWElBIAvmP1BFUklTUE9NRU5JIQTQPlNJTEklCdQ+IEFORCBPWElBKgvWPlBFUklTUE9NRU5JKgXSPlZBUklBIAXNB1RPTk9TIAX6PlZBUklBIQXgP1JBQ0hZEwK+B1hJEwS2B1pFVEEMI/0GUkVWRVJTRUQgRE9U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VU5EIE1BUksSHvD+Ay1ISVJBR0FOQSBQUk9MT05HRUQgU09VTkQgTUFSSwoT4v4DTEVGVCBDT1JORVIgQlJBQ0tFVA4L6f8DV0FSRFMgQVJST1cKFOP+A1JJR0hUIENPUk5FUiBCUkFDS0VUDwvr/wNXQVJEUyBBUlJPVwoN6v8DVVBXQVJEUyBBUlJPVwoM7v8DV0hJVEUgQ0lSQ0xFAgfU5gdNQlVSR0VSAwOo6gdNRVIGCZJNIEFORCBQSUNLCwatTFNJQ0tMRQsG4O0HV1JFTkNIAwKs9QdTQQQIuegHVEVSIEZBQ0UCKJ7yB05EIFdJVEggSU5ERVggQU5EIE1JRERMRSBGSU5HRVJTIENST1NTRUQQGPD1B0ZJTkdFUiBBTkQgVEhVTUIgQ1JPU1NFRAQD3OgHQkFHBgK+8gdMTAQFnfIHU0hBS0UDH9UiR1VMIENIT1NFT05HIENFT05HQ0hJRVVNQ0hJRVVDSBwE0CJJRVVDGwS+IlNJT1McCdEiU0FOR0NJRVVDIAS/IlNJT1MRBo4iSElFVUNIFwbTIi1ISUVVSBgH0iJLSElFVUtIEwzUIlRVRVVNQ0hJRVVDSBkEziJJRVVDGAS8IlNJT1MZCc8iU0FOR0NJRVVDHQS9IlNJT1MRBIwiSUVVQxUGzSItSUVVTkcQBt8iRklMTEVSEAWSIkhJRVVIFQX70gItU0lPUxAFiyJJRVVORxUIySItQ0hJRVVDSBcEyCJJRVVDFgX30gJISUVVSBYGwSJLSVlFT0sWBcMiTUlFVU0WB8YiUEFOU0lPUxcGyyJISUVVUEgXBMQiSUVVUBYF9tICUklFVUwWBMUiU0lPUxYHyiJUSElFVVRIFwXCIklLRVVUEA2dIktBUFlFT1VOTUlFVU0YB9ciUEhJRVVQSBkEqyJJRVVQGAWbIlJJRVVMGAqsIlNTQU5HUElFVVARBo8iSElFVUtIEQWAIklZRU9LFgfaIi1USUtFVVQQBYYiTUlFVU0VB+/SAi1LSVlFT0sWBZwiUElFVVAWBPHSAlNJT1MWBvDSAlRJS0VVVBAFgiJOSUVVThUG3CItQ0lFVUMWBd0iSElFVUgWBpMiS0lZRU9LFgWWIlBJRVVQFgTbIlNJT1MWBpUiVElLRVVUEAfAIlBBTlNJT1MRBpEiSElFVVBIFwb60gItSElFVUgYBdYiUElFVVARBIciSUVVUBUIqCItQ0hJRVVDSBcEpyJJRVVDFgX00gJISUVVSBYH89ICS0hJRVVLSBcFniJJWUVPSxYFnyJOSUVVThYHqiJQSElFVVBIFgShIlNJT1MaBqYiLUNJRVVDGwaiIktJWUVPSxsFpCJQSUVVUBsH8tICVEhJRVVUSBwFoyJJS0VVVBcIpSJTQU5HU0lPUxYHqSJUSElFVVRIFwWgIklLRVVUEAWFIlJJRVVMFQbt0gItQ0lFVUMWBZoiSElFVUgWDevSAktBUFlFT1VOUElFVVAXBu7SAkhJRVVLSBcF5NICSVlFT0sWBejSAk1JRVVNFgWYIk5JRVVOFgXp0gJQSUVVUBYE7NICU0lPUxcK5dICU0FOR0tJWUVPSxsF6tICUElFVVAbBufSAlRJS0VVVBYG5tICVElLRVVUEASJIlNJT1MUCLciLUNISUVVQ0gWBLYiSUVVQxUFuyJISUVVSBUFtSJJRVVORxUHuCJLSElFVUtIFgWtIklZRU9LFQWxIk1JRVVNFQWuIk5JRVVOFQe6IlBISUVVUEgWBLIiSUVVUBoHsyItS0lZRU9LFQWwIlJJRVVMFQm0IlNTQU5HU0lPUxUHuSJUSElFVVRIFgWvIklLRVVUEQmNIlNBTkdDSUVVQxoG+NICLUhJRVVIFQXYIkhJRVVIFQXHIklFVU5HFQaBIktJWUVPSxUFlCJOSUVVThUFiCJQSUVVUBUFmSJSSUVVTBUEiiJTSU9TGQb10gItUElFVVAVB/nSAlRISUVVVEgWBYQiSUtFVVQVC/zSAllFT1JJTkhJRVVIEAeQIlRISUVVVEgRBYMiSUtFVVQWBuPSAi1DSUVVQxcGlyJLSVlFT0sXBeDSAk1JRVVNFwXh0gJQSUVVUBcF3iJSSUVVTBcE4tICU0lPUxAL2SJZRU9SSU5ISUVVSBIGzCJTSUVVTkcHFK9gRE9VQkxFIERPVCBUT05FIE1BUksHBuRiRklMTEVSBxG+I0pPTkdTRU9ORyBDSElFVUNIEgS9I0lFVUMWBvevAy1QSUVVUBcK+K8DU1NBTkdQSUVVUBEFwiNISUVVSBYG9yMtTUlFVU0XBfUjTklFVU4XBfgjUElFVVAXBfYjUklFVUwRBbwjSUVVTkcWCO8jLUtISUVVS0gYBewjSVlFT0sXC+0jU1NBTkdLSVlFT0sRDeIjS0FQWUVPVU5NSUVVTRkH9CNQSElFVVBIGgTmI0lFVVAZBd2vA1JJRVVMEga/I0hJRVVLSBIFqCNJWUVPSxcI/CMtQ0hJRVVDSBgF/iNISUVVSBgH/SNLSElFVUtIGAX6I05JRVVOGAX7I1BJRVVQGAXDI1JJRVVMGASqI1NJT1McB8QjLUtJWUVPSxEFtyNNSUVVTRYI4CMtQ0hJRVVDSBgE4q8DSUVVQxcF4SNISUVVSBcG2iNLSVlFT0sXBd6vA05JRVVOFwffI1BBTlNJT1MYBNwjSUVVUBwF4a8DLVNJT1MXBdsjUklFVUwXBN0jU0lPUxgJ368DU0FOR05JRVVOHATeI1NJT1MRBasjTklFVU4WCMyvAy1DSElFVUNIGASsI0lFVUMXBa0jSElFVUgXBsUjS0lZRU9LFwfII1BBTlNJT1MXBcuvA1JJRVVMFwTHI1NJT1MXB8kjVEhJRVVUSBgFxiNJS0VVVBEH6yNQQU5TSU9TGA70rwMtS0FQWUVPVU5QSUVVUBkF868DUElFVVASBsEjSElFVVBIGAbzIy1QSUVVUBkE+q8DU0lPUxkH+68DVEhJRVVUSBIEuCNJRVVQFgjprwMtQ0hJRVVDSBgE6K8DSUVVQxcF5SNISUVVSBcF5a8DTUlFVU0XB+QjUEhJRVVQSBcF4yNSSUVVTBwI5K8DLVBISUVVUEgXBLkjU0lPUxsH568DLVRJS0VVVBcG468DVElLRVVUEQWvI1JJRVVMFga2Iy1ISUVVSBcN1SNLQVBZRU9VTlBJRVVQGAbYI0hJRVVLSBgFsCNJWUVPSx0G1q8DLUhJRVVIHgTMI1NJT1MXBbEjTUlFVU0cBtivAy1ISUVVSB0G0SNLSVlFT0sdBNIjU0lPUxcFzSNOSUVVThcH1yNQQU5TSU9TGAa1I0hJRVVQSBgEsiNJRVVQHAbUIy1ISUVVSB0H2q8DUEhJRVVQSB0E0yNTSU9THQbZrwNUSUtFVVQXBLMjU0lPUxgK1a8DU0FOR0tJWUVPSxwE1iNTSU9TFwe0I1RISUVVVEgYBc4jSUtFVVQdBs8jLUhJRVVIFwvZI1lFT1JJTkhJRVVIIgbcrwMtSElFVUgZBtuvA1NJRVVORxEEuiNTSU9TFQjwrwMtQ0hJRVVDSBcE768DSUVVQxYF8q8DSElFVUgWDeuvA0tBUFlFT1VOUElFVVAXBecjSVlFT0sWBeqvA01JRVVNFgfurwNQQU5TSU9TFwTqI0lFVVAWBekjUklFVUwWB/GvA1RISUVVVEgXBegjSUtFVVQSCfmvA1NBTkdDSUVVQxYF7iNJRVVORxYGqSNLSVlFT0sWBeCvA01JRVVNFgX/I05JRVVOFgXmrwNQSUVVUBYF0CNSSUVVTBsI168DLUtISUVVS0gWBLsjU0lPUxoH7K8DLUtJWUVPSxsG7a8DVElLRVVUFgbNrwNUSUtFVVQcBs6vAy1QSUVVUBYI7iNZRVNJRVVORxEHwCNUSElFVVRIEgWuI0lLRVVUFwjTrwMtQ0hJRVVDSBkE0q8DSUVVQxgGyiNLSVlFT0sYBc+vA1BJRVVQGAXLI1JJRVVMGATQrwNTSU9THAfRrwMtS0lZRU9LGAfUrwNUSElFVVRIEQv5I1lFT1JJTkhJRVVIEwbwI1NJRVVORxkG9q8DLUhJRVVIGgfvI0tISUVVS0gbBewjSVlFT0saBfWvA01JRVVNGgfyI1BBTlNJT1MaBPEjU0lPUxsK7SNTQU5HS0lZRU9LCArhIlVOR1NFT05HIEESA6MjLUVVEwH2Ik8TAfciVRIB4iJFEgSeI1JBRUEWAsWvAy1BFwHGrwNFGAGfI08XAaEjSRcBoCNVEQHmIkUSAeUiTxMD/CItRVUUAfoiTxQB+yJVEgHzIlUTArmvAy1BFAG7rwNFFQG6rwNPFQGWI1UUAbyvA08UAZUjVREG4CJGSUxMRVIRAfUiSRICmCMtQRQEnSNSQUVBEwKcI0VVEwHErwNJEwGaI08UAsGvAy1JEwGbI1UTApkjWUEVAr2vAy1PFQG+rwNFFAHArwNFFQG/rwNPFAHCrwNPFAHDrwNVEQHpIk8SAoAjLUUUAf8iTxMBgiNPFAKxrwMtSRMBgyNVEwKmI1lBFQGnI0UUAYEjRRUBsK8DTxIB7CJFEQqiI1NTQU5HQVJBRUERAe4iVRICiSMtQRQBiiNFEwWLI0VPLUVVEwO2rwNJLUkTAY0jVRMCjCNZRRUBta8DTxEC6iJXQRMB6yJFEgHwIkUTAe8iTxIB8SJJEQLjIllBEwL4Ii1PFAGkI1UUAvkiWU8TAeQiRRIB6CJFEwHnIk8UAv0iLU8VAf4iVRUCpSNZQRIB9CJJEwKXIy1VEgHtIk8TArKvAy1BFQGzrwNFFAK0rwNFTxQBiCNJFAGHI08UAoQjWUEWAYUjRRUChiNFTxIB8iJVEwKOIy1BFQG3rwNFFAGQI0UVAY8jTxQBlCNJFAG4rwNPFAGTI1UUApIjWUUWAZEjTwcIz2JMRVRURVIgQQ8B0GJFDwSNY1JBRUETAY5jRQ4HymJDSElFVUNIDwTIYklFVUMOAdRiRQ8B02JPDwHhYlUOBc5iSElFVUgOAeNiSQ8Ex2JFVU5HDg3xYktBUFlFT1VOTUlFVU0WB4RjUEhJRVVQSBcE+GJJRVVQFgr5YlNTQU5HUElFVVAPBstiSElFVUtIDwWxYklZRU9LFAWzYi1TSU9TDgXBYk1JRVVNEwjwYi1QQU5TSU9TFQTuYklFVVAUBO9iU0lPUw4FtGJOSUVVThMGtWItQ0lFVUMUBbZiSElFVUgUB+hiUEFOU0lPUxQE52JTSU9TFAbmYlRJS0VVVA4B12JPDwHaYkUOB/9iUEFOU0lPUw8GzWJISUVVUEgPBMJiSUVVUBMG9mItQ0lFVUMUBvJiS0lZRU9LFATEYlNJT1MYB/RiLUtJWUVPSxkG9WJUSUtFVVQUB/diVEhJRVVUSBUF82JJS0VVVA4FuWJSSUVVTBMGwGItSElFVUgUBrpiS0lZRU9LGgXpYi1TSU9TFAW7Yk1JRVVNFAfsYlBBTlNJT1MVBr9iSElFVVBIFQS8YklFVVAZBetiLVNJT1MUBL1iU0lPUxQHvmJUSElFVVRIFQXqYklLRVVUFAvtYllFT1JJTkhJRVVIDgTFYlNJT1MSBv5iLUNJRVVDEwb6YktJWUVPSxMF+2JOSUVVThMF/WJQSUVVUBMG/GJUSUtFVVQPCcliU0FOR0NJRVVDEwWFY0hJRVVIEwWAY0lFVU5HEwayYktJWUVPSxMF5WJOSUVVThMFw2JQSUVVUBMExmJTSU9TEwa4YlRJS0VVVA4HzGJUSElFVVRIDwW3YklLRVVUDgHcYlUOAthiV0EQAdliRQ8B3mJFEAHdYk8PAd9iSQ4C0WJZQRAB0mJFDwHWYkUQAdViTxEIhmNSSU5ISUVVSBAGgWNTSUVVTkcWCINjLVBBTlNJT1MXBIJjU0lPUw8B4mJJDwHbYk8QAoljLUkRAodjWUETAYhjRQ8B4GJVEAKMYy1JEQKLY1lFEwGKY08HFK5gU0lOR0xFIERPVCBU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Uk9LRSBBTkQgVFdPIERPVFMgQUJPVkUMCdlWRCBTQUxUSVJFByWoT09OQ0FWRS1QT0lOVEVEIEJMQUNLIFJJR0hUV0FSRFMgQVJST1cGJ6BPREFTSEVEIFRSSUFOR0xFLUhFQURFRCBSSUdIVFdBUkRTIEFSUk9XBwyXT0lWSVNJT04gU0lHTgcKsukHT0xMQVIgU0lHTggi3k5VQkxFIENPTU1BIFFVT1RBVElPTiBNQVJLIE9STkFNRU5UDSTdTlRVUk5FRCBDT01NQSBRVU9UQVRJT04gTUFSSyBPUk5BTUVOVAgom/AHV05XQVJEUyBBUlJPVyBXSVRIIEVRVUlMQVRFUkFMIEFSUk9XSEVBRBsbn/AHTEFSR0UgRVFVSUxBVEVSQUwgQVJST1dIRUFEBhjP7wdFSUdIVCBQT0lOVEVEIEJMQUNLIFNUQVIUDdHvB1BJTldIRUVMIFNUQVIUFrhOUkVDVElMSU5FQVIgQkxBQ0sgU1RBUgwPvu8HU1BPS0VEIEFTVEVSSVNLDCLLTlRFQVJEUk9QLVNQT0tFRCBQUk9QRUxMRVIgQVNURVJJU0sHCvDvB1FVQUxTIFNJR04HGOJOWENMQU1BVElPTiBNQVJLIE9STkFNRU5UFwbXTlNZTUJPTAYXyu8HRklWRSBQT0lOVEVEIEJMQUNLIFNUQVILD7LvB1NQT0tFRCBBU1RFUklTSwcbpE5PVVIgQkFMTE9PTi1TUE9LRUQgQVNURVJJU0sGC5pOR1JFRUsgQ1JPU1MGH+NOSEVBUlQgRVhDTEFNQVRJT04gTUFSSyBPUk5BTUVOVAcOl/cHT1JJWk9OVEFMIEZJTEwGFPnsB0lOVEVSUk9CQU5HIE9STkFNRU5UBgzVVkxBUkdFIENJUkNMRQgJx+oHVElOIENST1NTByPwTkVGVC1QT0lOVElORyBBTkdMRSBCUkFDS0VUIE9STkFNRU5UGhfuTlFVT1RBVElPTiBNQVJLIE9STkFNRU5UCiaY8AdXQVJEUyBBUlJPVyBXSVRIIEVRVUlMQVRFUkFMIEFSUk9XSEVBRBsbnPAHTEFSR0UgRVFVSUxBVEVSQUwgQVJST1dIRUFEBxjz7AdJR0FUVVJFIE9QRU4gRVQgT1JOQU1FTlQHJ+BOT1cgRE9VQkxFIENPTU1BIFFVT1RBVElPTiBNQVJLIE9STkFNRU5UCiTfTlNJTkdMRSBDT01NQSBRVU9UQVRJT04gTUFSSyBPUk5BTUVOVAkorU9FUiBSSUdIVC1TSEFET1dFRCBXSElURSBSSUdIVFdBUkRTIEFSUk9XBgqWT01JTlVTIFNJR04HD5ZOVUxUSVBMSUNBVElPTiBYBhCaT05PUlRIIEVBU1QgQVJST1cRDObsB1BPSU5USU5HIEJVRBoJ3uwHVklORSBMRUFGDBHk7AdXRVNUIFBPSU5USU5HIEJVRBoJ3OwHVklORSBMRUFGBhGcTk9QRU4gQ0VOVFJFIENST1NTBxKuTlVUTElORUQgQkxBQ0sgU1RBUgcU1lZWQUwgV0lUSCBPVkFMIElOU0lERQYJlU9QTFVTIFNJR04GJfFOUklHSFQtUE9JTlRJTkcgQU5HTEUgQlJBQ0tFVCBPUk5BTUVOVBsX705RVU9UQVRJT04gTUFSSyBPUk5BTUVOVAsLmU9XQVJEUyBBUlJPVxYbmvAHIFdJVEggRVFVSUxBVEVSQUwgQVJST1dIRUFEHBue8AdMQVJHRSBFUVVJTEFURVJBTCBBUlJPV0hFQUQHHJxPT1VORC1USVBQRUQgUklHSFRXQVJEUyBBUlJPVwYHrO8HU0FMVElSRQgd++wHTlMtU0VSSUYgSU5URVJST0JBTkcgT1JOQU1FTlQHGvHsB0NSSVBUIExJR0FUVVJFIEVUIE9STkFNRU5UByPcTklOR0xFIENPTU1BIFFVT1RBVElPTiBNQVJLIE9STkFNRU5UDSTbTlRVUk5FRCBDT01NQSBRVU9UQVRJT04gTUFSSyBPUk5BTUVOVAgUzO8HWCBQT0lOVEVEIEJMQUNLIFNUQVIKD7jvB1NQT0tFRCBBU1RFUklTSwcPmE9PVVRIIEVBU1QgQVJST1cRDOfsB1BPSU5USU5HIEJVRBoJ3+wHVklORSBMRUFGDBHl7AdXRVNUIFBPSU5USU5HIEJVRBoJ3ewHVklORSBMRUFGBwbITlBBUktMRQYhu09URUFSRFJPUC1TSEFOS0VEIFJJR0hUV0FSRFMgQVJST1cQDr1OUE9LRUQgQVNURVJJU0sWEcNOUElOV0hFRUwgQVNURVJJU0sHH55PUklBTkdMRS1IRUFERUQgUklHSFRXQVJEUyBBUlJPVwcY0+8HV0VMVkUgUE9JTlRFRCBCTEFDSyBTVEFSFQ3U7wdQSU5XSEVFTCBTVEFSBiuuT1VQUEVSIFJJR0hULVNIQURPV0VEIFdISVRFIFJJR0hUV0FSRFMgQVJST1cIJpnwB1dBUkRTIEFSUk9XIFdJVEggRVFVSUxBVEVSQUwgQVJST1dIRUFEGRud8AdMQVJHRSBFUVVJTEFURVJBTCBBUlJPV0hFQUQGDNpOVkVSVElDQUwgQkFSBh29T1dFREdFLVRBSUxFRCBSSUdIVFdBUkRTIEFSUk9XBwuH7wdISVRFIENJUkNMRQwW201ET1dOLVBPSU5USU5HIFRSSUFOR0xFDAaR7wdTUVVBUkUSI4WcByBDT05UQUlOSU5HIEJMQUNLIFZFUlkgU01BTEwgU1FVQVJFBxuUT0lERS1IRUFERUQgUklHSFRXQVJEUyBBUlJPVwICwJ0HQkUDF6ILUkVXIEFDQ0VOVCBBVE5BSCBIQUZVS0gOBacLREFSR0EPA60LRUhJDgeRC0VUTkFIVEEOBpwLR0VSRVNIFAedCyBNVVFEQU0RBp4LU0hBWUlNDgSsC0lMVVkOCKQLTUFIQVBBS0gPBaULRVJLSEEUB6YLIEtFRlVMQQ8EowtVTkFIDgOrC09MRQ4GmQtQQVNIVEEQA6ELWkVSDgWoC1FBRE1BEAmfC1JORVkgUEFSQQ4FlwtSRVZJQQ4FkgtTRUdPTA8JkwtIQUxTSEVMRVQODqALVEVMSVNIQSBHRURPTEEWBqkLUUVUQU5BEAObC1ZJUg8FlgtJUEVIQQ4OqgtZRVJBSCBCRU4gWU9NTxADmgtUSVYOC5ULWkFRRUYgR0FET0wUBZQLUUFUQU4QA5gLUlFBDwSuC0lOT1IHC9ALTEVUVEVSIEFMRUYSC7D2AyBXSVRIIE1BUElRGAWu9gNQQVRBSBgGr/YDUUFNQVRTEA6g9gNURVJOQVRJVkUgQVlJThoJqfYDUExVUyBTSUdODwPiC1lJTg4D0QtCRVQRDLH2AyBXSVRIIERBR0VTSBcEzPYDUkFGRQ4F0wtEQUxFVBMMs/YDIFdJVEggREFHRVNIDgnaC0ZJTkFMIEtBRhcMuvYDIFdJVEggREFHRVNIFAPdC01FTRQD3wtOVU4UAuMLUEUWDMP2AyBXSVRIIERBR0VTSBQF5QtUU0FESQ4F0gtHSU1FTBMMsvYDIFdJVEggREFHRVNIDgLU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7JsBR0FUSEVSSU5HIFRPR0VUSEVSDgTVmwFSQUNFDw7NmwFFQVQgUE9TU0VTU0lPThUD4ZsBV0VSFAzbmwFSRVBPTkRFUkFOQ0UTBtmbAVRBTUlORw0Qx5sBSE9MRElORyBUT0dFVEhFUg0I6ZsBSU5DUkVBU0UPB96bAUZMVUVOQ0UPCfybAU5FUiBUUlVUSBAG2JsBT0NFTkNFDQr7mwFMSU1JVEFUSU9ODQfOmwFNT0RFU1RZDwvamwFVVEggQ09STkVSUw0L5psBT0JTVFJVQ1RJT04OCeWbAVBQT1NJVElPThAH7psBUkVTU0lPTg0FypsBUEVBQ0UOB+KbAVJPR1JFU1MODe2bAVVTSElORyBVUFdBUkQNB+CbAVJFVFJFQVQQA9ebAVVSTg8I8JsBVk9MVVRJT04NE/2bAVNNQUxMIFBSRVBPTkRFUkFOQ0UTBsibAVRBTUlORw4O1psBUExJVFRJTkcgQVBBUlQOCcubAVRBTkRTVElMTA0R3JsBVEhFIEFCWVNNQUwgV0FURVISA8abAVJNWRMO8psBT1VTSU5HIFRIVU5ERVIRCPGbAUNBVUxEUk9OEgzdmwFMSU5HSU5HIEZJUkUSDsCbAVJFQVRJVkUgSEVBVkVOEQbkmwFGQU1JTFkRC/ibAUdFTlRMRSBXSU5EEQv5mwFKT1lPVVMgTEFLRREW85sBS0VFUElORyBTVElMTCBNT1VOVEFJThEP9ZsBTUFSUllJTkcgTUFJREVOEQ/BmwFSRUNFUFRJVkUgRUFSVEgRCPebAVdBTkRFUkVSEgPvmwFFTEwOB8mbAVJFQURJTkcNB8SbAVdBSVRJTkcOEtGbAU9SSyBPTiBUSEUgREVDQVlFRA0Ow5sBWU9VVEhGVUwgRk9MTFkBB7rmB0lCSVNDVVMCFIbqB0dIIEJSSUdIVE5FU1MgU1lNQk9MBQyBAU9DVEVUIFBSRVNFVAUMoU1WT0xUQUdFIFNJR04EDODoBy1IRUVMRUQgU0hPRQULhO0HU1BFRUQgVFJBSU4QEYXtByBXSVRIIEJVTExFVCBOT1NFAgn+8gdLSU5HIEJPT1QCCtXtB05EVSBURU1QTEUCCpvzB1BQT1BPVEFNVVMCE59hUkFHQU5BIERJR1JBUEggWU9SSQkOnWFJVEVSQVRJT04gTUFSSwkIwmBMRVRURVIgQREJn+IGUkNIQUlDIFdVGAKB4AZZRRAC8GBCQREB+WBFEQHzYEkRAfxgTxEB9mBVEALgYERBEQHnYEURAeJgSREB6WBPEQHlYFUQAchgRRACzGBHQREB0mBFEQHOYEkRAdRgTxEB0GBVEALvYEhBEQH4YEURAfJgSREB+2BPEQH1YFUQAcRgSRACy2BLQREB0WBFEQHNYEkRAdNgTxEBz2BVEAL+YE1BEQGBYUURAf9gSREBgmFPEQGAYVUQAZNhThEB6mBBEQHtYEURAetgSREB7mBPEQHsYFUQAcpgTxAC8WBQQREB+mBFEQH0YEkRAf1gTxEB92BVEAKJYVJBEQGMYUURAYphSREBjWFPEQGLYVUQAtVgU0ERAdtgRREB12BJEQbBYE1BTEwgQRYBx2BFFgHDYEkWApVhS0EXAZZhRRcBsuIGTxYByWBPFgLjYFRVFgHFYFUWAo5hV0EXAdHiBkUXAdDiBkkXAdLiBk8WAoNhWUEXAYdhTxcBhWFVEQHdYE8RAdlgVRAC32BUQREB5mBFEQHhYEkRAehgTxEB5GBVEAHGYFUQApRhVlUQAo9hV0ERAZFhRREBkGFJEQGSYU8QAoRhWUERAYhhTxEBhmFVEALWYFpBEQHcYEURAdhgSREB3mBPEQHaYFUJFZ5hVk9JQ0VEIElURVJBVElPTiBNQVJLAgvsTVNUT1JJQyBTSVRFAQSq6gdPQ0hPAgLz6gdMRQMZ6OwHTE9XIFFVSUxUIFNRVUFSRSBPUk5BTUVOVBwQ6ewHIElOIEJMQUNLIFNRVUFSRQIIu0RNT1RIRVRJQwIH7+YHTkVZIFBPVAUDnegHQkVFAgKd9QdPSwIBgQFQAgyVQFJJWk9OVEFMIEJBUgwMo1ZMQUNLIEhFWEFHT04RB8NXT0NUQUdPTgsJk5gHQ0FQQUNJVE9SCwimQEVMTElQU0lTCwaFmQdMQURERVIMDa9HSU5FIEVYVEVOU0lPThAUkZkHV0lUSCBGT1VSIFRJQ0sgTUFSS1MVEJKcB1RIUkVFIFRJQ0sgTUFSS1MWCI+cB0lDSyBNQVJLCxWpTU1BTEUgV0lUSCBTVFJPS0UgU0lHTgsVgfcHT05FIEVJR0hUSCBCTE9DSy0xMzU4HAH29gcyHAH39gczHAH49gc0HAH59gc1HAH69gc2HAH79gc3CwqbmQdSQUNJTkcgQ0FSDA+JmAdFU0lTVE9SIFNFR01FTlQLC7pHU0NBTiBMSU5FLTEVAbtHMxUBvEc3FQG9RzkLB/5WVEFCIEtFWQ4HCVVMQVRJT04VBIgBIFNFVBYSiQFXSVRIIEpVU1RJRklDQVRJT04MDKXtB1JBRkZJQyBMSUdIVAsLsJ0HWklHWkFHIExJTkUDAo7oB1NFBQW06AcgRkFDRQYGx+cHUkFDSU5HAgbl5wdTUElUQUwCCpVMVCBCRVZFUkFHRQQDreYHRE9HBAa25gdQRVBQRVIEB+hMU1BSSU5HUwMC6OcHRUwCB5tGVVJHTEFTUwkS80cgV0lUSCBGTE9XSU5HIFNBTkQDAoJGU0UFCeDnByBCVUlMRElORw4B2OcHUwYL4ecHV0lUSCBHQVJERU4BC7RBUllWTklBIFNJR04BAQlUAgGJAUoCAYgBUwELl/IHVUdHSU5HIEZBQ0UCE6/pB05EUkVEIFBPSU5UUyBTWU1CT0wCCa/sB1NIRUQgRkFDRQIB1u0HVAEHu/UHWUFDSU5USAIE2ldHSUVBBgn57gcgRk9STSBUV08CBJBAUEhFTgYHw0AgQlVMTEVUBw6aXFdJVEggRElBRVJFU0lTBgYtLU1JTlVTBgunQEFUSU9OIFBPSU5UAwmSXE9ESUFTVE9MRQIPjkdTVEVSRVNJUyBTWU1CT0wAFJ/yB0kgTE9WRSBZT1UgSEFORCBTSUdOAQjo5gdDRSBDUkVBTQUDyvMHVUJFBBXS5wdIT0NLRVkgU1RJQ0sgQU5EIFBVQ0sEBfhNU0tBVEUBC+FEREVOVElDQUwgVE8MFeVTIEFORCBTTEFOVEVEIFBBUkFMTEVMCg7nVFdJVEggRE9UIEFCT1ZFBg2q9QdGSUNBVElPTiBDQVJEAx+YY09HUkFQSElDIEFOTk9UQVRJT04gQk9UVE9NIE1BUksXCp5jRUFSVEggTUFSSxcKmWNGSVJTVCBNQVJLGAiVY09VUiBNQVJLGwecY1RIIE1BUksXC51jSEVBVkVOIE1BUksXDJBjTElOS0lORyBNQVJLFwifY01BTiBNQVJLGAqXY0lERExFIE1BUksXCJJjT05FIE1BUksXDJFjUkVWRVJTRSBNQVJLFwuaY1NFQ09ORCBNQVJLFwqbY1RISVJEIE1BUksZCJRjUkVFIE1BUksYB5ZjT1AgTUFSSxgHk2NXTyBNQVJLDAyGYENMT1NJTkcgTUFSSw0EgWBPTU1BDBOsYERFUEFSVElORyBUT05FIE1BUksOIvFfU0NSSVBUSU9OIENIQVJBQ1RFUiBBQk9WRSBUTyBCRUxPVysQ819NSURETEUgQU5EIEJFTE9XIg30X0ZVTEwgU1VSUk9VTkQiFf5fSE9SSVpPTlRBTCBSRUZMRUNUSU9OIhjyX0xFRlQgVE8gTUlERExFIEFORCBSSUdIVCoF8F9SSUdIVCII+19PVkVSTEFJRCII/19ST1RBVElPTiIL72NTVUJUUkFDVElPTiQR9V9SUk9VTkQgRlJPTSBBQk9WRTAF9l9CRUxPVzAE919MRUZUMQn6X09XRVIgTEVGVDYF/V9SSUdIVDAF/F9SSUdIVDAK+F9VUFBFUiBMRUZUNgX5X1JJR0hUDBKtYEVOVEVSSU5HIFRPTkUgTUFSSwwJgmBGVUxMIFNUT1AMD79gSEFMRiBGSUxMIFNQQUNFDA6FYElURVJBVElPTiBNQVJLDA+qYExFVkVMIFRPTkUgTUFSSwwLh2BOVU1CRVIgWkVSTwwQq2BSSVNJTkcgVE9ORSBNQVJLDAWAYFNQQUNFDA/2pgdUQUxMWSBNQVJLIEZJVkUYA/WmB09VUhcD8qYHT05FFwX0pgdUSFJFRRgC86YHV08NI7dgRUxFR1JBUEggTElORSBGRUVEIFNFUEFSQVRPUiBTWU1CT0wWEMNlU1lNQk9MIEZPUiBBUFJJTCIFx2VVR1VTVCEJ52dEQVkgRUlHSFQqA/FnRUVOJgXqZ0xFVkVOJQfuZ0ZJRlRFRU4nAuRnVkUmA+NnT1VSKQTtZ1RFRU4lBOhnTklORSkE8mdURUVOJQPgZ09ORSUF5mdTRVZFTioE8GdURUVOJgLlZ0lYKATvZ1RFRU4lA+lnVEVOJgfsZ0hJUlRFRU4qAf1nWSsE/mctT05FJwPiZ1JFRSYF62dXRUxWRSgD82dOVFkrBvtnLUVJR0hULAT4Z0ZJVkUtA/dnT1VSLAT8Z05JTkUsA/RnT05FLAX6Z1NFVkVOLQL5Z0lYLAX2Z1RIUkVFLQL1Z1dPJwHhZ08iB8tlRUNFTUJFUiEIwWVGRUJSVUFSWSEK4GZIT1VSIEVJR0hUKwPqZkVFTicF42ZMRVZFTiYH52ZGSUZURUVOKALdZlZFJwPcZk9VUioE5mZURUVOJgThZk5JTkUqBOtmVEVFTiYD2WZPTkUmBd9mU0VWRU4rBOlmVEVFTicC3mZJWCkE6GZURUVOJgPiZlRFTicH5WZISVJURUVOKAPbZlJFRScF5GZXRUxWRSkD7GZOVFksBfBmLUZPVVItA+1mT05FLQXvZlRIUkVFLgLuZldPKAHaZk8mBNhmWkVSTyEHwGVKQU5VQVJZIgPGZVVMWSMCxWVORSEFwmVNQVJDSCMBxGVZIQjKZU5PVkVNQkVSIQfJZU9DVE9CRVIhCchlU0VQVEVNQkVSDBO+YFZBUklBVElPTiBJTkRJQ0FUT1IBB7dFTUFHRSBPRgga00QgT1IgQVBQUk9YSU1BVEVMWSBFUVVBTCBUTwIB/+gHUAMawJAERVJJQUwgQVJBTUFJQyBMRVRURVIgQUxFUEgZA8+QBFlJThgEwZAEQkVUSBgGw5AEREFMRVRIGAXCkARHSU1FTBgCxJAESEUaAseQBFRIGATKkARLQVBIGAbLkARMQU1FREgYA8yQBE1FTRgDzZAETlVOGALQkARQRRgE0pAEUU9QSBgE05AEUkVTSBgF0ZAEU0FESEUaBM6QBE1FS0gZA9SQBEhJThgD1ZAEVEFXGQPIkARFVEgYA8WQBFdBVxgEyZAEWU9ESBgFxpAEWkFZSU4RCtiQBE5VTUJFUiBPTkUbCN2QBCBIVU5EUkVEHAjekARUSE9VU0FORBgD25AEVEVOGwnfkAQgVEhPVVNBTkQZBNqQBEhSRUUZBdyQBFdFTlRZGgHZkARPEQzXkARTRUNUSU9OIFNJR04BCeXpB05CT1ggVFJBWQIP6OkHQ09NSU5HIEVOVkVMT1BFBQ7cU1BMRVRFIElORklOSVRZAxba6wdSRUFTRSBGT05UIFNJWkUgU1lNQk9MCAThU1MgQVMFBIZETUVOVAIBhAFEAwKEAUVYBRf19QcgUE9JTlRJTkcgQVQgVEhFIFZJRVdFUgMOuUFJQU4gUlVQRUUgU0lHTgQbtNkHQyBTSVlBUSBBTFRFUk5BVEUgTEFLSCBNQVJLDBGu2QdGUkFDVElPTiBPTkUgSEFMRhkHrdkHUVVBUlRFUhUOr9kHVEhSRUUgUVVBUlRFUlMMCaDZB0xBS0ggTUFSSwwUsdkHTlVNQkVSIEFMVEVSTkFURSBPTkUdDLPZB1RFTiBUSE9VU0FORB4CstkHV08TBfjYB0VJR0hUGAiK2QcgSFVORFJFRBkIk9kHVEhPVVNBTkQYAYHZB1kZCZzZByBUSE9VU0FORBMF/tgHRklGVFkYCZnZByBUSE9VU0FORBUC9dgHVkUXCIfZByBIVU5EUkVEGAiQ2QdUSE9VU0FORBQE/dgHT1JUWRgJmNkHIFRIT1VTQU5EFQL02AdVUhcIhtkHIEhVTkRSRUQYCI/ZB1RIT1VTQU5EEwWh2QdLQVJPUhgCotkHQU4TBJ7ZB0xBS0gXAp/ZB0FOEwT52AdOSU5FFwiL2QcgSFVORFJFRBgIlNkHVEhPVVNBTkQXAoLZB1RZGQmd2QcgVEhPVVNBTkQTA/HYB09ORRYIg9kHIEhVTkRSRUQXCIzZB1RIT1VTQU5EEw6q2QdQUkVGSVhFRCBFSUdIVBwEp9kHRklWRR0DptkHT1VSHASr2QdOSU5FHAOj2QdPTkUcBanZB1NFVkVOHQKo2QdJWBwFpdkHVEhSRUUdAqTZB1dPEwX32AdTRVZFThgIidkHIEhVTkRSRUQZCJLZB1RIT1VTQU5EGAKA2QdUWRoJm9kHIFRIT1VTQU5EFAL22AdJWBYIiNkHIEhVTkRSRUQXCJHZB1RIT1VTQU5EFgL/2AdUWRgJmtkHIFRIT1VTQU5EEwP62AdURU4WCZXZByBUSE9VU0FORBQF/NgHSElSVFkZCZfZByBUSE9VU0FORBUD89gHUkVFGAiF2QcgSFVORFJFRBkIjtkHVEhPVVNBTkQUBfvYB1dFTlRZGQmW2QcgVEhPVVNB",
+ "TkQVAfLYB08WCITZByBIVU5EUkVEFwiN2QdUSE9VU0FORAwLrNkHUExBQ0VIT0xERVIMCrDZB1JVUEVFIE1BUksCBp5ERklOSVRZCBreUyBORUdBVEVEIFdJVEggVkVSVElDQUwgQkFSAxSB6QdPUk1BVElPTiBERVNLIFBFUlNPTgwOHFNFUEFSQVRPUiBGT1VSFgMfT05FFgUdVEhSRUUXAh5XTw0FuUJPVVJDRQIZ7EBISUJJVCBBUkFCSUMgRk9STSBTSEFQSU5HCBLqQFNZTU1FVFJJQyBTV0FQUElORwIkoOoHUFVUIFNZTUJPTCBGT1IgTEFUSU4gQ0FQSVRBTCBMRVRURVJTFwek6gdMRVRURVJTFw2h6gdTTUFMTCBMRVRURVJTEQei6gdOVU1CRVJTEQej6gdTWU1CT0xTAiDglgRTQ1JJUFRJT05BTCBQQUhMQVZJIExFVFRFUiBBTEVQSB0E4ZYEQkVUSB0G45YEREFMRVRIHQXilgRHSU1FTB0C5JYESEUfAueWBFRIHQTqlgRLQVBIHQbrlgRMQU1FREgdCOyWBE1FTS1RT1BIHQPtlgROVU4dAu+WBFBFHQXwlgRTQURIRR8E7pYETUVLSB4D8ZYESElOHQPylgRUQVceA+iWBEVUSB0N5ZYEV0FXLUFZSU4tUkVTSB0E6ZYEWU9ESB0F5pYEWkFZSU4WC/uWBE5VTUJFUiBGT1VSHQP4lgRPTkUgCP6WBCBIVU5EUkVEIQj/lgRUSE9VU0FORB0D/JYEVEVOHgT6lgRIUkVFHgX9lgRXRU5UWR8B+ZYETxATwJYEUlRISUFOIExFVFRFUiBBTEVQSB8Dz5YEWUlOHgTBlgRCRVRIHgbDlgREQUxFVEgeBcKWBEdJTUVMHgLElgRIRSACx5YEVEgeBMqWBEtBUEgeBsuWBExBTUVESB4DzJYETUVNHgPNlgROVU4eAtCWBFBFHgTSlgRRT1BIHgTTlgRSRVNIHgXRlgRTQURIRSAEzpYETUVLSB8D1JYESElOHgPVlgRUQVcfA8iWBEVUSB4DxZYEV0FXHgTJlgRZT0RIHgXGlgRaQVlJThcL25YETlVNQkVSIEZPVVIeA9iWBE9ORSEI3pYEIEhVTkRSRUQiCN+WBFRIT1VTQU5EHgPclgRURU4fBNqWBEhSRUUfBd2WBFdFTlRZIAHZlgRPAw2AR0VSVElPTiBTWU1CT0wCBqtEVEVHUkFMCBiVVCBBUk9VTkQgQSBQT0lOVCBPUEVSQVRPUgoRj1RWRVJBR0UgV0lUSCBTTEFTSAkJrkdFWFRFTlNJT04JEo5UV0lUSCBET1VCTEUgU1RST0tFDgyZVElOVEVSU0VDVElPTg4Zl1RMRUZUV0FSRFMgQVJST1cgV0lUSCBIT09LDgebVE9WRVJCQVIOCphUVElNRVMgU0lHTg4InFRVTkRFUkJBUhADmlRJT04EB7pFUkNBTEFURQULvFRJT1IgUFJPRFVDVAUY+f8DTElORUFSIEFOTk9UQVRJT04gQU5DSE9SFwn6/wNTRVBBUkFUT1IXCvv/A1RFUk1JTkFUT1IGGqRNT0NLRUQgRkVNQUxFIEFORCBNQUxFIFNJR04FBr1AUk9CQU5HBQepRFNFQ1RJT04MFslUIEFCT1ZFIEJBUiBBQk9WRSBVTklPThMFx1RVTklPTg0jy1RCRVNJREUgQU5EIEpPSU5FRCBXSVRIIElOVEVSU0VDVElPTg0IwFRXSVRIIERPVBILxFRMT0dJQ0FMIEFORBIHw1RPVkVSQkFSAhONmQdWRVJTRSBCTEFDSyBESUFNT05EDgyPmQdTTUFMTCBTUVVBUkUJBdhLVUxMRVQICrH3B0NIRUNLIE1BUksNDZb3B0VSIEJPQVJEIEZJTEwIIrT3B0RPV05XQVJEUyBBUlJPVyBXSVRIIFRJUCBMRUZUV0FSRFMIDJD3B01FRElVTSBTSEFERQgM2UtXSElURSBDSVJDTEUFFKEBVEVEIEVYQ0xBTUFUSU9OIE1BUksJC5hcSU5URVJST0JBTkcJBr5ETEFaWSBTCgnFXE9XIEtBVllLQRMSxlwgV0lUSCBLQVZZS0EgQUJPVkUJCKdCT0hNIFNJR04JCedNUEVOVEFHUkFNCQ2/AVFVRVNUSU9OIE1BUksJCNRAVU5ERVJUSUUDC+RASVNJQkxFIFBMVVMKCeNAU0VQQVJBVE9SCgXiQFRJTUVTAQTHnQdSRU5FAgLBnQdJUwEO7ucHWkFLQVlBIExBTlRFUk4ADoPnB0pBQ0stTy1MQU5URVJOAhL7TVBBTkVTRSBCQU5LIFNZTUJPTAkG7+cHQ0FTVExFCQWO5wdET0xMUwkG+ugHR09CTElOCRqEYElORFVTVFJJ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Q0lSQ0xFDAbr7wdTUVVBUkUGBu9LQ0lSQ0xFBgnZT0RPV04gVEFDSwYM4u8HR1JFRU4gQ0lSQ0xFDAbp7wdTUVVBUkUGFp5UTEVGVCBUUklBTkdMRSBPUEVSQVRPUgYhvZYET05FIERPVCBPVkVSIFRXTyBET1RTIFBVTkNUVUFUSU9OCh+/lgRSSU5HIE9WRVIgVFdPIFJJTkdTIFBVTkNUVUFUSU9OBwzg7wdSQU5HRSBDSVJDTEUNB7bqB0RJQU1PTkQNBufvB1NRVUFSRQYN4+8HUFVSUExFIENJUkNMRQ0G6u8HU1FVQVJFBgq06gdSRUQgQ0lSQ0xFCgbl7wdTUVVBUkUGHPxVVFJJUExFIFZFUlRJQ0FMIEJBUiBPUEVSQVRPUgcgvJYEV08gRE9UUyBPVkVSIE9ORSBET1QgUFVOQ1RVQVRJT04KH76WBFJJTkdTIE9WRVIgT05FIFJJTkcgUFVOQ1RVQVRJT04HFa+cB1lQRSBQSUVDRSBDRU5UUkUgT0YgSxsBspwHWBsBs5wHWRsPtJwHWiBXSVRIIENST1NTQkFSEgefnAdST1NTQkFSGRCgnAcgV0lUSCBMT1dFUiBTVEVNHwrAnAdVUFBFUiBTVEVNEROinAdESUFHT05BTCBMT1dFUiBMRUZUIAWsnAdSSUdIVBoKu5wHVVBQRVIgTEVGVCQPuJwHIEFORCBMT1dFUiBMRUZUIAWrnAdSSUdIVCUQqpwHIEFORCBMT1dFUiBSSUdIVBEInpwHTEVGVCBBUk0SFbCcB09XRVIgSEFMRiBWRVJURVggT0YgTSYBwpwHVxcXrpwHTEVGVCBBTkQgVVBQRVIgTEVGVCBBUkMdAr6cB1JDHAa9nAdDT1JORVIdBb+cB1JPVENIFxm3nAdSSUdIVCBBTkQgVVBQRVIgUklHSFQgQVJDHgLDnAdSQyAKxZwHIFdJVEggVEFJTB0GxJwHQ09STkVSHgXGnAdST1RDSBcIvJwHVEVSTUlOQUwRFbWcB1JBSVNFRCBVUFBFUiBMRUZUIEFSQxIIpZwHSUdIVCBBUk0RFK2cB1NIT1JUIExPV0VSIFRFUk1JTkFMFw6jnAdVUFBFUiBURVJNSU5BTBIDqZwHVEVNFQ66nAcgV0lUSCBDUk9TU0JBUhsNtpwHTEVGVCBDUk9TU0JBUiAFuZwHSk9JTlQbDqicB1JJR0hUIENST1NTQkFSFQPQnActMTIYAsycBzM0FgHPnAcyFwHOnAczGAHLnAc0GQHInAc1FgHNnAczFwHKnAc0FgHJnAc0FwHHnAc1ERahnAdVUFBFUiBIQUxGIFZFUlRFWCBPRiBNJgGxnAdXFwianAdMRUZUIEFSQxwGm5wHQ09STkVSHQWdnAdST1RDSBcJpJwHUklHSFQgQVJDHQannAdDT1JORVIeBaacB1JPVENIFwicnAdURVJNSU5BTBELwZwHVkVSVEVYIE9GIFYGB9hPVVAgVEFDSwYN4e8HWUVMTE9XIENJUkNMRQ0G6O8HU1FVQVJFBQarVVIgVEhBTgsMrVUgT1IgRVFVQUwgVE8DBr5BSSBTSUdOAg++TFNUIFFVQVJURVIgTU9PThEHl+YHIFNZTUJPTBIJnOYHV0lUSCBGQUNFAhRBVElOIENBUElUQUwgTEVUVEVSIEEWC8EBIFdJVEggQUNVVEUcBYICQlJFVkUhCq49IEFORCBBQ1VURSYJtj1ET1QgQkVMT1cmBbA9R1JBVkUmCrI9SE9PSyBBQk9WRSYFtD1USUxERRwFzQNDQVJPTh0JwgFJUkNVTUZMRVgmCqQ9IEFORCBBQ1VURSsJrD1ET1QgQkVMT1crBaY9R1JBVkUrCqg9SE9PSyBBQk9WRSsFqj1USUxERRwJxAFESUFFUkVTSVMlC94DIEFORCBNQUNST04dCKYET1QgQUJPVkUlC+ADIEFORCBNQUNST04gBaA9QkVMT1ceCoAEVUJMRSBHUkFWRRwFwAFHUkFWRRwKoj1IT09LIEFCT1ZFHA6CBElOVkVSVEVEIEJSRVZFHAaAAk1BQ1JPThwGhAJPR09ORUscCsUBUklORyBBQk9WRSYK+gMgQU5EIEFDVVRFIQWAPEJFTE9XHAa6BFNUUk9LRRwFwwFUSUxERRYBss4CQRYBxgFFFwv8AyBXSVRIIEFDVVRFHQbiA01BQ1JPThYIiQNGUklDQU4gRBYE7VhMUEhBFgrCzwJOR0xJQ0FOQSBXFgG0zgJPFgG2zgJVFgG4zgJWFxS6zgIgV0lUSCBIT1JJWk9OVEFMIEJBUhYBvM4CWRUBQkIWD4I8IFdJVEggRE9UIEFCT1ZFIAWEPEJFTE9XHAiWzwJGTE9VUklTSBwEgQNIT09LHAqGPExJTkUgQkVMT1ccBsMEU1RST0tFHAaCA1RPUEJBUhYDtM8CRVRBFgfGzgJST0tFTiBMFQFDQxYLhgIgV0lUSCBBQ1VURRwDks8CQkFSHAWMAkNBUk9OHQbHAUVESUxMQSMKiDwgQU5EIEFDVVRFHQmIAklSQ1VNRkxFWBwJigJET1QgQUJPVkUcBIcDSE9PSxwMxM8CUEFMQVRBTCBIT09LHAa7BFNUUk9LRRYCs88CSEkWD9DPAkxPU0VEIElOU1VMQVIgRxYC7s4CT04WCKzOAlVBVFJJTExPHguuzgIgV0lUSCBDT01NQRUBREQWC44CIFdJVEggQ0FST04dBpA8RURJTExBHQ+SPElSQ1VNRkxFWCBCRUxPVxwJijxET1QgQUJPVkUgBYw8QkVMT1ccBIoDSE9PSxwKjjxMSU5FIEJFTE9XHBTHzwJTSE9SVCBTVFJPS0UgT1ZFUkxBWR0N8gNNQUxMIExFVFRFUiBaKgvFAyBXSVRIIENBUk9OHQWQAlRST0tFHAaLA1RPUEJBUhYL0s8CT1VCTEUgVEhPUk4cBNTPAldZTk4WAfEDWhcLxAMgV0lUSCBDQVJPThUBRUUWC8kBIFdJVEggQUNVVEUcBZQCQlJFVkUcBZoCQ0FST04dBqgERURJTExBIwqcPCBBTkQgQlJFVkUdCcoBSVJDVU1GTEVYJgq+PSBBTkQgQUNVVEUrCcY9RE9UIEJFTE9XKwXAPUdSQVZFKwrCPUhPT0sgQUJPVkUrBcQ9VElMREUnBZg8QkVMT1ccCcsBRElBRVJFU0lTHQiWAk9UIEFCT1ZFIAW4PUJFTE9XHgqEBFVCTEUgR1JBVkUcBcgBR1JBVkUcCro9SE9PSyBBQk9WRRwOhgRJTlZFUlRFRCBCUkVWRRwGkgJNQUNST04iCpY8IEFORCBBQ1VURScFlDxHUkFWRRwGmAJPR09ORUscBsYEU1RST0tFHAW8PVRJTERFIQaaPCBCRUxPVxYQpM4CR1lQVE9MT0dJQ0FMIEFJTiQDos4CTEVGFgLKAk5HFgKpA1NIFgHqzgJUFwHQAUgWArcDWkgYCbgDIFJFVkVSU0VEGQruA1dJVEggQ0FST04VAUZGFg+ePCBXSVRIIERPVCBBQk9WRRwEkQNIT09LHAaYzwJTVFJPS0UVAUdHFgv0AyBXSVRIIEFDVVRFHAWeAkJSRVZFHAXmA0NBUk9OHQaiAkVESUxMQR0JnAJJUkNVTUZMRVgcCaACRE9UIEFCT1ZFHASTA0hPT0scBqA8TUFDUk9OHA6gzwJPQkxJUVVFIFNUUk9LRRwG5ANTVFJPS0UWBJQDQU1NQRYCogNIQRYIus8CTE9UVEFMIEEdAbzPAkkdBMEEU1RPUB0Bvs8CVRUBSEgWEao8IFdJVEggQlJFVkUgQkVMT1ccBZ4EQ0FST04dBqg8RURJTExBHQmkAklSQ1VNRkxFWBwJ51hERVNDRU5ERVIdCKY8SUFFUkVTSVMdCKI8T1QgQUJPVkUgBaQ8QkVMT1ccBKrPAkhPT0scBqYCU1RST0tFFgX1WEFMRiBIFgOmzgJFTkcWBPYDV0FJUhUBSUkWC80BIFdJVEggQUNVVEUcBawCQlJFVkUcBc8DQ0FST04dCc4BSVJDVU1GTEVYHAnPAURJQUVSRVNJUyUKrjwgQU5EIEFDVVRFHQiwAk9UIEFCT1ZFIAXKPUJFTE9XHgqIBFVCTEUgR1JBVkUcBcwBR1JBVkUcCsg9SE9PSyBBQk9WRRwOigRJTlZFUlRFRCBCUkVWRRwGqgJNQUNST04cBq4CT0dPTkVLHAaXA1NUUk9LRRwFqAJUSUxERSEGrDwgQkVMT1cWCPnOAk5TVUxBUiBEHQH7zgJGHQH9zgJHHQGCzwJSHQGEzwJTHQGGzwJUFgOWA09UQRYB7M4CUxUBSkoWELQCIFdJVEggQ0lSQ1VNRkxFWB0Lss8CUk9TU0VELVRBSUwcBsgEU1RST0tFFQFLSxYLsDwgV0lUSCBBQ1VURRwF6ANDQVJPTh0GtgJFRElMTEEcCelYREVTQ0VOREVSHQ7CzgJJQUdPTkFMIFNUUk9LRR0IsjxPVCBCRUxPVxwEmANIT09LHAq0PExJTkUgQkVMT1ccDqLPAk9CTElRVUUgU1RST0tFHAbAzgJTVFJPS0UiFMTOAiBBTkQgRElBR09OQUwgU1RST0tFFQFMTBYLuQIgV0lUSCBBQ1VURRwDvQRCQVIdA63PAkVMVBwFvQJDQVJPTh0GuwJFRElMTEEdD7w8SVJDVU1GTEVYIEJFTE9XHAm2PERPVCBCRUxPVyULuDwgQU5EIE1BQ1JPTh4I4FhVQkxFIEJBUhwLyM4CSElHSCBTVFJPS0UcCro8TElORSBCRUxPVxwKvwJNSURETEUgRE9UIwXiWFRJTERFHA7IA1NNQUxMIExFVFRFUiBKHQXBAlRST0tFFgXazwJBTUJEQRsM3M8CIFdJVEggU1RST0tFFgHHA0oVAU1NFgu+PCBXSVRIIEFDVVRFHAnAPERPVCBBQk9WRSAFwjxCRUxPVxwE7lhIT09LFg3WzwJJRERMRSBTQ09UUyBTGwn6PS1XRUxTSCBMTCIB/D1WFQFOThYLwwIgV0lUSCBBQ1VURRwFxwJDQVJPTh0GxQJFRElMTEEdD8o8SVJDVU1GTEVYIEJFTE9XHAmQzwJERVNDRU5ERVIdCMQ8T1QgQUJPVkUgBcY8QkVMT1ccBfgDR1JBVkUcCZ0DTEVGVCBIT09LHQnIPElORSBCRUxPVx0NoARPTkcgUklHSFQgTEVHHA6kzwJPQkxJUVVFIFNUUk9LRRwOywNTTUFMTCBMRVRURVIgShwF0QFUSUxERRYBygNKFQFPTxYL0wEgV0lUSCBBQ1VURRwFzgJCUkVWRRwF0QNDQVJPTh0J1AFJUkNVTUZMRVgmCtA9IEFORCBBQ1VURSsJ2D1ET1QgQkVMT1crBdI9R1JBVkUrCtQ9SE9PSyBBQk9WRSsF1j1USUxERRwJ1gFESUFFUkVTSVMlC6oEIEFORCBNQUNST04dCK4ET1QgQUJPVkUlC7AEIEFORCBNQUNST04gBcw9QkVMT1ceCtACVUJMRSBBQ1VURSMFjARHUkFWRRwF0gFHUkFWRRwKzj1IT09LIEFCT1ZFHgKgA1JOIAraPSBBTkQgQUNVVEUlCeI9RE9UIEJFTE9XJQXcPUdSQVZFJQrePUhPT0sgQUJPVkUlBeA9VElMREUcDo4ESU5WRVJURUQgQlJFVkUcE8rOAkxPTkcgU1RST0tFIE9WRVJMQVkeAszOAk9QHAbMAk1BQ1JPTiIK0jwgQU5EIEFDVVRFJwXQPEdSQVZFHQufA0lERExFIFRJTERFHAbqA09HT05FSyIL7AMgQU5EIE1BQ1JPThwG2AFTVFJPS0UiCv4DIEFORCBBQ1VURRwF1QFUSUxERSEKzDwgQU5EIEFDVVRFJgnOPERJQUVSRVNJUyYGrARNQUNST04WAaIDSRYLwM8CTEQgUE9MSVNIIE8WBLbPAk1FR0EWAc7OAk8WBZADUEVOIEUaAYYDTxYBogRVFQFQUBYL1DwgV0lUSCBBQ1VURRwJ1jxET1QgQUJPVkUcCNLOAkZMT1VSSVNIHASkA0hPT0scDdTOAlNRVUlSUkVMIFRBSUwdBeNYVFJPS0UiEtDOAiBUSFJPVUdIIERFU0NFTkRFUhYazs8CSEFSWU5HRUFMIFZPSUNFRCBGUklDQVRJVkUVAVFRFhXYzgIgV0lUSCBESUFHT05BTCBTVFJPS0UcGNbOAlNUUk9LRSBUSFJPVUdIIERFU0NFTkRFUhUBUlIWCNrOAiBST1RVTkRBFwrUAldJVEggQUNVVEUcBdgCQ0FST04dBtYCRURJTExBHAnYPERPVCBBQk9WRSAF2jxCRUxPVyUL3DwgQU5EIE1BQ1JPTh4KkARVQkxFIEdSQVZFHA6SBElOVkVSVEVEIEJSRVZFHArePExJTkUgQkVMT1ccDqbPAk9CTElRVUUgU1RST0tFHAbMBFNUUk9LRRwE5FhUQUlMFgjLzwJBTVMgSE9SThYSvs4CRVZFUlNFRCBDIFdJVEggRE9UHgGOA0UeBvXPAkhBTEYgSB4Gq88CT1BFTiBFFgrczgJVTSBST1RVTkRBFQFTUxYL2gIgV0lUSCBBQ1VURSEO5DwgQU5EIERPVCBBQk9WRRwF4AJDQVJPTiEO5jwgQU5EIERPVCBBQk9WRR0G3gJFRElMTEEdCdwCSVJDVU1GTEVYHQqYBE9NTUEgQkVMT1ccD8zPAkRJQUdPTkFMIFNUUk9LRR0I4DxPVCBBQk9WRSAF4jxCRUxPVyUO6DwgQU5EIERPVCBBQk9WRRwExc8CSE9PSxwOqM8C",
+ "T0JMSVFVRSBTVFJPS0UcFMnPAlNIT1JUIFNUUk9LRSBPVkVSTEFZHQn+WFdBU0ggVEFJTBYHi88CQUxUSUxMTxYEjwNDSFdBFwaszwJSSVBUIEcWBp49SEFSUCBTFgjYzwJJR01PSUQgUxYOrs8CTUFMTCBDQVBJVEFMIEkbEMoEUSBXSVRIIEhPT0sgVEFJTBUBVFQWC+QCIFdJVEggQ0FST04dBuICRURJTExBHQ/wPElSQ1VNRkxFWCBCRUxPVx0KmgRPTU1BIEJFTE9XHA++BERJQUdPTkFMIFNUUk9LRR0I6jxPVCBBQk9WRSAF7DxCRUxPVxwErANIT09LHAruPExJTkUgQkVMT1ccDq4DUkVUUk9GTEVYIEhPT0scBuYCU1RST0tFFgTeAUhPUk4aDOTOAiBXSVRIIFNUUk9LRSYS5s4CIFRIUk9VR0ggREVTQ0VOREVSFgi8A09ORSBGSVZFGgOEA1NJWBoDpwNUV08WB6rOAlJFU0lMTE8WB+9YVVJORUQgQR0E8FhMUEhBHAGNzwJIHAn+zgJJTlNVTEFSIEccAbDPAkscAYDPAkwcAZwDTRwBsc8CVBwBxQRWFgGozgJaFQFVVRYExAQgQkFSFwraAVdJVEggQUNVVEUcBewCQlJFVkUcBdMDQ0FST04dCdsBSVJDVU1GTEVYJgb2PCBCRUxPVxwJ3AFESUFFUkVTSVMlCtcDIEFORCBBQ1VURSoF2QNDQVJPTioF2wNHUkFWRSoG1QNNQUNST04mBfI8QkVMT1cdCOQ9T1QgQkVMT1ceCvACVUJMRSBBQ1VURSMFlARHUkFWRRwF2QFHUkFWRRwK5j1IT09LIEFCT1ZFHgKvA1JOIAroPSBBTkQgQUNVVEUlCfA9RE9UIEJFTE9XJQXqPUdSQVZFJQrsPUhPT0sgQUJPVkUlBe49VElMREUcDpYESU5WRVJURUQgQlJFVkUcBuoCTUFDUk9OIg76PCBBTkQgRElBRVJFU0lTHAbyAk9HT05FSxwK7gJSSU5HIEFCT1ZFHAa4zwJTVFJPS0UcBegCVElMREUhCvg8IEFORCBBQ1VURSIF9DxCRUxPVxYGsQNQU0lMT04VAVZWFhXezgIgV0lUSCBESUFHT05BTCBTVFJPS0UdCP48T1QgQkVMT1ccBLIDSE9PSxwF/DxUSUxERRYD6M4CRU5EFgvizgJJU0lHT1RISUMgWhYJms8CT0xBUFVLIEFFHQKczwJPRR0Cns8CVUUWAeDOAlkVAVdXFguCPSBXSVRIIEFDVVRFHAr0AkNJUkNVTUZMRVgcCYQ9RElBRVJFU0lTHQiGPU9UIEFCT1ZFIAWIPUJFTE9XHAWAPUdSQVZFHATyWEhPT0sWA/cDWU5OFQFYWBYPjD0gV0lUSCBESUFFUkVTSVMdCIo9T1QgQUJPVkUVAVlZFgvdASBXSVRIIEFDVVRFHAr2AkNJUkNVTUZMRVgcCfgCRElBRVJFU0lTHQiOPU9UIEFCT1ZFIAX0PUJFTE9XHAXyPUdSQVZFHASzA0hPT0sgBvY9IEFCT1ZFHAT+PUxPT1AcBrIETUFDUk9OHAbOBFNUUk9LRRwF+D1USUxERRYDnARPR0gVAVpaFgv5AiBXSVRIIEFDVVRFHAX9AkNBUk9OHQmQPUlSQ1VNRkxFWBwJ61hERVNDRU5ERVIdCPsCT1QgQUJPVkUgBZI9QkVMT1ccBKQESE9PSxwKlD1MSU5FIEJFTE9XHAzGzwJQQUxBVEFMIEhPT0scBrUDU1RST0tFHQn/WFdBU0ggVEFJTA8KsgJJR0FUVVJFIElKFwLSAk9FBwSdTlJPU1MGG//PAkVQSUdSQVBISUMgTEVUVEVSIEFSQ0hBSUMgTRgH/s8CSSBMT05HQRkJ/c8CTlZFUlRFRCBNGAr7zwJSRVZFUlNFRCBGIQH8zwJQGAr3zwJTSURFV0FZUyBJBgqlOkxFVFRFUiBBSU4ODcIDTFZFT0xBUiBDTElDSw0TrQVCSURFTlRBTCBQRVJDVVNTSVZFDwyYBUxBQklBTCBDTElDSxYKrAVQRVJDVVNTSVZFDQzAA0RFTlRBTCBDTElDSw0MlAVHTE9UVEFMIFNUT1AZDKEFIFdJVEggU1RST0tFDRWWBUlOVkVSVEVEIEdMT1RUQUwgU1RPUCIKjr4HIFdJVEggQ1VSTCgGvgNTVFJPS0UNDcEDTEFURVJBTCBDTElDSw0blQVQSEFSWU5HRUFMIFZPSUNFRCBGUklDQVRJVkUND8MDUkVUUk9GTEVYIENMSUNLHBSKvgcgV0lUSCBSRVRST0ZMRVggSE9PSw8PqgNWRVJTRUQgRVNIIExPT1AWGKIFR0xPVFRBTCBTVE9QIFdJVEggU1RST0tFDQ+PzwJTSU5PTE9HSUNBTCBET1QODoA6TUFMTCBDQVBJVEFMIEEcAYE6RRsBmQVCHAeDOkFSUkVEIEIbAYQ6QxsBhTpEGwGHOkUcAoY6VEgcAqM6WkgbAbDOAkYbAeIERxwKmwUgV0lUSCBIT09LGwGcBUgbAeoESRwJgQVOVkVSVEVEIFIbAYo6ShsBizpLGwGfBUwcCoS+ByBXSVRIIEJFTFQiBow6U1RST0tFGwGNOk0bAfQEThsBjzpPHAH2BEUcBZA6UEVOIE8cAZU6VRsBmDpQGwGvzwJRGwGABVIcD8bWAiBXSVRIIFJJR0hUIExFRxwJjjpFVkVSU0VEIE4kAZk6UhwC9s4CVU0bAbHOAlMbAZs6VBwH+1hVUk5FRCBFIgGCvgdHIgGQvgdLIgH6zwJNIgGaOlIbAZw6VRsBoDpWGwGhOlcbAY8FWRsBojpaDgqXBVRSRVRDSEVEIEMYCo++ByBXSVRIIENVUkwND7sDVFdPIFdJVEggU1RST0tFDRikOlZPSUNFRCBMQVJZTkdFQUwgU1BJUkFOVA0EvwNXWU5ODQKmA1lSBiL7OlNNQUxMIENBUElUQUwgTEVUVEVSIEkgV0lUSCBTVFJPS0UbDf46VSBXSVRIIFNUUk9LRQwIYUxFVFRFUiBBFA+x1gIgUkVWRVJTRUQtU0NIV0EVCuEBV0lUSCBBQ1VURRoFgwJCUkVWRR8Krz0gQU5EIEFDVVRFJAm3PURPVCBCRUxPVyQFsT1HUkFWRSQKsz1IT09LIEFCT1ZFJAW1PVRJTERFGgXOA0NBUk9OGwniAUlSQ1VNRkxFWCQKpT0gQU5EIEFDVVRFKQmtPURPVCBCRUxPVykFpz1HUkFWRSkKqT1IT09LIEFCT1ZFKQWrPVRJTERFGgnkAURJQUVSRVNJUyML3wMgQU5EIE1BQ1JPThsIpwRPVCBBQk9WRSML4QMgQU5EIE1BQ1JPTh4FoT1CRUxPVxwKgQRVQkxFIEdSQVZFGgXgAUdSQVZFGgqjPUhPT0sgQUJPVkUaDoMESU5WRVJURUQgQlJFVkUaBoECTUFDUk9OGgaFAk9HT05FSxoOjztSRVRST0ZMRVggSE9PSxsOmj1JR0hUIEhBTEYgUklORxwI5QFORyBBQk9WRSQK+wMgQU5EIEFDVVRFHwWBPEJFTE9XGgblWFNUUk9LRRoF4wFUSUxERRQBs84CQRQB5gFFFQv9AyBXSVRIIEFDVVRFGwbjA01BQ1JPThQE0QRMUEhBGBSQOyBXSVRIIFJFVFJPRkxFWCBIT09LFArDzwJOR0xJQ0FOQSBXFAG1zgJPFAG3zgJVFAG5zgJWFRS7zgIgV0lUSCBIT1JJWk9OVEFMIEJBUhQBvc4CWRMBYkIUD4M8IFdJVEggRE9UIEFCT1ZFHgWFPEJFTE9XGgiXzwJGTE9VUklTSBoE0wRIT09LGgqHPExJTkUgQkVMT1caDOw6TUlERExFIFRJTERFGgyAO1BBTEFUQUwgSE9PSxoGgANTVFJPS0UaBoMDVE9QQkFSFAuw1gJBUlJFRCBBTFBIQRoBs9YCRRoB9QRPFQrN1gJTRUxJTkUgRVNIFAO1zwJFVEEUDLLWAkxBQ0tMRVRURVIgRR8BvdYCTyAMvtYCIFdJVEggU1RST0tFFAyXOk9UVE9NIEhBTEYgTxQHx84CUk9LRU4gTBMBY0MUC4cCIFdJVEggQUNVVEUaA5PPAkJBUhoFjQJDQVJPThsG5wFFRElMTEEhCok8IEFORCBBQ1VURRsJiQJJUkNVTUZMRVgbA9UEVVJMGgmLAkRPVCBBQk9WRRoEiANIT09LGgyUzwJQQUxBVEFMIEhPT0saDp2+B1JFVFJPRkxFWCBIT09LGga8BFNUUk9LRRQC09YCSEkWFNXWAiBXSVRIIExPVyBMRUZUIFNFUklGIArU1gJSSUdIVCBSSU5HFA/RzwJMT1NFRCBJTlNVTEFSIEcaBfcET01FR0EbBZoFUEVOIEUaD94EUkVWRVJTRUQgT1BFTiBFFALvzgJPThQIrc4CVUFUUklMTE8cC6/OAiBXSVRIIENPTU1BEwFkRBQLjwIgV0lUSCBDQVJPThsGkTxFRElMTEEbD5M8SVJDVU1GTEVYIEJFTE9XGwOhBFVSTBoJizxET1QgQUJPVkUeBY08QkVMT1caBNcESE9PSx4JkTsgQU5EIFRBSUwaCo88TElORSBCRUxPVxoUpb4HTUlELUhFSUdIVCBMRUZUIEhPT0sdCe06RExFIFRJTERFGgyBO1BBTEFUQUwgSE9PSxoUyM8CU0hPUlQgU1RST0tFIE9WRVJMQVkbBZECVFJPS0UaBNYEVEFJTBsFjANPUEJBUhQJuARCIERJR1JBUEgUBJ89RUxUQRUKpAVaSCBESUdSQVBIHxKSvgcgV0lUSCBQQUxBVEFMIEhPT0slDpm+B1JFVFJPRkxFWCBIT09LFAixAk9UTEVTUyBJGwG3BEocDN8EIFdJVEggU1RST0tFKAmEBSBBTkQgSE9PSxUGyNYCVUJMRSBSGxLK1gIgV0lUSCBDUk9TU0VELVRBSUwaBdPPAlRIT1JOGgTVzwJXWU5OFALxzgJVTRQB8wNaFQijBSBESUdSQVBIHQqlBSBXSVRIIENVUkwjDubWAlJFVFJPRkxFWCBIT09LFgrGA1dJVEggQ0FST04TAWVFFAvpASBXSVRIIEFDVVRFGgWVAkJSRVZFGgWbAkNBUk9OGwapBEVESUxMQSEKnTwgQU5EIEJSRVZFGwnqAUlSQ1VNRkxFWCQKvz0gQU5EIEFDVVRFKQnHPURPVCBCRUxPVykFwT1HUkFWRSkKwz1IT09LIEFCT1ZFKQXFPVRJTERFJQWZPEJFTE9XGgnrAURJQUVSRVNJUxsIlwJPVCBBQk9WRR4FuT1CRUxPVxwKhQRVQkxFIEdSQVZFGgi01gJGTE9VUklTSBoF6AFHUkFWRRoKuz1IT09LIEFCT1ZFGg6HBElOVkVSVEVEIEJSRVZFGgaTAk1BQ1JPTiAKlzwgQU5EIEFDVVRFJQWVPEdSQVZFGgX4WE5PVENIGgaZAk9HT05FSxoOkjtSRVRST0ZMRVggSE9PSxoGxwRTVFJPS0UaBb09VElMREUfBps8IEJFTE9XFBClzgJHWVBUT0xPR0lDQUwgQUlOIgOjzgJMRUYUAssCTkcWErzWAiBXSVRIIENST1NTRUQtVEFJTBwMlL4HUEFMQVRBTCBIT09LFAKDBVNIFgqGBSBXSVRIIENVUkwcCou+B0RPVUJMRSBCQVImCYy+ByBBTkQgQ1VSTBwMiztQQUxBVEFMIEhPT0scDpg7UkVUUk9GTEVYIEhPT0sUAevOAlQVAfABSBQCkgVaSBYJuQMgUkVWRVJTRUQXCu8DV0lUSCBDQVJPTh0DkwVVUkwcDJi+B1BBTEFUQUwgSE9PSxwOmjtSRVRST0ZMRVggSE9PSxwEugNUQUlMEwFmRhQPnzwgV0lUSCBET1QgQUJPVkUaBJIDSE9PSxoM7jpNSURETEUgVElMREUaDII7UEFMQVRBTCBIT09LGgaZzwJTVFJPS0UUC6kFRU5HIERJR1JBUEgfC4C+ByBXSVRIIFRSSUxMEwFnRxQL9QMgV0lUSCBBQ1VURRoFnwJCUkVWRRoF5wNDQVJPThsGowJFRElMTEEbCZ0CSVJDVU1GTEVYGgmhAkRPVCBBQk9WRRoE4ARIT09LGgahPE1BQ1JPThoOoc8CT0JMSVFVRSBTVFJPS0UaDIM7UEFMQVRBTCBIT09LGgblA1NUUk9LRRQE4wRBTU1BFAKjA0hBFAi7zwJMT1RUQUwgQRsBvc8CSRsEwgRTVE9QGwG/zwJVEwFoSBQRqzwgV0lUSCBCUkVWRSBCRUxPVxoFnwRDQVJPThsGqTxFRElMTEEbCaUCSVJDVU1GTEVYGgnoWERFU0NFTkRFUhsIpzxJQUVSRVNJUxsIozxPVCBBQk9WRR4FpTxCRUxPVxoE5gRIT09LGgqWPUxJTkUgQkVMT1caDJXPAlBBTEFUQUwgSE9PSxoGpwJTVFJPS0UUBfZYQUxGIEgUA6fOAkVORxcK5wQgV0lUSCBIT09LFAGVA1YTAWlJFAvtASBXSVRIIEFDVVRFGgWtAkJSRVZFGgXQA0NBUk9OGwnuAUlSQ1VNRkxFWBoJ7wFESUFFUkVTSVMjCq88IEFORCBBQ1VURRsIyz1PVCBCRUxPVxwKiQRVQkxFIEdSQVZFGgXsAUdSQVZFGgrJPUhPT0sgQUJPVkUaDosESU5WRVJURUQgQlJFVkUaBqsCTUFDUk9OGgavAk9HT05FSxoOljtSRVRST0ZMRVggSE9PSxoG6ARTVFJPS0UgE5q+ByBBTkQgUkVUUk9GTEVYIEhPT0saBakCVElMREUfBq08IEJFTE9XFAj6zgJOU1VMQVIgRBsB/M4CRhsB+TpHGwGDzwJSGwGFzwJTGwGHzwJUFQzk1gJWRVJURUQgQUxQSEEcAsDWAk9FFAPpBE9UQRcM/DogV0lUSCBTVFJPS0UWB+HWAklGSUVEIEUUAe3OAlMTAWpKFAvwAyBXSVRIIENBUk9OGwm1AklSQ1VNRkxFWBsLnQVST1NTRUQtVEFJTBoGyQRTVFJPS0UTAWtLFAuxPCBXSVRIIEFDVVRFGgXpA0NBUk9OGwa3AkVESUxMQRoJ6lhERVNDRU5ERVIbDsPOAklBR09OQUwgU1RST0tFGwizPE9UIEJFTE9XGgSZA0hPT0saCrU8TElORSBCRUxPVxoOo88CT0JMSVFVRSBTVFJPS0UaDIQ7UEFMQVRBTCBIT09LGgbBzgJTVFJPS0UgFMXOAiBBTkQgRElBR09OQUwgU1RST0tFFAK4AlJBEwFsTBQLugIgV0lUSCBBQ1VURRoDmgNCQVIbA+wERUxUHhGTvgcgQU5EIFBBTEFUQUwgSE9PSxoFvgJDQVJPThsGvAJFRElMTEEbD708SVJDVU1GTEVYIEJFTE9XGwO0BFVSTBoJtzxET1QgQkVMT1cjC7k8IEFORCBNQUNST04cCOFYVUJMRSBCQVIhDLjWAk1JRERMRSBUSUxERRoIkb4HRklTSEhPT0saC8nOAkhJR0ggU1RST0tFGg+31gJJTlZFUlRFRCBMQVpZIFMaCrs8TElORSBCRUxPVxoUpr4HTUlELUhFSUdIVCBMRUZUIEhPT0sdB8ACRExFIERPVCEEudYCUklORyEF6wRUSUxERRoMhTtQQUxBVEFMIEhPT0saDu0EUkVUUk9GTEVYIEhPT0soCY7PAiBBTkQgQkVMVBoGwgJTVFJPS0UUBdvPAkFNQkRBGQybAyBXSVRIIFNUUk9LRRQGtdYCRU5JUyBGFQLuBFpIFxSFvgcgV0lUSCBSRVRST0ZMRVggSE9PSxQByQNKFAX/Ak9ORyBTGRWcPSBXSVRIIERJQUdPTkFMIFNUUk9LRSAImz1PVCBBQk9WRR8LnT1ISUdIIFNUUk9LRRQJqgVTIERJR1JBUEgUAvLOAlVNFAmrBVogRElHUkFQSBMBbU0UC788IFdJVEggQUNVVEUaDLrWAkNST1NT",
+ "RUQtVEFJTBoJwTxET1QgQUJPVkUeBcM8QkVMT1caBPEESE9PSxoM7zpNSURETEUgVElMREUaDIY7UEFMQVRBTCBIT09LFA3XzwJJRERMRSBTQ09UUyBTGQn7PS1XRUxTSCBMTCAB/T1WFALzzgJVTRMBbk4UF8kCIFBSRUNFREVEIEJZIEFQT1NUUk9QSEUVCsQCV0lUSCBBQ1VURRoFyAJDQVJPThsGxgJFRElMTEEbD8s8SVJDVU1GTEVYIEJFTE9XGwu71gJST1NTRUQtVEFJTBsDtQRVUkwaCZHPAkRFU0NFTkRFUhsIxTxPVCBBQk9WRR4FxzxCRUxPVxoF+QNHUkFWRRoJ8gRMRUZUIEhPT0sbCck8SU5FIEJFTE9XGw2eA09ORyBSSUdIVCBMRUcaFKe+B01JRC1IRUlHSFQgTEVGVCBIT09LHQnwOkRMRSBUSUxERRoOpc8CT0JMSVFVRSBTVFJPS0UaDIc7UEFMQVRBTCBIT09LGg7zBFJFVFJPRkxFWCBIT09LGgXxAVRJTERFFAHMA0oUAvTOAlVNEwFvTxQL8wEgV0lUSCBBQ1VURRoFzwJCUkVWRRoF0gNDQVJPThsJ9AFJUkNVTUZMRVgkCtE9IEFORCBBQ1VURSkJ2T1ET1QgQkVMT1cpBdM9R1JBVkUpCtU9SE9PSyBBQk9WRSkF1z1USUxERRoJ9gFESUFFUkVTSVMjC6sEIEFORCBNQUNST04bCK8ET1QgQUJPVkUjC7EEIEFORCBNQUNST04eBc09QkVMT1ccCtECVUJMRSBBQ1VURSEFjQRHUkFWRRoF8gFHUkFWRRoKzz1IT09LIEFCT1ZFHAKhA1JOHgrbPSBBTkQgQUNVVEUjCeM9RE9UIEJFTE9XIwXdPUdSQVZFIwrfPUhPT0sgQUJPVkUjBeE9VElMREUaDo8ESU5WRVJURUQgQlJFVkUaE8vOAkxPTkcgU1RST0tFIE9WRVJMQVkcAs3OAk9QHA36WFcgUklORyBJTlNJREUaBs0CTUFDUk9OIArTPCBBTkQgQUNVVEUlBdE8R1JBVkUaBusDT0dPTkVLIAvtAyBBTkQgTUFDUk9OGg6bvgdSRVRST0ZMRVggSE9PSxoG+AFTVFJPS0UgCv8DIEFORCBBQ1VURRoF9QFUSUxERR8KzTwgQU5EIEFDVVRFJAnPPERJQUVSRVNJUyQGrQRNQUNST04UAaMDSRQLwc8CTEQgUE9MSVNIIE8UBLfPAk1FR0EUAc/OAk8UBdsEUEVOIEUZFJM7IFdJVEggUkVUUk9GTEVYIEhPT0sYAdQETxkUlzsgV0lUSCBSRVRST0ZMRVggSE9PSx8Gv9YCU1RST0tFGQHi1gJFFAGjBFUTAXBQFAvVPCBXSVRIIEFDVVRFGgnXPERPVCBBQk9WRRoI084CRkxPVVJJU0gaBKUDSE9PSxoM8TpNSURETEUgVElMREUaDIg7UEFMQVRBTCBIT09LGg3VzgJTUVVJUlJFTCBUQUlMGwX9OlRST0tFIBLRzgIgVEhST1VHSCBERVNDRU5ERVIUGs/PAkhBUllOR0VBTCBWT0lDRUQgRlJJQ0FUSVZFFQH4BEkTAXFRFBXZzgIgV0lUSCBESUFHT05BTCBTVFJPS0UaBKAFSE9PSx4FywQgVEFJTBoY184CU1RST0tFIFRIUk9VR0ggREVTQ0VOREVSFAm5BFAgRElHUkFQSBMBclIUCNvOAiBST1RVTkRBFQrVAldJVEggQUNVVEUaBdkCQ0FST04bBtcCRURJTExBGwvJ1gJST1NTRUQtVEFJTBoJ2TxET1QgQUJPVkUeBds8QkVMT1cjC908IEFORCBNQUNST04cCpEEVUJMRSBHUkFWRRoI/gRGSVNISE9PSyIR8zogQU5EIE1JRERMRSBUSUxERScMlr4HUEFMQVRBTCBIT09LGg6TBElOVkVSVEVEIEJSRVZFGgrfPExJTkUgQkVMT1cbB/wET05HIExFRxoUqL4HTUlELUhFSUdIVCBMRUZUIEhPT0sdCfI6RExFIFRJTERFGg6nzwJPQkxJUVVFIFNUUk9LRRoMiTtQQUxBVEFMIEhPT0saBs0EU1RST0tFGgT9BFRBSUwZCsfWAk9VVCBIQU5ETEUUCOQEQU1TIEhPUk4UCYRDRVZFUlNFRCBDHQm/zgIgV0lUSCBET1QcAdgERR0Ch74HTkccBvbPAkhBTEYgSBwBg74HSxwG3ARPUEVOIEUiCt0EIFdJVEggSE9PSygOlDtSRVRST0ZMRVggSE9PSxwP/wRSIFdJVEggRklTSEhPT0scCIG+B1NDUklQVCBHFAL1zgJVTRYI3c4CIFJPVFVOREETAXNTFAvbAiBXSVRIIEFDVVRFHw7lPCBBTkQgRE9UIEFCT1ZFGgXhAkNBUk9OHw7nPCBBTkQgRE9UIEFCT1ZFGwbfAkVESUxMQRsJ3QJJUkNVTUZMRVgbCpkET01NQSBCRUxPVxsDnr4HVVJMGg/NzwJESUFHT05BTCBTVFJPS0UbCOE8T1QgQUJPVkUeBeM8QkVMT1cjDuk8IEFORCBET1QgQUJPVkUaBIIFSE9PSxoUqb4HTUlELUhFSUdIVCBMRUZUIEhPT0sdCfQ6RExFIFRJTERFGg6pzwJPQkxJUVVFIFNUUk9LRRoMijtQQUxBVEFMIEhPT0saFMrPAlNIT1JUIFNUUk9LRSBPVkVSTEFZGwm/BFdBU0ggVEFJTBQI4NYCQUtIQSBZQVQVBozPAkxUSUxMTxQE2QRDSFdBGAraBCBXSVRIIEhPT0seDpU7UkVUUk9GTEVYIEhPT0sVBuEEUklQVCBHGxK21gIgV0lUSCBDUk9TU0VELVRBSUwaAcvWAlIbCszWAiBXSVRIIFJJTkcUBt8BSEFSUCBTFBWeOklERVdBWVMgRElBRVJFU0laRUQgVRwBkTpPHQyTOiBXSVRIIFNUUk9LRR0FkjpQRU4gTxwInzpUVVJORUQgTRwBnTpVFQfZzwJHTU9JRCBTFBGFBVFVQVQgUkVWRVJTRUQgRVNIFAjF1gJUSVJSVVAgUhMBdFQUC+UCIFdJVEggQ0FST04bBuMCRURJTExBGw/xPElSQ1VNRkxFWCBCRUxPVxsKmwRPTU1BIEJFTE9XGwO2BFVSTBoJlz1ESUFFUkVTSVMdDOZYR09OQUwgU1RST0tFGwjrPE9UIEFCT1ZFHgXtPEJFTE9XGgStA0hPT0seE4m+ByBBTkQgUkVUUk9GTEVYIEhPT0saCu88TElORSBCRUxPVxoUqr4HTUlELUhFSUdIVCBMRUZUIEhPT0sdCfU6RExFIFRJTERFGgyrA1BBTEFUQUwgSE9PSxoOiAVSRVRST0ZMRVggSE9PSxoG5wJTVFJPS0UUC/dYQUlMTEVTUyBQSEkUE6gFQyBESUdSQVBIIFdJVEggQ1VSTBQLpwVFU0ggRElHUkFQSB8Sl74HIFdJVEggUEFMQVRBTCBIT09LJQ6cvgdSRVRST0ZMRVggSE9PSxQU+jpIIFdJVEggU1RSSUtFVEhST1VHSBUD/gFPUk4YDOXOAiBXSVRIIFNUUk9LRSQS584CIFRIUk9VR0ggREVTQ0VOREVSFAi9A09ORSBGSVZFGAOFA1NJWBgDqANUV08VCJY6UCBIQUxGIE8UB6vOAlJFU0lMTE8UCaYFUyBESUdSQVBIHRTn1gIgV0lUSCBSRVRST0ZMRVggSE9PSxQC984CVU0VBtAEUk5FRCBBGwGCOkUbBNIETFBIQRoFjQNERUxUQRoB3QNFGgH3OkcaAeUESBsOrgUgV0lUSCBGSVNISE9PSykJrwUgQU5EIFRBSUwaAYk6SRsI/84CTlNVTEFSIEcaAZ4FSxoBgc8CTBoB7wRNGw7wBCBXSVRIIExPTkcgTEVHGgjD1gJPIE9QRU4tTyIMxNYCIFdJVEggU1RST0tFGwGUOkUcF8LWAiBXSVRIIEhPUklaT05UQUwgU1RST0tFIgbB1gJTVFJPS0UbBYg6UEVOIEUaAfkEUhsK+wQgV0lUSCBIT09LIQj6BExPTkcgTEVHKROIvgcgQU5EIFJFVFJPRkxFWCBIT09LIQzo1gJNSURETEUgVElMREUhDJW+B1BBTEFUQUwgSE9PSyEE+VhUQUlMGgGHBVQbCo2+ByBXSVRIIENVUkwaAtHWAlVJGgGMBVYaAY0FVxoBjgVZGwqGvgcgV0lUSCBCRUxUFAGpzgJaEwF1VRQEiQUgQkFSGBXP1gIgV0lUSCBTSE9SVCBSSUdIVCBMRUcVCvoBV0lUSCBBQ1VURRoF7QJCUkVWRRoF1ANDQVJPThsJ+wFJUkNVTUZMRVgkBvc8IEJFTE9XGgn8AURJQUVSRVNJUyMK2AMgQU5EIEFDVVRFKAXaA0NBUk9OKAXcA0dSQVZFKAbWA01BQ1JPTiQF8zxCRUxPVxsI5T1PVCBCRUxPVxwK8QJVQkxFIEFDVVRFIQWVBEdSQVZFGgX5AUdSQVZFGgrnPUhPT0sgQUJPVkUcArADUk4eCuk9IEFORCBBQ1VURSMJ8T1ET1QgQkVMT1cjBes9R1JBVkUjCu09SE9PSyBBQk9WRSMF7z1USUxERRoOlwRJTlZFUlRFRCBCUkVWRRoJ0tYCTEVGVCBIT09LGgbrAk1BQ1JPTiAO+zwgQU5EIERJQUVSRVNJUxoG8wJPR09ORUsaDpk7UkVUUk9GTEVYIEhPT0sbCe8CSU5HIEFCT1ZFGg/O1gJTSE9SVCBSSUdIVCBMRUcbBbnPAlRST0tFGgXpAlRJTERFHwr5PCBBTkQgQUNVVEUgBfU8QkVMT1cUAes6RRQB0NYCSRQB+M4CTRQB49YCTxQGigVQU0lMT04aDP86IFdJVEggU1RST0tFEwF2VhQK9FggV0lUSCBDVVJMGg/fzgJESUFHT05BTCBTVFJPS0UbCP88T1QgQkVMT1caBIsFSE9PSxoMjDtQQUxBVEFMIEhPT0saCvFYUklHSFQgSE9PSxoF/TxUSUxERRQD6c4CRU5EFAvjzgJJU0lHT1RISUMgWhQJm88CT0xBUFVLIEFFGwKdzwJPRRsCn88CVUUUAeHOAlkTAXdXFAuDPSBXSVRIIEFDVVRFGgr1AkNJUkNVTUZMRVgaCYU9RElBRVJFU0lTGwiHPU9UIEFCT1ZFHgWJPUJFTE9XGgWBPUdSQVZFGgTzWEhPT0saCpg9UklORyBBQk9WRRMBeFgUD409IFdJVEggRElBRVJFU0lTGwiLPU9UIEFCT1ZFGg3X1gJMT05HIExFRlQgTEVHJxPY1gIgQU5EIExPVyBSSUdIVCBSSU5HKArZ1gJXSVRIIFNFUklGHAzW1gJXIFJJR0hUIFJJTkcaDI07UEFMQVRBTCBIT09LEwF5WRQL/QEgV0lUSCBBQ1VURRoK9wJDSVJDVU1GTEVYGgn/AURJQUVSRVNJUxsIjz1PVCBBQk9WRR4F9T1CRUxPVxoF8z1HUkFWRRoEtANIT09LHgb3PSBBQk9WRRoE/z1MT09QGgazBE1BQ1JPThoKmT1SSU5HIEFCT1ZFGg/a1gJTSE9SVCBSSUdIVCBMRUcbBc8EVFJPS0UaBfk9VElMREUUA50ET0dIEwF6WhQL+gIgV0lUSCBBQ1VURRoF/gJDQVJPThsJkT1JUkNVTUZMRVgbA5EFVVJMGgnsWERFU0NFTkRFUhsI/AJPVCBBQk9WRR4Fkz1CRUxPVxoEpQRIT09LGgqVPUxJTkUgQkVMT1caDPY6TUlERExFIFRJTERFGgyOO1BBTEFUQUwgSE9PSxoOkAVSRVRST0ZMRVggSE9PSxoGtgNTVFJPS0UbCcAEV0FTSCBUQUlMDQqA9gNJR0FUVVJFIEZGFwGD9gNJFwGE9gNMFgGB9gNJFgGC9gNMFQKzAklKFQiF9gNMT05HIFMgVBUC0wJPRRUChvYDU1QHF5BBVUJTQ1JJUFQgU01BTEwgTEVUVEVSIEEdAZFBRR0BlUFIHQHiOkkdAfxYSh0BlkFLHQGXQUwdAZhBTR0BmUFOHQGSQU8dAZpBUB0B4zpSHQGbQVMeBJRBQ0hXQR0BnEFUHQHkOlUdAeU6Vh0Bk0FYARbD5gdFQUYgRkxVVFRFUklORyBJTiBXSU5EBAm+9QdMRVNTIFRSRUUEB+zyB1kgR1JFRU4CBNLpB0RHRVICNun2B0ZUIEFORCBMT1dFUiBBTkQgUklHSFQgVFJJQU5HVUxBUiBUSFJFRSBRVUFSVEVSUyBCTE9DSw8Q/PYHT05FIEVJR0hUSCBCTE9DSwkW2k9SSUdIVCBET1VCTEUgVFVSTlNUSUxFDwTbT1RBQ0sQFJv3B1JJQU5HVUxBUiBIQUxGIEJMT0NLCS/r9gdVUFBFUiBBTkQgUklHSFQgVFJJQU5HVUxBUiBUSFJFRSBRVUFSVEVSUyBCTE9DSw8Q/fYHT05FIEVJR0hUSCBCTE9DSx8ejpkHIENPTlRBSU5JTkcgQkxBQ0sgU01BTEwgU1FVQVJFBwru6wdHRVIgQlVCQkxFCAqIYExFIEJSQUNLRVQSCZFTIFdJVEggRE9UBhSTU1JDIExFU1MtVEhBTiBCUkFDS0VUBxWyVlJPVyBXSVRIIENJUkNMRUQgUExVUxAMsFZTTUFMTCBDSVJDTEUFIdBSQkFSQiBET1dOIFJJR0hUIEJBUkIgRE9XTiBIQVJQT09OGgrLUlVQIEhBUlBPT04KGspSVVAgUklHSFQgQkFSQiBET1dOIEhBUlBPT04YCs5SVVAgSEFSUE9PTgYXkGBMQUNLIExFTlRJQ1VMQVIgQlJBQ0tFVAsWl1NUT1JUT0lTRSBTSEVMTCBCUkFDS0VUBQeIRkNFSUxJTkcGC9xNTE9TRUQgRU5UUlkGDYxgT1JORVIgQlJBQ0tFVAYMe1VSTFkgQlJBQ0tFVBILqUcgTE9XRVIgSE9PSxMMqEdNSURETEUgUElFQ0UTCqdHVVBQRVIgSE9PSwUbhFxET1RURUQgU1VCU1RJVFVUSU9OIEJSQUNLRVQHEopgVUJMRSBBTkdMRSBCUkFDS0VUDAuoXFBBUkVOVEhFU0lTDA6cQFFVT1RBVElPTiBNQVJLDAzaU1dJR0dMWSBGRU5DRQUJ/FJGSVNIIFRBSUwHEItLVkUgRUlHSFRIUyBCTE9DSwYEikZMT09SBRHWS0hBTEYgQkxBQ0sgQ0lSQ0xFEAToV1NUQVIMA4xLT0NLCgaH8gdDSVJDTEUQCYbyByBXSVRIIERPVBYJg/IHRk9VUiBET1RTFgqE8gdUSFJFRSBET1RTFweF8gdXTyBET1RTCg2kmQdGTFlJTkcgU0FVQ0VSCwW59wdPTERFUgolkJwHSE9SSVpPTlRBTCBMSU5FIFdJVEggVEhSRUUgVElDSyBNQVJLUwoplPcHSU5WRVJTRSBNRURJVU0gU0hBREUgQU5EIFJJR0hUIEhBTEYgQkxPQ0sKF6OdB0xPV0VSIE9ORSBRVUFSVEVSIEJMT0NLCgyM9wdNRURJVU0gU0hBREUKC7L3B1JVTk5JTkcgTUFOChaNnAdUUklQTEUgREFTSCBIT1JJWk9OVEFMCheonQdVUFBFUiBPTkUgUVVBUlRFUiBCTE9DSwoNi5wHV0hJVEUgRUxMSVBTRQcV++oHTkQgVEVMRVBIT05FIFJFQ0VJVkVSBSHr9wdKVVNUSUZJRUQgUklHSFQgSEFMRiBCTEFDSyBDSVJDTEUaDOP3B1dISVRFIENJUkNMRQUWnFxMT1cgUEFSQVBIUkFTRSBCUkFDS0VUBgbF7QdVR0dBR0UFCNxPTVVMVElNQVAFIMlFTk9STUFMIEZBQ1RPUiBTRU1JRElSRUNUIFBST0RVQ1QFEI9LT05FIEVJR0hUSCBCTE9DSwkNjktRVUFSVEVSIEJMT0NLCQvP9wdUSElSRCBCTE9DSwYJ1U9VVEVSIEpPSU4FCyhQQVJFTlRIRVNJUxAK",
+ "nEcgRVhURU5TSU9OEQqdR0xPV0VSIEhPT0sRCptHVVBQRVIgSE9PSwUXjFxSQUlTRUQgT01JU1NJT04gQlJBQ0tFVAYKlENJR0hUIEFSUk9XEBXIUiBUSFJPVUdIIFNNQUxMIENJUkNMRREb/ENXSVRIIERPVUJMRSBWRVJUSUNBTCBTVFJPS0UWBq5DU1RST0tFFg/5Q1ZFUlRJQ0FMIFNUUk9LRQsLjFZCTEFDSyBBUlJPVwsM1ENET1VCTEUgQVJST1cXDM5DIFdJVEggU1RST0tFHQ+EUlZFUlRJQ0FMIFNUUk9LRQsR/0NPUEVOLUhFQURFRCBBUlJPVwsQ2PAHU0FOUy1TRVJJRiBBUlJPVwsV5FZUUklBTkdMRS1IRUFERUQgQVJST1cLCq1DV0FWRSBBUlJPVwwKhFZISVRFIEFSUk9XBRbFT1MtU0hBUEVEIEJBRyBERUxJTUlURVIGEctFRU1JRElSRUNUIFBST0RVQ1QHEYlLVkVOIEVJR0hUSFMgQkxPQ0sGEaZcSURFV0FZUyBVIEJSQUNLRVQHE5hATkdMRSBRVU9UQVRJT04gTUFSSwYM6OsHUEVFQ0ggQlVCQkxFBg1bUVVBUkUgQlJBQ0tFVBMKokcgRVhURU5TSU9OFAyjR0xPV0VSIENPUk5FUhQMoUdVUFBFUiBDT1JORVIUEtdcV0lUSCBET1VCTEUgU1RST0tFGQXFQFFVSUxMGQbVXFNUUk9LRRkVj1NUSUNLIElOIEJPVFRPTSBDT1JORVIhCo1TVE9QIENPUk5FUhkIi1NVTkRFUkJBUgYTglxVQlNUSVRVVElPTiBCUkFDS0VUBQSjRVRBQ0sGDYuYB0hJUkQgSU5EVUNUT1ILGsH3B1dISVRFIFJJR0hUIFBPSU5USU5HIElOREVYBwzs6wdPVUdIVCBCVUJCTEUHEY1LUkVFIEVJR0hUSFMgQkxPQ0sLDopLUVVBUlRFUlMgQkxPQ0sUF6KdB0xPV0VSIE9ORSBRVUFSVEVSIEJMT0NLFBepnQdVUFBFUiBPTkUgUVVBUlRFUiBCTE9DSwYVlGBPUlRPSVNFIFNIRUxMIEJSQUNLRVQGFIlcUkFOU1BPU0lUSU9OIEJSQUNLRVQHGs9TSUFOR0xFIEJFU0lERSBWRVJUSUNBTCBCQVILFuz2B1VMQVIgT05FIFFVQVJURVIgQkxPQ0sGD873B1dPIFRISVJEUyBCTE9DSwUXoFxWRVJUSUNBTCBCQVIgV0lUSCBRVUlMTA8HuEdPWCBMSU5FBRSOYFdISVRFIENPUk5FUiBCUkFDS0VUDAyDU1VSTFkgQlJBQ0tFVAsSlmBMRU5USUNVTEFSIEJSQUNLRVQLC4VTUEFSRU5USEVTSVMLDppgU1FVQVJFIEJSQUNLRVQLFphgVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVAYL2FNJR0dMWSBGRU5DRQYLjusHUklUSU5HIEhBTkQEHo4LLUZBQ0lORyBBUk1FTklBTiBFVEVSTklUWSBTSUdODAjImAdCQVNTSU5FVAwEkpkHRklTSBAQlJkHIFdJVEggT1BFTiBNT1VUSA8Bm/IHVAwc9JgHU05BS0UgSEVBRCBXSVRIIENMT1NFRCBNT1VUSBwK8JgHT1BFTiBNT1VUSA0K1h9WQVNUSSBTSUdOFwrYHyBXSVRIIERPVFMFG+ZNSEFOREVEIElOVEVSTEFDRUQgUEVOVEFHUkFNBRH5mwdQT0lOVElORyBBSVJQTEFORQ8MqUZOR0xFIEJSQUNLRVQPCuCYB1RPTUlDIEJPTUIOFPxTQ1VSVkVEIEFOR0xFIEJSQUNLRVQOBY6YB0RJT0RFDxqrAU9VQkxFIEFOR0xFIFFVT1RBVElPTiBNQVJLDgv4mAdFTkVSR1kgV0FWRQ4QjeoHTUFHTklGWUlORyBHTEFTUw4Kl5kHUkFDSU5HIENBUg8E5ZgHSUZMRQ8NoJkHT0NLRVQgQk9PU1RFUhUE1pgHU0hJUBAQopkHTExFUiBDT0FTVEVSIENBUg4MgpgHU1RJQ0sgRklHVVJFDgSemQdUQU5LBR2qT1NIQURFRCBXSElURSBSSUdIVFdBUkRTIEFSUk9XBhu5UklERSBBUkMgQU5USUNMT0NLV0lTRSBBUlJPVwUSqkBUTy1SSUdIVCBFTUJFRERJTkcOB+ZASVNPTEFURQ4EjkBNQVJLDgitQE9WRVJSSURFBAuQQ1dBUkRTIEFSUk9XDxbKViBBQk9WRSBBTE1PU1QgRVFVQUwgVE8WF8JWUkVWRVJTRSBBTE1PU1QgRVFVQUwgVE8eDstWVElMREUgT1BFUkFUT1IWFsNSU0hPUlQgUklHSFRXQVJEUyBBUlJPVxYO81JUSUxERSBPUEVSQVRPUhEjtfcHTkQgVVBQRVIgQU5EIExPV0VSIE9ORSBFSUdIVEggQkxPQ0sQCKRDRlJPTSBCQVIYEZ9SIFRPIEJMQUNLIERJQU1PTkQVD8DxB0RPV05XQVJEUyBBUlJPVxAVxkNPVkVSIFJJR0hUV0FSRFMgQVJST1cQEfdSVEhST1VHSCBMRVNTLVRIQU4YBvpSU1VCU0VUGAG+VlgRBeRDTyBCQVIWHblDIE9WRVIgUklHSFRXQVJEUyBBUlJPVyBUTyBCQVIUDJ1STEFDSyBESUFNT05EEBC4VldJVEggRE9UVEVEIFNURU0XFPpDVUJMRSBWRVJUSUNBTCBTVFJPS0UVFZTwB0VRVUlMQVRFUkFMIEFSUk9XSEVBRBUEqUNIT09LFRiI8AdMQVJHRSBUUklBTkdMRSBBUlJPV0hFQUQWA6tDT09QFRmE8AdNRURJVU0gVFJJQU5HTEUgQVJST1dIRUFEFQyY8QdOT1RDSEVEIFRBSUwVCsZSUExVUyBCRUxPVxUbkPAHU01BTEwgRVFVSUxBVEVSQUwgQVJST1dIRUFEGxKA8AdUUklBTkdMRSBBUlJPV0hFQUQWBZpDVFJPS0UVBKJDVEFJTBkculYgV0lUSCBET1VCTEUgVkVSVElDQUwgU1RST0tFHw+5VlZFUlRJQ0FMIFNUUk9LRRYMkFZJUCBET1dOV0FSRFMZB5FWVVBXQVJEUxUP90NWRVJUSUNBTCBTVFJPS0UPBZlSLVRBSUwKIKjxB0JBQ0stVElMVEVEIFNIQURPV0VEIFdISVRFIEFSUk9XCwqFVkxBQ0sgQVJST1cQE4hXQ0lSQ0xFRCBXSElURSBBUlJPVwsYoPEHT1RUT00tU0hBREVEIFdISVRFIEFSUk9XChC88AdDT01QUkVTU0VEIEFSUk9XCgzgQ0RBU0hFRCBBUlJPVwsL0ENPVUJMRSBBUlJPVxYJhlIgRlJPTSBCQVIXC81DV0lUSCBTVFJPS0UcD4JSVkVSVElDQUwgU1RST0tFFgWbUi1UQUlMEQqMUkRBU0ggQVJST1cKEbTwB0ZJTkdFUi1QT1NUIEFSUk9XCyCq8QdST05ULVRJTFRFRCBTSEFET1dFRCBXSElURSBBUlJPVwoE8vUHSEFORAwdy0NSUE9PTiBPVkVSIFJJR0hUV0FSRFMgSEFSUE9PThI251JXSVRIIEJBUkIgRE9XTiBBQk9WRSBSSUdIVFdBUkRTIEhBUlBPT04gV0lUSCBCQVJCIERPV04hD+tSQkVMT1cgTE9ORyBEQVNIIQjeUkZST00gQkFSIQbWUlRPIEJBUiAFvUNXQVJEUxwp4lJVUCBBQk9WRSBMRUZUV0FSRFMgSEFSUE9PTiBXSVRIIEJBUkIgRE9XTiYI6lJPTkcgREFTSCUf5lJSSUdIVFdBUkRTIEhBUlBPT04gV0lUSCBCQVJCIFVQHwjaUkZST00gQkFSHwbSUlRPIEJBUh4FvENXQVJEUwsKxPAHRUFWWSBBUlJPVxAQwPAHQ09NUFJFU1NFRCBBUlJPVwoXpPEHTEVGVC1TSEFERUQgV0hJVEUgQVJST1cKEf1DT1BFTi1IRUFERUQgQVJST1cKDcdDUEFJUkVEIEFSUk9XUwsL9/UHVVNISU5HIEhBTkQKD8VWUVVBRFJVUExFIEFSUk9XChim8QdSSUdIVC1TSEFERUQgV0hJVEUgQVJST1cLBezsB09DS0VUChDQ8AdTQU5TLVNFUklGIEFSUk9XCwy48AdRVUFSRUQgQVJST1cNC9xDSUdHTEUgQVJST1cKFqLxB1RPUCBTSEFERUQgV0hJVEUgQVJST1cLEZDxB1JJQU5HTEUgQVJST1dIRUFEEg3gVi1IRUFERUQgQVJST1cfJoBXIE9WRVIgUklHSFRXQVJEUyBUUklBTkdMRS1IRUFERUQgQVJST1cgBvBWVE8gQkFSIA+o8AdXSVRIIEJPTEQgU0hBRlQlGPpWRE9VQkxFIEhPUklaT05UQUwgU1RST0tFLA/6VlZFUlRJQ0FMIFNUUk9LRSULrPAHSEVBVlkgU0hBRlQlEqZXTE9ORyBUSVAgRE9XTldBUkRTLgekV1VQV0FSRFMlDKTwB01FRElVTSBTSEFGVCUMoPAHTkFSUk9XIFNIQUZUJRCw8AdWRVJZIEhFQVZZIFNIQUZUGgzqVkRBU0hFRCBBUlJPVxoNhFdQQUlSRUQgQVJST1dTDQnaQ1BMRSBBUlJPVxEKjlJEQVNIIEFSUk9XCw+eQ1dPIEhFQURFRCBBUlJPVw0WtlYtSEVBREVEIEFSUk9XIEZST00gQkFSGxu1VldJVEggRE9VQkxFIFZFUlRJQ0FMIFNUUk9LRSAEu1ZUQUlMJBy9ViBXSVRIIERPVUJMRSBWRVJUSUNBTCBTVFJPS0UqD7xWVkVSVElDQUwgU1RST0tFIRLsV1JJQU5HTEUgQVJST1dIRUFEUyAPtFZWRVJUSUNBTCBTVFJPS0UVEbdWVFJJUExFIERBU0ggQVJST1cKCpxDV0FWRSBBUlJPVwsK5kNISVRFIEFSUk9XFRqU8QcgV0lUSElOIFRSSUFOR0xFIEFSUk9XSEVBRAIBtfMHRwIDy+YHTU9OAgHMTE8DBIboB1BBUkQCFa04UENIQSBDT05TT05BTlQgU0lHTiBLFwO1OEFORxYBrzhMFgGuOE0WAbA4ThcGtDhZSU4tRE8WAbE4UBYBsjhSFgGzOFQHC8g4RElHSVQgRUlHSFQNBMU4RklWRQ4DxDhPVVINBMk4TklORQ0DwThPTkUNBcc4U0VWRU4OAsY4SVgNBcM4VEhSRUUOAsI4V08NBMA4WkVSTwcIozhMRVRURVIgQQ4CkzhCQQ8ClDhMQQ4ChjhDQQ8ChzhIQQ4CjDhEQQ8CzzhEQQ8CmThaQQ4CkThGQQ8CkjhMQQ4CgzhHQQ8ChDhMQQ4CnThIQQ8CnjhMQQ4CiDhKQQ4CgDhLQQ8CgjhIQQ8CgThMQQ4CnDhMQQ4ClThNQQ8CljhMQQ4CjThOQQ8ChThHQQ8CiThZQQ4CjjhQQQ8CkDhIQQ8CjzhMQQ4CmzhSQQ4CoDhTQQ8CoThIQQ4CijhUQQ8CizhIQQ8ClzhTQRACmDhIQQ8CzThUQRACzjhIQQ4CnzhWQQ4CojhXQQ4CmjhZQQcSvThQVU5DVFVBVElPTiBDRVItV0ETErw4TllFVCBUSFlPT00gVEEtUk9MEwa7OFRBLVJPTBQFvzhTSE9PSxkHvjggQ0VSLVdBBwq3OFNJR04gTlVLVEEMA7Y4UkFOCBKlOFVCSk9JTkVEIExFVFRFUiBSQRgCpDhZQQcNpjhWT1dFTCBTSUdOIEFBEgGsOEUSAac4SRIBqDhPEwGpOE8SAao4VRMBqzhVAjKLVVNTLVRIQU4gQUJPVkUgRE9VQkxFLUxJTkUgRVFVQUwgQUJPVkUgR1JFQVRFUi1USEFOECSRVUdSRUFURVItVEhBTiBBQk9WRSBET1VCTEUtTElORSBFUVVBTBAP9lJMRUZUV0FSRFMgQVJST1cQGo9VU0lNSUxBUiBBQk9WRSBHUkVBVEVSLVRIQU4YCI1VT1IgRVFVQUwRM5NVTEFOVEVEIEVRVUFMIEFCT1ZFIEdSRUFURVItVEhBTiBBQk9WRSBTTEFOVEVEIEVRVUFMCxKJVU5EIE5PVCBBUFBST1hJTUFURQ4Yh1VTSU5HTEUtTElORSBOT1QgRVFVQUwgVE8KEOhEQlVUIE5PVCBFUVVBTCBUTxUK5kVJVkFMRU5UIFRPCg+mVUNMT1NFRCBCWSBDVVJWRRkUqFUgQUJPVkUgU0xBTlRFRCBFUVVBTAoY2kVFUVVBTCBUTyBPUiBHUkVBVEVSLVRIQU4KDoVVT1IgQVBQUk9YSU1BVEUNCORERVFVQUwgVE8QCvJESVZBTEVOVCBUTw0M9kRHUkVBVEVSLVRIQU4NEP1UU0xBTlRFRCBFUVVBTCBUTx0PgVUgV0lUSCBET1QgQUJPVkUsBoNVIFJJR0hUJwb/VElOU0lERQsM5kRWRVIgRVFVQUwgVE8KBDxTSUdOChL5VFdJVEggQ0lSQ0xFIElOU0lERQ8D1kVET1QPE/tUUVVFU1RJT04gTUFSSyBBQk9WRQIH0J0HVUtPVEhFQQIKmucHVkVMIFNMSURFUgEBCkYBBM5MSUJSQQIX8uwHR0FUVVJFIE9QRU4gRVQgT1JOQU1FTlQDDfX0B0hUIEJMVUUgSEVBUlQGCvjrB0NIRUNLIE1BUksGFbvvB0VJR0hUIFNQT0tFRCBBU1RFUklTSwYXye8HRklWRSBQT0lOVEVEIEJMQUNLIFNUQVILD6/vB1NQT0tFRCBBU1RFUklTSwcWzFdPVVIgUE9JTlRFRCBCTEFDSyBDVVNQGQTE7wdTVEFSBgui7wdHUkVFSyBDUk9TUwYk8k5MRUZUIFRPUlRPSVNFIFNIRUxMIEJSQUNLRVQgT1JOQU1FTlQGBIjtB1JBSUwHJPNOSUdIVCBUT1JUT0lTRSBTSEVMTCBCUkFDS0VUIE9STkFNRU5UBgep7wdTQUxUSVJFBwSRS0hBREUHErXvB0lYIFNQT0tFRCBBU1RFUklTSwYYwO8HVEhSRUUgUE9JTlRFRCBCTEFDSyBTVEFSBxjS7wdXRUxWRSBQT0lOVEVEIEJMQUNLIFNUQVIGDNhOVkVSVElDQUwgQkFSBgyO7wdXSElURSBTUVVBUkUFBIdMTklORwkF8usHIE1PT0QOB/HrByBCVUJCTEUCD84yTUJV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R0hUIFRBQ0sKC/ZPV0FSRFMgQVJST1cVCfxPIEZST00gQkFSFhnQ8QdPVkVSIExPTkcgTEVGVFdBUkRTIEFSUk9XFgnW8QdUSFJPVUdIIFgWEdfxB1dJVEggRE9VQkxFIFNMQVNIEAz5T0RPVUJMRSBBUlJPVxwJ/k8gRlJPTSBCQVIQJdLxB0hBUlBPT04gQUJPVkUgU0hPUlQgTEVGVFdBUkRTIEhBUlBPT04YG9HxB09WRVIgTE9ORyBMRUZUV0FSRFMgSEFSUE9PThAO/09TUVVJR0dMRSBBUlJPVwIM9O4HVCBPRiBGT1JUVU5FAwr08wdJT04gQk9UVExFAwK39QdVUwIQrewHVURMWSBDUllJTkcgRkFDRQII6ecHVkUgSE9URUwFBozpB0xFVFRFUgIKzkBXIEFTVEVSSVNLBAer9QdCQVRURVJZBRCF6gdSSUdIVE5FU1MgU1lNQk9MBBufYERPVUJMRSBQUklNRSBRVU9UQVRJT04gTUFSSwQGx1xLQVZZS0EKCchcIFdJVEggRE9UBARfTElORQMRg05FUiBCTEFERSBTQ0lTU09SUwYfnZ0HQ0VOVFJFIExFRlQgT05FIFNJWFRFRU5USCBCTE9DSxIOvZgHVFdFTEZUSCBDSVJDTEUNEeX3B09ORSBRVUFSVEVSIEJMT0NLDRmenQdSSUdIVCBPTkUgU0lYVEVFTlRIIEJMT0NLEw6+mAdUV0VMRlRIIENJUkNMRQYShUtGSVZFIEVJR0hUSFMgQkxPQ0sGIIGcB0hBTEYgQU5EIFVQ",
+ "UEVSIEhBTEYgV0hJVEUgQ0lSQ0xFCwWES0JMT0NLCwbhS0NJUkNMRQsShJwHSEVBVlkgV0hJVEUgU1FVQVJFCxTbS0lOVkVSU0UgV0hJVEUgQ0lSQ0xFCxaknQdMRUZUIE9ORSBRVUFSVEVSIEJMT0NLCwyP9wdNRURJVU0gU0hBREULF6+dB1JJR0hUIE9ORSBRVUFSVEVSIEJMT0NLCyOTnAdWRVJUSUNBTCBMSU5FIFdJVEggVEhSRUUgVElDSyBNQVJLUwcXhZgHT1JJWk9OVEFMIFJVTEVSIFNFR01FTlQGEorrB0xFRlQgQkFMTFBPSU5UIFBFTgwvvPYHTE9DSyBESUFHT05BTCBMT1dFUiBNSURETEUgTEVGVCBUTyBMT1dFUiBDRU5UUkU1Bb32B1JJR0hUGiLO9gdVUFBFUiBDRU5UUkUgVE8gTE9XRVIgTUlERExFIFJJR0hUMAXQ9gdSSUdIVCoSzPYHVVBQRVIgTUlERExFIFJJR0hUIBTA9gdMRUZUIFRPIExPV0VSIENFTlRSRS4Mz/YHTUlERExFIFJJR0hUKBLN9gdVUFBFUiBNSURETEUgUklHSFQgG772B01JRERMRSBMRUZUIFRPIExPV0VSIENFTlRSRTUM0fYHTUlERExFIFJJR0hUNQW/9gdSSUdIVAsGjesHQ1JBWU9OCwyL6wdGT1VOVEFJTiBQRU4LE5ydB09ORSBTSVhURUVOVEggQkxPQ0sLCozrB1BBSU5UQlJVU0gMBYnrB0VOQ0lMCxXImQdRVUFEUkFOVCBDSEVTUyBCSVNIT1AaBLyZB0tJTkcbBcyZB05JR0hUGgTQmQdQQVdOGgXAmQdRVUVFThoExJkHUk9PSxUL30tJUkNVTEFSIEFSQxQUrpkHRkFDRSBXSVRIIE9QRU4gTU9VVEgVDLCZB1JPV05JTkcgRkFDRRQNuJkHTUlDUk9DT01QVVRFUhQMrJkHTkVVVFJBTCBGQUNFFAyqmQdTTUlMSU5HIEZBQ0UVDtSZB1RBTkRJTkcgS05JR0hUFAq0mQdURUxFVklTSU9ODgu5mAdSVEVSIENJUkNMRQsgv1JTRU1JQ0lSQ1VMQVIgQU5USUNMT0NLV0lTRSBBUlJPVwsI+ktUUklBTkdMRRERn/cHVUxBUiBNRURJVU0gU0hBREUMDbyYB1dFTEZUSCBDSVJDTEUGJpmdB01JRERMRSBDRU5UUkUgTEVGVCBPTkUgU0lYVEVFTlRIIEJMT0NLFBmanQdSSUdIVCBPTkUgU0lYVEVFTlRIIEJMT0NLDRiYnQdMRUZUIE9ORSBTSVhURUVOVEggQkxPQ0sSDriYB1RXRUxGVEggQ0lSQ0xFDRmbnQdSSUdIVCBPTkUgU0lYVEVFTlRIIEJMT0NLEw67mAdUV0VMRlRIIENJUkNMRQYQgUtPTkUgRUlHSFRIIEJMT0NLCg2CS1FVQVJURVIgQkxPQ0sGN8f2B1JJR0hUIEJMT0NLIERJQUdPTkFMIExPV0VSIENFTlRSRSBUTyBMT1dFUiBNSURETEUgUklHSFQrEsn2B1VQUEVSIE1JRERMRSBSSUdIVDEFy/YHUklHSFQhGsj2B0xFRlQgVE8gTE9XRVIgTUlERExFIFJJR0hUKQzF9gdVUFBFUiBDRU5UUkUvDMr2B01JRERMRSBSSUdIVCEbw/YHTUlERExFIExFRlQgVE8gVVBQRVIgQ0VOVFJFNgzG9gdNSURETEUgUklHSFQ2BcT2B1JJR0hUGyHB9gdVUFBFUiBNSURETEUgTEVGVCBUTyBVUFBFUiBDRU5UUkU2BcL2B1JJR0hUDA/TT0NPUk5FUiBXSVRIIERPVAwaz05EUk9QLVNIQURPV0VEIFdISVRFIFNRVUFSRQwTn50HT05FIFNJWFRFRU5USCBCTE9DSwwGjk5QRU5DSUwMFcmZB1FVQURSQU5UIENIRVNTIEJJU0hPUBsEvZkHS0lORxwFzZkHTklHSFQbBNGZB1BBV04bBcGZB1FVRUVOGwTFmQdST09LFgveS0lSQ1VMQVIgQVJDFRSvmQdGQUNFIFdJVEggT1BFTiBNT1VUSBYMsZkHUk9XTklORyBGQUNFFQ25mQdNSUNST0NPTVBVVEVSFQytmQdORVVUUkFMIEZBQ0UVDKuZB1NNSUxJTkcgRkFDRRYO1ZkHVEFORElORyBLTklHSFQVCrWZB1RFTEVWSVNJT04PC7qYB1JURVIgQ0lSQ0xFDA2HmAdSVUxFUiBTRUdNRU5UDBy+UlNFTUlDSVJDVUxBUiBDTE9DS1dJU0UgQVJST1cNFL7qB0hBRE9XRUQgV0hJVEUgQ0lSQ0xFGwbRTlNRVUFSRQwI/0tUUklBTkdMRRIRnvcHVUxBUiBNRURJVU0gU0hBREUNDb+YB1dFTEZUSCBDSVJDTEUGE4dLU0VWRU4gRUlHSFRIUyBCTE9DSwYTg0tUSFJFRSBFSUdIVEhTIEJMT0NLDA6GS1FVQVJURVJTIEJMT0NLFRalnQdMRUZUIE9ORSBRVUFSVEVSIEJMT0NLFReunQdSSUdIVCBPTkUgUVVBUlRFUiBCTE9DSwcb7/YHUklBTkdVTEFSIE9ORSBRVUFSVEVSIEJMT0NLAgXKS1pFTkdFBxvgTyBESVZJREVEIEJZIEhPUklaT05UQUwgUlVMRQECqkBSRQIB5kBJAgGOQE0CAa1ATwEG8/MHVUdHQUdFAgv27gdOQVIgRUNMSVBTRQMCwfUHR1MBDoCFBFlDSUFOIExFVFRFUiBBDwGZhQRODgGChQRCDwGDhQRIDgGFhQREDgGBhQRFDwGahQRODgGEhQRHDgGbhQRIDgGGhQRJDgGKhQRKDgGLhQRLDwGUhQRLDgGNhQRMDgGOhQRNDwGQhQRNDgGPhQRODwGRhQRODgGThQRQDgGMhQRRDgGVhQRSDgGWhQRTDgGXhQRUDwGJhQRIDwGYhQRUDgGShQRVDgGHhQRXDgGchQRYDgGIhQRaAg2gkgRESUFOIExFVFRFUiBBDwG1kgRODgGhkgRCDgG5kgRDDgGjkgREDgGkkgRFDwG2kgRODgGxkgRGDgGikgRHDgGmkgRJDgGokgRLDgGpkgRMDwG3kgRZDgGqkgRNDgGrkgRODwG4kgRODgGskgRPDgGykgRRDgGtkgRSDgGzkgRTDwGukgRTDgGvkgRUDwG0kgRUDgGwkgRVDgGlkgRWDgGnkgRZBw+/kgRUUklBTkdVTEFSIE1BUksCCKXyB0lORyBGQUNFAAavAU1BQ1JPTgIC2fMHR0UDB4T1B0lDIFdBTkQDA/LzB05FVAIY9KIESEFKQU5JIEFCQlJFVklBVElPTiBTSUdOCQjQogRMRVRURVIgQRAC6qIEQkERAuuiBEhBEALZogRDQREC2qIESEEQAuWiBERBEQLgogREQRIC4aIESEERAuaiBEhBEAHTogRFEALXogRHQREC2KIESEEQAvGiBEhBEAHRogRJEALbogRKQREC3KIESEEQAtWiBEtBEQLWogRIQRAC7qIETEEQAuyiBE1BEALnogROQREC4qIETkERAt2iBFlBEAHUogRPEALoogRQQREC6aIESEEQAu2iBFJBEQLyogRSQRAC8KIEU0EQAuOiBFRBEQLkogRIQREC3qIEVEESAt+iBEhBEAHSogRVEALvogRWQQoM9qIESUdBVFVSRSBTSFJJCQz1ogRTRUNUSU9OIE1BUksKCfOiBElHTiBOVUtUQQMQqOAHSk9ORyBUSUxFIEFVVFVNTg0Eq+AHQkFDSw8EpOAHTUJPTw0NpeAHQ0hSWVNBTlRIRU1VTQ0JgOAHRUFTVCBXSU5EDg+X4AdJR0hUIE9GIEJBTUJPT1MWCo7gB0NIQVJBQ1RFUlMXBqDgB0lSQ0xFUw0PlOAHRklWRSBPRiBCQU1CT09TFQqL4AdDSEFSQUNURVJTFgad4AdJUkNMRVMODpPgB09VUiBPRiBCQU1CT09TFQqK4AdDSEFSQUNURVJTFgac4AdJUkNMRVMNDIXgB0dSRUVOIERSQUdPTg0FquAHSk9LRVIND5jgB05JTkUgT0YgQkFNQk9PUxUKj+AHQ0hBUkFDVEVSUxYGoeAHSVJDTEVTDgmD4AdPUlRIIFdJTkQNDpDgB09ORSBPRiBCQU1CT09TFAqH4AdDSEFSQUNURVJTFQaZ4AdJUkNMRVMOBaPgB1JDSElEDQSi4AdQTFVNDQqE4AdSRUQgRFJBR09ODRCW4AdTRVZFTiBPRiBCQU1CT09TFgqN4AdDSEFSQUNURVJTFwaf4AdJUkNMRVMODZXgB0lYIE9GIEJBTUJPT1MUCozgB0NIQVJBQ1RFUlMVBp7gB0lSQ0xFUw4JgeAHT1VUSCBXSU5EDgWm4AdQUklORw4Fp+AHVU1NRVINEJLgB1RIUkVFIE9GIEJBTUJPT1MWCongB0NIQVJBQ1RFUlMXBpvgB0lSQ0xFUw4NkeAHV08gT0YgQkFNQk9PUxQKiOAHQ0hBUkFDVEVSUxUGmuAHSVJDTEVTDQmC4AdXRVNUIFdJTkQOC4bgB0hJVEUgRFJBR09ODgWp4AdJTlRFUgIL8r0ES0FTQVIgQU5HS0EIDvi9BEVORCBPRiBTRUNUSU9OCAjxvQRMRVRURVIgQQ8C5L0EQkEPAum9BENBDwLnvQREQQ8C4b0ER0EPAuq9BEpBDwLgvQRLQQ8C7r0ETEEPAuW9BE1BDwLovQROQRAC4r0ER0EQAuu9BFlBDwLjvQRQQQ8C7b0EUkEPAvC9BFNBDwLmvQRUQQ8C770EVkEPAuy9BFlBCAr3vQRQQVNTSU1CQU5HCAz1vQRWT1dFTCBTSUdOIEUTAfO9BEkTAfa9BE8TAfS9BFUDBfzuB0VNQUtFAhbXGkxBWUFMQU0gQVUgTEVOR1RIIE1BUksKCfkaREFURSBNQVJLCwruGklHSVQgRUlHSFQQBOsaRklWRRED6hpPVVIQBO8aTklORRAD5xpPTkUQBe0aU0VWRU4RAuwaSVgQBekaVEhSRUURAugaV08QBOYaWkVSTwoT9xpGUkFDVElPTiBPTkUgRUlHSFRIFwXeGkZJRlRIGAfZGk9SVElFVEgXBPQaSEFMRhcY2BpPTkUtSFVORFJFRC1BTkQtU0lYVElFVEgXB/MaUVVBUlRFUhcJ9hpTSVhURUVOVEgXBdwaVEVOVEgYCNsaV0VOVElFVEgTENoaVEhSRUUgRUlHSFRJRVRIUxkI9RpRVUFSVEVSUxkK+BpTSVhURUVOVEhTGQrdGlRXRU5USUVUSFMKCIUaTEVUVEVSIEESAYYaQRIBkBpJEgnfGlJDSEFJQyBJSRIBlBpVEQKsGkJBEgKtGkhBEQKaGkNBEgKbGkhBEwb/GklMTFUgSxgB/RpMGQH+GkwaAdYaTBgB1BpNGAH7Gk4ZAfoaThgC/BpSUhgB1RpZEQKmGkRBEgKhGkRBEwKiGkhBEgKnGkhBEgfOGk9UIFJFUEgRAY4aRRIBjxpFEQKXGkdBEgKYGkhBEQK5GkhBEQGHGkkSAYgaSRECnBpKQRICnRpIQREClRpLQRIClhpIQRECshpMQRICsxpMQRMCtBpMQRECrhpNQRECqBpOQRICmRpHQRICoxpOQRMCqRpOQRICnhpZQREBkhpPEgGTGk8RAqoaUEESAqsaSEERArAaUkESArEaUkERArgaU0ESArYaSEESArcaU0ERAqQaVEESAqUaSEESAp8aVEETAqAaSEETAroaVEERAYkaVRIBihpVEQK1GlZBEg2EGkVESUMgQU5VU1ZBUkESCIwaT0NBTElDIEwaAeEaTBkBixpSGgHgGlIRAq8aWUEKEvEaTlVNQkVSIE9ORSBIVU5EUkVEFQjyGlRIT1VTQU5EEQPwGlRFTgoNghpTSUdOIEFOVVNWQVJBEAe9GlZBR1JBSEEPC4EaQ0FORFJBQklORFUQDrwaSVJDVUxBUiBWSVJBTUEQF4AaT01CSU5JTkcgQU5VU1ZBUkEgQUJPVkUPBM8aUEFSQQ8TuxpWRVJUSUNBTCBCQVIgVklSQU1BEAXNGklSQU1BEQWDGlNBUkdBCg2+GlZPV0VMIFNJR04gQUEWAcgaSRYBzBpVFQHGGkUWAccaRRUBvxpJFgHAGkkVAcoaTxYByxpPFQHBGlUWAcIaVRUJ4hpWT0NBTElDIEweAeMaTB0BwxpSHgHEGlIDEaVNRSBBTkQgRkVNQUxFIFNJR04FBMJMU0lHTgUkp01XSVRIIFNUUk9LRSBBTkQgTUFMRSBBTkQgRkVNQUxFIFNJR04RBKZNU0lHTgMKoE5URVNFIENST1NTAgWj8wdNTU9USAIB6OgHTgMY6+gHIEFORCBXT01BTiBIT0xESU5HIEhBTkRTBAf66gdEQU5DSU5HBBv06gdJTiBCVVNJTkVTUyBTVUlUIExFVklUQVRJTkcHBrXyB1RVWEVETwQP8ugHV0lUSCBHVUEgUEkgTUFPCQbz6AdUVVJCQU4DB7xBQVQgU0lHTgMV2RBEQUlDIEFGRlJJQ0FUSU9OIE1BUksID9sQR0VNSU5BVElPTiBNQVJLCAnBEExFVFRFUiBBQhABwxBEEAHCEEcQAcQQSBAC2BBJThAByhBLEQLJEFNBEAHLEEwQAcwQTRABzRBOEAHQEFAQAdIQURAB0xBSEAHOEFMRAdQQSBEB0RBaEAHVEFQRAcgQVBABxhBaDwjWEERVU0hFTk5BDwXAEEhBTFFBDwLPEElOEAHHEFQPA9cQS0FEDwfFEFVTSEVOTkEIC94QUFVOQ1RVQVRJT04IEdoQVk9DQUxJWkFUSU9OIE1BUksDAu3yB0dPAx/llQRJQ0hBRUFOIEFCQlJFVklBVElPTiBNQVJLIEFCT1ZFHQXmlQRCRUxPVwsM2pUETEVUVEVSIEFBWUlOEwTAlQRMRVBIEwPZlQRZSU4SBMGVBEJFVEgTBMKVBEhFVEgSBsWVBERBTEVUSBMG1JUESEFNRURIEgLclQRGRRIGxJUER0hJTUVMEwTDlQRJTUVMEgLGlQRIRRQCzZUEVEgSBcuVBEpBWUlOEwXMlQRIQVlJThIE0JUES0FQSBME0pUESEFQSBIG05UETEFNRURIEgPWlQRNRU0SA9eVBE5VThIC25UEUEUSBeCVBFFIT1BIEwPelQRPUEgSBOGVBFJFU0gSBd2VBFNBREhFFATYlQRNRUtIEwPilQRISU4TBOOVBFNISU4SA+SVBFRBVxMDzpUERVRIEwbVlQRIQU1FREgSA8eVBFdBVxIE0ZUEWEFQSBMD35UET1BIEgTPlQRZT0RIEgXJlQRaQVlJThMFypUESEFZSU4LC+yVBE5VTUJFUiBGSVZFEgPrlQRPTkUVCO+VBCBIVU5EUkVEEgPtlQRURU4TBe6VBFdFTlRZCw/0lQRQVU5DVFVBVElPTiBET1QaC/OVBCBXSVRISU4gRE9UGRPylQRVQkxFIERPVCBXSVRISU4gRE9UFwfxlQRGTEVVUk9OFwv2lQRMSU5FIEZJTExFUhcE8JUEU1RBUhcI9ZUEVFdPIERPVFMLB8iVBFNJR04gVUQDBt7oB1MgU0hPRQMO8OoHVEVMUElFQ0UgQ0xPQ0sDDr3zB1VBTCBXSEVFTENIQUlSAhfvTVAgU1lNQk9MIEZPUiBMSUdIVEhPVVNFAwfB5gdMRSBMRUFGAgWH9QdSQUNBUwMO8LgEQ0hFTiBIRUFEIE1BUksICYi5BExFVFRFUiAtQQ8Bj7kEQQ8CgLkEQkEPAva4BENBEAL3uARIQQ8C/LgEREEQAoS5BFpBDwL0uARHQQ8CjrkESEEPAvi4BEpBDwLyuARLQRAC87gESEEPAou5BExBDwKBuQRNQQ8C/bgETkEQAvW4BEdBEAL5uARZQQ8C/rgEUEEQAv+4BEhBDwKKuQRSQQ8CjbkEU0EQAoy5BEhBDwL6uARUQRAC+7gESEEQAoK5BFNB",
+ "EQKDuQRIQQ8ChbkEV0EPAom5BFlBDwKHuQRaQRAChrkESEEICfG4BE1BUksgU0hBRAgNtbkEU0lHTiBBTlVTVkFSQQ0LtrkEQ0FORFJBQklORFUJEa+5BFVCSk9JTkVEIExFVFRFUiBBGQKguQRCQRkClrkEQ0EaApe5BEhBGQKcuQREQRoCpLkEWkEZApS5BEdBGQKuuQRIQRkCmLkESkEZApK5BEtBGgKTuQRIQRkCq7kETEEZAqG5BE1BGQKduQROQRoClbkER0EaApm5BFlBGQKeuQRQQRoCn7kESEEZAqq5BFJBGQKtuQRTQRoCrLkESEEZApq5BFRBGgKbuQRIQRoCorkEU0EbAqO5BEhBGQKluQRXQRkCqbkEWUEZAqe5BFpBGgKmuQRIQQgNsLkEVk9XRUwgU0lHTiBBQRMBs7kERRMBsbkESRMBtLkETxMBsrkEVQMMrU1SSUFHRSBTWU1CT0wDEcvyB1RJQUwgQVJUUyBVTklGT1JNAhfYugRTQVJBTSBHT05ESSBESUdJVCBFSUdIVBQE1boERklWRRUD1LoET1VSFATZugROSU5FFAPRugRPTkUUBde6BFNFVkVOFQLWugRJWBQF07oEVEhSRUUVAtK6BFdPFATQugRaRVJPDgiAugRMRVRURVIgQRYBgboEQRYBiLoESRYBi7oEVRUCoroEQkEWAqO6BEhBFQKRugRDQRYCkroESEEVAp26BERBFgKYugREQRcCmboESEEWAp66BEhBFQGGugRFFQKOugRHQRYCj7oESEEVAqy6BEhBFQGCugRJFgGDugRJFQKTugRKQRYClLoESEEWA6+6BE5ZQRUCjLoES0EWAo26BEhBFgOuugRTU0EVAqe6BExBFgKtugRMQRUCpLoETUEVAp+6BE5BFgKQugRHQRYCmroETkEWApW6BFlBFQGJugRPFQKgugRQQRYCoboESEEVAqa6BFJBFQKrugRTQRYCqboESEEWAqq6BFNBFQKbugRUQRYCnLoESEEWArC6BFJBFgKWugRUQRcCl7oESEEVAYS6BFUWAYW6BFUVAqi6BFZBFQKlugRZQQ4Hx7oEUkEtS0FSQQ8ExroERVBIQQ4NwLoEU0lHTiBBTlVTVkFSQRMGw7oEQ0FORFJBEwfEugRIQUxBTlRBEwXCugROVUtUQRMHwboEVklTQVJHQQ4GxboEVklSQU1BDwyxugRPV0VMIFNJR04gQUEaAby6BEkaAb+6BFUZAbq6BEUZAbK6BEkaAbO6BEkZAb26BE8ZAbS6BFUaAbW6BFUZCba6BFZPQ0FMSUMgUgMYugFDVUxJTkUgT1JESU5BTCBJTkRJQ0FUT1IDDa3jB0sgV09SSyBTWU1CT0wDBrxgVSBNQVJLAgjJ8wdURSBEUklOSwMYgKgHSEVNQVRJQ0FMIEJPTEQgQ0FQSVRBTCBBGwSorQdMUEhBGgGBqAdCGwOprQdFVEEaAYKoB0MbAr6tB0hJGgGDqAdEGwSrrQdFTFRBGwbKrwdJR0FNTUEaAYSoB0UbBqytB1BTSUxPThsCrq0HVEEaAYWoB0YaAYaoB0cbBKqtB0FNTUEaAYeoB0gaAYioB0kbA7CtB09UQRoBiagHShoBiqgHSxsEsa0HQVBQQRoBi6gHTBsEsq0HQU1EQRoBjKgHTRsBs60HVRoBjagHThsBtK0HVRoBjqgHTxsEwK0HTUVHQRwFtq0HSUNST04aAY+oB1AbAr2tB0hJGwG3rQdJGwK/rQdTSRoBkKgHURoBkagHUhsCuK0HSE8aAZKoB1MbBLqtB0lHTUEaAZOoB1QbArutB0FVGwSvrQdIRVRBHwe5rQcgU1lNQk9MGgGUqAdVGwa8rQdQU0lMT04aAZWoB1YaAZaoB1caAZeoB1gbAbWtB0kaAZioB1kaAZmoB1obA62tB0VUQRIL1q8HRElHSVQgRUlHSFQYBNOvB0ZJVkUZA9KvB09VUhgE168HTklORRgDz68HT05FGAXVrwdTRVZFThkC1K8HSVgYBdGvB1RIUkVFGQLQrwdXTxgEzq8HWkVSTxIO3K0HRVBTSUxPTiBTWU1CT0wSEeyqB0ZSQUtUVVIgQ0FQSVRBTCBBIgHtqgdCIgHuqgdDIgHvqgdEIgHwqgdFIgHxqgdGIgHyqgdHIgHzqgdIIgH0qgdJIgH1qgdKIgH2qgdLIgH3qgdMIgH4qgdNIgH5qgdOIgH6qgdPIgH7qgdQIgH8qgdRIgH9qgdSIgH+qgdTIgH/qgdUIgGAqwdVIgGBqwdWIgGCqwdXIgGDqwdYIgGEqwdZIgGFqwdaGgeGqwdTTUFMTCBBIAGHqwdCIAGIqwdDIAGJqwdEIAGKqwdFIAGLqwdGIAGMqwdHIAGNqwdIIAGOqwdJIAGPqwdKIAGQqwdLIAGRqwdMIAGSqwdNIAGTqwdOIAGUqwdPIAGVqwdQIAGWqwdRIAGXqwdSIAGYqwdTIAGZqwdUIAGaqwdVIAGbqwdWIAGcqwdXIAGdqwdYIAGeqwdZIAGfqwdaEhDoqAdJVEFMSUMgQ0FQSVRBTCBBIgScrgdMUEhBIQHpqAdCIgOdrgdFVEEhAeqoB0MiArKuB0hJIQHrqAdEIgSfrgdFTFRBIQHsqAdFIgagrgdQU0lMT04iAqKuB1RBIQHtqAdGIQHuqAdHIgSergdBTU1BIQHvqAdIIQHwqAdJIgOkrgdPVEEhAfGoB0ohAfKoB0siBKWuB0FQUEEhAfOoB0wiBKauB0FNREEhAfSoB00iAaeuB1UhAfWoB04iAaiuB1UhAfaoB08iBLSuB01FR0EjBaquB0lDUk9OIQH3qAdQIgKxrgdISSIBq64HSSICs64HU0khAfioB1EhAfmoB1IiAqyuB0hPIQH6qAdTIgSurgdJR01BIQH7qAdUIgKvrgdBVSIEo64HSEVUQSYHra4HIFNZTUJPTCEB/KgHVSIGsK4HUFNJTE9OIQH9qAdWIQH+qAdXIQH/qAdYIgGprgdJIQGAqQdZIQGBqQdaIgOhrgdFVEEZDtCuB0VQU0lMT04gU1lNQk9MGQzSrgdLQVBQQSBTWU1CT0wZBbWuB05BQkxBGRTPrgdQQVJUSUFMIERJRkZFUkVOVElBTBoJ064HSEkgU1lNQk9MGgjVrgdJIFNZTUJPTBkK1K4HUkhPIFNZTUJPTBkHgqkHU01BTEwgQSAEtq4HTFBIQR8Bg6kHQiADt64HRVRBHwGEqQdDIALMrgdISR8BhakHRCAEua4HRUxUQR8BhqkHRSAGuq4HUFNJTE9OIAK8rgdUQR8Bh6kHRiAKx64HSU5BTCBTSUdNQR8BiKkHRyAEuK4HQU1NQR8BiakHSB8BiqkHSSADvq4HT1RBHwGLqQdKHwGMqQdLIAS/rgdBUFBBHwGNqQdMIATArgdBTURBHwGOqQdNIAHBrgdVHwGPqQdOIAHCrgdVHwGQqQdPIATOrgdNRUdBIQXErgdJQ1JPTh8BkakHUCACy64HSEkgAcWuB0kgAs2uB1NJHwGSqQdRHwGTqQdSIALGrgdITx8BlKkHUyAEyK4HSUdNQR8BlakHVCACya4HQVUgBL2uB0hFVEEfAZapB1UgBsquB1BTSUxPTh8Bl6kHVh8BmKkHVx8BmakHWCABw64HSR8BmqkHWR8Bm6kHWiADu64HRVRBGQzRrgdUSEVUQSBTWU1CT0wSDN6tB0tBUFBBIFNZTUJPTBIFwa0HTkFCTEESFNutB1BBUlRJQUwgRElGRkVSRU5USUFMEwnfrQdISSBTWU1CT0wTCOGtB0kgU1lNQk9MEgrgrQdSSE8gU1lNQk9MEhDQqQdTQ1JJUFQgQ0FQSVRBTCBBIQHRqQdCIQHSqQdDIQHTqQdEIQHUqQdFIQHVqQdGIQHWqQdHIQHXqQdIIQHYqQdJIQHZqQdKIQHaqQdLIQHbqQdMIQHcqQdNIQHdqQdOIQHeqQdPIQHfqQdQIQHgqQdRIQHhqQdSIQHiqQdTIQHjqQdUIQHkqQdVIQHlqQdWIQHmqQdXIQHnqQdYIQHoqQdZIQHpqQdaGQfqqQdTTUFMTCBBHwHrqQdCHwHsqQdDHwHtqQdEHwHuqQdFHwHvqQdGHwHwqQdHHwHxqQdIHwHyqQdJHwHzqQdKHwH0qQdLHwH1qQdMHwH2qQdNHwH3qQdOHwH4qQdPHwH5qQdQHwH6qQdRHwH7qQdSHwH8qQdTHwH9qQdUHwH+qQdVHwH/qQdWHwGAqgdXHwGBqgdYHwGCqgdZHwGDqgdaEwaaqAdNQUxMIEEZBMKtB0xQSEEYAZuoB0IZA8OtB0VUQRgBnKgHQxkC2K0HSEkYAZ2oB0QZBMWtB0VMVEEZBsuvB0lHQU1NQRgBnqgHRRkGxq0HUFNJTE9OGQLIrQdUQRgBn6gHRhkK060HSU5BTCBTSUdNQRgBoKgHRxkExK0HQU1NQRgBoagHSBgBoqgHSRkDyq0HT1RBGAGjqAdKGAGkqAdLGQTLrQdBUFBBGAGlqAdMGQTMrQdBTURBGAGmqAdNGQHNrQdVGAGnqAdOGQHOrQdVGAGoqAdPGQTarQdNRUdBGgXQrQdJQ1JPThgBqagHUBkC160HSEkZAdGtB0kZAtmtB1NJGAGqqAdRGAGrqAdSGQLSrQdITxgBrKgHUxkE1K0HSUdNQRgBragHVBkC1a0HQVUZBMmtB0hFVEEYAa6oB1UZBtatB1BTSUxPThgBr6gHVhgBsKgHVxgBsagHWBkBz60HSRgBsqgHWRgBs6gHWhkDx60HRVRBEgzdrQdUSEVUQSBTWU1CT0wNF7iqB0RPVUJMRS1TVFJVQ0sgQ0FQSVRBTCBBIwG5qgdCIwG7qgdEIwG8qgdFIwG9qgdGIwG+qgdHIwHAqgdJIwHBqgdKIwHCqgdLIwHDqgdMIwHEqgdNIwHGqgdPIwHKqgdTIwHLqgdUIwHMqgdVIwHNqgdWIwHOqgdXIwHPqgdYIwHQqgdZGwvgrwdESUdJVCBFSUdIVCEE3a8HRklWRSID3K8HT1VSIQThrwdOSU5FIQPZrwdPTkUhBd+vB1NFVkVOIgLerwdJWCEF268HVEhSRUUiAtqvB1dPIQTYrwdaRVJPGwfSqgdTTUFMTCBBIQHTqgdCIQHUqgdDIQHVqgdEIQHWqgdFIQHXqgdGIQHYqgdHIQHZqgdIIQHaqgdJIQHbqgdKIQHcqgdLIQHdqgdMIQHeqgdNIQHfqgdOIQHgqgdPIQHhqgdQIQHiqgdRIQHjqgdSIQHkqgdTIQHlqgdUIQHmqgdVIQHnqgdWIQHoqgdXIQHpqgdYIQHqqgdZIQHrqgdaDRDNT0ZBTExJTkcgRElBR09OQUwOEISqB1JBS1RVUiBDQVBJVEFMIEEdAYWqB0IdAYeqB0QdAYiqB0UdAYmqB0YdAYqqB0cdAY2qB0odAY6qB0sdAY+qB0wdAZCqB00dAZGqB04dAZKqB08dAZOqB1AdAZSqB1EdAZaqB1MdAZeqB1QdAZiqB1UdAZmqB1YdAZqqB1cdAZuqB1gdAZyqB1kVB56qB1NNQUxMIEEbAZ+qB0IbAaCqB0MbAaGqB0QbAaKqB0UbAaOqB0YbAaSqB0cbAaWqB0gbAaaqB0kbAaeqB0obAaiqB0sbAamqB0wbAaqqB00bAauqB04bAayqB08bAa2qB1AbAa6qB1EbAa+qB1IbAbCqB1MbAbGqB1QbAbKqB1UbAbOqB1YbAbSqB1cbAbWqB1gbAbaqB1kbAbeqB1oNELSoB0lUQUxJQyBDQVBJVEFMIEEdBOKtB0xQSEEcAbWoB0IdA+OtB0VUQRwBtqgHQx0C+K0HSEkcAbeoB0QdBOWtB0VMVEEcAbioB0UdBuatB1BTSUxPTh0C6K0HVEEcAbmoB0YcAbqoB0cdBOStB0FNTUEcAbuoB0gcAbyoB0kdA+qtB09UQRwBvagHShwBvqgHSx0E660HQVBQQRwBv6gHTB0E7K0HQU1EQRwBwKgHTR0B7a0HVRwBwagHTh0B7q0HVRwBwqgHTx0E+q0HTUVHQR4F8K0HSUNST04cAcOoB1AdAvetB0hJHQHxrQdJHQL5rQdTSRwBxKgHURwBxagHUh0C8q0HSE8cAcaoB1MdBPStB0lHTUEcAceoB1QdAvWtB0FVHQTprQdIRVRBIQfzrQcgU1lNQk9MHAHIqAdVHQb2rQdQU0lMT04cAcmoB1YcAcqoB1ccAcuoB1gdAe+tB0kcAcyoB1kcAc2oB1odA+etB0VUQRQOlq4HRVBTSUxPTiBTWU1CT0wUDJiuB0tBUFBBIFNZTUJPTBQF+60HTkFCTEEUFJWuB1BBUlRJQUwgRElGRkVSRU5USUFMFQmZrgdISSBTWU1CT0wVCJuuB0kgU1lNQk9MFAqargdSSE8gU1lNQk9MFAfOqAdTTUFMTCBBGwT8rQdMUEhBGgHPqAdCGwP9rQdFVEEaAdCoB0MbApKuB0hJGgHRqAdEGwT/rQdFTFRBGwikrQdPVExFU1MgSSIBpa0HShoB0qgHRRsGgK4HUFNJTE9OGwKCrgdUQRoB06gHRhsKja4HSU5BTCBTSUdNQRoB1KgHRxsE/q0HQU1NQRoB1qgHSRsDhK4HT1RBGgHXqAdKGgHYqAdLGwSFrgdBUFBBGgHZqAdMGwSGrgdBTURBGgHaqAdNGwGHrgdVGgHbqAdOGwGIrgdVGgHcqAdPGwSUrgdNRUdBHAWKrgdJQ1JPThoB3agHUBsCka4HSEkbAYuuB0kbApOuB1NJGgHeqAdRGgHfqAdSGwKMrgdITxoB4KgHUxsEjq4HSUdNQRoB4agHVBsCj64HQVUbBIOuB0hFVEEaAeKoB1UbBpCuB1BTSUxPThoB46gHVhoB5KgHVxoB5agHWBsBia4HSRoB5qgHWRoB56gHWhsDga4HRVRBFAyXrgdUSEVUQSBTWU1CT0wNEuhPTEVGVCBBTkdMRSBCUkFDS0VUEhTqT0RPVUJMRSBBTkdMRSBCUkFDS0VUEhXuT0ZMQVRURU5FRCBQQVJFTlRIRVNJUxIU5k9XSElURSBTUVVBUkUgQlJBQ0tFVBgW7E9UT1JUT0lTRSBTSEVMTCBCUkFDS0VUDRPwrAdNT05PU1BBQ0UgQ0FQSVRBTCBBHwHxrAdCHwHyrAdDHwHzrAdEHwH0rAdFHwH1rAdGHwH2rAdHHwH3rAdIHwH4rAdJHwH5rAdKHwH6rAdLHwH7rAdMHwH8rAdNHwH9rAdOHwH+rAdPHwH/rAdQHwGArQdRHwGBrQdSHwGCrQdTHwGDrQdUHwGErQdVHwGFrQdWHwGGrQdXHwGHrQdYHwGIrQdZHwGJrQdaFwv+rwdESUdJVCBFSUdIVB0E+68HRklWRR4D+q8HT1VSHQT/rwdOSU5FHQP3rwdPTkUdBf2vB1NFVkVOHgL8rwdJWB0F+a8HVEhSRUUeAvivB1dPHQT2rwdaRVJPFweKrQdTTUFMTCBBHQGLrQdCHQGMrQdDHQGNrQdEHQGOrQdFHQGPrQdGHQGQrQdHHQGRrQdIHQGSrQdJHQGTrQdKHQGUrQdLHQGVrQdMHQGWrQdNHQGXrQdOHQGYrQdPHQGZrQdQHQGarQdRHQGbrQdSHQGcrQdTHQGdrQdUHQGerQdVHQGfrQdWHQGgrQdXHQGhrQdYHQGirQdZHQGjrQdaDRPpT1JJR0hUIEFOR0xFIEJSQUNLRVQTFOtPRE9VQkxFIEFOR0xFIEJSQUNLRVQTFe9PRkxBVFRFTkVEIFBBUkVOVEhFU0lTExTnT1dISVRFIFNRVUFSRSBCUkFDS0VUGRbtT1RPUlRPSVNFIFNIRUxMIEJSQUNLRVQPDctPU0lORyBESUFHT05BTA0Z1KsHU0FOUy1TRVJJRiBCT0xEIENBUElUQUwgQSYE1q4HTFBIQSUB1asHQiYD164HRVRBJQHWqwdDJgLsrgdISSUB16sHRCYE2a4HRUxUQSUB2KsHRSYG2q4HUFNJTE9OJgLcrgdU",
+ "QSUB2asHRiUB2qsHRyYE2K4HQU1NQSUB26sHSCUB3KsHSSYD3q4HT1RBJQHdqwdKJQHeqwdLJgTfrgdBUFBBJQHfqwdMJgTgrgdBTURBJQHgqwdNJgHhrgdVJQHhqwdOJgHirgdVJQHiqwdPJgTurgdNRUdBJwXkrgdJQ1JPTiUB46sHUCYC664HSEkmAeWuB0kmAu2uB1NJJQHkqwdRJQHlqwdSJgLmrgdITyUB5qsHUyYE6K4HSUdNQSUB56sHVCYC6a4HQVUmBN2uB0hFVEEqB+euByBTWU1CT0wlAeirB1UmBuquB1BTSUxPTiUB6asHViUB6qsHVyUB66sHWCYB464HSSUB7KsHWSUB7asHWiYD264HRVRBHQv0rwdESUdJVCBFSUdIVCME8a8HRklWRSQD8K8HT1VSIwT1rwdOSU5FIwPtrwdPTkUjBfOvB1NFVkVOJALyrwdJWCMF768HVEhSRUUkAu6vB1dPIwTsrwdaRVJPHQ6KrwdFUFNJTE9OIFNZTUJPTB0QvKwHSVRBTElDIENBUElUQUwgQS0EkK8HTFBIQSwBvawHQi0Dka8HRVRBLAG+rAdDLQKmrwdISSwBv6wHRC0Ek68HRUxUQSwBwKwHRS0GlK8HUFNJTE9OLQKWrwdUQSwBwawHRiwBwqwHRy0Ekq8HQU1NQSwBw6wHSCwBxKwHSS0DmK8HT1RBLAHFrAdKLAHGrAdLLQSZrwdBUFBBLAHHrAdMLQSarwdBTURBLAHIrAdNLQGbrwdVLAHJrAdOLQGcrwdVLAHKrAdPLQSorwdNRUdBLgWerwdJQ1JPTiwBy6wHUC0Cpa8HSEktAZ+vB0ktAqevB1NJLAHMrAdRLAHNrAdSLQKgrwdITywBzqwHUy0Eoq8HSUdNQSwBz6wHVC0Co68HQVUtBJevB0hFVEExB6GvByBTWU1CT0wsAdCsB1UtBqSvB1BTSUxPTiwB0awHViwB0qwHVywB06wHWC0Bna8HSSwB1KwHWSwB1awHWi0Dla8HRVRBJA7ErwdFUFNJTE9OIFNZTUJPTCQMxq8HS0FQUEEgU1lNQk9MJAWprwdOQUJMQSQUw68HUEFSVElBTCBESUZGRVJFTlRJQUwlCcevB0hJIFNZTUJPTCUIya8HSSBTWU1CT0wkCsivB1JITyBTWU1CT0wkB9asB1NNQUxMIEErBKqvB0xQSEEqAdesB0IrA6uvB0VUQSoB2KwHQysCwK8HSEkqAdmsB0QrBK2vB0VMVEEqAdqsB0UrBq6vB1BTSUxPTisCsK8HVEEqAdusB0YrCruvB0lOQUwgU0lHTUEqAdysB0crBKyvB0FNTUEqAd2sB0gqAd6sB0krA7KvB09UQSoB36wHSioB4KwHSysEs68HQVBQQSoB4awHTCsEtK8HQU1EQSoB4qwHTSsBta8HVSoB46wHTisBtq8HVSoB5KwHTysEwq8HTUVHQSwFuK8HSUNST04qAeWsB1ArAr+vB0hJKwG5rwdJKwLBrwdTSSoB5qwHUSoB56wHUisCuq8HSE8qAeisB1MrBLyvB0lHTUEqAemsB1QrAr2vB0FVKwSxrwdIRVRBKgHqrAdVKwa+rwdQU0lMT04qAeusB1YqAeysB1cqAe2sB1grAbevB0kqAe6sB1kqAe+sB1orA6+vB0VUQSQMxa8HVEhFVEEgU1lNQk9MHQyMrwdLQVBQQSBTWU1CT0wdBe+uB05BQkxBHRSJrwdQQVJUSUFMIERJRkZFUkVOVElBTB4Jja8HSEkgU1lNQk9MHgiPrwdJIFNZTUJPTB0Kjq8HUkhPIFNZTUJPTB0H7qsHU01BTEwgQSQE8K4HTFBIQSMB76sHQiQD8a4HRVRBIwHwqwdDJAKGrwdISSMB8asHRCQE864HRUxUQSMB8qsHRSQG9K4HUFNJTE9OJAL2rgdUQSMB86sHRiQKga8HSU5BTCBTSUdNQSMB9KsHRyQE8q4HQU1NQSMB9asHSCMB9qsHSSQD+K4HT1RBIwH3qwdKIwH4qwdLJAT5rgdBUFBBIwH5qwdMJAT6rgdBTURBIwH6qwdNJAH7rgdVIwH7qwdOJAH8rgdVIwH8qwdPJASIrwdNRUdBJQX+rgdJQ1JPTiMB/asHUCQCha8HSEkkAf+uB0kkAoevB1NJIwH+qwdRIwH/qwdSJAKArwdITyMBgKwHUyQEgq8HSUdNQSMBgawHVCQCg68HQVUkBPeuB0hFVEEjAYKsB1UkBoSvB1BTSUxPTiMBg6wHViMBhKwHVyMBhawHWCQB/a4HSSMBhqwHWSMBh6wHWiQD9a4HRVRBHQyLrwdUSEVUQSBTWU1CT0wYCaCrB0NBUElUQUwgQSABoasHQiABoqsHQyABo6sHRCABpKsHRSABpasHRiABpqsHRyABp6sHSCABqKsHSSABqasHSiABqqsHSyABq6sHTCABrKsHTSABrasHTiABrqsHTyABr6sHUCABsKsHUSABsasHUiABsqsHUyABs6sHVCABtKsHVSABtasHViABtqsHVyABt6sHWCABuKsHWSABuasHWhgL6q8HRElHSVQgRUlHSFQeBOevB0ZJVkUfA+avB09VUh4E668HTklORR4D468HT05FHgXprwdTRVZFTh8C6K8HSVgeBeWvB1RIUkVFHwLkrwdXTx4E4q8HWkVSTxgQiKwHSVRBTElDIENBUElUQUwgQScBiawHQicBiqwHQycBi6wHRCcBjKwHRScBjawHRicBjqwHRycBj6wHSCcBkKwHSScBkawHSicBkqwHSycBk6wHTCcBlKwHTScBlawHTicBlqwHTycBl6wHUCcBmKwHUScBmawHUicBmqwHUycBm6wHVCcBnKwHVScBnawHVicBnqwHVycBn6wHWCcBoKwHWScBoawHWh8HoqwHU01BTEwgQSUBo6wHQiUBpKwHQyUBpawHRCUBpqwHRSUBp6wHRiUBqKwHRyUBqawHSCUBqqwHSSUBq6wHSiUBrKwHSyUBrawHTCUBrqwHTSUBr6wHTiUBsKwHTyUBsawHUCUBsqwHUSUBs6wHUiUBtKwHUyUBtawHVCUBtqwHVSUBt6wHViUBuKwHVyUBuawHWCUBuqwHWSUBu6wHWhgHuqsHU01BTEwgQR4Bu6sHQh4BvKsHQx4BvasHRB4BvqsHRR4Bv6sHRh4BwKsHRx4BwasHSB4BwqsHSR4Bw6sHSh4BxKsHSx4BxasHTB4BxqsHTR4Bx6sHTh4ByKsHTx4ByasHUB4ByqsHUR4By6sHUh4BzKsHUx4BzasHVB4BzqsHVR4Bz6sHVh4B0KsHVx4B0asHWB4B0qsHWR4B06sHWg4PnKkHQ1JJUFQgQ0FQSVRBTCBBHAGeqQdDHAGfqQdEHAGiqQdHHAGlqQdKHAGmqQdLHAGpqQdOHAGqqQdPHAGrqQdQHAGsqQdRHAGuqQdTHAGvqQdUHAGwqQdVHAGxqQdWHAGyqQdXHAGzqQdYHAG0qQdZHAG1qQdaFAe2qQdTTUFMTCBBGgG3qQdCGgG4qQdDGgG5qQdEGgG7qQdGGgG9qQdIGgG+qQdJGgG/qQdKGgHAqQdLGgHBqQdMGgHCqQdNGgHDqQdOGgHFqQdQGgHGqQdRGgHHqQdSGgHIqQdTGgHJqQdUGgHKqQdVGgHLqQdWGgHMqQdXGgHNqQdYGgHOqQdZGgHPqQdaAgbW6wdYSU1JWkUCEeilB1lBTiBOVU1FUkFMIEVJR0hUEwPypQdFRU4PBeulB0xFVkVODgfvpQdGSUZURUVOEALlpQdWRQ8D5KUHT1VSEgTupQdURUVODgTppQdOSU5FEgTzpQdURUVODgPhpQdPTkUOBeelB1NFVkVOEwTxpQdURUVODwLmpQdJWBEE8KUHVEVFTg4D6qUHVEVODwftpQdISVJURUVOEAPjpQdSRUUPBeylB1dFTFZFEAHipQdPDgTgpQdaRVJPAQ2hREVBU1VSRUQgQU5HTEUODZtTIE9QRU5JTkcgTEVGVA80q1NXSVRIIE9QRU4gQVJNIEVORElORyBJTiBBUlJPVyBQT0lOVElORyBET1dOIEFORCBMRUZUPwWqU1JJR0hUNg2vU0xFRlQgQU5EIERPV04/Aq1TVVA2Dq5TUklHSFQgQU5EIERPV05AAqxTVVA2C6lTVVAgQU5EIExFRlQ9BahTUklHSFQJAt5EQlkJFJ1TUklHSFQgQU5HTEUgV0lUSCBET1QDCdbmB1QgT04gQk9ORQIMvvMHQ0hBTklDQUwgQVJNCwO/8wdMRUcCGs3cBURFRkFJRFJJTiBDQVBJVEFMIExFVFRFUiBBHAHe3AVJHAPE3AVUSVUbAdDcBUIbAdHcBUMbAdncBUQbAc/cBUUbAcrcBUYbAcncBUcbAdbcBUgcAdbcBVAbAcvcBUkbAc7cBUobAczcBUscAcbcBVAbAdTcBUwbAcDcBU0bAdvcBU4cAdfcBUccAdfcBVkbAd3cBU8cAdrcBUUbAcfcBVAbAdXcBVEbAdzcBVIbAcHcBVMbAcjcBVQbAdLcBVUbAcLcBVYbAcPcBVcbAdjcBVgbAd/cBVkcAdPcBVUbAcXcBVoNBJfdBU9NTUEMC4jdBURJR0lUIEVJR0hUEgSF3QVGSVZFEwOE3QVPVVISBIndBU5JTkUSA4HdBU9ORRUPlN0FIEFMVEVSTkFURSBGT1JNEgWH3QVTRVZFThMCht0FSVgSBYPdBVRIUkVFFw+W3QUgQUxURVJOQVRFIEZPUk0TAoLdBVdPFQ+V3QUgQUxURVJOQVRFIEZPUk0SBIDdBVpFUk8MDprdBUVYQ0xBTUFUSU9OIE9IDAmY3QVGVUxMIFNUT1AMD5LdBU5VTUJFUiBFSUdIVEVFThQFi90FTEVWRU4TB4/dBUZJRlRFRU4UB47dBU9VUlRFRU4TCJPdBU5JTkVURUVOEwmR3QVTRVZFTlRFRU4UBpDdBUlYVEVFThMDit0FVEVOFAeN3QVISVJURUVOFAWM3QVXRUxWRQwO7dwFU01BTEwgTEVUVEVSIEEaAf7cBUkaA+TcBVRJVRkB8NwFQhkB8dwFQxkB+dwFRBkB79wFRRkB6twFRhkB6dwFRxkB9twFSBoB9twFUBkB69wFSRkB7twFShkB7NwFSxoB5twFUBkB9NwFTBkB4NwFTRkB+9wFThoB99wFRxoB99wFWRkB/dwFTxoB+twFRRkB59wFUBkB9dwFURkB/NwFUhkB4dwFUxkB6NwFVBkB8twFVRkB4twFVhkB49wFVxkB+NwFWBkB/9wFWRoB89wFVRkB5dwFWg0Kmd0FWU1CT0wgQUlWQQMLzFxJRVZBTCBDT01NQQkQ01xFWENMQU1BVElPTiBNQVJLCQ3UXFFVRVNUSU9OIE1BUksED6tNVU0gQkxBQ0sgQ0lSQ0xFCBCF7wdPTEQgV0hJVEUgQ0lSQ0xFBxjO7wdFSUdIVCBQT0lOVEVEIEJMQUNLIFNUQVIND7zvB1NQT0tFRCBBU1RFUklTSwcUsO8HRklWRSBTUE9LRUQgQVNURVJJU0sIIupOTEFUVEVORUQgTEVGVCBQQVJFTlRIRVNJUyBPUk5BTUVOVBEa605SSUdIVCBQQVJFTlRIRVNJUyBPUk5BTUVOVAgWxe8HT1VSIFBPSU5URUQgQkxBQ0sgU1RBUhQNx+8HUElOV0hFRUwgU1RBUgcLo+8HR1JFRUsgQ1JPU1MHG/ROTEVGVCBDVVJMWSBCUkFDS0VUIE9STkFNRU5UDBToTlBBUkVOVEhFU0lTIE9STkFNRU5UCyDsTi1QT0lOVElORyBBTkdMRSBCUkFDS0VUIE9STkFNRU5UBxLfQE1BVEhFTUFUSUNBTCBTUEFDRQcc9U5SSUdIVCBDVVJMWSBCUkFDS0VUIE9STkFNRU5UDRTpTlBBUkVOVEhFU0lTIE9STkFNRU5UDCDtTi1QT0lOVElORyBBTkdMRSBCUkFDS0VUIE9STkFNRU5UBweq7wdT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SUNLEQ3CBUVGVCBBUlJPV0hFQUQVCb8FSEFMRiBSSU5HFQTq1gJUQUNLEQ/PBU9XIEFDVVRFIEFDQ0VOVBQRiM8CQ0lSQ1VNRkxFWCBBQ0NFTlQUGZDOAkRPVFRFRCBMRUZULVNURU0gVE9ORSBCQVIbCIvOAlRPTkUgQkFSFgzvBVdOIEFSUk9XSEVBRBQMzgVHUkFWRSBBQ0NFTlQUGZ/OAklOVkVSVEVEIEVYQ0xBTUFUSU9OIE1BUksUCv8FTEVGVCBBUlJPVx4E8QVIRUFEGA6VzgItU1RFTSBUT05FIEJBUhQGzQVNQUNST04UD/IFUklHSFQgQVJST1dIRUFEFgLzBU5HFAX3BVRJTERFFQfoBU9ORSBCQVIUDPAFVVAgQVJST1dIRUFEFA3MBVZFUlRJQ0FMIExJTkUTFZrOAkVSIFJJR0hUIENPUk5FUiBBTkdMRRAGyQVNQUNST04RHI/OAklEIERPVFRFRCBMRUZULVNURU0gVE9ORSBCQVIbCIrOAlRPTkUgQkFSFBKUzgJMRUZULVNURU0gVE9ORSBCQVIUCOcFVE9ORSBCQVITF/YFRExFIERPVUJMRSBBQ1VURSBBQ0NFTlQeDPUFR1JBVkUgQUNDRU5UFwz0BUdSQVZFIEFDQ0VOVBII1wVOVVMgU0lHThAK/gVPUEVOIFNIRUxGEAnWBVBMVVMgU0lHThEEuQVSSU1FEAz4BVJBSVNFRCBDT0xPThcKnM4CRE9XTiBBUlJPVxcQnc4CRVhDTEFNQVRJT04gTUFSSxcZns4CSU5WRVJURUQgRVhDTEFNQVRJT04gTUFSSxcIm84CVVAgQVJST1cRIrmPBEVUUk9GTEVYIENMSUNLIFdJVEggUkVUUk9GTEVYIEhPT0sSDL0FVkVSU0VEIENPTU1BGQzBBUdMT1RUQUwgU1RPUCUMtI8EIFdJVEggU1RST0tFEQreBUhPVElDIEhPT0sRDsMFSUdIVCBBUlJPV0hFQUQWCb4FSEFMRiBSSU5HFgTr1gJUQUNLEAX9BVNIRUxGEg+KzwJPUlQgRVFVQUxTIFNJR04RBsM6TUFMTCBBFwGDjwRFFwLcOklOFwTFOkxQSEEWAcc6QhcKhY8EIFdJVEggSE9PSxcHsTtBUlJFRCBPFwPdOkVUQRcM1TpPVFRPTSBIQUxGIE8WAZw7QxcKnTsgV0lUSCBDVVJMFwmAjwRBUElUQUwgQUEeAYSPBEIeAZKPBEcfCpSPBCBXSVRIIEhPT0seAZaPBEgeAaY7SR8MpzsgV0lUSCBTVFJPS0UfCbYFTlZFUlRFRCBSHgGrO0wfCpyPBCBXSVRIIEJFTFQeAbA7Th4Co48ET0UeAaqPBFIeAbg7VR4Bso8EWRcC4TpISRcLpI8ETE9TRUQgT01FR0EdD4+PBFJFVkVSU0VEIE9QRU4gRRYByDpEFwqMjwQgV0lUSCBIT09LIQmNjwQgQU5EIFRBSUwdBIuPBFRBSUwXBN86RUxUQRgKio8EWkggRElHUkFQSBcUoTtPVExFU1MgSiBXSVRIIFNUUk9LRSsJmI8EIEFORCBIT09LFwmHjwRaIERJR1JBUEggComPBCBXSVRIIENVUkwmDoiPBFJFVFJPRkxFWCBIT09LFgHJOkUXAtE6TkcXArQ7U0gXAp47VEgXAr47WkgWAaA7RhcLkI8ERU5HIERJR1JBUEgWAc06RxcKk48EIFdJVEggSE9PSxcE4AVBTU1BFwreOlJFRUsgR0FNTUEcA+A6UEhJFgGwBUgXCrEFIFdJVEggSE9PSx0GlY8EU1RST0tFFwPc1gJFTkcaCpePBCBXSVRIIEhPT0sWDaQ7SSBXSVRIIFNUUk9LRRcDpTtPVEEWAbIFShcSqDsgV0lUSCBDUk9TU0VELVRBSUwWAc86SxYB4QVMFwqbjwQgV0lUSCBCRUxUHQ/d1gJJTlZFUlRFRCBMQVpZIFMdDN7WAk1JRERMRSBUSUxERR0MqjtQQUxBVEFMIEhPT0sdDqk7UkVUUk9GTEVYIEhPT0srCZ2PBCBBTkQgQkVMVBcDno8ERVpIGhSfjwQgV0lUSCBSRVRST0ZMRVggSE9PSxcK+c8CSUdBVFVSRSBPRRcJmY8EUyBESUdSQVBIFwmajwRaIERJR1JBUEgWAdA6TRcKrDsgV0lUSCBIT09LFhCuO04gV0lUSCBMRUZUIEhPT0sdDq87UkVUUk9GTEVYIEhPT0sWAdI6TxcMoo8EIFdJVEggU1RST0tFFwXLOlBFTiBFGwHTOk8WAdY6UBcCsjtISRYBpY8EURYBswVSFw6pjwQgV0lUSCBGSVNISE9PSx0EqI8EVEFJTBcIkY8EQU1TIEhPUk4XCY6PBEVWRVJTRUQgRR8M5AVHTE9UVEFMIFNUT1AfBp87T1BFTiBFFgHiBVMXCrqPBCBXSVRIIENVUkwdBLM7SE9PSxcEyjpDSFdBGAaiO1JJUFQgRxcJ2TpJREVXQVlTIFUWAdc6VBcStTsgV0lUSCBQQUxBVEFMIEhPT0sdDq+PBFJFVFJPRkxFWCBIT09LFxOrjwRDIERJR1JBUEggV0lUSCBDVVJMFwuujwRFU0ggRElHUkFQSBcEvztIRVRBFwnUOk9QIEhBTEYgTxcJrI8EUyBESUdSQVBIIBStjwQgV0lUSCBSRVRST0ZMRVggSE9PSxcHxDpVUk5FRCBBHgHGOkUeBJs7TFBIQR0BoztIHQHOOkkdAdo6TR4OrTsgV0lUSCBMT05HIExFRx0GzDpPUEVOIEUdAbQFUh4KtQUgV0lUSCBIT09LJAimjwRMT05HIExFRywTp48EIEFORCBSRVRST0ZMRVggSE9PSx0BujtWHQHp1gJXHQGgjwRZHgqhjwQgV0lUSCBCRUxUFgHYOlUXBLY7IEJBUhgO39YCV0lUSCBMRUZUIEhPT0sXBrc7UFNJTE9OFgHbOlYXCrk7IFdJVEggSE9PSx0KsI8EUklHSFQgSE9PSxYBtwVXFgHjBVgWAbgFWRYBuztaFwq9OyBXSVRIIENVUkwdDrw7UkVUUk9GTEVYIEhPT0sRE6DOAlRSRVNTIEFORCBISUdIIFRPTkUbCKHOAkxPVyBUT05FESCCjwRVUEVSU0NSSVBUIEhBTEYgVFJJQU5HVUxBUiBDT0xPThwQgY8EVFJJQU5HVUxBUiBDT0xPThAQ0AVUUklBTkdVTEFSIENPTE9OEQu7BVVSTkVEIENPTU1BEAvtBVVOQVNQSVJBVEVEEQvEBVAgQVJST1dIRUFEEwTUBVRBQ0sRAfDOAlMQDcgFVkVSVElDQUwgTElORREG7AVPSUNJTkcQGOsFWUFORyBERVBBUlRJTkcgVE9ORSBNQVJLERbqBUlOIERFUEFSVElORyBUT05FIE1BUksDC4pUVUxPIFRXTyBTVU0CB7DpB05FWSBCQUcGCrjpB1dJVEggV0lOR1MFC5HyBy1NT1VUSCBGQUNFAwyAMEdPTElBTiBCSVJHQQ8V5KwEIFdJVEggRE9VQkxFIE9STkFNRU5UFQjgrARPUk5BTUVOVAoFhDBDT0xPTgwDgjBNTUEKC5gwRElHSVQg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IE5PVEUQBOqjB09ST04PEIaiB0xFRlQgUkVQRUFUIFNJR04QBLejB09OR0EUEMKjByBJTVBFUkZFQ1RBIFJFU1QVDcGjB1BFUkZFQ1RBIFJFU1QPBrajB01BWElNQRAEkKMHRVpaTxAFu6MHSU5JTUEVBryjByBCTEFDSxYExaMHUkVTVBAS06IHT09OIE5PVEVIRUFEIEJMQUNLHQXSogdXSElURRAJuqIHVUxUSSBSRVNUFBCpogdQTEUgTUVBU1VSRSBSRVNUDwyvogdOQVRVUkFMIERPV04XAq6iB1VQEAqnowdFQkVOU1RJTU1FEA3YogdPVEVIRUFEIEJMQUNLEAzZogdVTEwgTk9URUhFQUQPHuSiB09ORSBIVU5EUkVEIFRXRU5UWS1FSUdIVEggTk9URSkEwqIHUkVTVBILlqIHLUxJTkUgU1RBRkYQEJujB1JOQU1FTlQgU1RST0tFLTEgAaSjBzAgAaWjBzEfAZyjBzIfAZ2jBzMfAZ6jBzQfAZ+jBzUfAaCjBzYfAaGjBzcfAaKjBzgfAaOjBzkQCraiB1RUQVZBIEFMVEEWBbeiB0JBU1NBDxTWogdQQVJFTlRIRVNJUyBOT1RFSEVBRBAJrqMHRURBTCBNQVJLFQevowdVUCBNQVJLEQzdowdTIFNVQlBVTkNUSVMQBI+jB0lBTk8QDMSiB0xVUyBOT1RFSEVBRBAG1KMHT0RBVFVTEQfZowdSUkVDVFVTGAfaowcgRkxFWFVTDwzfogdRVUFSVEVSIE5PVEUXBL2iB1JFU1QXCbOiB1RPTkUgRkxBVBwFsqIHU0hBUlARD7iiB0lORElDRVNJTUEgQUxUQRwFuaIHQkFTU0EPC4iiB1JFUEVBVCBET1RTFQuNogdFRCBGSUdVUkUtMR8BjqIHMh8Bj6IHMxETg6IHVkVSU0UgRklOQUwgQkFSTElORRAQh6IHSUdIVCBSRVBFQVQgU0lHThEJjKMHTkZPUlpBTkRPDwnWowdTQ0FORElDVVMYB9ujByBGTEVYVVMQBIuiB0VHTk8RDrqjB01JQlJFVklTIEJMQUNLGgTEowdSRVNUGgW5owdXSElURRMMvqMHTUlOSU1BIEJMQUNLGgTGowdSRVNUGgW9owdXSElURRAJsaIHSEFSUCBET1dOFQKwogdVUBELhaIHT1JUIEJBUkxJTkUQDYCiB0lOR0xFIEJBUkxJTkURDJuiB1gtTElORSBTVEFGRhMQnKIHU1RSSU5HIEZSRVRCT0FSRBIL4aIHVEVFTlRIIE5PVEUZBL+iB1JFU1QTDeOiB1ktRk9VUlRIIE5PVEUcBMGiB1JFU1QQA+mjB09SSRAH0qMHUVVBUkUgQhYOx6IHTk9URUhFQUQgQkxBQ0sfBcaiB1dISVRFEAWNowdVQklUTw8sy6MHVEVNUFVTIElNUEVSRkVDVFVNIENVTSBQUk9MQVRJT05FIElNUEVSRkVDVEE7DcyjByBESU1JTlVUSU9OLTFHAc2jBzJHAc6jBzMxCMqjB1BFUkZFQ1RBFiPIowdQRVJGRUNUVU0gQ1VNIFBST0xBVElPTkUgSU1QRVJGRUNUQS8Ix6MHUEVSRkVDVEE3DcmjByBESU1JTlVUSU9OLTEQEeKiB0hJUlRZLVNFQ09ORCBOT1RFHQTAogdSRVNUEQ6YogdSRUUtTElORSBTVEFGRhAH2KMHT1JDVUxVUxcK3KMHIFJFU1VQSU5VUxABlqMHUhEaz6IHSUFOR0xFIE5PVEVIRUFEIERPV04gQkxBQ0smBc6iB1dISVRFIQrLogdMRUZUIEJMQUNLJgXKogdXSElURSELzaIHUklHSFQgQkxBQ0snBcyiB1dISVRFIQjJogdVUCBCTEFDSyQL0aIHUklHSFQgQkxBQ0sqBdCiB1dISVRFJAXIogdXSElURRca1aIHLVJPVU5EIE5PVEVIRUFEIERPV04gQkxBQ0ssBdSiB1dISVRFEAOXowdVUk4TBpmjByBTTEFTSBQCmqMHVVAQDZeiB1dPLUxJTkUgU1RBRkYPBdOjB1ZJUkdBEAzXogdPSUQgTk9URUhFQUQPCt2iB1dIT0xFIE5PVEUVBLuiB1JFU1QQD7OjB0lUSCBGSU5HRVJOQUlMUw8Kw6IHWCBOT1RFSEVBRA8BjqMHWgECjjBWUwEBlQFXAR++IFlBTk1BUiBDT05TT05BTlQgU0lHTiBNRURJQUwgSEEeArwgUkEeAr0gV0EeArsgWUEYDOAgT04gTUVESUFMIExBIgLfIE1BIgLeIE5BFw6CIVNIQU4gTUVESUFMIFdBCAvIIERJR0lUIEVJR0hUDgTFIEZJVkUPA8QgT1VSDgTJIE5JTkUOA8EgT05FDgXHIFNFVkVODwLGIElY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Uk8JGeMgT05FIE1BUksgU0dBVyBLQVJFTiBIQVRISR0G5CBLRSBQSE8IDawgVk9XRUwgU0lHTiBBQRQBsiBJFQWcIVRPTiBBGgGdIUkTAbEgRRQGtSAgQUJPVkUTDPEgR0VCQSBLQVJFTiBJEwGtIEkUAa4gSRMI9CBLQVlBSCBFRRkC8iBPRRkB8yBVEwazIE1PTiBJSRcBtCBPEw3iIFNHQVcgS0FSRU4gRVUUBoMhSEFOIEFBGAGEIUUZBoUhIEFCT1ZFGAeGIUZJTkFMIFkTB6sgVEFMTCBBQRMBryBVFAGwIFUTCdggVk9DQUxJQyBMHAHZIEwbAdYgUhwB1yBSExTnIFdFU1RFUk4gUFdPIEtBUkVOIEVVJQLoIFVFABqAVE4tQVJZIENJUkNMRUQgRE9UIE9QRVJBVE9SDg2BVFBMVVMgT1BFUkFUT1IODoJUVElNRVMgT1BFUkFUT1IHCJBET1BST0RVQ1QGDMJFSU5URVJTRUNUSU9OBgvARUxPR0lDQUwgQU5EDgLBRU9SBgePRFBST0RVQ1QGHIVUU1FVQVJFIElOVEVSU0VDVElPTiBPUEVSQVRPUg0OhlRVTklPTiBPUEVSQVRPUgcIkURVTU1BVElPTgYOiVRUSU1FUyBPUEVSQVRPUgYFw0VVTklPTgsSg1QgT1BFUkFUT1IgV0lUSCBET1QaBIRUUExVUwYS/1VXSElURSBWRVJUSUNBTCBCQVIBHquRBEFCQVRBRUFOIENSVUNJRk9STSBOVU1CRVIgRk9VUgoMgZEETEVUVEVS",
+ "IEFMRVBIEgOXkQRZSU4RBIORBEJFVEgRBoWRBERBTEVUSBELgJEERklOQUwgQUxFUEgXBIKRBEJFVEgXAoaRBEhFFwSOkQRLQVBIFwaQkQRMQU1FREgXA5KRBE1FTRcDlJEETlVOFwSckQRTSElOFwSMkQRZT0RIEQWEkQRHSU1FTBECh5EESEUTAoqRBFRIEQSPkQRLQVBIEQaRkQRMQU1FREgRA5ORBE1FTREDlZEETlVOEQKYkQRQRREEmpEEUU9QSBEEm5EEUkVTSBEFmZEEU0FESEUTBJaRBE1FS0gSA52RBEhJThEDnpEEVEFXEgOLkQRFVEgRA4iRBFdBVxEEjZEEWU9ESBEFiZEEWkFZSU4KC6yRBE5VTUJFUiBGSVZFEgOqkQRPVVIRA6eRBE9ORRQIr5EEIEhVTkRSRUQRA62RBFRFThIEqZEESFJFRRIFrpEEV0VOVFkTAaiRBE8DAodETEECFfjJB0cgTVVOREFSSSBESUdJVCBFSUdIVBIE9ckHRklWRRMD9MkHT1VSEgT5yQdOSU5FEgPxyQdPTkUSBffJB1NFVkVOEwL2yQdJWBIF88kHVEhSRUUTAvLJB1dPEgTwyQdaRVJPDAjVyQdMRVRURVIgQRQB18kHQhQB2ckHSBQB1skHShQC2MkHTlkTAeTJB0UUAebJB0cUAurJB0xMFAHnyQdNFAHoyQdOFQHlyQdOFALpyQdUVBMB2skHSRQC3MkHREQUAd7JB0gUAdvJB1MUAd3JB1QTAdDJB08UAdLJB0wUAtTJB05HFAHRyQdQFAHTyQdZEwHfyQdVFAHgyQdDFAHhyQdEFAHiyQdLFAHjyQdSDAnuyQdTSUdOIElLSVIRBezJB01VSE9SEQTryQdPSk9EEQXvyQdTVVRVSBEF7ckHVE9ZT1ICCYXpB0lMIFBPTElTSAMHpkFSQSBTSUdOAgEVSwII2+kHTUUgQkFER0UCArxFTkQEEuOzBElOQUdBUkkgSEVBRFNUUk9LRQwIoLMETEVUVEVSIEEUAaGzBEEUAauzBEkUAa2zBFUTAsSzBEJBFALFswRIQRMCs7MEQ0EUArSzBEhBEwK/swREQRQCurMEREEVAruzBEhBFALAswRIQRMBqrMERRMCsLMER0EUArGzBEhBEwLOswRIQRMBorMESRQBo7MESRMCtbMESkEUArazBEhB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RVJFAwXUTUVOVFJZCAWr7QcgU0lHTgMN9ekHTU9CSUxFIFBIT05FUwMZnuoHT05FIFVOREVSIEVJR0hURUVOIFNZTUJPTAMLt+0HUEVERVNUUklBTlMEBfLqB0lSQUNZAw6t7QdTTU9LSU5HIFNZTUJPTAIMoAEtQlJFQUsgU1BBQ0UCEu9ATUlOQUwgRElHSVQgU0hBUEVTBAiOgwRTTUEgU0lHTgIRkUBOLUJSRUFLSU5HIEhZUEhFTgQUse0HUE9UQUJMRSBXQVRFUiBTWU1CT0wDB91VRk9SS0lORwIBvUVSAw27QURJQyBNQVJLIFNJR04DD7JFTUFMIFNVQkdST1VQIE9GEgy0RSBPUiBFUVVBTCBUTwMcolJUSCBFQVNUIEFORCBTT1VUSCBXRVNUIEFSUk9XDASXQ1JST1cQFahSIEFORCBTT1VUSCBFQVNUIEFSUk9XERmxUkNST1NTSU5HIE5PUlRIIFdFU1QgQVJST1caEK5SU09VVEggRUFTVCBBUlJPVxEIufEHRlJPTSBCQVIRCaRSV0lUSCBIT09LCwuIVkJMQUNLIEFSUk9XCwzXQ0RPVUJMRSBBUlJPVwsM4uwHUE9JTlRJTkcgQlVEFATS7AdMRUFGFAna7AdWSU5FIExFQUYLENXwB1NBTlMtU0VSSUYgQVJST1cLFedWVFJJQU5HTEUtSEVBREVEIEFSUk9XIAf3ViBUTyBCQVILC4BWV0hJVEUgQVJST1cGGbTQAklORElDIEZSQUNUSU9OIE9ORSBFSUdIVEgZBLHQAkhB",
+ "TEYZB7DQAlFVQVJURVIZCbPQAlNJWFRFRU5USBUOstACVEhSRUUgUVVBUlRFUlMbCrXQAlNJWFRFRU5USFMMELfQAlBMQUNFSE9MREVSIE1BUksMDbnQAlFVQU5USVRZIE1BUksPCbbQAlJURVIgTUFSSwwKuNACUlVQRUUgTUFSSwYZoVJXRVNUIEFORCBTT1VUSCBFQVNUIEFSUk9XDASWQ1JST1cQFadSIEFORCBOT1JUSCBFQVNUIEFSUk9XERmyUkNST1NTSU5HIE5PUlRIIEVBU1QgQVJST1cRCLjxB0ZST00gQkFSEQnxQ1RPIENPUk5FUhQIuENMT05HIEJBUhEJo1JXSVRIIEhPT0sLC4lWQkxBQ0sgQVJST1cLDNZDRE9VQkxFIEFSUk9XCwzg7AdQT0lOVElORyBCVUQUBNDsB0xFQUYUCdjsB1ZJTkUgTEVBRgsQ1PAHU0FOUy1TRVJJRiBBUlJPVwsV5lZUUklBTkdMRS1IRUFERUQgQVJST1cgB/ZWIFRPIEJBUgsLgVZXSElURSBBUlJPVwUW6u0HRUFTVC1QT0lOVElORyBBSVJQTEFORQICw+gHU0UEB/yZByBTWU1CT0wCDYRFVCBBIFNVQlNFVCBPRggJhUVQRVJTRVQgT0YFDslETE1PU1QgRVFVQUwgVE8FDIlETiBFTEVNRU5UIE9GBRbERFNZTVBUT1RJQ0FMTFkgRVFVQUwgVE8ECvtGQ0hFQ0sgTUFSSwQI4ERFUVVBTCBUTwcK7URJVkFMRU5UIFRPBAzvREdSRUFURVItVEhBTgQM4kRJREVOVElDQUwgVE8ECe5ETEVTUy1USEFOBBLqRU5PUk1BTCBTVUJHUk9VUCBPRhYM7EUgT1IgRVFVQUwgVE8EC6ZEUEFSQUxMRUwgVE8EBKwBU0lHTgUa4kVRVUFSRSBJTUFHRSBPRiBPUiBFUVVBTCBUTwsX40VPUklHSU5BTCBPRiBPUiBFUVVBTCBUTwQFwURUSUxERQUDrUVSVUUDJMPqB0NIRUQgTEVGVCBTRU1JQ0lSQ0xFIFdJVEggVEhSRUUgRE9UUwkqr09PV0VSIFJJR0hULVNIQURPV0VEIFdISVRFIFJJR0hUV0FSRFMgQVJST1cIIMTqB1JJR0hUIFNFTUlDSVJDTEUgV0lUSCBUSFJFRSBET1RTCCuxT1VQUEVSIFJJR0hULVNIQURPV0VEIFdISVRFIFJJR0hUV0FSRFMgQVJST1cDAcjrB0UEBMrrByBQQUQHAsnrB0dFBATT6QdCT09LCBbU6QcgV0lUSCBERUNPUkFUSVZFIENPVkVSAQ2QmAdQTiBUUkFOU0lTVE9SAQIAVUwDAQBMAheZSU1CRVIgRUlHSFRFRU4gRlVMTCBTVE9QCA+SSUxFVkVOIEZVTEwgU1RPUAcRlklGSUZURUVOIEZVTEwgU1RPUAgRlUlPVVJURUVOIEZVTEwgU1RPUAcSmklOSU5FVEVFTiBGVUxMIFNUT1AHE5hJU0VWRU5URUVOIEZVTEwgU1RPUAgDI0lHTgkPl0lYVEVFTiBGVUxMIFNUT1AHDZFJVEVOIEZVTEwgU1RPUAgRlElISVJURUVOIEZVTEwgU1RPUAgPk0lXRUxWRSBGVUxMIFNUT1AKDZtJTlRZIEZVTEwgU1RPUAMIlkJFUk8gU0lHTgIS4d8FU0hVIElURVJBVElPTiBNQVJLAgqp6gdUIEFORCBCT0xUASDPwgdZSUFLRU5HIFBVQUNIVUUgSE1PTkcgQ0lSQ0xFRCBDQRcLyMIHRElHSVQgRUlHSFQdBMXCB0ZJVkUeA8TCB09VUh0EycIHTklORR0DwcIHT05FHQXHwgdTRVZFTh4CxsIHSVgdBcPCB1RIUkVFHgLCwgdXTx0EwMIHWkVSTxcIpMIHTEVUVEVSIEEfAaXCB0EeAojCB0NBHgKPwgdEQR8CncIHTEEeAarCB0UfAavCB0UeApXCB0ZBHgKiwgdHQR4ChMIHSEEgAZ/CB0geAabCB0keAo7CB0tBHgKJwgdMQR4CgMIHTUEfAqDCB0xBHgKFwgdOQR8CjMIHQ0EfAofCB0tBHwKcwgdQQSACnsIHTEEfApnCB1FBHwKRwgdSQR8CgsIHVEEgAo3CB1NBIAKTwgdYQR8CkMIHWUEeAajCB08fAanCB08eAprCB1BBHwKhwgdMQR4Cl8IHUUEeApbCB1JBHwKjwgdSQR4CisIHU0EeAoPCB1RBHwKBwgdTQR8ClMIHWEEeAafCB1UeApLCB1ZBHgGswgdXHgKGwgdYQR8Cm8IHWUEeApjCB1lBHgKLwgdaQRgMzsIHT0dPR1JBTSBOWUFKFw+6wgdTSUdOIEZPUiBBTklNQUwgDLvCB0lOVkVSVEVCUkFURSAIucIHTE9DQVRJT04gBrfCB1BFUlNPTiAFuMIHVEhJTkccBbzCB1hXIFhXGBK9wgdZTExBQkxFIExFTkdUSEVORVIXBrDCB1RPTkUtQhwBtsIHRBwBtcIHRxwBssIHShwBscIHTRwBtMIHUxwBs8IHVgAc/P8DT0JKRUNUIFJFUExBQ0VNRU5UIENIQVJBQ1RFUgIYp1NMSVFVRSBBTkdMRSBPUEVOSU5HIERPV04WAqZTVVAIBt1cSFlQSEVOAhH/R1NFUlZFUiBFWUUgU1lNQk9MAQr17gdDQ1VMVEFUSU9OAhHHSFIgQU1PVU5UIE9GIENIRUNLBAvESEJFTFQgQlVDS0xFBQbFSE9XIFRJRQUZxkhSQU5DSCBCQU5LIElERU5USUZJQ0FUSU9OBAXBSENIQUlSBRbJSFVTVE9NRVIgQUNDT1VOVCBOVU1CRVIEBMhIREFTSAUPykhPVUJMRSBCQUNLU0xBU0gEBMJIRk9SSwQEwEhIT09LBA3DSElOVkVSVEVEIEZPUksCDNHtB1RBR09OQUwgU0lHTgMEmegHT1BVUwED4uYHREVOAQ7i5wdGRklDRSBCVUlMRElORw8Hu50HIFNZTUJPTAERmy1HSEFNIEZFQVRIRVIgTUFSSwYLkC1MRVRURVIgQUlMTQ0FgS1CRUlUSA0Fii1DRUlSVA4DiS1PTEwNBIctREFJUg0HlS1FQUJIQURIDwWTLURIQURIDwmZLU1IQU5DSE9MTA0Fgy1GRUFSTg0EjC1HT1JUDQSYLUlGSU4OBpQtT0RIQURIDQSCLUxVSVMNBIstTVVJTg0HjS1OR0VBREFMDgOFLUlPTg0DkS1PTk4OAZYtUg0Fmi1QRUlUSA0Ejy1SVUlTDQSELVNBSUwOBY4tVFJBSUYNBYgtVElOTkUNBIYtVUFUSA4Hly1JTExFQU5ODgGSLVIGFZwtUkVWRVJTRUQgRkVBVEhFUiBNQVJLBgqALVNQQUNFIE1BUksCBNsFT05FSwEHpkJITSBTSUdOAQfi7QdJTCBEUlVNAQvM6AdLIEhBTkQgU0lHTgEM/ThMIENISUtJIEFIQUQJC9g4RElHSVQgRUlHSFQPBNU4RklWRRAD1DhPVVIPBNk4TklORQ8D0ThPTkUPBdc4U0VWRU4QAtY4SVgPBdM4VEhSRUUQAtI4V08PBNA4WkVS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ngRCRVRIEgr1ngRGSU5BTCBIRVRIEgryngRHSU1FTC1IRVRIEgT3ngRLQVBIEgb4ngRMQU1FREgTA4GfBEVTSBID+Z4ETUVNEgP6ngROVU4SAvyeBFBFEgT+ngRSRVNIEgX9ngRTQURIRRQE+54ETUVLSBMD/54ESElOEgOAnwRUQVcSA/OeBFdBVxIE9p4EWU9ESBIF9J4EWkFZSU4LD4afBFBVTkNUVUFUSU9OIEJBUhcJiZ8ERk9VUiBET1RTFwiHnwRUV08gQkFSUxsEiJ8ERE9UUwMI0/MHRVIgQURVTFQGA/ToB01BTgYF9egHV09NQU4CA9L1B0lWRQEIyeoHTSBTWU1CT0wBM5vqB04gV0lUSCBFWENMQU1BVElPTiBNQVJLIFdJVEggTEVGVCBSSUdIVCBBUlJPVyBBQk9WRQIRmO0HQ09NSU5HIEFVVE9NT0JJTEUJA43tB0JVUwkL8e0HRklSRSBFTkdJTkUJCpTtB1BPTElDRSBDQVIJBJbtB1RBWEkCDq/rB0UgQlVUVE9OIE1PVVNFBAqkQERPVCBMRUFERVIIGatcT1ZFUiBUV08gRE9UUyBQVU5DVFVBVElPTgMP8fQHLVBJRUNFIFNXSU1TVUlUAgPF8wdJT04BCNbpB1BFTiBCT09LBwGjSFgFD7JOQ0VOVFJFIEFTVEVSSVNLDAqrTkJMQUNLIFNUQVIMBZtOQ1JPU1MMGLxOVEVBUkRST1AtU1BPS0VEIEFTVEVSSVNLBQvC6QdGSUxFIEZPTERFUgYFwesHT0xERVIFCtDoB0hBTkRTIFNJR04FBJPqB0xPQ0sFGe3pB01BSUxCT1ggV0lUSCBMT1dFUkVEIEZMQUcSC+zpB1JBSVNFRCBGTEFHBQbDT1NVQlNFVAcGxE9QRVJTRVQEHY9HLUNJUkNVSVQtT1VUUFVUIEgtVFlQRSBTWU1CT0wUDZBHTC1UWVBFIFNZTUJPTAUZvk9PVVRMSU5FRCBSSUdIVFdBUkRTIEFSUk9XAxWdAVJBVElORyBTWVNURU0gQ09NTUFORAIHzk1ISVVDSFVTAgiNTFBPU0lUSU9OAgq/6QdUSUNBTCBESVNDDAW46wcgSUNPTgQGpUZPTiBLRVkBEcdPUiBXSVRIIERPVCBJTlNJREUCCdnpB0FOR0UgQk9PSwcF4fMHSEVBUlQFBKfzB1VU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DI3nB0UgREVDT1JBVElPTgQFzeYHQVBQTEUDB/f0B0sgSEVBUlQDCq9OV0hFRUwgU1RBUgIE00xTQ0VTAwOr6gdUT0wCB9RFVENIRk9SSwkN2lUgV0lUSCBURUUgVE9QAQan9QdMQUNBUkQEEphGRSBPRiBJTlRFUkVTVCBTSUdOCQfQ7QdXT1JTSElQAwyOQk5DSyBDT05TVEFOVA8Mj0IgT1ZFUiBUV08gUEkDDd3tB1lHUk9VTkQgU0xJREUEFdHhB0lORyBDQVJEIEFDRSBPRiBDTFVCUxQIweEHRElBTU9ORFMUBrHhB0hFQVJUUxQGoeEHU1BBREVTDQSg4QdCQUNLDgrP4QdMQUNLIEpPS0VSDQ7Y4QdFSUdIVCBPRiBDTFVCUxYIyOEHRElBTU9ORFMWBrjhB0hFQVJUUxYGqOEHU1BBREVTDQ3V4QdGSVZFIE9GIENMVUJTFQjF4QdESUFNT05EUxUGteEHSEVBUlRTFQal4QdTUEFERVMOA+DhB09PTA8L1OEHVVIgT0YgQ0xVQlMVCMThB0RJQU1PTkRTFQa04QdIRUFSVFMVBqThB1NQQURFUw0N2+EHSkFDSyBPRiBDTFVCUxUIy+EHRElBTU9ORFMVBrvhB0hFQVJUUxUGq+EHU1BBREVTDQ3e4QdLSU5HIE9GIENMVUJTFQjO4QdESUFNT05EUxUGvuEHSEVBUlRTFQau4QdTUEFERVMODtzhB05JR0hUIE9GIENMVUJTFwjM4QdESUFNT05EUxcGvOEHSEVBUlRTFwas4QdTUEFERVMNDdnhB05JTkUgT0YgQ0xVQlMVCMnhB0RJQU1PTkRTFQa54QdIRUFSVFMVBqnhB1NQQURFUw0O3eEHUVVFRU4gT0YgQ0xVQlMWCM3hB0RJQU1PTkRTFga94QdIRUFSVFMWBq3hB1NQQURFUw0Jv+EHUkVEIEpPS0VSDQ7X4QdTRVZFTiBPRiBDTFVCUxYIx+EHRElBTU9ORFMWBrfhB0hFQVJUUxYGp+EHU1BBREVTDgvW4QdJWCBPRiBDTFVCUxQIxuEHRElBTU9ORFMUBrbhB0hFQVJUUxQGpuEHU1BBREVTDQza4QdURU4gT0YgQ0xVQlMUCMrhB0RJQU1PTkRTFAa64QdIRUFSVFMUBqrhB1NQQURFUw4N0+EHSFJFRSBPRiBDTFVCUxYIw+EHRElBTU9ORFMWBrPhB0hFQVJUUxYGo+EHU1BBREVTDgbh4QdSVU1QLTEUAerhBzAUAevhBzEUAezhBzIUAe3hBzMUAe7hBzQUAe/hBzUUAfDhBzYUAfHhBzcUAfLhBzgUAfPhBzkTAeLhBzIUAfThBzAUAfXhBzETAePhBzMTAeThBzQTAeXhBzUTAebhBzYTAefhBzcTAejhBzgTAenhBzkOC9LhB1dPIE9GIENMVUJTFAjC4QdESUFNT05EUxQGsuEHSEVBUlRTFAai4QdTUEFERVMNC9/hB1dISVRFIEpPS0VSAgGLAUQCAYwBVQMEoPUHTkdFUgMGK1MgU0lHTgkS8lQgQUJPVkUgRVFVQUxTIFNJR04KE61USU4gTEVGVCBIQUxGIENJUkNMRQ0RrlRSSUdIVCBIQUxGIENJUkNMRQ0IuVRUUklBTkdMRQoTqFRXSVRIIEJMQUNLIFRSSUFOR0xFDxejVENJUkNVTUZMRVggQUNDRU5UIEFCT1ZFDwmlVERPVCBCRUxPVw8SolRTTUFMTCBDSVJDTEUgQUJPVkUQDKdUVUJTQ1JJUFQgVFdPDwukVFRJTERFIEFCT1ZFFQWmVEJFTE9XBAuxAS1NSU5VUyBTSUdOAwLHTFRPBQrWVyBGT1JNIEZJVkUMA9VXT1VSCwXUV1RIUkVFDALTV1dPAQGeAU0BDZGYB05QIFRSQU5TSVNUT1IBEKnrB09DS0VUIENBTENVTEFUT1ICCJPtB0xJQ0UgQ0FSChGo7QdTIFJFVk9MVklORyBMSUdIVAcH",
+ "7ugHT0ZGSUNFUgIEqegHT0RMRQIYrEBQIERJUkVDVElPTkFMIEZPUk1BVFRJTkcQB+lASVNPTEFURQME/+YHQ09STgIN/ukHUlRBQkxFIFNURVJFTwIG51dTRUlET04DD5ZGSVRJT04gSU5ESUNBVE9SAwjv6QdUQUwgSE9STgcEkmBNQVJLCwWgYCBGQUNFBAPu6QdCT1gCCfLmB1QgT0YgRk9PRAMRsO0HQUJMRSBXQVRFUiBTWU1CT0wEAtTyB1RPAwm09QdURUQgUExBTlQCA93oB1VDSAMI1+YHTFRSWSBMRUcDB6MBTkQgU0lHTgML1/UHUklORyBMSVFVSUQDDb7sB1RJTkcgQ0FUIEZBQ0UIBKHsB0ZBQ0UCDf1HV0VSIE9OIFNZTUJPTAgL/EctT0ZGIFNZTUJPTAYM/kdTTEVFUCBTWU1CT0wHBftHWU1CT0wBC//pB1JBWUVSIEJFQURTAgb6REVDRURFUwgWt1UgQUJPVkUgQUxNT1NUIEVRVUFMIFRPDwuzVUVRVUFMUyBTSUdODxO5VU5PVCBBTE1PU1QgRVFVQUwgVE8TCLVVRVFVQUwgVE8PF69VU0lOR0xFLUxJTkUgRVFVQUxTIFNJR04bDLFVTk9UIEVRVUFMIFRPCRXoRUJVVCBOT1QgRVFVSVZBTEVOVCBUTwkL/ERPUiBFUVVBTCBUTw8K/kRJVkFMRU5UIFRPCQ6wRVVOREVSIFJFTEFUSU9OAwnD9QdHTkFOVCBNQU4JBsT1B1BFUlNPTgkFsPIHV09NQU4DDp5CU0NSSVBUSU9OIFRBS0UEIJP8A0VOVEFUSU9OIEZPUk0gRk9SIFZFUlRJQ0FMIENPTE9OIQOQ/ANNTUEfB7H8A0VNIERBU0ggBrL8A04gREFTSCAPlfwDWENMQU1BVElPTiBNQVJLHxOZ/ANIT1JJWk9OVEFMIEVMTElQU0lTHxGR/ANJREVPR1JBUEhJQyBDT01NQSsJkvwDRlVMTCBTVE9QHxK//ANMRUZUIEFOR0xFIEJSQUNLRVQkGLv8A0JMQUNLIExFTlRJQ1VMQVIgQlJBQ0tFVCQOwfwDQ09STkVSIEJSQUNLRVQlDLf8A1VSTFkgQlJBQ0tFVCQUvfwDRE9VQkxFIEFOR0xFIEJSQUNLRVQkC7X8A1BBUkVOVEhFU0lTJA7H/ANTUVVBUkUgQlJBQ0tFVCQWufwDVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVCQUw/wDV0hJVEUgQ09STkVSIEJSQUNLRVQqEpf8A0xFTlRJQ1VMQVIgQlJBQ0tFVCAHs/wDT1cgTElORR8NlvwDUVVFU1RJT04gTUFSSx8TwPwDUklHSFQgQU5HTEUgQlJBQ0tFVCUYvPwDQkxBQ0sgTEVOVElDVUxBUiBCUkFDS0VUJQ7C/ANDT1JORVIgQlJBQ0tFVCYMuPwDVVJMWSBCUkFDS0VUJRS+/ANET1VCTEUgQU5HTEUgQlJBQ0tFVCULtvwDUEFSRU5USEVTSVMlDsj8A1NRVUFSRSBCUkFDS0VUJRa6/ANUT1JUT0lTRSBTSEVMTCBCUkFDS0VUJRTE/ANXSElURSBDT1JORVIgQlJBQ0tFVCsSmPwDTEVOVElDVUxBUiBCUkFDS0VUOQSY/ANLQ0VUHwmU/ANTRU1JQ09MT04fDrD8A1RXTyBET1QgTEVBREVSHw20/ANXQVZZIExPVyBMSU5FAwTo8gdUWkVMAwqXR1ZJT1VTIFBBR0UCA7JASU1FAwO08gdOQ0UGAvjoB1NTBA+ZR1QgU0NSRUVOIFNZTUJPTAUCqOsHRVIHBbbrByBJQ09OAwyeAVZBQ1kgTUVTU0FHRQUKkQFURSBVU0UgT05FDAOSAVRXTwsCkQEtMQwBkgEyAgqv8wdPQklORyBDQU5FAwzH7QdISUJJVEVEIFNJR04DB4VGSkVDVElWRQMKykJQRVJUWSBMSU5FBAa3RE9SVElPTgoFnURBTCBUTwMH2FdTRVJQSU5BASOblwRTQUxURVIgUEFITEFWSSBGT1VSIERPVFMgV0lUSCBDUk9TUx8DnJcERE9UEAyAlwRMRVRURVIgQUxFUEgXBIGXBEJFVEgXBoOXBERBTEVUSBcFgpcER0lNRUwXAoSXBEhFGQKHlwRUSBcEiZcES0FQSBcGipcETEFNRURIFwiLlwRNRU0tUU9QSBcDjJcETlVOFwKOlwRQRRcFj5cEU0FESEUZBI2XBE1FS0gYA5CXBEhJThcDkZcEVEFXFw2FlwRXQVctQVlJTi1SRVNIFwSIlwRZT0RIFwWGlwRaQVlJThALrJcETlVNQkVSIEZPVVIXA6mXBE9ORRoIr5cEIEhVTkRSRUQXA62XBFRFThgEq5cESFJFRRgFrpcEV0VOVFkZAaqXBE8QDJmXBFNFQ1RJT04gTUFSSxATmpcEVFVSTkVEIFNFQ1RJT04gTUFSSwIEyZ0HWUNIRQECkQFVMQIBkgEyAhji6QdCTElDIEFERFJFU1MgTE9VRFNQRUFLRVICD4hATkNUVUFUSU9OIFNQQUNFBg/OXFMgRUxFVkFUVVMgTUFSSwIKnOkHUlBMRSBIRUFSVAMC2+gHU0UCBczpB1NIUElOAhyu7QdUIExJVFRFUiBJTiBJVFMgUExBQ0UgU1lNQk9MABOWS1FVQURSQU5UIExPV0VSIExFRlQPBZdLUklHSFQJCphLVVBQRVIgTEVGVBMfmUsgQU5EIExPV0VSIExFRlQgQU5EIExPV0VSIFJJR0hUHgWaS1JJR0hUGBqbS1VQUEVSIFJJR0hUIEFORCBMT1dFUiBMRUZULgWcS1JJR0hUDwWdS1JJR0hUFA+eSyBBTkQgTE9XRVIgTEVGVCMQn0sgQU5EIExPV0VSIFJJR0hUBRaMVFVQTEUgSU5URUdSQUwgT1BFUkFUT1IKBddAUFJJTUUDA/7uB09BUgMJ6UxSVEVSIE5PVEUDGZZUVEVSTklPTiBJTlRFR1JBTCBPUEVSQVRPUgIXyEBFU1RJT04gRVhDTEFNQVRJT04gTUFSSwkEP01BUksIC99ERUQgRVFVQUwgVE8CBrtNSU5DVU5YAgwiT1RBVElPTiBNQVJLAAaH6AdSQUJCSVQGBbDoByBGQUNFAgWd8wdDQ09PTgMHzucHSU5HIENBUgcKzecHTU9UT1JDWUNMRQITt0dESUNBTCBTWU1CT0wgQk9UVE9NBAH76QdPBQeY6gcgQlVUVE9OBQuiTEFDVElWRSBTSUdOAgmD7QdJTFdBWSBDQVIIBeTtB1RSQUNLAwHGTU4EA4jmB0JPVwMQmvIHU0VEIEJBQ0sgT0YgSEFORAcFtFxDT01NQQcDs1xET1QKGIdcVEVEIElOVEVSUE9MQVRJT04gTUFSS0VSBwSKTkZJU1QHBItOSEFORAsVkOsHIFdJVEggRklOR0VSUyBTUExBWUVEESSW6wdQQVJUIEJFVFdFRU4gTUlERExFIEFORCBSSU5HIEZJTkdFUlMHFIZcSU5URVJQT0xBVElPTiBNQVJLRVIHB+riB01DIFNJR04IBuviB0QgU0lHTggG7OIHUiBTSUdOBxnM9wdTTUFMTCBMRUZUIFNRVUFSRSBCUkFDS0VUCAWLXFFVQVJFAgGP6AdNAgGA6AdUAwK2RElPAgOS9QdaT1IBBv7zB0VDRUlQVAQGkpgHUFRBQ0xFAw0eT1JEIFNFUEFSQVRPUgMRme0HUkVBVElPTkFMIFZFSElDTEUDEvxMWUNMRUQgUEFQRVIgU1lNQk9MBiD6TElORyBTWU1CT0wgRk9SIEdFTkVSSUMgTUFURVJJQUxTFQ/zTFRZUEUtMSBQTEFTVElDUxoK9EwyIFBMQVNUSUNTGgr1TDMgUExBU1RJQ1MaCvZMNCBQTEFTVElDUxoK90w1IFBMQVNUSUNTGgr4TDYgUExBU1RJQ1MaCvlMNyBQTEFTVElDUwIHzuYHRCBBUFBMRQQN5/MHR0lGVCBFTlZFTE9QRQIMu0BGRVJFTkNFIE1BUksCIObjB0dJT05BTCBJTkRJQ0FUT1IgU1lNQk9MIExFVFRFUiBBIQHn4wdCIQHo4wdDIQHp4wdEIQHq4wdFIQHr4wdGIQHs4wdHIQHt4wdIIQHu4wdJIQHv4wdKIQHw4wdLIQHx4wdMIQHy4wdNIQHz4wdOIQH04wdPIQH14wdQIQH24wdRIQH34wdSIQH44wdTIQH54wdUIQH64wdVIQH74wdWIQH84wdXIQH94wdYIQH+4wdZIQH/4wdaBAuuAVNURVJFRCBTSUdOAhXS0gJKQU5HIENPTlNPTkFOVCBTSUdOIEgWAdDSAk4XAc/SAkcWAdHSAlIHCMbSAkxFVFRFUiBBDgK30gJCQQ4CudICQ0EOArTSAkRBDgKx0gJHQQ4CwdICSEEOArrSAkpBDgKw0gJLQQ4CvtICTEEOArjSAk1BDwLC0gJCQQ4CtdICTkEPAsTSAkRBDwKy0gJHQRACw9ICR0EPArvSAllBEALF0gJKQQ4CttICUEEOAr3SAlJBDgK80gJTQQ4Cs9ICVEEOAsDSAldBDgK/0gJZQQcM39ICU0VDVElPTiBNQVJLBwbT0gJWSVJBTUEIDMrSAk9XRUwgU0lHTiBBSRMBzNICVRIBydICRRMBztICQRMBzdICVRIBx9ICSRIBy9ICTxIByNICVQILjOwHTElFVkVEIEZBQ0UCDZfnB01JTkRFUiBSSUJCT04CE/3/A1BMQUNFTUVOVCBDSEFSQUNURVICBp9CU1BPTlNFAxTgTVRSSUNURUQgTEVGVCBFTlRSWS0xFgHhTTIFA7vtB09PTQIJkFdUVVJOIExFRlQHBZFXUklHSFQHBs5HU1lNQk9MAhP/7AdWRVJTRSBDSEVDS0VSIEJPQVJECAWNAUlOREVYCCDI7wdMSUdIVCBGT1VSIFBPSU5URUQgUElOV0hFRUwgU1RBUgoHjQFORSBGRUVECAdcU09MSURVUw8J9VMgT1BFUkFUT1IQEMhPUFJFQ0VESU5HIFNVQlNFVBAW91NXSVRIIEhPUklaT05UQUwgU1RST0tFCCTBVlRJTERFIE9QRVJBVE9SIEFCT1ZFIExFRlRXQVJEUyBBUlJPVx0Qx1ZSSUdIVFdBUkRTIEFSUk9XBwejU0QgQU5HTEUODqVTIFdJVEggVU5ERVJCQVIJBcFcQ09NTUEJDLZARE9VQkxFIFBSSU1FFQ+dYCBRVU9UQVRJT04gTUFSSxAP7VVTVFJPS0UgTk9UIFNJR04JCbBTRU1QVFkgU0VUCRKRXEZPUktFRCBQQVJBR1JBUEhPUwkglesHSEFORCBXSVRIIE1JRERMRSBGSU5HRVIgRVhURU5ERUQJCJBGTk9UIFNJR04JDMtAUElMQ1JPVyBTSUdOCgS1QFJJTUUJDa5cUVVFU1RJT04gTUFSSwkgkesHUkFJU0VEIEhBTkQgV0lUSCBGSU5HRVJTIFNQTEFZRUQKCv5XSUdIVCBBTkdMRQoamUxPVEFURUQgRkxPUkFMIEhFQVJUIEJVTExFVAkUw0JTQU5TLVNFUklGIENBUElUQUwgTAoIz0BFTUlDT0xPTgkQk+sHVEhVTUJTIERPV04gU0lHThAHkusHVVAgU0lHTgoEvURJTERFDgfNRSBFUVVBTFMKC7dAUklQTEUgUFJJTUUJDJTrB1ZJQ1RPUlkgSEFORAMNnukHT0xWSU5HIEhFQVJUUwEJj/MHSElOT0NFUk9TAQGNAUkCB/z7A0FMIFNJR04CBIDnB0JCT04GELBXIEFSUk9XIERPV04gTEVGVBIFsVdSSUdIVA0JtldMRUZUIERPV04SArRXVVANCrdXUklHSFQgRE9XThMCtVdVUA0HsldVUCBMRUZUEAWzV1JJR0hUAgfZ5gdDRSBCQUxMBQfY5gdDUkFDS0VSAgPG8gdGTEUCHv/2B0dIVCBBTkQgTE9XRVIgT05FIEVJR0hUSCBCTE9DSwoW/vYHVVBQRVIgT05FIEVJR0hUSCBCTE9DSwgK7+sHR0VSIEJVQkJMRQkCn0RMRQsIiWAgQlJBQ0tFVBMJklMgV0lUSCBET1QMGoFcRE9UVEVEIFNVQlNUSVRVVElPTiBNQVJLRVIME4BcU1VCU1RJVFVUSU9OIE1BUktFUgwTnFNWQVJJQU5UIFdJVEggU1FVQVJFDAi+RVdJVEggQVJDERb8RkRPV05XQVJEUyBaSUdaQUcgQVJST1cHF5RTUkMgR1JFQVRFUi1USEFOIEJSQUNLRVQIFfRPUk9XIFdJVEggQ0lSQ0xFRCBQTFVTEQz0Q1NNQUxMIENJUkNMRQYYkWBCTEFDSyBMRU5USUNVTEFSIEJSQUNLRVQMFphTVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVAYHiUZDRUlMSU5HBw2NYE9STkVSIEJSQUNLRVQHDH1VUkxZIEJSQUNLRVQTC61HIExPV0VSIEhPT0sUDKxHTUlERExFIFBJRUNFFAqrR1VQUEVSIEhPT0sGG4VcRE9UVEVEIFNVQlNUSVRVVElPTiBCUkFDS0VUCBKLYFVCTEUgQU5HTEUgQlJBQ0tFVA4W8FJSUk9XIFdJVEggUk9VTkRFRCBIRUFEDQupXFBBUkVOVEhFU0lTDQ6dQFFVT1RBVElPTiBNQVJLDQzbU1dJR0dMWSBGRU5DRQYJ/VJGSVNIIFRBSUwIEIn3B1ZFIEVJR0hUSFMgQkxPQ0sHBItGTE9PUgYfgJwHSEFMRiBBTkQgTEVGVCBIQUxGIFdISVRFIENJUkNMRQsM10tCTEFDSyBDSVJDTEURBOlXU1RBUg0DkEtPQ0sLDaWZB0ZMWUlORyBTQVVDRVIMBbr3B09MREVSCyWRnAdIT1JJWk9OVEFMIExJTkUgV0lUSCBUSFJFRSBUSUNLIE1BUktTCxegnQdMT1dFUiBPTkUgUVVBUlRFUiBCTE9DSwsMjfcHTUVESVVNIFNIQURFCwuz9wdSVU5OSU5HIE1BTgsWjpwHVFJJUExFIERBU0ggSE9SSVpPTlRBTAsXq50HVVBQRVIgT05FIFFVQVJURVIgQkxPQ0sLDYycB1dISVRFIEVMTElQU0UIFf3qB05EIFRFTEVQSE9ORSBSRUNFSVZFUgYg6fcHSlVTVElGSUVEIExFRlQgSEFMRiBCTEFDSyBDSVJDTEUaDOH3B1dISVRFIENJUkNMRQYWnVxMT1cgUEFSQVBIUkFTRSBCUkFDS0VUBiDKRU5PUk1BTCBGQUNUT1IgU0VNSURJUkVDVCBQUk9EVUNUBhCVS09ORSBFSUdIVEggQkxPQ0sKDYf3B1FVQVJURVIgQkxPQ0sHD7z3B1BFTiBTUVVBUkVEIERPVAcJ1k9VVEVSIEpPSU4GCylQQVJFTlRIRVNJUxEKn0cgRVhURU5TSU9OEgqgR0xPV0VSIEhPT0sSCp5HVVBQRVIgSE9PSwYXjVxSQUlTRUQgT01JU1NJT04gQlJBQ0tFVAYWxk9TLVNIQVBFRCBCQUcgREVMSU1JVEVSBxHMRUVNSURJUkVDVCBQUk9EVUNUCBGL9wdWRU4gRUlHSFRIUyBCTE9DSwcRp1xJREVXQVlTIFUgQlJBQ0tFVAgTmUBOR0xFIFFVT1RBVElPTiBNQVJLBwbo6gdQRUFLRVINFOnqByBXSVRIIE9ORSBTT1VORCBXQVZFExHq6gdUSFJFRSBTT1VORCBXQVZFUwkK6esHRUNIIEJVQkJMRQcNXVFVQVJFIEJSQUNLRVQUCqVHIEVYVEVOU0lPThUMpkdMT1dFUiBDT1JORVIVDKRHVVBQRVIgQ09STkVSFRLYXFdJVEggRE9VQkxFIFNUUk9LRRoFxkBRVUlMTBoG1lxTVFJPS0UaFY5TVElDSyBJTiBCT1RUT00gQ09STkVSIgqQU1RPUCBDT1JORVIaCIxTVU5ERVJCQVIHE4NcVUJTVElUVVRJT04gQlJB",
+ "Q0tFVAYEokVUQUNLBw2NmAdISVJEIElORFVDVE9SDBrD9wdXSElURSBSSUdIVCBQT0lOVElORyBJTkRFWAgM7esHT1VHSFQgQlVCQkxFCBGI9wdSRUUgRUlHSFRIUyBCTE9DSwwOivcHUVVBUlRFUlMgQkxPQ0sVF6GdB0xPV0VSIE9ORSBRVUFSVEVSIEJMT0NLFReqnQdVUFBFUiBPTkUgUVVBUlRFUiBCTE9DSwcVlWBPUlRPSVNFIFNIRUxMIEJSQUNLRVQHFIpcUkFOU1BPU0lUSU9OIEJSQUNLRVQIBr9FSUFOR0xFDhTOUyBBQk9WRSBMRUZUIFRSSUFOR0xFDBbu9gdVTEFSIE9ORSBRVUFSVEVSIEJMT0NLBhehXFZFUlRJQ0FMIEJBUiBXSVRIIFFVSUxMEAe5R09YIExJTkUPDYaYB1JVTEVSIFNFR01FTlQGFI9gV0hJVEUgQ09STkVSIEJSQUNLRVQNDIRTVVJMWSBCUkFDS0VUDBKXYExFTlRJQ1VMQVIgQlJBQ0tFVAwLhlNQQVJFTlRIRVNJUwwOm2BTUVVBUkUgQlJBQ0tFVAwWmWBUT1JUT0lTRSBTSEVMTCBCUkFDS0VUBwvZU0lHR0xZIEZFTkNFBR6NCy1GQUNJTkcgQVJNRU5JQU4gRVRFUk5JVFkgU0lHTg0IyZgHQkFTU0lORVQNBJOZB0ZJU0gREJWZByBXSVRIIE9QRU4gTU9VVEgQAZzyB1QNHPaYB1NOQUtFIEhFQUQgV0lUSCBDTE9TRUQgTU9VVEgdCvKYB09QRU4gTU9VVEgOCtUfVkFTVEkgU0lHThgK1x8gV0lUSCBET1RTBhvlTUhBTkRFRCBJTlRFUkxBQ0VEIFBFTlRBR1JBTQYWqkZQT0lOVElORyBBTkdMRSBCUkFDS0VUEArimAdUT01JQyBCT01CDxT9U0NVUlZFRCBBTkdMRSBCUkFDS0VUDwWPmAdESU9ERRAauwFPVUJMRSBBTkdMRSBRVU9UQVRJT04gTUFSSw8L+pgHRU5FUkdZIFdBVkUPB4GYB0dPLUtBUlQPEI7qB01BR05JRllJTkcgR0xBU1MPCpmZB1JBQ0lORyBDQVIQBOeYB0lGTEUQDaGZB09DS0VUIEJPT1NURVIWBNiYB1NISVAREKOZB0xMRVIgQ09BU1RFUiBDQVIPDIOYB1NUSUNLIEZJR1VSRQ8En5kHVEFOSwYdqU9TSEFERUQgV0hJVEUgUklHSFRXQVJEUyBBUlJPVwcXuFJJREUgQVJDIENMT0NLV0lTRSBBUlJPVwYRq0BUTy1MRUZUIEVNQkVERElORw4H50BJU09MQVRFDgSPQE1BUksOCK5AT1ZFUlJJREUFFb1USEFORCBJTlRFUklPUiBQUk9EVUNUBQuSQ1dBUkRTIEFSUk9XEBb1UiBBQk9WRSBBTE1PU1QgRVFVQUwgVE8XF8hWUkVWRVJTRSBBTE1PU1QgRVFVQUwgVE8fDsxWVElMREUgT1BFUkFUT1IXFcJSU0hPUlQgTEVGVFdBUkRTIEFSUk9XFw70UlRJTERFIE9QRVJBVE9SEiO29wdORCBVUFBFUiBBTkQgTE9XRVIgT05FIEVJR0hUSCBCTE9DSxEIpkNGUk9NIEJBUhkRoFIgVE8gQkxBQ0sgRElBTU9ORBYPwfEHRE9XTldBUkRTIEFSUk9XERTEQ09WRVIgTEVGVFdBUkRTIEFSUk9XERTDVlRIUk9VR0ggR1JFQVRFUi1USEFOGQjEVlNVUEVSU0VUGQHHUlgSBeVDTyBCQVIVDJ5STEFDSyBESUFNT05EERW0Q1dJVEggQ09STkVSIERPV05XQVJEUxYLkVJET1RURUQgU1RFTRgU+0NVQkxFIFZFUlRJQ0FMIFNUUk9LRRYVlvAHRVFVSUxBVEVSQUwgQVJST1dIRUFEFgSqQ0hPT0sWGIrwB0xBUkdFIFRSSUFOR0xFIEFSUk9XSEVBRBcDrENPT1AYCLLxB1dFUiBIT09LFhmG8AdNRURJVU0gVFJJQU5HTEUgQVJST1dIRUFEFgya8QdOT1RDSEVEIFRBSUwWCsVSUExVUyBCRUxPVxYbkvAHU01BTEwgRVFVSUxBVEVSQUwgQVJST1dIRUFEHBKC8AdUUklBTkdMRSBBUlJPV0hFQUQXBZtDVFJPS0UWBKNDVEFJTBoclVIgV0lUSCBET1VCTEUgVkVSVElDQUwgU1RST0tFIA+UUlZFUlRJQ0FMIFNUUk9LRRcMjlZJUCBET1dOV0FSRFMaB49WVVBXQVJEUxYP+ENWRVJUSUNBTCBTVFJPS0UQBZpSLVRBSUwLIKnxB0JBQ0stVElMVEVEIFNIQURPV0VEIFdISVRFIEFSUk9XDAqVV0xBQ0sgQVJST1cRE4pXQ0lSQ0xFRCBXSElURSBBUlJPVwwYofEHT1RUT00gU0hBREVEIFdISVRFIEFSUk9XCxC+8AdDT01QUkVTU0VEIEFSUk9XCwziQ0RBU0hFRCBBUlJPVwwL0kNPVUJMRSBBUlJPVxcJh1IgRlJPTSBCQVIYC89DV0lUSCBTVFJPS0UdD4NSVkVSVElDQUwgU1RST0tFFwWcUi1UQUlMEgqNUkRBU0ggQVJST1cLEbbwB0ZJTkdFUi1QT1NUIEFSUk9XDCCr8QdST05ULVRJTFRFRCBTSEFET1dFRCBXSElURSBBUlJPVwsE8fUHSEFORA0czENSUE9PTiBPVkVSIExFRlRXQVJEUyBIQVJQT09OEzXpUldJVEggQkFSQiBET1dOIEFCT1ZFIExFRlRXQVJEUyBIQVJQT09OIFdJVEggQkFSQiBET1dOIg/tUkJFTE9XIExPTkcgREFTSCII31JGUk9NIEJBUiIG11JUTyBCQVIhBcFDV0FSRFMdJ+hSVVAgQUJPVkUgTEVGVFdBUkRTIEhBUlBPT04gV0lUSCBCQVJCIFVQJwjsUk9ORyBEQVNIJiHkUlJJR0hUV0FSRFMgSEFSUE9PTiBXSVRIIEJBUkIgRE9XTiAI21JGUk9NIEJBUiAG01JUTyBCQVIfBcBDV0FSRFMMCsbwB0VBVlkgQVJST1cREMLwB0NPTVBSRVNTRUQgQVJST1cLF6fxB0xFRlQtU0hBREVEIFdISVRFIEFSUk9XCxH+Q09QRU4tSEVBREVEIEFSUk9XCw3JQ1BBSVJFRCBBUlJPV1MMC/j1B1VTSElORyBIQU5ECw/GVlFVQURSVVBMRSBBUlJPVwsYpfEHUklHSFQtU0hBREVEIFdISVRFIEFSUk9XDAXu7AdPQ0tFVAsQ0vAHU0FOUy1TRVJJRiBBUlJPVwwMuvAHUVVBUkVEIEFSUk9XDgvdQ0lHR0xFIEFSUk9XCxaj8QdUT1AgU0hBREVEIFdISVRFIEFSUk9XDBGS8QdSSUFOR0xFIEFSUk9XSEVBRBMN4lYtSEVBREVEIEFSUk9XICWCVyBPVkVSIExFRlRXQVJEUyBUUklBTkdMRS1IRUFERUQgQVJST1chBvJWVE8gQkFSIQ+q8AdXSVRIIEJPTEQgU0hBRlQmGPxWRE9VQkxFIEhPUklaT05UQUwgU1RST0tFLQ/8VlZFUlRJQ0FMIFNUUk9LRSYLrvAHSEVBVlkgU0hBRlQmEqdXTE9ORyBUSVAgRE9XTldBUkRTLwelV1VQV0FSRFMmDKbwB01FRElVTSBTSEFGVCYMovAHTkFSUk9XIFNIQUZUJhCy8AdWRVJZIEhFQVZZIFNIQUZUGwzsVkRBU0hFRCBBUlJPVxsNhldQQUlSRUQgQVJST1dTDgnbQ1BMRSBBUlJPVxIKj1JEQVNIIEFSUk9XDA+gQ1dPIEhFQURFRCBBUlJPVw4WhVItSEVBREVEIEFSUk9XIEZST00gQkFSHBuBUldJVEggRE9VQkxFIFZFUlRJQ0FMIFNUUk9LRSEEllJUQUlMJRyYUiBXSVRIIERPVUJMRSBWRVJUSUNBTCBTVFJPS0UrD5dSVkVSVElDQUwgU1RST0tFIhLuV1JJQU5HTEUgQVJST1dIRUFEUyEPgFJWRVJUSUNBTCBTVFJPS0UWEZBSVFJJUExFIERBU0ggQVJST1cLCp1DV0FWRSBBUlJPVwwK6ENISVRFIEFSUk9XFgrwQyBGUk9NIFdBTEwXGZbxB1dJVEhJTiBUUklBTkdMRSBBUlJPV0hFQUQCAo3pB05HBAbaBSBBQk9WRQUE3+0HQlVPWQUI10RFUVVBTCBUTwUL1kRJTiBFUVVBTCBUTwUImERPUEVSQVRPUgUFsFxQT0lOVAQJkPUHRUQgUExBTkVUBAjt6gdJTkcgQkVMTAInq1JTSU5HIERJQUdPTkFMIENST1NTSU5HIEZBTExJTkcgRElBR09OQUwZELBSU09VVEggRUFTVCBBUlJPVwECq0BMRQIB50BJAgGPQE0CAa5ATwET4OYHT0FTVEVEIFNXRUVUIFBPVEFUTwIIlvIHQk9UIEZBQ0UCAqj1B0NLBAKA7QdFVAIL+/MHTEwgT0YgUEFQRVIED97rB0VELVVQIE5FV1NQQVBFUgUJoucHUiBDT0FTVEVSBwX87QdTS0FURQQZo/IHSU5HIE9OIFRIRSBGTE9PUiBMQVVHSElORwILmoMETUFOIEFTIFNJR04GDpuDBENFTlRVUklBTCBTSUdOBg2WgwRERU5BUklVUyBTSUdOBxOUgwRJTUlESUEgU0VYVFVMQSBTSUdOBw2ZgwRVUE9ORElVUyBTSUdOBg3nQk5VTUVSQUwgRUlHSFQPBepCTEVWRU4OBexCRklGVFkTC4ZDIEVBUkxZIEZPUk0UCIdDVEhPVVNBTkQQAuRCVkUSCO5CIEhVTkRSRUQTCIFDVEhPVVNBTkQPA+NCT1VSDgToQk5JTkUOA+BCT05FEQjtQiBIVU5EUkVEGQmIQyBUSE9VU0FORBII70JUSE9VU0FORBoEgEMgQyBEDhSDQ1JFVkVSU0VEIE9ORSBIVU5EUkVEDgXmQlNFVkVODwLlQklYEQqFQyBMQVRFIEZPUk0OA+lCVEVOEQmCQyBUSE9VU0FORA8E4kJIUkVFDwXrQldFTFZFEAHhQk8GDpeDBFFVSU5BUklVUyBTSUdOBg2SgwRTRU1VTkNJQSBTSUdOCA2YgwRTVEVSVElVUyBTSUdOCAqQgwRYVEFOUyBTSUdOCgiTgwRVTEEgU0lHTgcLlYMESUxJUVVBIFNJR04GCpGDBFVOQ0lBIFNJR04CBZPoB09TVEVSAwvc9QdUIFZFR0VUQUJMRQICueYHU0UEA/XnB1RURQIPukJUQVRFRCBDQVBJVEFMIFEIE+dORkxPUkFMIEhFQVJUIEJVTExFVAgY5U5IRUFWWSBCTEFDSyBIRUFSVCBCVUxMRVQIHc1XTElHSFQgRk9VUiBQT0lOVEVEIEJMQUNLIENVU1AIF89XV0hJVEUgRk9VUiBQT0lOVEVEIENVU1ACC83pB1VORCBQVVNIUElOBgaL7wdUQVJHRVQFEeXkB0VEIFNZTUJPTCBGT1IgQ0FJEwLg5AdGVRMC4eQHTFUTBOLkB1NIT1UVBuTkB1VBTkdYSRMC4+QHWEkCBaPtB1dCT0FUAQEeUwEJvUFVQkxFIFNJR04CDMnnB0dCWSBGT09UQkFMTAIK9FNMRS1ERUxBWUVEAg7nnARNSSBESUdJVCBFSUdIVAsE5JwERklWRQwD45wET1VSCwTonAROSU5FCwPgnARPTkULBeacBFNFVkVODALlnARJWAsF4pwEVEhSRUUMAuGcBFdPBRH7nARGUkFDVElPTiBPTkUgSEFMRhIH/JwEUVVBUlRFUhIF/ZwEVEhJUkQOCv6cBFRXTyBUSElSRFMFFPmcBE5VTUJFUiBFSUdIVCBIVU5EUkVEEQHwnARZDAXtnARGSUZUWQ4K9pwEVkUgSFVORFJFRA0E7JwET1JUWQ4K9ZwEVVIgSFVORFJFRAwM+pwETklORSBIVU5EUkVEEALxnARUWQwL8pwET05FIEhVTkRSRUQMDficBFNFVkVOIEhVTkRSRUQRAu+cBFRZDQr3nARJWCBIVU5EUkVEDwLunARUWQwD6ZwEVEVODQXrnARISVJUWQ4L9JwEUkVFIEhVTkRSRUQNBeqcBFdFTlRZDgnznARPIEhVTkRSRUQCEe4tTklDIEFSTEFVRyBTWU1CT0wGD/AtQkVMR1RIT1IgU1lNQk9MBhHtLUNST1NTIFBVTkNUVUFUSU9OBguqLUxFVFRFUiBBQyBBDgOrLUVTQw4KyS1MR0laIEVPTEhYDgaoLU5TVVogQQ0Y0i1CRVJLQU5BTiBCRU9SQyBCSkFSS0FOIEINAc0tQw4D4y1BTEMOBOQtRUFMQw8Bsy1ODgbiLVdFT1JUSA0B0S1EDgveLUFHQVogREFFRyBEDgfbLU9UVEVELUwUAcAtThQB1C1QDQHCLUUOAuAtQVIOCdYtSFdBWiBFSCBFDgK2LU5HDgKnLVRIDQ6gLUZFSFUgRkVPSCBGRSBGDg/3LVJBTktTIENBU0tFVCBBQxwD+C1FU0MbAvYtRUgbAvUtSVMbAvQtT1MNAbUtRw4CuC1BUg4Kty1FQk8gR1lGVSBHDwHELVINB7stSEFFR0wgSA8Gui1HTEFaIEgNDOgtSUNFTEFORElDLVlSDgLdLU5HEAPcLVdBWg4C4S1PUg4MwS1TQVogSVMgSVNTIEkOB8ctV0FaIEVPSA0Hwy1KRVJBTiBKDQHxLUsOBbQtQVVOIEsRAbItQQ0S2i1MQVVLQVogTEFHVSBMT0dSIEwOEMUtT05HLUJSQU5DSC1BUiBBRRkIvC1IQUdBTEwgSBkG2C1NQURSIE0ZBawtT1NTIE8ZAuYtWVINDNctTUFOTkFaIE1BTiBNDRG+LU5BVURJWiBOWUQgTkFVRCBODQGuLU8OAa8tRQ4BsC1ODgHzLU8OBdUtUEVOLVAOA6ktUyBPDg7fLVRIQUxBTiBFVEhFTCBPDQ/ILVBFUlRITyBQRU9SVEggUA0B6S1RDRCxLVJBSURPIFJBRCBSRUlEIFINAvItU0gPDcYtT1JULVRXSUctQVIgQRgJ0y1CSkFSS0FOIEIYCL0tSEFHQUxMIEgYBtktTUFEUiBNGAa/LU5BVUQgThgFrS1PU1MgTxgFzC1TT0wgUxgF0C1UWVIgVBgC5y1ZUg4Wyy1JR0VMIExPTkctQlJBTkNILVNPTCBTDgfKLU9XSUxPIFMOA+UtVEFODRSmLVRIVVJJU0FaIFRIVVJTIFRIT1JODg7PLUlXQVogVElSIFRZUiBUDQmiLVVSVVogVVIgVQ0BoS1WDQGlLVcOC7ktVU5KTyBXWU5OIFcNAeotWA0BpC1ZDgGjLVINAc4tWgYU7C1NVUxUSVBMRSBQVU5DVFVBVElPTgYS6y1TSU5HTEUgUFVOQ1RVQVRJT04GD+8tVFZJTUFEVVIgU1lNQk9MAwPD5wdORVIEE73nB0lORyBTSElSVCBXSVRIIFNBU0gCCKhBUEVFIFNJR04CIvZXU1NJQU4gQVNUUk9MT0dJQ0FMIFNZTUJPTCBCSU5PVklMRRwG9FdOT1ZJTEUcCPVXUVVJTlRJTEUcCPdXU0VOVEFHT04cCfhXVFJFREVDSUxFHAnzV1ZJR0lOVElMRQANzFNTIElOIFRSSUFOR0xFAQn38wdBRkVUWSBQSU4HBLrzB1ZFU1QCCdBMR0lUVEFSSVVTAgb1TUlMQk9BVAIR9uYHS0UgQk9UVExFIEFORCBDVVACCcLzB0xUIFNIQUtFUgQDk0xJUkUDCuH1B1VUSU5HIEZBQ0UCGbYQTUFSSVRBTiBBQkJSRVZJQVRJT04gTUFSSwoLgBBMRVRURVIgQUxBRhEDhRBCQUESAoEQSVQRBYMQREFMQVQRApAQRkkRBYIQR0FNQU4RAo8QSU4SAYcQVBIBhBBZEQSKEEtBQUYRBYsQTEFCQVQRA4wQTUlNEQONEE5V",
+ "ThEDkhBRVUYRBJMQUklTSBEElBBTSEFOEgaOEElOR0FBVBEElRBUQUFGEgKIEElUEgaREFNBQURJWREDiRBZVVQRA4YQWkVOCguZEE1BUksgREFHRVNIDw6bEEVQRU5USEVUSUMgWVVUDwKWEElOEQWXEC1BTEFGDwetEE5FUVVEQUEPCZgQT0NDTFVTSU9OCx2aEE9ESUZJRVIgTEVUVEVSIEVQRU5USEVUSUMgWVVUGgGoEEkaB6QQU0hPUlQgQQoSsRBQVU5DVFVBVElPTiBBRlNBQVEXBLIQTkdFRBgEvhBOQUFVFwa8EFJLQUFOVRcFtBBUTUFBVRYDsxBCQVUWDbcQTUVMT0RJQyBRSVRTQRYHsBBORVFVREFBFgW5EFFJVFNBFgq1EFNISVlZQUFMQUEXC70QT0YgTUFTSEZBQVQWBLsQVFVSVRYEuhBaQUVGFwS4EElRQUEKDKMQVk9XRUwgU0lHTiBBFgGgEEEVAZ0QRRUBqhBJFQaiEExPTkcgQRsBnxBBGgGcEEUaAakQSRoBphBVFQGrEE8WCaEQVkVSTE9ORyBBHwGeEEEVB6UQU0hPUlQgQRYErBBVS1VOFQGnEFUCBuryB05EV0lDSAMy9+wHUy1TRVJJRiBIRUFWWSBET1VCTEUgQ09NTUEgUVVPVEFUSU9OIE1BUksgT1JOQU1FTlQYJPbsB1RVUk5FRCBDT01NQSBRVU9UQVRJT04gTUFSSyBPUk5BTUVOVBEo+OwHTE9XIERPVUJMRSBDT01NQSBRVU9UQVRJT04gTUFSSyBPUk5BTUVOVAsU+uwHSU5URVJST0JBTkcgT1JOQU1FTlQCAvvyB1JJAgfw7QdURUxMSVRFCQjh6QcgQU5URU5OQQMDxExVUk4CDsFBVURJIFJJWUFMIFNJR04DHLTRAlJBU0hUUkEgQ09OU09OQU5UIFNJR04gSEFBUlULBc7RAkRBTkRBDArY0QJJR0lUIEVJR0hUEQTV0QJGSVZFEgPU0QJPVVIRBNnRAk5JTkURA9HRAk9ORREF19ECU0VWRU4SAtbRAklYEQXT0QJUSFJFRRIC0tECV08RBNDRAlpFUk8MC8/RAk9VQkxFIERBTkRBCwiC0QJMRVRURVIgQRMBg9ECQRMBjtECSRMBkdECVRICqNECQkETAqnRAkhBEgKX0QJDQRMCmNECSEES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TEUgQ1JFU0NFTlRTCgvFqwRQQVJBVE9SIEJBUhIDxKsERE9UCQy9qwRJR04gQU5VU1ZBUkENC7yrBENBTkRSQUJJTkRVDQXAqwROVUtUQQ0HwasEU0lEREhBTQ0Gv6sEVklSQU1BDwW+qwRTQVJHQQgNr6sEVk9XRUwgU0lHTiBBQRQBuasESRQK3KsETFRFUk5BVEUgVR4B3asEVRQBu6sEVRMBuKsERRMBsKsESRQBsasESRMBuqsETxMBsqsEVRQBs6sEVRMJtKsEVk9DQUxJQyBSHAG1qwRSAw/AkgRFVElDIExFVFRFUiBOMDERAcGSBDIRAcKSBDMRAcOSBDQRAcSSBDURAcWSBDYRAcaSBDcRAceSBDgRAciSBDkQAsmSBDEwEQHKkgQxEQHLkgQyEQHMkgQzEQHNkgQ0EQHOkgQ1EQHPkgQ2EQHQkgQ3EQHRkgQ4EQHSkgQ5EALTkgQyMBEB1JIEMREB1ZIEMhEB1pIEMxEB15IENBEB2JIENREB2ZIENgQeoesHV0FZUyBCTEFDSyBET1dOIFBPSU5USU5HIElOREVYDxOa6wdMRUZUIFBPSU5USU5HIElOREVYDxSb6wdSSUdIVCBQT0lOVElORyBJTkRFWA8RoOsHVVAgUE9JTlRJTkcgSU5ERVgJGZ/rB1dISVRFIERPV04gUE9JTlRJTkcgSU5ERVgPE5jrB0xFRlQgUE9JTlRJTkcgSU5ERVgPFJnrB1JJR0hUIFBPSU5USU5HIElOREVYDxGe6wdVUCBQT0lOVElORyBJTkRFWAIP",
+ "mPIHR04gT0YgVEhFIEhPUk5TBCC3tAdXUklUSU5HIEFJUiBCTE9XIFNNQUxMIFJPVEFUSU9OUxQHtbQHSU5HIE9VVBAUuLQHU1VDSyBTTUFMTCBST1RBVElPTlMUBra0B0lORyBJTgwNurQHQlJFQVRIIEVYSEFMRRMGubQHSU5IQUxFDguQsgdVU0ggQkVUV0VFThIIj7IHTVVMVElQTEUSBo6yB1NJTkdMRQwOq7QHQ0hFRUtTIE5FVVRSQUwTBqq0B1BVRkZFRBMGrLQHU1VDS0VEDQSKtQdPTE9ODgOHtQdNTUEMHI60B0RSRUFNWSBFWUVCUk9XUyBET1dOIE5FVVRSQUwcDI20B05FVVRSQUwgRE9XTiQCkLQHVVAcCo+0B1VQIE5FVVRSQUwNFvazB1lOQU1JQyBBUlJPV0hFQUQgTEFSR0UeBfWzB1NNQUxMFBD9swdFVkVSWSBPVEhFUiBUSU1FFAT3swdGQVNUFAf+swdHUkFEVUFMFAf6swdSRUxBWEVEFAz7swdTSU1VTFRBTkVPVVMgDPyzByBBTFRFUk5BVElORxUD+LMHTE9XFAX5swdURU5TRQwEsLQHRUFSUw0J7LQHWENJVEVNRU5UDRGYtAdZRSBCTElOSyBNVUxUSVBMRRYGl7QHU0lOR0xFEASdtAdXSU5LDxOMtAdCUk9XUyBTVFJBSUdIVCBET1dOHgeLtAdORVVUUkFMHgKKtAdVUA8WqLQHR0FaRS1GTE9PUlBMQU5FIENVUlZFRB8IpLQHU1RSQUlHSFQnDKa0ByBBTFRFUk5BVElORygGpbQHRE9VQkxFFBKptAdXQUxMUExBTkUgQ0lSQ0xJTkcfBae0B1VSVkVEHgihtAdTVFJBSUdIVCYMo7QHIEFMVEVSTkFUSU5HJwaitAdET1VCTEUPC5+0B0xBU0hFUyBET1dOFgqgtAdGTFVUVEVSSU5HFgKetAdVUA8IlrQHUyBDTE9TRUQRC5u0B0hBTEYgQ0xPU0VEFgSZtAdPUEVOEQSUtAdPUEVOEQiVtAdTUVVFRVpFRBEJmrQHV0lERSBPUEVOFQ2ctAdOSU5HIE1PVkVNRU5UDCyHtAdGQUNFIERJUkVDVElPTiBQT1NJVElPTiBOT1NFIEZPUldBUkQgVElMVElORykKiLQHVVAgT1IgRE9XTjMIibQHIFRJTFRJTkcNDpu1B0lMTCBNT0RJRklFUi0yGgGctQczGgGdtQc0GgGetQc1GgGftQc2DgT+tAdOR0VSDROdsgdMSUNLIExBUkdFIE1VTFRJUExFGAabsgdTSU5HTEUSCp+yB1NFUVVFTlRJQUwTDZ6yB01BTEwgTVVMVElQTEUYBpyyB1NJTkdMRQ4a8LQHT09SUExBTkUgU0hPVUxERVIgSElQIE1PVkUND5K0B09SRUhFQUQgQ09OVEFDVBUHkbQHTkVVVFJBTBUIk7QHV1JJTktMRUQNCIi1B1VMTCBTVE9QDA2KsgdHUkFTUCBCRVRXRUVOEgiJsgdNVUxUSVBMRRIGiLIHU0lOR0xFDATrtAdIQUlSDgiFsQdORC1BTkdMRRYGhbAHIElOREVYHA6osQcgTUlERExFIExJVFRMRSQEibEHUklORx0Lw7EHUklORyBMSVRUTEUoBMKxByBPVVQdBfSxB1RIVU1CIgPzsQcgSU4jA/KxB09VVBcMo7EHTElUVExFIElOREVYIxKfsQcgVEhVTUIgSU5ERVggVEhVTUI1BJ6xByBPVVQeApaxB1VQFxLUsQdNSURETEUgUklORyBMSVRUTEUpA9OxByBJTioD0rEHT1VUFyKtsQdSSU5HIERPV04gTUlERExFIFRIVU1CIElOREVYIENST1NTHAazsQdMSVRUTEURBvawB0NJUkNMRRcGgbAHIElOREVYHQWHsAcgQkVOVB4FjbAHSElOR0UeBo+wB01JRERMRSQNqrEHIENST1NTIExJVFRMRSoCm7AHRUQlBqWxB0xJVFRMRSUEh7EHUklORyUFn7AHVEhVTUIeC7uxB1JJTkcgTElUVExFGAyhsQdMSVRUTEUgSU5ERVgfApSxB1VQGBLOsQdNSURETEUgUklORyBMSVRUTEUqBdWxByBCRU5UHwLHsQdVUBgLsbEHUklORyBMSVRUTEUSA+awB0xBVxUXybAHIEZPVVIgRklOR0VSUyBDT05KT0lORUQWHumxB0lOREVYIFRIVU1CIENVUlZFIFRIVU1CIElOU0lERRYc1rEHTUlERExFIFJJTkcgTElUVExFIENPTkpPSU5FRDIF17EHIFNJREUWCOiwB05PIFRIVU1CFg3psAdUSFVNQiBGT1JXQVJEHATnsAdTSURFEgLtsAdVUBQU07AHIEZJVkUgRklOR0VSUyBTUFJFQUQoBdSwByBPUEVOFQWCsAdJTkRFWBoMwLEHIFJJTkcgTElUVExFGwXssQdUSFVNQiAF7bEHIE9QRU4VEtCxB01JRERMRSBSSU5HIExJVFRMRRUI8bAHTk8gVEhVTUIVBOywB09QRU4ZCfCwByBOTyBUSFVNQhoN8rAHVEhVTUIgRk9SV0FSRCAE7rAHU0lERRUN87AHVEhVTUIgRk9SV0FSRBsE77AHU0lERRMG9bAHUkxJQ1VFGRW8sQcgSU5ERVggUklORyBMSVRUTEUgT04aFc+xB01JRERMRSBSSU5HIExJVFRMRSBPThoE9LAHT1BFThEEg7IHRklTVBUcyrAHIEZPVVIgRklOR0VSUyBDT05KT0lORUQgQkVOVBYEhLIHSEVFTBYFgLAHSU5ERVgbBYawByBCRU5UIAydsAcgT1ZFUiBNSURETEUcBoqwB0NVUFBFRBwGi7AHSElOR0VEIgSMsAcgTE9XIwmUsAdNSURETEUgVVAsC6awByBUSFVNQiBTSURFHAaOsAdNSURETEUiBZCwByBCRU5UJw+hsAcgVEhVTUIgU1RSQUlHSFQjCZWwB0NPTkpPSU5FRCwHmLAHIENVUFBFRDMOtbAHIFRIVU1CIEZPUldBUkQtBpmwB0hJTkdFRDMLsrAHIFRIVU1CIFNJREUtCpawB0lOREVYIEJFTlQtC5ewB01JRERMRSBCRU5ULQ20sAdUSFVNQiBGT1JXQVJEMwStsAdTSURFNwWvsAcgQkVOVDgJrrAHQ09OSk9JTkVEJAupsQdST1NTIExJVFRMRSgCmrAHRUQqC7OwByBUSFVNQiBTSURFIwaSsAdISU5HRUQpEqOwByBTUFJFQUQgVEhVTUIgU0lERSMGpLEHTElUVExFIw+RsAdSQUlTRUQgS05VQ0tMRVMkA4axB0lORycFi7EHIEJFTlQoCYyxB0NPTkpPSU5FRCMToLAHU1RSQUlHSFQgVEhVTUIgQkVOVCMFnrAHVEhVTUIoB7+wByBBTkdMRUQvBL6wByBPVVQpBKKwB0JFTlQpB6mwB0NJUkNMRUQqD72wB09OSk9JTkVEIEhJTkdFRCoFqLAHVVBQRUQpBquwB0hJTkdFRCoFqrAHT09LRUQjF6ewB1VQIFNQUkVBRCBUSFVNQiBGT1JXQVJEHA6JsAdSQUlTRUQgS05VQ0tMRR0KurEHSU5HIExJVFRMRRwZu7AHVEhVTUIgQU5HTEVEIElOIE1JRERMRSBVUCkNurAHT1VUIE1JRERMRSBVUCIG67EHQ0lSQ0xFKAu8sAdEIE1JRERMRSBVUCMPt7AHVVBQRUQgTUlERExFIFVQJAbnsQdSTElDVUUlD+ixB1ZFIFRIVU1CIElOU0lERS4F6rEHVU5ERVIiEuWxB0ZPUldBUkQgSU5ERVggQkVOVDAI5LEHU1RSQUlHSFQiBOaxB0hPT0smEMOwB0VEIE1JRERMRSBISU5HRUQwArGwB1VQIgTcsQdTSURFJgrisQcgQk9USCBCRU5UJwrhsQdJTkRFWCBCRU5ULQXjsQdISU5HRScK4LEHVEhVTUIgQkVOVC0J37EHQ09OSk9JTkVELQjesQdESUFHT05BTBwQk7AHVVAgTUlERExFIEhJTkdFRCwQpbAHIFRIVU1CIENPTkpPSU5FRDMEpLAHU0lERRYLmLEHTElUVExFIEJFTlQdBI6xB0RPV04hD5GxByBPVEhFUlMgQ0lSQ0xFRCINkLEHUklQUExFIENVUlZFRCkIj7EHU1RSQUlHSFQdBaCxB0lOREVYIgacsQcgVEhVTUIdDpexB1JBSVNFRCBLTlVDS0xFHQWasQdUSFVNQh4MmbEHT1VDSEVTIFRIVU1CHQKSsQdVUBYWnLAHTUlERExFIEJFTlQgT1ZFUiBJTkRFWB0ExLEHRE9XTh0GzLEHTElUVExFHQ7IsQdSQUlTRUQgS05VQ0tMRR4KzbEHSU5HIExJVFRMRR0VwrAHVEhVTUIgQU5HTEVEIElOREVYIFVQKhHBsAdPVVQgSU5ERVggQ1JPU1NFRDQCwLAHVVAjFLmwB0NJUkNMRUQgSU5ERVggSElOR0VEMQK4sAdVUCQOtrAHVVBQRUQgSU5ERVggVVAjD7CwB0hPT0tFRCBJTkRFWCBVUCMGy7EHTElUVExFHQLGsQdVUB8LybEHIFRIVU1CIFNJREUWCauxB1JJTkcgRE9XThsFt7EHSU5ERVgbBrCxB0xJVFRMRRsGtLEHTUlERExFIQq1sQcgQ09OSk9JTkVEIg+2sQdSQUlTRUQgS05VQ0tMRVMbDq+xB1JBSVNFRCBLTlVDS0xFGwW4sQdUSFVNQhsCrrEHVVAWBfWxB1RIVU1CGxX7sQcgQkVUV0VFTiBJTkRFWCBNSURETEUwCaywByBTVFJBSUdIVCQL/LEHTUlERExFIFJJTkckC/2xB1JJTkcgTElUVExFHAf6sQdGT1JXQVJEHAT2sQdIRUVMHBmCsgdPVkVSIEZPVVIgUkFJU0VEIEtOVUNLTEVTIQv/sQdUV08gRklOR0VSUxwJ+bEHU0lERSBCRU5UIQn4sQdDT05KT0lORUQhCPexB0RJQUdPTkFMHBKBsgdVTkRFUiBGT1VSIEZJTkdFUlMiCoiwB0lOREVYIEJFTlQiCZOxB0xJVFRMRSBVUCINgLIHVEhSRUUgRklOR0VSUyMK/rEHV08gRklOR0VSUxID2rAHTEFUFRXbsAcgQkVUV0VFTiBQQUxNIEZBQ0lOR1MWE8ywB0ZJVkUgRklOR0VSUyBTUFJFQUQpBdCwByBCRU5UKgnOsAdGT1VSIEJFTlQqBtewB0hJTkdFRDAJ2bAHIE5PIFRIVU1CMQrYsAdUSFVNQiBTSURFKg3SsAdUSFVNQiBGT1JXQVJEFwvEsAdPVVIgRklOR0VSUyIFxbAHIEJFTlQjCcewB0NPTkpPSU5FRCwGyLAHIFNQTElUIwbGsAdISU5HRUQWBNywB0hFRUwaFM2wByBGSVZFIEZJTkdFUlMgU1BSRUFELgXRsAcgQkVOVC8Jz7AHRk9VUiBCRU5UGwresAdUSFVNQiBTSURFFgzisAdTUExJVCBDRU5UUkUiC+OwByBUSFVNQiBTSURFLQXksAcgQkVOVBwQ4bAHSU5ERVggVEhVTUIgU0lERRwG5bAHTElUVExFFgrfsAdUSFVNQiBCRU5UHAfgsAdGT1JXQVJEHATdsAdTSURFEQX9sAdISU5HRRYZ1bAHIEZJVkUgRklOR0VSUyBTUFJFQUQgT1BFThgVy7AHT1VSIEZJTkdFUlMgQ09OSk9JTkVEFwWEsAdJTkRFWBwH27EHIEhJTkdFRB0NprEHTUlERExFIExJVFRMRSQEiLEHUklORygKjbEHIENPTkpPSU5FRB0LwbEHUklORyBMSVRUTEUdBfCxB1RIVU1CIgbvsQcgTEFSR0UjBO6xB09QRU4jBN2xB1NJREUkBPGxB01BTEwXBoqxB0xJVFRMRR0GorEHIElOREVYIwadsQcgVEhVTUIeBZuxB1RIVU1CFwbFsQdNSURETEUdDNGxByBSSU5HIExJVFRMRRcIgrEHTk8gVEhVTUIXBPuwB09QRU4bCYGxByBOTyBUSFVNQhwN/LAHVEhVTUIgRk9SV0FSRCIE/7AHU0lERRcEp7EHUklORxsdrLEHIERPV04gSU5ERVggVEhVTUIgSE9PSyBNSURETEUXBf6wB1NNQUxMFxmEsQdUSFVNQiBCRVRXRUVOIE1JRERMRSBSSU5HHQSAsQdTSURFIQ+DsQcgVE9VQ0hJTkcgSU5ERVgSA+uwB09PSxUJ6rAHIENVUkxJQ1VFFhS+sQdJTkRFWCBSSU5HIExJVFRMRSBJTigDvbEHT1VUKAW/sQdVTkRFUhYc2rEHTUlERExFIFJJTkcgTElUVExFIENPTkpPSU5FRDID2bEHIElOMwPYsQdPVVQdBcqxB1RIVU1CFgq5sQdSSU5HIFRIVU1CEQT3sAdPVkFMFRTWsAcgRklWRSBGSU5HRVJTIFNQUkVBRBYFg7AHSU5ERVgWCZWxB0xJVFRMRSBVUBYI+bAHTk8gVEhVTUIWC7KxB1JJTkcgTElUVExFFg36sAdUSFVNQiBGT1JXQVJEHAT4sAdTSURFDQP/swdFQUQQEIa0ByBNT1ZFTUVOVCBDSVJDTEUZEYW0By1GTE9PUlBMQU5FIENVUlZFJQiDtAdTVFJBSUdIVBoPhLQHV0FMTFBMQU5FIENVUlZFJAiBtAdTVFJBSUdIVCQEgrQHVElMVBEDgLQHUklNDBD2tAdMSU1CIENPTUJJTkFUSU9OEQj3tAdMRU5HVEgtMRgB+LQHMhgB+bQHMxgB+rQHNBgB+7QHNRgB/LQHNhgB/bQHNw4S1LQHUCBMT1dFUiBPVkVSIFVQUEVSEBDVtAdVUFBFUiBPVkVSIExPV0VSDxLTtAdTIFBSRVNTRUQgVE9HRVRIRVINDYO1B09DQVRJT04gREVQVEgVCYS1B0hFQUQgTkVDSxcEgbUHSUdIVBUMhrUHTElNQlMgRElHSVRTFQWFtQdUT1JTTxUFgrUHV0lEVEgUEYC1By1GTE9PUlBMQU5FIFNQQUNFFQ//tAdXQUxMUExBTkUgU1BBQ0UMFL20B01PVVRIIENMT1NFRCBDT05UQUNUGQe8tAdGT1JXQVJEGQe7tAdORVVUUkFMEwbWtAdPUk5FUlMSBcG0B0ZST1dOFwXDtAcgT1BFThgIwrQHV1JJTktMRUQSBM20B0tJU1MWCM60ByBGT1JXQVJEFwjPtAdXUklOS0xFRBILxLQHT1BFTiBDSVJDTEUXB8W0B0ZPUldBUkQXBMe0B09WQUwbCci0ByBXUklOS0xFRBwEybQHWUFXThcJyrQHUkVDVEFOR0xFIAnLtAcgV1JJTktMRUQhBMy0B1lBV04XCMa0B1dSSU5LTEVEEgW+tAdTTUlMRRcFwLQHIE9QRU4YCL+0B1dSSU5LTEVEEgXQtAdURU5TRRcI0bQHIEZPUldBUkQYBtK0B1NVQ0tFRBIP2LQHV1JJTktMRVMgRE9VQkxFGwbXtAdTSU5HTEUOGteyB1ZFTUVOVC1ESUFHT05BTCBBV0FZIExBUkdFKALYsgdTVCMG1rIHTUVESVVNIwXVsgdTTUFMTB4S37IHQkVUV0VFTiBBV0FZIExBUkdFMALgsgdTVCsG3rIHTUVESVVNKwXdsgdTTUFMTCYN47IHVE9XQVJEUyBMQVJHRTMC5LIHU1QuBuKyB01FRElVTS4F4bIHU01BTEweDduyB1RPV0FSRFMgTEFSR0UrAtyyB1NUJgbasgdNRURJVU0mBdmyB1NNQUxMFS/sswdGTE9PUlBMQU5FIEFSTSBDSVJDTEUgSElUVElORyBXQUxMIExBUkdFIERPVUJMRT4G6bMHU0lOR0xFOA3rswdNRURJVU0gRE9VQkxFPwboswdTSU5HTEU4DOqzB1NNQUxMIERPVUJMRT4G57MHU0lOR0xFIATzsgdCRU5EIQj6sgdPWCBMQVJHRSQG+bIHTUVESVVNJAX4",
+ "sgdTTUFMTCAF97IHQ0hFQ0shC/ayB09STkVSIExBUkdFJwb1sgdNRURJVU0nBfSyB1NNQUxMIQTusgdST1NTIQ3ZswdVUlZFIENPTUJJTkVEJhW4swdISVRUSU5HIENFSUxJTkcgTEFSR0U2BbezB1NNQUxMLgvHswdGTE9PUiBMQVJHRTQFxrMHU01BTEwmBdezB0xBUkdFKwLYswdTVCYG1rMHTUVESVVNJgXVswdTTUFMTCAS7LIHRE9VQkxFIEFMVEVSTkFUSU5HMgvtsgcgV1JJU1QgRkxFWCcI6rIHU1RSQUlHSFQnCuuyB1dSSVNUIEZMRVggIvSzB0ZJTkdFUiBDSVJDTEVTIEhJVFRJTkcgV0FMTCBET1VCTEU8BvOzB1NJTkdMRSgGqbIHT05UQUNUICG6swdIVU1QIEhJVFRJTkcgQ0VJTElORyBMQVJHRSBET1VCTEU7BryzB1RSSVBMRTUMubMHU01BTEwgRE9VQkxFOwa7swdUUklQTEUtEsmzB0ZMT09SIExBUkdFIERPVUJMRTMMyLMHU01BTEwgRE9VQkxFMxPLswdUUklQTEUgTEFSR0UgVFJJUExFOgzKswdTTUFMTCBUUklQTEUlBdqzB1NNQUxMIAPptAdKQVcgIcCzB0xPT1AgSElUVElORyBDRUlMSU5HIExBUkdFIERPVUJMRTsGvrMHU0lOR0xFNQy/swdTTUFMTCBET1VCTEU7Br2zB1NJTkdMRS0Sz7MHRkxPT1IgTEFSR0UgRE9VQkxFOQbNswdTSU5HTEUzDM6zB1NNQUxMIERPVUJMRTkGzLMHU0lOR0xFJQXbswdTTUFMTCALgLMHUEVBS1MgTEFSR0UmBv+yB01FRElVTSYF/rIHU01BTEwgEOKzB1NIQUtJTkcgUEFSQUxMRUwhFOeyB0lOR0xFIFNUUkFJR0hUIExBUkdFNQLosgdTVDAG5rIHTUVESVVNMAXlsgdTTUFMTCcK6bIHV1JJU1QgRkxFWCAb8bIHVFJJUExFIEFMVEVSTkFUSU5HIE1PVkVNRU5UMwrysgdXUklTVCBGTEVYJxHvsgdTVFJBSUdIVCBNT1ZFTUVOVCcK8LIHV1JJU1QgRkxFWCAawrMHV0FWRSBISVRUSU5HIENFSUxJTkcgTEFSR0U1BcGzB1NNQUxMLQvRswdGTE9PUiBMQVJHRTMF0LMHU01BTEwlBd6zB0xBUkdFJQXdswdTTUFMTCYE3LMHTkFLRSEf8LMHUklTVCBDSVJDTEUgSElUVElORyBXQUxMIERPVUJMRToG77MHU0lOR0xFIAz9sgdaSUdaQUcgTEFSR0UnBvyyB01FRElVTScF+7IHU01BTEwVFaSyB0hJTkdFIERPV04gU0VRVUVOVElBTBsVp7IHU0lERSBUTyBTSURFIFNDSVNTT1JTGxmlsgdVUCBET1dOIEFMVEVSTkFUSU5HIExBUkdFLwWmsgdTTUFMTCMFobIHTEFSR0UjBaKyB1NNQUxMHgqjsgdTRVFVRU5USUFMFSLmswdXQUxMUExBTkUgQVJNIENJUkNMRSBNRURJVU0gRE9VQkxFMQbkswdTSU5HTEUqDOWzB1NNQUxMIERPVUJMRTAG47MHU0lOR0xFHwq6sgdCRU5EIExBUkdFJAa5sgdNRURJVU0kBbiyB1NNQUxMIAjEsgdPWCBMQVJHRSMGw7IHTUVESVVNIwXCsgdTTUFMTB8LwbIHQ0hFQ0sgTEFSR0UlBsCyB01FRElVTSUFv7IHU01BTEwgC72yB09STkVSIExBUkdFJga8sgdNRURJVU0mCL6yB1JPVEFUSU9OJgW7sgdTTUFMTCAEs7IHUk9TUyAWjrMHVVJWRSBIQUxGLUNJUkNMRSBMQVJHRTYCj7MHU1QxBo2zB01FRElVTTEFjLMHU01BTEwmDK2zB0lUVElORyBDSEVTVC0KprMHRlJPTlQgV0FMTCUNirMHUVVBUlRFUiBMQVJHRTICi7MHU1QtBomzB01FRElVTS0FiLMHU01BTEwlDZ+zB1RIRU4gU1RSQUlHSFQnGZGzB1JFRS1RVUFSVEVSIENJUkNMRSBNRURJVU06BZCzB1NNQUxMJA6hswdEIENST1NTIE1FRElVTSwFoLMHU01BTEwfErGyB0RPVUJMRSBBTFRFUk5BVElORzELsrIHIFdSSVNUIEZMRVgmCK+yB1NUUkFJR0hUJgqwsgdXUklTVCBGTEVYHxXyswdGSU5HRVIgQ0lSQ0xFUyBET1VCTEUuBvGzB1NJTkdMRScGqLIHT05UQUNUHxKuswdIVU1QIEhJVFRJTkcgQ0hFU1QsCqezB0ZST05UIFdBTEwkBZSzB0xBUkdFJAaTswdNRURJVU0kBZKzB1NNQUxMHwPotAdKQVcfEq+zB0xPT1AgSElUVElORyBDSEVTVCwKqLMHRlJPTlQgV0FMTCQFl7MHTEFSR0UkBpazB01FRElVTSQFlbMHU01BTEwpB5izByBET1VCTEUfC8qyB1BFQUtTIExBUkdFJQbJsgdNRURJVU0lBciyB1NNQUxMHwelswdTSEFLSU5HIBSssgdJTkdMRSBTVFJBSUdIVCBMQVJHRTQCrbIHU1QvBquyB01FRElVTS8FqrIHU01BTEwmCq6yB1dSSVNUIEZMRVgfErayB1RSSVBMRSBBTFRFUk5BVElORzELt7IHIFdSSVNUIEZMRVgmEbSyB1NUUkFJR0hUIE1PVkVNRU5UJgq1sgdXUklTVCBGTEVYHxebswdXQVZFIENVUlZFIERPVUJMRSBMQVJHRTEGmrMHTUVESVVNMQWZswdTTUFMTCoMnrMHVFJJUExFIExBUkdFMQadswdNRURJVU0xBZyzB1NNQUxMJBO2swdESUFHT05BTCBQQVRIIExBUkdFMga1swdNRURJVU0yBbSzB1NNQUxMJA2wswdISVRUSU5HIENIRVNULAqpswdGUk9OVCBXQUxMIBjuswdSSVNUIENJUkNMRSBGUk9OVCBET1VCTEUyBu2zB1NJTkdMRR8Mx7IHWklHWkFHIExBUkdFJgbGsgdNRURJVU0mBcWyB1NNQUxMDATqtAdORUNLDQuytAdPU0UgQ09OVEFDVBEHsbQHTkVVVFJBTBEHtLQHV0lHR0xFUxIHs7QHUklOS0xFUwwLi7UHUEFSRU5USEVTSVMMFKm1B1JPVEFUSU9OIE1PRElGSUVSLTEwHwGqtQcxHwGrtQcyHwGstQczHwGttQc0HwGutQc1HwGvtQc2HgGhtQcyHgGitQczHgGjtQc0HgGktQc1HgGltQc2HgGmtQc3HgGntQc4HgGotQc5FBfhswctRkxPT1JQTEFORSBBTFRFUk5BVElORysQxbMHIEhJVFRJTkcgQ0VJTElORzQF1LMHRkxPT1IgBuCzB0RPVUJMRSYQxLMHIEhJVFRJTkcgQ0VJTElORy8F07MHRkxPT1IgBt+zB1NJTkdMRSYQw7MHIEhJVFRJTkcgQ0VJTElORy8F0rMHRkxPT1IVE6SzB1dBTExQTEFORSBBTFRFUk5BVEUnEbOzB0lORyBISVRUSU5HIENIRVNUMwqsswdGUk9OVCBXQUxMHwajswdET1VCTEUlDrKzByBISVRUSU5HIENIRVNULgqrswdGUk9OVCBXQUxMHwaiswdTSU5HTEUlDrGzByBISVRUSU5HIENIRVNULgqqswdGUk9OVCBXQUxMDQqTsgdVQiBCRVRXRUVOEAiSsgdNVUxUSVBMRRAGkbIHU0lOR0xFDAmJtQdTRU1JQ09MT04NFe60B0hPVUxERVIgSElQIFBPU0lUSU9OUxkF7bQHU1BJTkUVEvG0B1RJTFRJTkcgRlJPTSBXQUlTVA0YoLIHUVVFRVpFIEZMSUNLIEFMVEVSTkFUSU5HFA6YsgdMQVJHRSBNVUxUSVBMRRoGlrIHU0lOR0xFFAqasgdTRVFVRU5USUFMFQ2ZsgdNQUxMIE1VTFRJUExFGgaXsgdTSU5HTEUNDY2yB1RSSUtFIEJFVFdFRU4TCIyyB01VTFRJUExFEwaLsgdTSU5HTEUNDpWyB1VSRkFDRSBCRVRXRUVOFAeUsgdTWU1CT0xTDAXhtAdURUVUSBEK57QHIEJJVEUgTElQUxII4rQHTU9WRU1FTlQSB+W0B09OIExJUFMZCea0ByBNT1ZFTUVOVBUG47QHVE9OR1VFGwnktAcgTU9WRU1FTlQOD620B05TRSBDSEVFS1MgSElHSBkDr7QHTE9XGQautAdNSURETEUNGeC0B09OR1VFIENFTlRSRSBJTlNJREUgTU9VVEgaDN+0B1NUSUNLSU5HIE9VVBMU3bQHSU5TSURFIE1PVVRIIFJFTEFYRUQTDNq0B0xJQ0tJTkcgTElQUxMT3rQHTU9WRVMgQUdBSU5TVCBDSEVFSxMQ2bQHU1RJQ0tJTkcgT1VUIEZBUhMQ27QHVElQIEJFVFdFRU4gTElQUxcV3LQHVE9VQ0hJTkcgSU5TSURFIE1PVVRIDhf0tAdSU08tRkxPT1JQTEFORSBUV0lTVElORxIV87QHV0FMTFBMQU5FIENVUlZFRCBCRU5EHBDytAdTVFJBSUdIVCBTVFJFVENIDguHsgdVQ0ggQkVUV0VFThIIhrIHTVVMVElQTEUSBoWyB1NJTkdMRQ0wg7MHUkFWRUwtRkxPT1JQTEFORSBST1RBVElPTi1GTE9PUlBMQU5FIEFMVEVSTkFUSU5HMgaCswdET1VCTEUyBoGzB1NJTkdMRScVhrMHV0FMTFBMQU5FIEFMVEVSTkFUSU5HMQaFswdET1VCTEUxBoSzB1NJTkdMRR4Hh7MHU0hBS0lORxMb07IHV0FMTFBMQU5FIEFSTSBTUElSQUwgRE9VQkxFKAbSsgdTSU5HTEUoBtSyB1RSSVBMRR0f0LIHUk9UQVRJT04tRkxPT1JQTEFORSBBTFRFUk5BVElORzEGz7IHRE9VQkxFMQbOsgdTSU5HTEUmFc2yB1dBTExQTEFORSBBTFRFUk5BVElORzAGzLIHRE9VQkxFMAbLsgdTSU5HTEUdB9GyB1NIQUtJTkcMIvW0B1VQUEVSIEJPRFkgVElMVElORyBGUk9NIEhJUCBKT0lOVFMMG++0B1dBTExQTEFORSBTSE9VTERFUiBISVAgTU9WRQIR/usHTEhPVUVUVEUgT0YgSkFQQU4CKqBVTUlMQVIgQUJPVkUgR1JFQVRFUi1USEFOIEFCT1ZFIEVRVUFMUyBTSUdODhufVUxFU1MtVEhBTiBBQk9WRSBFUVVBTFMgU0lHTggN7FRNSU5VUyBTSU1JTEFSCA+eVU9SIEdSRUFURVItVEhBTgsJnVVMRVNTLVRIQU4CB79ETkUgV0FWRQMYmgFHTEUgQ0hBUkFDVEVSIElOVFJPRFVDRVIHHJkBR1JBUEhJQyBDSEFSQUNURVIgSU5UUk9EVUNFUgcem0BISUdILVJFVkVSU0VELTkgUVVPVEFUSU9OIE1BUksHIrlATEVGVC1QT0lOVElORyBBTkdMRSBRVU9UQVRJT04gTUFSSwgTmkBPVy05IFFVT1RBVElPTiBNQVJLByO6QFJJR0hULVBPSU5USU5HIEFOR0xFIFFVT1RBVElPTiBNQVJLBwuPAVNISUZUIFRIUkVFDgKOAVdPBgiOAS1TSElGVC0yDQGPATMDGOijBEhBTEEgQVJDSEFJQyBESUdJVCBFSUdIVBYE5aMERklWRRcD5KMET1VSFgTpowROSU5FFgPhowRPTkUWBeejBFNFVkVOFwLmowRJWBYF46MEVEhSRUUXAuKjBFdPEA3xowROVU1CRVIgRUlHSFRZFwXuowRGSUZUWRgE7aMET1JUWRcG8qMETklORVRZFwvzowRPTkUgSFVORFJFRBsI9KMEVEhPVVNBTkQXB/CjBFNFVkVOVFkYBO+jBElYVFkXA+qjBFRFThgF7KMESElSVFkYBeujBFdFTlRZCA6GG0xFVFRFUiBBQVlBTk5BEAeIG0VFWUFOTkERBYcbWUFOTkEQBpMbSVlBTk5BEBG2G0xQQVBSQUFOQSBCQVlBTk5BGgegG0NBWUFOTkEaB68bREFZQU5OQRsHqRtEQVlBTk5BGgecG0dBWUFOTkEaB6IbSkFZQU5OQRoHmhtLQVlBTk5BGge0G1BBWUFOTkEaB60bVEFZQU5OQRsHpxtUQVlBTk5BEAu5G01CQSBCQVlBTk5BEAaWG1VZQU5OQRAFhRtZQU5OQQ8PvRtEQU5UQUpBIExBWUFOTkEXB7EbTkFZQU5OQRcHwxtTQVlBTk5BDweSG0VFWUFOTkEQBZEbWUFOTkEPB8YbRkFZQU5OQQ8HxBtIQVlBTk5BDweKG0lJWUFOTkEQCJAbTFVVWUFOTkESBY8bWUFOTkEQCI4bUlVVWUFOTkESBY0bWUFOTkEQBYkbWUFOTkEPEp4bS0FOVEFKQSBOQUFTSUtZQVlBDxO3G01BSEFBUFJBQU5BIEJBWUFOTkEbB6EbQ0FZQU5OQRsHsBtEQVlBTk5BHAeqG0RBWUFOTkEbB50bR0FZQU5OQRsHoxtKQVlBTk5BGwebG0tBWUFOTkEbB7UbUEFZQU5OQRsHrhtUQVlBTk5BHAeoG1RBWUFOTkERBbgbWUFOTkEQEMUbVVVSREhBSkEgTEFZQU5OQRkHqxtOQVlBTk5BGQfCG1NBWUFOTkEPB5UbT09ZQU5OQRAFlBtZQU5OQQ8HuxtSQVlBTk5BDw+zG1NBTllBS0EgREFZQU5OQRgHrBtEQVlBTk5BFwefG0dBWUFOTkEXB6YbSkFZQU5OQQ8SpBtUQUFMVUpBIE5BQVNJS1lBWUEXFKUbU0FOWU9PR0EgTkFBS1NJS1lBWUEZBcEbWUFOTkEPB4wbVVVZQU5OQRAFixtZQU5OQQ8HwBtWQVlBTk5BDwe6G1lBWUFOTkEJD+4bSVRIIERJR0lUIEVJR0hUEwTrG0ZJVkUUA+obT1VSEwTvG05JTkUTA+cbT05FEwXtG1NFVkVOFALsG0lYEwXpG1RIUkVFFALoG1dPEwTmG1pFUk8IFvQbUFVOQ1RVQVRJT04gS1VORERBTElZQQgOyhtTSUdOIEFMLUxBS1VOQQ4JghtOVVNWQVJBWUENC4EbQ0FORFJBQklORFUNCYMbVklTQVJHQVlBCBXPG1ZPV0VMIFNJR04gQUVMQS1QSUxMQRMP0RtESUdBIEFFREEtUElMTEEYDPIbR0FFVFRBLVBJTExBGgnzG1lBTlVLSVRUQRgI0xtJUy1QSUxMQRgH2htLT01CVVZBGAnWG1BBQS1QSUxMQRMM2BtHQUVUVEEtUElMTEEVCd8bWUFOVUtJVFRBExDQG0tFVFRJIEFFREEtUElMTEEZCNIbSVMtUElMTEEZCdQbUEFBLVBJTExBFAnbG09NQlUgREVLQRgC2RtWQRoP3BsgSEFBIEFFTEEtUElMTEEfD90bRElHQSBBRUxBLVBJTExBHwveG0dBWUFOVUtJVFRBAiO+TlggUEVUQUxMRUQgQkxBQ0sgQU5EIFdISVRFIEZMT1JFVFRFBRG2Tk9JTlRFRCBCTEFDSyBTVEFSDA3N7wdQSU5XSEVFTCBTVEFSDBSv6gdTVEFSIFdJVEggTUlERExFIERPVAMNhkAtUEVSLUVNIFNQQUNFAxW6TlRFRU4gUE9JTlRFRCBBU1RFUklTSwEJ+e0HS0FURUJPQVJEAg6/5wdJIEFORCBTS0kgQk9PVAMC901FUgIDgOkHVUxMBQ+gTCBBTkQgQ1JPU1NCT05FUwMCqPMHTksBH5ZVTEFOVEVEIEVRVUFMIFRPIE9SIEdSRUFURVItVEhBTiAQmFUgV0lUSCBET1QgSU5TSURFFAmVVUxFU1MtVEhBTh0Ql1UgV0lUSCBET1QgSU5TSURFCBzaVk5PUlRIIEFSUk9XIFdJVEggSE9PS0VEIEhFQUQbDdxW",
+ "UklaT05UQUwgVEFJTAMO88wCVk9OSUMgQVNURVJJU0sCAvftB0VEAxPM7QdFUElORyBBQ0NPTU1PREFUSU9OCQS07AdGQUNFCQak6QdTWU1CT0wFBqrsB1kgRkFDRQMK9eoHVVRIIE9SIFNQWQIM1eYHSUNFIE9GIFBJWlpBAxPB7AdHSFRMWSBGUk9XTklORyBGQUNFCQzC7AdTTUlMSU5HIEZBQ0UCA7NGT1BFBA3YVElORyBMQVJHRSBBTkQOAtdUT1IDCbDnB1QgTUFDSElORQQBpfMHSAEN6e0HTUFMTCBBSVJQTEFORQcI4PwDTVBFUlNBTkQHB+H8A1NURVJJU0sGDLnqB0JMVUUgRElBTU9ORAYF1fwDQ09MT04IA9D8A01NQQoJ6/wDRVJDSUFMIEFUCBCNRE5UQUlOUyBBUyBNRU1CRVIPDP5FV0lUSCBPVkVSQkFSFCj8RVZFUlRJQ0FMIEJBUiBBVCBFTkQgT0YgSE9SSVpPTlRBTCBTVFJPS0UGC+n8A0RPTExBUiBTSUdOBgqKREVMRU1FTlQgT0YQDfdFIFdJVEggT1ZFUkJBUhYo9EVWRVJUSUNBTCBCQVIgQVQgRU5EIE9GIEhPUklaT05UQUwgU1RST0tFBwbY/ANNIERBU0gHCub8A1FVQUxTIFNJR04HD9f8A1hDTEFNQVRJT04gTUFSSwYJ0vwDRlVMTCBTVE9QBhHl/ANHUkVBVEVSLVRIQU4gU0lHTgYM4/wDSFlQSEVOLU1JTlVTBhHR/ANJREVPR1JBUEhJQyBDT01NQQYS2/wDTEVGVCBDVVJMWSBCUkFDS0VUCwvZ/ANQQVJFTlRIRVNJUwsW3fwDVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVAgM5PwDU1MtVEhBTiBTSUdOBgvf/ANOVU1CRVIgU0lHTgYOuOoHT1JBTkdFIERJQU1PTkQGDOr8A1BFUkNFTlQgU0lHTgcI4vwDTFVTIFNJR04GDdb8A1FVRVNUSU9OIE1BUksGD+j8A1JFVkVSU0UgU09MSURVUwcS3PwDSUdIVCBDVVJMWSBCUkFDS0VUDAva/ANQQVJFTlRIRVNJUwwW3vwDVE9SVE9JU0UgU0hFTEwgQlJBQ0tFVAcS90JPTUFOIE5VTUVSQUwgRUlHSFQVBfpCTEVWRU4UBfxCRklGVFkWAvRCVkUYCP5CIEhVTkRSRUQVA/NCT1VSFAT4Qk5JTkUUA/BCT05FFwj9QiBIVU5EUkVEGAj/QlRIT1VTQU5EFAX2QlNFVkVOFQL1QklYFAP5QlRFThUE8kJIUkVFFQX7QldFTFZFFgHxQk8GCdT8A1NFTUlDT0xPTgYF3AVUSUxERQcgu5YEV08gRE9UUyBPVkVSIE9ORSBET1QgUFVOQ1RVQVRJT04GFPqbB1VQLVBPSU5USU5HIEFJUlBMQU5FBhHhVFZFRSBXSVRIIFVOREVSQkFSBQeqVUVSIFRIQU4MDKxVIE9SIEVRVUFMIFRPAwqzVFNIIFBST0RVQ1QCA6NGSUxFBCO77AdJTkcgQ0FUIEZBQ0UgV0lUSCBIRUFSVC1TSEFQRUQgRVlFUxYKuuwHT1BFTiBNT1VUSAgOh+wHRkFDRSBXSVRIIEhBTE8TEI3sB0VBUlQtU0hBUEVEIEVZRVMTBIjsB09STlMSCoPsB09QRU4gTU9VVEgcD4XsByBBTkQgQ09MRCBTV0VBVCEMhOwHU01JTElORyBFWUVTIROG7AdUSUdIVExZLUNMT1NFRCBFWUVTEgyK7AdTTUlMSU5HIEVZRVMeGK3yByBBTkQgSEFORCBDT1ZFUklORyBNT1VUSCMM8PIHVEhSRUUgSEVBUlRTEwmO7AdVTkdMQVNTRVMSBPLyB1RFQVIDCo/sB1JLSU5HIEZBQ0UCDKztB09LSU5HIFNZTUJPTAEEjOgHTkFJTAMCjegHS0UFB/qZByBTWU1CT0wCC6fyB0VFWklORyBGQUNFAhLU5wdPVyBDQVBQRUQgTU9VTlRBSU4EB8LnB0JPQVJERVIEBcRORkxBS0UEA4NMTUFOBw3ETSBXSVRIT1VUIFNOT1cBAQ5PAgm9TUNDRVIgQkFMTAMC5vMHS1MCCa0BRlQgSFlQSEVOBQnm5gdJQ0UgQ1JFQU0FEazrB1NIRUxMIEZMT1BQWSBESVNLBATO8gdCQUxMBBSUR1dBUkUtRlVOQ1RJT04gU1lNQk9MAhvKngRHRElBTiBDT01CSU5JTkcgQ1VSVkUgQUJPVkUYBcueBEJFTE9XEgnIngRET1QgQUJPVkUWBcaeBEJFTE9XEgrMngRIT09LIEFCT1ZFFwXNngRCRUxPVxIPzp4ETE9ORyBIT09LIEJFTE9XEgrPngRSRVNIIEJFTE9XEgzQ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TUZGIE9GIEFFU0NVTEFQSVVTCQaaTUhFUk1FUwMNg+sHTVBFRCBFTlZFTE9QRQMMzfMHTkRJTkcgUEVSU09OAw6qTFIgQU5EIENSRVNDRU5UBQbbREVRVUFMUwUIoU5PRiBEQVZJRAYHxkVQRVJBVE9SBRTqV1dJVEggTEVGVCBIQUxGIEJMQUNLChDrV1JJR0hUIEhBTEYgQkxBQ0sEEZYBVCBPRiBHVUFSREVEIEFSRUEJBwFIRUFESU5HCQ6WAVBST1RFQ1RFRCBBUkVBCQ2GAVNFTEVDVEVEIEFSRUEKBZgBVFJJTkcJBAJURVhUAwSJ7QdUSU9OBA396wdVRSBPRiBMSUJFUlRZAg6C7QdFQU0gTE9DT01PVElWRQUI3OYHSU5HIEJPV0wDE7xcTk9HUkFQSElDIEZVTEwgU1RPUAMI+vQHVEhPU0NPUEUCCsX3B0lDSyBGSUdVUkUMDcf3ByBMRUFOSU5HIExFRlQVBcj3B1JJR0hUDRDG9wdXSVRIIEFSTVMgUkFJU0VEEgXJ9wdEUkVTUwIJ4OsHT0NLIENIQVJUAwbxR1BXQVRDSAIMz+kHUkFJR0hUIFJVTEVSCATkR05FU1MEBtPmB1dCRVJSWQoHv50HIFNZTUJPTAMXqU5FU1MgT1VUTElORUQgV0hJVEUgU1RBUgMT40RJQ1RMWSBFUVVJVkFMRU5UIFRPBA2cAU5HIFRFUk1JTkFUT1IEGoOZB1BFRCBET1dOLVBPSU5USU5HIFRSSUFOR0xFCBaAmQdMRUZULVBPSU5USU5HIFRSSUFOR0xFCBeCmQdSSUdIVC1QT0lOVElORyBUUklBTkdMRQgUgZkHVVAtUE9JTlRJTkcgVFJJQU5HTEUCAZMBUwIPmecHVURJTyBNSUNST1BIT05FAw7Z8gdGRkVEIEZMQVRCUkVBRAMC0+0HUEECAQJYAQIaVUIDDIhBU0NSSVBUIEVJR0hUCwqMQVFVQUxTIFNJR04KBIVBRklWRQsDhEFPVVIKEI1BTEVGVCBQQVJFTlRIRVNJUwoFi0FNSU5VUwoEiUFOSU5FCgOBQU9ORQoJikFQTFVTIFNJR04KEY5BUklHSFQgUEFSRU5USEVTSVMKBYdBU0VWRU4LAoZBSVgKBYNBVEhSRUULAoJBV08KBIBBWkVSTwQZ+VJFVCBBQk9WRSBSSUdIVFdB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SFJSSUFHRSBSRVRVUk4LEJBIREFUQSBMSU5LIEVTQ0FQRQwFoUhFTEVURREJpUggRk9STSBUV08SEalITUVESVVNIFNIQURFIEZPUk0SHqhIUkVDVEFOR1VMQVIgQ0hFQ0tFUiBCT0FSRCBGT1JNEhmnSFNRVUFSRSBDSEVDS0VSIEJPQVJEIEZPUk0NEZRIVklDRSBDT05UUk9MIEZPVVIaA5FIT05FGgWTSFRIUkVFGwKSSFdPCw2ZSEVORCBPRiBNRURJVU0SBINIVEVYVBMLhEhSQU5TTUlTU0lPTh4Gl0ggQkxPQ0sNBYVIUVVJUlkMBZtIU0NBUEULDpxIRklMRSBTRVBBUkFUT1IMCIxIT1JNIEZFRUQLD51IR1JPVVAgU0VQQVJBVE9SCxWJSEhPUklaT05UQUwgVEFCVUxBVElPTgsJikhMSU5FIEZFRUQLDcXqB01BUktTIENIQVBURVILFJVITkVHQVRJVkUgQUNLTk9XTEVER0UNBaRIV0xJTkUMA4BIVUxMCxCeSFJFQ09SRCBTRVBBUkFUT1ILEM9CU0FNQVJJVEFOIFNPVVJDRQwHj0hISUZUIElOEQOOSE9VVAwEoEhQQUNFDA+BSFRBUlQgT0YgSEVBRElORxQEgkhURVhUDAmaSFVCU1RJVFVURRUJpkggRk9STSBUV08MD5ZIWU5DSFJPTk9VUyBJRExFCxKXV1RZUEUgQSBFTEVDVFJPTklDUwsOn0hVTklUIFNFUEFSQVRPUgsTi0hWRVJUSUNBTCBUQUJVTEFUSU9OAwWv",
+ "Rk1FVFJZAgEWTgMGzeoHQUdPR1VFAw0WQ0hST05PVVMgSURMRQIWjw5SSUFDIEFCQlJFVklBVElPTiBNQVJLBwfKDkJBUlJFS0gHEYYOQ09MT04gU0tFV0VEIExFRlQUBYcOUklHSFQJCYoOTlRSQUNUSU9OBxS5DkRPVFRFRCBaTEFNQSBBTkdVTEFSFAq4DkhPUklaT05UQUwHEIAORU5EIE9GIFBBUkFHUkFQSAgKvQ5TQVNBIEFCT1ZFDQW+DkJFTE9XBwzADkZFTUlOSU5FIERPVAcTjQ5IQVJLTEVBTiBBU1RFUklTQ1VTEAmMDk1FVE9CRUxVUxAGiw5PQkVMVVMICroOQkFTQSBBQk9WRQ0Fuw5CRUxPVwwNvA4tRVNBU0EgRE9UVEVECA+FDk9SSVpPTlRBTCBDT0xPTgcMkA5MRVRURVIgQUxBUEgOBJIOQkVUSA4GlQ5EQUxBVEgPEpYOT1RMRVNTIERBTEFUSCBSSVNIDgGlDkUODaQORklOQUwgU0VNS0FUSA4Fkw5HQU1BTBMJlA4gR0FSU0hVTkkOApcOSEUQApoOVEgOBJ8OS0FQSA4GoA5MQU1BREgODeYQTUFMQVlBTEFNIEJIQRgC4RBKQRgD6BBMTEEaAukQTEEYA+AQTkdBGQLkEE5BGgLlEE5BGQLiEFlBGALnEFJBGAPqEFNTQRgD4xBUVEEPAqEOSU0OA6IOTlVODgKmDlBFEAutDlJTSUFOIEJIRVRIFgevDkRIQUxBVEgWBq4OR0hBTUFMDgSpDlFBUEgOC6cOUkVWRVJTRUQgUEUPA6oOSVNIDgWoDlNBREhFDwajDkVNS0FUSA8Dqw5ISU4PCc8OT0dESUFOIEZFFgXODktIQVBIFgXNDlpIQUlODxCRDlVQRVJTQ1JJUFQgQUxBUEgOA6wOVEFXDwObDkVUSBIJnA4gR0FSU0hVTkkOA5gOV0FXDgSdDllVREgSA54OIEhFDgSZDlpBSU4HBckOTVVTSUMHEscOT0JMSVFVRSBMSU5FIEFCT1ZFFAXIDkJFTE9XBwywDlBUSEFIQSBBQk9WRQ4FsQ5CRUxPVw4Gsg5ET1RURUQHCcEOUVVTSFNIQVlBBwu2DlJCQVNBIEFCT1ZFDQW3DkJFTE9XCAfCDlVLS0FLSEEIBL8OV0FIQQcPhA5TVUJMSU5FQVIgQ09MT04W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4hhWT0NBTElDIEwbAeMYTBoBwxhSGwHEGFICCLhBTkdFIFNJR04DFL7nB05JUyBSQUNRVUVUIEFORCBCQUxMAwH6TVQCB+rzB1NUIFRVQkUCGMGmB1RSQUdSQU0gRk9SIEFDQ1VNVUxBVElPTg8GmaYHRFZBTkNFDwrRpgdHR1JBVkFUSU9ODwWMpgdTQ0VOVA4HiaYHQkFSUklFUg8Oo6YHT0xEIFJFU09MVVRJT04PDI6mB1JBTkNISU5HIE9VVA4GhqYHQ0VOVFJFDwWhpgdIQU5HRQ8LvaYHTE9TRUQgTU9VVEgTBKamB05FU1MSA8+mB1VSRQ8JzqYHT01QTEVUSU9OEwXSpgdJQU5DRRAHuKYHTlNUQU5DWREElaYHVEFDVBIGnqYHRU5USU9OEgeLpgdSQVJJRVRZDgnIpgdEQVJLRU5JTkcPC6KmB0VDSVNJVkVORVNTEBmPpgdGRUNUSVZFTkVTUyBPUiBESVNUT1JUSU9OEAfHpgdQQVJUVVJFDwvUpgdJRkZJQ1VMVElFUxAKvKYHTUlOSVNITUVOVBEEyaYHTUlORxAIkKYHVkVSR0VOQ0UPBMOmB09VQlQPBaCmB1VUSUVTDgScpgdFQVNFDwzCpgdNQkVMTElTSE1FTlQPCbCmB05DT1VOVEVSUxAHn6YHREVBVk9VUhAJs6YHTEFSR0VNRU5UDwe6pgdURVJOSVRZDwnKpgdYSEFVU1RJT04OB9CmB0ZBSUxVUkUPBbamB0xJR0hUDwiYpgdPTExPV0lORxAH1qYHU1RFUklORw8Kh6YHVUxMIENJUkNMRRIEq6YHTkVTUw4JqKYHR0FUSEVSSU5HFwO/pgcgSU4PDK+mB09JTkcgVE8gTUVFVA8IsqYHUkVBVE5FU1MPCr6mB1VBUkRFRE5FU1MOCM2mB0hBUkRORVNTDwuWpgdPTERJTkcgQkFDSw4IkqYHSU5DUkVBU0UQA8amB05FUg4DnaYHSk9ZDg2KpgdLRUVQSU5HIFNNQUxMDwanpgdJTlNISVAOCdWmB0xBQk9VUklORxAKraYHVyBPUiBNT0RFTA8FpaYHRUdJT04OB8CmB01BU1NJTkcPBrmmB0VBU1VSRQ8EiKYHSVJFRA4M06YHT04gVEhFIFZFUkdFDwmNpgdQUE9TSVRJT04OB6SmB1BBQ0tJTkcQBbSmB1RURVJODwqTpgdFTkVUUkFUSU9ODwWqpgdVUklUWQ4FlKYHUkVBQ0gQBZqmB0xFQVNFEAespgdTSURFTkNFEgabpgdTVEFOQ0URBa6mB1BPTlNFDwW1pgdJVFVBTA4Jy6YHU0VWRVJBTkNFDwbFpgdJTktJTkcPB8ymB1RPUFBBR0URArGmB1ZFEAappgdSRU5HVEgOBbumB1VOSVRZDhO3pgdWQVNUTkVTUyBPUiBXQVNUSU5HDgeXpgdXQUlUSU5HEAPEpgdUQ0gODJGmB1lPVVRIRlVMTkVTUwEPpw9IQUFOQSBBQUJBQUZJTEkIBqYPQkFGSUxJBwesD0VCRUZJTEkICKkPRUJFRUZJTEkICK0PWUJFWUZJTEkHB6gPSUJJRklMSQcLog9MRVRURVIgQUlOVQ8Ehw9MSUZVDgOED0JBQQ4Jlw9DSEFWSVlBTkkOBp8PREFBREhVEAaRD1ZJWUFOSQ8Fiw9IQUFMVQ4Fig9GQUFGVQ4Fjg9HQUFGVQ8Fow9IQUlOVQ8Ijw9OQVZJWUFOSQ4DgA9IQUEPA5kPSEFBDgiWD0pBVklZQU5JDgWGD0tBQUZVDwOaD0hBQQ4FjQ9MQUFNVQ8IhQ9IQVZJWUFOSQ4FiQ9NRUVNVQ4DsQ9OQUEPBIIPT09OVQ4IlQ9QQVZJWUFOSQ4FpA9RQUFGVQ4Dgw9SQUEOBp4PU0FBREhVDwSQD0VFTlUPCIEPSEFWSVlBTkkQBJ0PRUVOVQ4Ikw9UQVZJWUFOSQ8DjA9IQUESApsPTFUPAaAPTw8DmA9UQUEOBYgPVkFBVlUOBaUPV0FBVlUOA5QPWUFBDgOcD1pBQRAGkg9WSVlBTkkPAaEPTwcJrw9PQUJPQUZJTEkIBq4PQk9GSUxJCAirD09CT09GSUxJBwWwD1NVS1VOBweqD1VCVUZJTEkDFtocSSBDSEFSQUNURVIgQU5HS0hBTktIVQ8JmhxCTyBCQUlNQUkPCIgcQ0hPIENIQU4XAYocRxUDiRxJTkcVAowcT0UPCI4cRE8gQ0hBREESA5QcREVLDwWdHEZPIEZBFAGfHE4RBc8cTkdNQU4PBqscSE8gSElQEgauHE5PS0hVSw8IghxLSE8gS0hBSRUChRxPThUDgxxVQVQVA4QcV0FJEweGHFJBS0hBTkcSA9scTVVUEAWBHE8gS0FJDwvFHExBS0tIQU5HWUFPEAesHE8gQ0hVTEESBKUcTElORxABphxVDwzLHE1BSSBDSEFUVEFXQRMCyBxFSxMIsRxIQU4tQUtBVBMDyRxUSE8UAsocUkkSBsccVEFJS0hVEgXGHFlBTU9LEAShHE8gTUEPB4ccTkdPIE5HVRAHzRxJS0hBSElUEAWTHE8gTkVOEwGZHFUPBa0cTyBBTkcPCa8cUEFJWUFOTk9JEAa6HEhJTlRIVREGnhxPIFBIQU4VA5wcVU5HEwegHFNBTVBIQU8QBZscTyBQTEEPBqMcUk8gUlVBEAGkHFUPBrAcU0FSQSBBFQGyHEEVAcEcRRUKxBxJIE1BSU1BTEFJGwPDHFVBThUBsxxNFAHAHEUUAbQcSRUBtRxJFAHCHE8UAbgcVRUBthxFFgG3HEUVAbkcVRAGqRxPIFJVU0kSBKgcU0FMQRMBixxPEwKqHFVBDwvMHFRIQU5USEFLSEFUEQyRHE8gTkFOR01PTlRITxMHkhxQSFVUSEFPEwaXHFRIQUhBThYBkBxOFQOYHE9ORxUDlhxVTkcQB48cTyBQQVRBSxIDlRxUQU8PB6ccV08gV0FFTg8IzhxZQU1BS0tBThAFohxPIFlBSxMDjRxJTkcGE78cVVJSRU5DWSBTWU1CT0wgQkFIVAUL2BxESUdJVCBFSUdIVAsE1RxGSVZFDAPUHE9VUgsE2RxOSU5FCwPRHE9ORQsF1xxTRVZFTgwC1hxJWAsF0xxUSFJFRQwC0hxXTwsE0BxaRVJPAhKEREVSRSBET0VTIE5PVCBFWElTVAYGg0RFWElTVFMFBLRERk9SRQQJ51NNT0RZTkFNSUMGBaHmB01FVEVSAwPKnQdUSVMCDqHvB0lOIEdSRUVLIENST1NTBQeo7wdTQUxUSVJFBgSJQFBBQ0UECZTyB0tJTkcgRkFDRQMOyfIHUkQgUExBQ0UgTUVEQUwCCvT0B09ORyBTQU5EQUwDDK3pB1VHSFQgQkFMTE9PTgIQsesHUkVFIEJVVFRPTiBNT1VTRQYY9lRDT05TRUNVVElWRSBFUVVBTFMgU0lHTlMGEcBPRElNRU5TSU9OQUwgQU5HTEUHDtZAT1QgUFVOQ1RVQVRJT04GELFWTEVGVFdBUkRTIEFSUk9XUwcUn01JTkVTIENPTlZFUkdJTkcgTEVGVBcFnk1SSUdIVAYTp+sHTkVUV09SS0VEIENPTVBVVEVSUwYSwu8HUE9JTlRFRCBCTEFDSyBTVEFSBgrk6wdSQVlTIEFCT1ZFCwXl6wdCRUxPVwsE5usHTEVGVAsF5+sHUklHSFQHEPZDSUdIVFdBUkRTIEFSUk9XUwYO6+sHU1BFRUNIIEJVQkJMRVMFJqNPLUQgQk9UVE9NLUxJR0hURUQgUklHSFRXQVJEUyBBUlJPV0hFQUQILJtXTEVGVC1MSUdIVEVEIERPV05XQVJEUyBFUVVJTEFURVJBTCBBUlJPV0hFQUQIK5lXUklHSFQtTElHSFRFRCBVUFdBUkRTIEVRVUlMQVRFUkFMIEFSUk9XSEVBRAgrmFdUT1AtTElHSFRFRCBMRUZUV0FSRFMgRVFVSUxBVEVSQUwgQVJST1dIRUFEFBSiT1JJR0hUV0FSRFMgQVJST1dIRUFEHxWaV0VRVUlMQVRFUkFMIEFSUk9XSEVBRAYHu1xFTSBEQVNIBgyEQFBFUi1FTSBTUEFDRQIOzugHVU1CUyBET1dOIFNJR04HB83oB1VQIFNJR04DE8hNTkRFUiBDTE9VRCBBTkQgUkFJTgcFiExTVE9STQEimB5JQkVUQU4gQVNUUk9MT0dJQ0FMIFNJR04gLUtIWVVEIFBBGgyZHlNET05HIFRTSFVHUxsUlx5HUkEgR0NBTiAtQ0hBUiBSVEFHUwgbwh9DQU5USUxMQVRJT04gU0lHTiBDQU5HIFRFLVUaCsAfSEVBVlkgQkVBVBoKwR9MSUdIVCBCRUFUGgrDH1NCVUIgLUNIQUwIC6geRElHSVQgRUlHSFQOBKUeRklWRQ8DpB5PVVIOCrEeSEFMRiBFSUdIVBMErh5GSVZFFAOtHk9VUhMEsh5OSU5FEwOqHk9ORRMFsB5TRVZFThQCrx5JWBMFrB5USFJFRRQCqx5XTxMEsx5aRVJPDgSpHk5JTkUOA6EeT05FDgWnHlNFVkVODwKmHklYDgWjHlRIUkVFDwKiHldPDgSgHlpFUk8ICb4fS1UgUlUgS0hBEQ2/HyBCWkhJIE1JRyBDQU4ICeAeTEVUVEVSIC1BDwHoHkEPAtYeQkEQAtceSEEPAsUeQ0EQAsYeSEEPAtEeREEQAsweREERAs0eSEEQAtIeSEEQAtseWkERAtweSEEPDeoeRklYRUQtRk9STSBSQQ8Cwh5HQRACwx5IQQ8C5x5IQQ8Cxx5KQQ8C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SUxJTkcgUk9CT1QgRlJBTUUtMR4B95sHMgoT25gHT1JXQVJELUZBQ0lORyBST0JPVBkF8JsHVU5ORVIJCKBGSU5URUdSQUwJENlcTEVGVCBQQVJFTlRIRVNJUw0N2pgHLUZBQ0lORyBST0JPVBYN7JsHVU5ORVIgRlJBTUUtMSIB7psHMgkR2lxSSUdIVCBQQVJFTlRIRVNJUw4N3JgHLUZBQ0lORyBST0JPVBcN6JsHVU5ORVIgRlJBTUUtMSMB6psHMgkMuVxTRUNUSU9OIFNJR04KDuabB1RBTkRJTkcgUEVSU09OBgGp5wdUBCHo9wdKVVNUSUZJRUQgTE9XRVIgSEFMRiBCTEFDSyBDSVJDTEUZDOD3B1dISVRFIENJUkNMRQQnh5wHTEVGVCBCTEFDSyBMRUZULVBPSU5USU5HIFNNQUxMIFRSSUFOR0xFCQacRkNPUk5FUgoDj0ZST1AJDKJcSEFMRiBCUkFDS0VUCSrv9wdKVVNUSUZJRUQgTE9XRVIgUklHSFQgUVVBUlRFUiBCTEFDSyBDSVJDTEUEC9xHUEFSRU5USEVTSVMEDJ1GUklHSFQgQ09STkVSCwOORlJPUAoMo1xIQUxGIEJSQUNLRVQKKez3B0pVU1RJRklFRCBMT1dFUiBMRUZUIFFVQVJURVIgQkxBQ0sgQ0lSQ0xFBA60R1NRVUFSRSBCUkFDS0VUBBbgR1RPUlRPSVNFIFNIRUxMIEJSQUNLRVQEGJ3qB1dJVEggVVBXQVJEUyBBUlJPVyBBQk9WRQIxweQHUlRPSVNFIFNIRUxMIEJSQUNLRVRFRCBDSksgVU5JRklFRCBJREVPR1JBUEgtNEUwOTECwuQHOEMvBMfkBzUyREQwA8PkB0I4OS8ExeQHNjI1MzADyOQHNTU3MAPA5Ac3MkMvBMTkBzcwQjkwA8bkBzZENxkWquIHTEFUSU4gQ0FQSVRBTCBMRVRURVIgUwIKsEZUQUwgUlVOT1VUAwqtxQdPIExFVFRFUiBBDQGrxQdFDAKRxQdCQQ0JrMUHUkVBVEhZIEFFFAGnxQdFFQGpxQdPFAGixQdJFQGkxQdVDAOaxQdDSEEMApPFB0RBDAGmxQdFDQGoxQdPDAKVxQdHQQwCnsUHSEEMAaHFB0kNAaPFB1UMAp3FB0pBDAKUxQdLQQwCoMUHTEEMApbFB01BDAKXxQdOQQ0CmMUHR0EMAarFB08MApDFB1BBDAKfxQdSQQwCmcUHU0EMApLFB1RBDAGlxQdVDAKcxQdXQQwCm8UHWUEFEK7FB1NJR04gUklTSU5HIFRPTkUBCLLrB1JBQ0tCQUxMBAOc7QdUT1IDDKJCREUgTUFSSyBTSUdOAwKG7QdJTgMBiu0HTQQEi+0HIENBUgMH11dOU1BMVVRPBRPbVVZFUlNBTCBJTlRFUlNFQ1RJT04CDI71B0VBU1VSRSBDSEVTVAMIvJ0HRSBTWU1CT0wCFcpTSUFOR0xFIFdJVEggRE9UIEFCT1ZFDg/G7QdST1VOREVEIENPUk5FUlMOEM1TU0VSSUZTIEFUIEJPVFRPTQ4Iy1NVTkRFUkJBUggYnU8tSEVBREVEIFJJR0hUV0FSRFMgQVJST1cGC6NAVUxBUiBCVUxMRVQLDKntB0ZMQUcgT04gUE9TVAsF0OkHUlVMRVIDBd1AQ09MT04DC7HqB0RFTlQgRU1CTEVNAw63TEdSQU0gRk9SIEVBUlRIDASyTEZJUkUMBrBMSEVBVkVODASxTExBS0UMCLZMTU9VTlRBSU4MB7NMVEhVTkRFUgwFtUxXQVRFUg0DtExJTkQDEvZVUExFIENPTE9OIE9QRVJBVE9SBwbLXERBR0dFUgcq6FRIT1JJWk9OVEFMIEJBUiBXSVRIIERPVUJMRSBWRVJUSUNBTCBTVFJPS0UbFulUVFJJUExFIFZFUlRJQ0FMIFNUUk9LRQcIrURJTlRFR1JBTAcT+FVORVNURUQgR1JFQVRFUi1USEFODgn3VUxFU1MtVEhBTgcE+1NQTFVTCAS0QFJJTUUHF/tVU09MSURVUyBCSU5BUlkgUkVMQVRJT04HBctEVElMREUHHPRVVkVSVElDQUwgQkFSIEJJTkFSWSBSRUxBVElPThQJgFNERUxJTUlURVIUD6pFUklHSFQgVFVSTlNUSUxFFBb1VVdJVEggSE9SSVpPTlRBTCBTVFJPS0UCA8zzB09MTAUFju0HRVlCVVMDBYr1B01CT05FAwPG5wdQSFkECvnmB0lDQUwgRFJJTksJBKDoB0ZJU0gCAqhFVUUEEN9XIExJR0hUIE1PT04gQVJUQQMEuucHTVBFVAEKrkFVR1JJSyBTSUdOAgO35gdMSVADGcmnBFUtVElHQUxBUkkgQVUgTEVOR1RIIE1BUksOCdCnBENPTkpPSU5FUg4F1KcEREFOREEPC9WnBE9VQkxFIERBTkRBDg/SpwRHRU1JTkFUSU9OIE1BUksOCICnBExFVFRFUiBBFgGBpwRBFgGOpwRJFgGRpwRVFQKopwRCQRYCqacESEEVApenBENBFgKYpwRIQRUCo6cEREEWAp6nBERBFwKfpwRIQRYCpKcESEEVAounBEVFFQKUpwRHQRYClacESEEVArKnBEhBFQGCpwRJFgGDpwRJFQKZpwRKQRYCmqcESEEVApKnBEtBFgKTpwRIQRUCracETEEWArOnBExBFwK1pwRMQRUCqqcETUEVAqWnBE5BFgKWpwRHQRYCoKcETkEWApunBFlBFQKQpwRPTxUCpqcEUEEWAqenBEhBFQKspwRSQRYCtKcEUkEVArGnBFNBFgKvpwRIQRYCsKcEU0EVAqGnBFRBFgKipwRIQRYCnKcEVEEXAp2nBEhBFQGEpwRVFgGFpwRVFQKupwRWQRYIiKcET0NBTElDIEweAYmnBEwdAYanBFIeAYenBFIVAqunBFlBDgXRpwRSRVBIQQ4NzKcEU0lHTiBBTlVTVkFSQRQHt6cEVkFHUkFIQRMQyqcEQ0FORFJBIEFOVU5BU0lLQRMNz6cETE9PUEVEIFZJUkFNQRML16cET00gUFVTSFBJS0ETBdOnBFBMVVRBEw7YpwRTSFJJSSBQVVNIUElLQRMGzqcEVklSQU1BFQXNpwRTQVJHQQ4T4qcEVkVESUMgVE9ORSBBTlVEQVRUQRkH4acEU1ZBUklUQQ8M",
+ "uKcET1dFTCBTSUdOIEFBGgHFpwRJGgHIpwRVGQLCpwRFRRkBuacESRoBuqcESRkCx6cET08ZAbunBFUaAbynBFUZCb+nBFZPQ0FMSUMgTCIBwKcETCEBvacEUiIBvqcEUgILw/IHTUJMRVIgR0xBU1MCBIPzB1JLRVkEDbpBSVNIIExJUkEgU0lHTgMNy0JORUQgQU1QRVJTQU5ECASiU05HTEUHDsJXQkxBQ0sgUEVOVEFHT04NC8pNU0hPR0kgUElFQ0UHCbJCQ0FQSVRBTCBGCASyXE9NTUEHBrhcREFHR0VSCAqLQ0lHSVQgVEhSRUUOAopDV08HF6lCR1JFRUsgU01BTEwgTEVUVEVSIElPVEEHGNbsB05PUlRIIEVBU1QgUE9JTlRJTkcgTEVBRg0S1OwHV0VTVCBQT0lOVElORyBMRUFGCQaZRlQgU0lHTgcMj+sHT0sgSEFORCBTSUdOBxTBQlNBTlMtU0VSSUYgQ0FQSVRBTCBHGgHCQkwaAcRCWQgItVxFTUlDT0xPTggGzkJNQUxMIEYIF9fsB09VVEggRUFTVCBQT0lOVElORyBMRUFGDRLV7AdXRVNUIFBPSU5USU5HIExFQUYHEclNV0hJVEUgU0hPR0kgUElFQ0UDA6LoB1RMRQEYuU5XRUxWRSBQT0lOVEVEIEJMQUNLIFNUQVICF4DqB0lTVEVEIFJJR0hUV0FSRFMgQVJST1dTAh7RQE8gQVNURVJJU0tTIEFMSUdORUQgVkVSVElDQUxMWQQMsOsHQlVUVE9OIE1PVVNFBBj1VENPTlNFQ1VUSVZFIEVRVUFMUyBTSUdOUwQKpUBET1QgTEVBREVSCAvaQFBVTkNUVUFUSU9OBxqqXFMgT1ZFUiBPTkUgRE9UIFBVTkNUVUFUSU9OBAaV6QdIRUFSVFMEGNVUSU5URVJTRUNUSU5HIExPR0lDQUwgQU5EGQLWVE9SBA7JU0pPSU5FRCBTUVVBUkVTBBSHVExPR0lDQUwgQU5EIE9QRVJBVE9SDAuIVE9SIE9QRVJBVE9SBBHs6AdNRU4gSE9MRElORyBIQU5EUwQaiJkHUklOR1MgQUxJR05FRCBIT1JJWk9OVEFMTFkEDurrB1NQRUVDSCBCVUJCTEVTBBPt6AdXT01FTiBIT0xESU5HIEhBTkRTAwi6XC1FTSBEQVNIABOThwRVR0FSSVRJQyBMRVRURVIgQUlOEQOAhwRMUEEQBIGHBEJFVEEQBYSHBERFTFRBEQOPhwRIQUwQBYKHBEdBTUxBEQSZhwRIQUlOEAKFhwRITxICiIcEVEEQAZuHBEkQA4uHBEtBRhECg4cESEEQBY2HBExBTURBEAOOhwRNRU0QA5CHBE5VThAClIcEUFUQBJaHBFFPUEEQBZeHBFJBU0hBEASVhwRTQURFEgOShwRNS0ERA4yHBEhJThECnYcEU1UQA4mHBFRFVBEFmIcESEFOTkERAZqHBE8QAZyHBFUQAoaHBFdPEAOKhwRZT0QQBIeHBFpFVEERAZGHBFUJDJ+HBFdPUkQgRElWSURFUgEHgkxNQlJFTExBCArxTSBPTiBHUk9VTkQJD5RMV0lUSCBSQUlOIERST1BTAQyS7AdOQU1VU0VEIEZBQ0UCDtFXQ0VSVEFJTlRZIFNJR04CBr9AREVSVElFAwiMR08gU1lNQk9MAgqE8wdJQ09STiBGQUNFAwKqRE9OBR3IVCBBQk9WRSBCQVIgQUJPVkUgSU5URVJTRUNUSU9ODAzGVElOVEVSU0VDVElPTgYcylRCRVNJREUgQU5EIEpPSU5FRCBXSVRIIFVOSU9OBg/FVFdJVEggTE9HSUNBTCBPUgsKwVRNSU5VUyBTSUdOCwfCVE9WRVJCQVIDCx9UIFNFUEFSQVRPUgQJ+ldFRCBTWU1CT0wDF/JMVkVSU0FMIFJFQ1lDTElORyBTWU1CT0wCGq9NTUFSUklFRCBQQVJUTkVSU0hJUCBTWU1CT0wBFr1TUCBBUlJPVyBUSFJPVUdIIENJUkNMRQgEg0ZIRUFEDBykRiBCRVRXRUVOIFRXTyBIT1JJWk9OVEFMIEJBUlMNErlXSU4gQSBSRUNUQU5HTEUgQk9YAyDRUkJBUkIgTEVGVCBET1dOIEJBUkIgTEVGVCBIQVJQT09OFw3NUlJJR0hUIEhBUlBPT04IHMxSUklHSFQgRE9XTiBCQVJCIExFRlQgSEFSUE9PThgNz1JSSUdIVCBIQVJQT09OAwqVQ0RPV04gQVJST1cNCqhDIFdJVEggQkFTRQgLjVZCTEFDSyBBUlJPVwgM1UNET1VCTEUgQVJST1cIENnwB1NBTlMtU0VSSUYgQVJST1cIFeVWVFJJQU5HTEUtSEVBREVEIEFSUk9XCAvzQ1dISVRFIEFSUk9XAwn+UkZJU0ggVEFJTAMX8EVSSUdIVCBESUFHT05BTCBFTExJUFNJUwMEpUVUQUNLBxLfTyBXSVRIIENJUkNMRSBBQk9WRQIk9ZgHLUZBQ0lORyBTTkFLRSBIRUFEIFdJVEggQ0xPU0VEIE1PVVRIGgrxmAdPUEVOIE1PVVRIAxHn7QdQT0lOVElORyBBSVJQTEFORQ0K4ZgHVE9NSUMgQk9NQgwL+ZgHRU5FUkdZIFdBVkUMBPubB0ZST0cMB4CYB0dPLUtBUlQMEebtB01JTElUQVJZIEFJUlBMQU5FDAqYmQdSQUNJTkcgQ0FSDQu66gdFRCBUUklBTkdMRQ0E5pgHSUZMRQ0K15gHT0NLRVQgU0hJUAwO6O0HU01BTEwgQUlSUExBTkUSDLzqB1JFRCBUUklBTkdMRQwd7UtUUklBTkdMRSBXSVRIIExFRlQgSEFMRiBCTEFDSxoQ7ktSSUdIVCBIQUxGIEJMQUNLAjbq9gdQRVIgQU5EIExFRlQgQU5EIExPV0VSIFRSSUFOR1VMQVIgVEhSRUUgUVVBUlRFUlMgQkxPQ0sLFYD3B09XRVIgT05FIEVJR0hUSCBCTE9DSxAVmvcHVFJJQU5HVUxBUiBIQUxGIEJMT0NLCi/o9gdSSUdIVCBBTkQgTE9XRVIgVFJJQU5HVUxBUiBUSFJFRSBRVUFSVEVSUyBCTE9DSwYOgU5CTEFERSBTQ0lTU09SUwYfkZ0HQ0VOVFJFIExFRlQgT05FIFNJWFRFRU5USCBCTE9DSxIOsZgHVFdFTEZUSCBDSVJDTEUNEeT3B09ORSBRVUFSVEVSIEJMT0NLDRmSnQdSSUdIVCBPTkUgU0lYVEVFTlRIIEJMT0NLEw6ymAdUV0VMRlRIIENJUkNMRQYShPcHRklWRSBFSUdIVEhTIEJMT0NLBgqAS0hBTEYgQkxPQ0sQJJH3ByBBTkQgTE9XRVIgSEFMRiBJTlZFUlNFIE1FRElVTSBTSEFERQsG4EtDSVJDTEULEoOcB0hFQVZZIFdISVRFIFNRVUFSRQspkvcHSU5WRVJTRSBNRURJVU0gU0hBREUgQU5EIExPV0VSIEhBTEYgQkxPQ0sTDNpLV0hJVEUgQ0lSQ0xFCxannQdMRUZUIE9ORSBRVUFSVEVSIEJMT0NLCwyO9wdNRURJVU0gU0hBREULF6ydB1JJR0hUIE9ORSBRVUFSVEVSIEJMT0NLCyOUnAdWRVJUSUNBTCBMSU5FIFdJVEggVEhSRUUgVElDSyBNQVJLUwY23fYHTEVGVCBCTE9DSyBESUFHT05BTCBMT1dFUiBDRU5UUkUgVE8gTE9XRVIgTUlERExFIFJJR0hUKhLf9gdVUFBFUiBNSURETEUgUklHSFQwBeH2B1JJR0hUIBre9gdMRUZUIFRPIExPV0VSIE1JRERMRSBSSUdIVCgM2/YHVVBQRVIgQ0VOVFJFLgzg9gdNSURETEUgUklHSFQgG9n2B01JRERMRSBMRUZUIFRPIFVQUEVSIENFTlRSRTUM3PYHTUlERExFIFJJR0hUNQXa9gdSSUdIVBoh1/YHVVBQRVIgTUlERExFIExFRlQgVE8gVVBQRVIgQ0VOVFJFNQXY9gdSSUdIVAsP1E9DT1JORVIgV0lUSCBET1QLE5CdB09ORSBTSVhURUVOVEggQkxPQ0sMI7BHUiBMT1dFUiBSSUdIVCBDVVJMWSBCUkFDS0VUIFNFQ1RJT04LFcaZB1FVQURSQU5UIENIRVNTIEJJU0hPUBoEupkHS0lORxsFypkHTklHSFQaBM6ZB1BBV04aBb6ZB1FVRUVOGgTCmQdST09LFQvcS0lSQ1VMQVIgQVJDFBWomQdGQUNFIFdJVEggQ0xPU0VEIEVZRVMeCaaZB09QRU4gRVlFUxQNtpkHTUlDUk9DT01QVVRFUhQP0pkHU1RBTkRJTkcgS05JR0hUFAqymQdURUxFVklTSU9ODgu1mAdSVEVSIENJUkNMRQsTmPcHVE8gTE9XRVIgUklHSFQgRklMTAwH+EtSSUFOR0xFERGc9wdVTEFSIE1FRElVTSBTSEFERQwNsJgHV0VMRlRIIENJUkNMRQYmlZ0HTUlERExFIENFTlRSRSBMRUZUIE9ORSBTSVhURUVOVEggQkxPQ0sUGZadB1JJR0hUIE9ORSBTSVhURUVOVEggQkxPQ0sNGJSdB0xFRlQgT05FIFNJWFRFRU5USCBCTE9DSxIOtJgHVFdFTEZUSCBDSVJDTEUNGZedB1JJR0hUIE9ORSBTSVhURUVOVEggQkxPQ0sTDreYB1RXRUxGVEggQ0lSQ0xFBhCUS09ORSBFSUdIVEggQkxPQ0sKDYL3B1FVQVJURVIgQkxPQ0sGNtL2B1JJR0hUIEJMT0NLIERJQUdPTkFMIExPV0VSIE1JRERMRSBMRUZUIFRPIExPV0VSIENFTlRSRTYF0/YHUklHSFQbIuT2B1VQUEVSIENFTlRSRSBUTyBMT1dFUiBNSURETEUgUklHSFQxBeb2B1JJR0hUKxLi9gdVUFBFUiBNSURETEUgUklHSFQhFNb2B0xFRlQgVE8gTE9XRVIgQ0VOVFJFLwzl9gdNSURETEUgUklHSFQpEuP2B1VQUEVSIE1JRERMRSBSSUdIVCEb1PYHTUlERExFIExFRlQgVE8gTE9XRVIgQ0VOVFJFNgzn9gdNSURETEUgUklHSFQ2BdX2B1JJR0hUDBrQTkRST1AtU0hBRE9XRUQgV0hJVEUgU1FVQVJFDBOTnQdPTkUgU0lYVEVFTlRIIEJMT0NLDSKxR1IgTE9XRVIgTEVGVCBDVVJMWSBCUkFDS0VUIFNFQ1RJT04MBpBOUEVOQ0lMDBXHmQdRVUFEUkFOVCBDSEVTUyBCSVNIT1AbBLuZB0tJTkccBcuZB05JR0hUGwTPmQdQQVdOGwW/mQdRVUVFThsEw5kHUk9PSxYL3UtJUkNVTEFSIEFSQxUVqZkHRkFDRSBXSVRIIENMT1NFRCBFWUVTHwmnmQdPUEVOIEVZRVMVDbeZB01JQ1JPQ09NUFVURVIVD9OZB1NUQU5ESU5HIEtOSUdIVBUKs5kHVEVMRVZJU0lPTg8LtpgHUlRFUiBDSVJDTEUMFb/qB1NIQURPV0VEIFdISVRFIENJUkNMRRsG0k5TUVVBUkUMEpn3B1RPIExPV0VSIExFRlQgRklMTA0H+UtSSUFOR0xFEhGd9wdVTEFSIE1FRElVTSBTSEFERQ0Ns5gHV0VMRlRIIENJUkNMRQYThvcHU0VWRU4gRUlHSFRIUyBCTE9DSwYTg/cHVEhSRUUgRUlHSFRIUyBCTE9DSwwOhfcHUVVBUlRFUlMgQkxPQ0sVFqadB0xFRlQgT05FIFFVQVJURVIgQkxPQ0sVF62dB1JJR0hUIE9ORSBRVUFSVEVSIEJMT0NLBxvt9gdSSUFOR1VMQVIgT05FIFFVQVJURVIgQkxPQ0sCDsPsB1NJREUtRE9XTiBGQUNFAgyVXFdBUkRTIEFOQ09SQQkEkUNSUk9XDRu49wcgQU5EIFJJR0hUIE9ORSBFSUdIVEggQkxPQ0sOCKVDRlJPTSBCQVIOHMVDTEVGVFdBUkRTIE9GIERPV05XQVJEUyBBUlJPVw4GklJUTyBCQVIOEt5DV0lUSCBET1VCTEUgU1RST0tFExWV8AdFUVVJTEFURVJBTCBBUlJPV0hFQUQTEYlSSE9SSVpPTlRBTCBTVFJPS0UTGInwB0xBUkdFIFRSSUFOR0xFIEFSUk9XSEVBRBMZhfAHTUVESVVNIFRSSUFOR0xFIEFSUk9XSEVBRBMMmfEHTk9UQ0hFRCBUQUlMExuR8AdTTUFMTCBFUVVJTEFURVJBTCBBUlJPV0hFQUQZEoHwB1RSSUFOR0xFIEFSUk9XSEVBRBMNsENUSVAgTEVGVFdBUkRTFwqxQ1JJR0hUV0FSRFMIC4ZWQkxBQ0sgQVJST1cOE4lXQ0lSQ0xFRCBXSElURSBBUlJPVwgQvfAHQ09NUFJFU1NFRCBBUlJPVwgM4UNEQVNIRUQgQVJST1cJC9FDT1VCTEUgQVJST1cIEbXwB0ZJTkdFUi1QT1NUIEFSUk9XCD/uUkhBUlBPT04gV0lUSCBCQVJCIExFRlQgQkVTSURFIERPV05XQVJEUyBIQVJQT09OIFdJVEggQkFSQiBSSUdIVCYf41JVUFdBUkRTIEhBUlBPT04gV0lUSCBCQVJCIFJJR0hUHwjgUkZST00gQkFSHwbYUlRPIEJBUh4Fv0NXQVJEUxoO3FJSSUdIVCBGUk9NIEJBUiAG1FJUTyBCQVIfBb5DV0FSRFMJCsXwB0VBVlkgQVJST1cOEMHwB0NPTVBSRVNTRUQgQVJST1cIDchDUEFJUkVEIEFSUk9XUwgP8E9RVUFEUlVQTEUgQVJST1cIBu3sB1JPQ0tFVAgQ0fAHU0FOUy1TRVJJRiBBUlJPVwkMufAHUVVBUkVEIEFSUk9XCBKR8QdUUklBTkdMRSBBUlJPV0hFQUQQDeFWLUhFQURFRCBBUlJPVx0tgVcgTEVGVFdBUkRTIE9GIERPV05XQVJEUyBUUklBTkdMRS1IRUFERUQgQVJST1ceBvFWVE8gQkFSHg+p8AdXSVRIIEJPTEQgU0hBRlQjGPtWRE9VQkxFIEhPUklaT05UQUwgU1RST0tFIwut8AdIRUFWWSBTSEFGVCMSoldMT05HIFRJUCBMRUZUV0FSRFMsCqNXUklHSFRXQVJEUyMMpfAHTUVESVVNIFNIQUZU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QU5VU1ZBUkELBu05VElSWUFLCwv2OVVQQURITUFOSVlBCxDlOVZJU0FSR0EgQU5VREFUVEEbCug5IFdJVEggVEFJTBMH4jlTVkFSSVRBEwbjOVVEQVRUQRkK5zkgV0lUSCBUQUlMCxrUOVlBSlVSVkVESUMgTUlETElORSBTVkFSSVRBBiXhOVRPTkUgQVRIQVJWQVZFRElDIElOREVQRU5ERU5UIFNWQVJJVEELDPQ5Q0FORFJBIEFCT1ZFEgXYOUJFTE9XCwndOURPVCBCRUxPVw0P+TlVQkxFIFJJTkcgQUJPVkUSB9o5U1ZBUklUQQsI0DlLQVJTSEFOQQ0O3DlUSEFLQSBBTlVEQVRUQQsH0jlQUkVOS0hBCyXgOVJJR1ZFRElDIEtBU0hNSVJJIElOREVQRU5ERU5UIFNWQVJJVEENCPg5TkcgQUJPVkULBdE5U0hBUkELEN85VEhSRUUgRE9UUyBCRUxPVwwN2zlSSVBMRSBTVkFSSVRBDA3eOVdPIERPVFMgQkVMT1cLKdU5WUFKVVJWRURJQyBBR0dSQVZBVEVEIElOREVQRU5ERU5UIFNWQVJJVEEWE9Y5SU5ERVBFTkRFTlQgU1ZBUklUQRYb1zlLQVRIQUtBIElOREVQRU5ERU5UIFNWQVJJVEExCtk5IFNDSFJPRURFUgIGo0JSU0lDTEUDH9BTVElDQUwgQkFSIEJFU0lERSBSSUdIVCBUUklBTkdMRQ0V5FVET1VCTEUgTEVGVCBUVVJOU1RJTEUNFuJVVFJJUExFIFJJR0hUIFRVUk5TVElMRQ0Wyk9XSVRIIEhPUklaT05UQUwgU1RST0tFCQmUmAdDQVBBQ0lUT1IJCO5FRUxMSVBTSVMJCd5ARk9VUiBET1RTCQedmQdHTy1LQVJUCRq7YElERU9HUkFQSElDIElURVJBVElPTiBNQVJLCRCxYEtBTkEgUkVQRUFUIE1BUksZC7VgIExPV0VSIEhBTEYaCrNgVVBQRVIgSEFMRhUWsmBXSVRIIFZPSUNFRCBTT1VORCBNQVJLKwu0YCBVUFBFUiBIQUxGCQaEmQdMQURERVIKA3xJTkUNCtBHIEVYVEVOU0lPTg4R71VXSVRIIENJUkNMRSBBQk9WRRoF8FVCRUxPVxMPkJkHRk9VUiBUSUNLIE1BUktTEwr/Rk1JRERMRSBET1QT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AwmgTU5JTkcgU0lHTgIJ0esHU1RFQkFTS0VUAgOaRlRDSAMKg+gHRVIgQlVGRkFMTwYGvu0HQ0xPU0VUBgS98gdQT0xPBgSK5gdXQVZFBQXJ5gdNRUxPTgIfv1ZWRSBBUlJPVyBQT0lOVElORyBESVJFQ1RMWSBMRUZUHQWzUlJJR0hUBQScYERBU0gDDvTnB0lORyBCTEFDSyBGTEFHBwnL6AdIQU5EIFNJR04HCvPnB1dISVRFIEZMQUcDBrBgWSBEQVNIBQSHRkxJTkUGB8/8A09XIExJTkUFCMv8A09WRVJMSU5FAhmS5gdYSU5HIENSRVNDRU5UIE1PT04gU1lNQk9MBxOU5gdHSUJCT1VTIE1PT04gU1lNQk9MAQ3A7AdFQVJZIENBVCBGQUNFBgSp7AdGQUNFAgWS6QdERElORwMavE9HRS1UQUlMRUQgUklHSFRXQVJEUyBBUlJPVwIbmEJJRVJTVFJBU1MgRUxMSVBUSUMgRlVOQ1RJT04DCsvnB0dIVCBMSUZURVICD/BMU1QgU1lSSUFDIENST1NTAQSL6AdIQUxFAgPe7QdFRUwFCrhMIE9GIERIQVJNQQUM/0xDSEFJUiBTWU1CT0wCGazxB0lURSBBUlJPVyBTSEFGVCBXSURUSCBPTkUYCq3xB1RXTyBUSElSRFMGBuZLQlVMTEVUBgvV9AdDSEVTUyBBTEZJTAwG10xCSVNIT1ASF4z0ByBST1RBVEVEIE5JTkVUWSBERUdSRUVTGxu29AdUV08gSFVORFJFRCBTRVZFTlRZIERFR1JFRVMMCsj0B0VRVUlIT1BQRVIWF8v0ByBST1RBVEVEIE5JTkVUWSBERUdSRUVTDATU9AdGRVJaDATUTEtJTkcQF4n0ByBST1RBVEVEIE5JTkVUWSBERUdSRUVTGRuz9AdUV08gSFVORFJFRCBTRVZFTlRZIERFR1JFRVMNBdhMTklHSFQSG4b0ByBST1RBVEVEIEZPUlRZLUZJVkUgREVHUkVFUxsOjfQHTklORVRZIERFR1JFRVMbH5v0B09ORSBIVU5EUkVEIFRISVJUWS1GSVZFIERFR1JFRVMbHcX0B1RIUkVFIEhVTkRSRUQgRklGVEVFTiBERUdSRUVTHBq39AdXTyBIVU5EUkVEIFNFVkVOVFkgREVHUkVFUycTsPQHVFdFTlRZLUZJVkUgREVHUkVFUxIH0PQHLUJJU0hPUBMFzvQHUVVFRU4TBM/0B1JPT0sMBNlMUEFXThAXjvQHIFJPVEFURUQgTklORVRZIERFR1JFRVMZG7j0B1RXTyBIVU5EUkVEIFNFVkVOVFkgREVHUkVFUwwF1UxRVUVFThEXivQHIFJPVEFURUQgTklORVRZIERFR1JFRVMaG7T0B1RXTyBIVU5EUkVEIFNFVkVOVFkgREVHUkVFUwwE1kxST09LEBeL9AcgUk9UQVRFRCBOSU5FVFkgREVHUkVFUxkbtfQHVFdPIEhVTkRSRUQgU0VWRU5UWSBERUdSRUVTDA2h9AdUVVJORUQgQklTSE9QEwSe9AdLSU5HFAWi9AdOSUdIVBMEo/QHUEFXThMFn/QHUVVFRU4TBKD0B1JPT0sHBctLSVJDTEUMHorvByBDT05UQUlOSU5HIEJMQUNLIFNNQUxMIENJUkNMRQ0Ohk1XSVRIIERPVCBSSUdIVBQI7FNXTiBBUlJPVxIT9UtMT1dFUiBMRUZUIFFVQURSQU5UGA72S1JJR0hUIFFVQURSQU5UEgiHTVRXTyBET1RTEhP0S1VQUEVSIExFRlQgUVVBRFJBTlQYDvdLUklHSFQgUVVBRFJBTlQHCOdMTFVCIFNVSVQHFOFPT05DQVZFLVNJREVEIERJQU1PTkQbFOJPIFdJVEggTEVGVFdBUkRTIFRJQ0shD+NPUklHSFRXQVJEUyBUSUNLBwnL9wdST1NTIE1BUksGB8dLRElBTU9ORA0gm+8HIENPTlRBSU5JTkcgQkxBQ0sgTUVESVVNIERJQU1PTkQfDchLU01BTEwgRElBTU9ORB8Smu8HVkVSWSBTTUFMTCBESUFNT05EDgnLTUlOIFNRVUFSRQ4E4kxTVUlUDhDQT1dJVEggQ0VOVFJFRCBET1QHG8foB09XTiBQT0lOVElORyBCQUNLSEFORCBJTkRFWBQFn0xJTkRFWBQPl+sHTEVGVCBIQU5EIElOREVYChi/Sy1QT0lOVElORyBTTUFMTCBUUklBTkdMRRQIvUtUUklBTkdMRQcMwU1SQVVHSFRTIEtJTkcPA8BNTUFOBhnVTkVYQ0xBTUFUSU9OIE1BUksgT1JOQU1FTlQGBJBNRkxBRwok/00gV0lUSCBIT1JJWk9OVEFMIE1JRERMRSBCTEFDSyBTVFJJUEUIBsBOT1JFVFRFCQOu6QdXRVIHEM5XT1VSIFBPSU5URUQgQ1VTUBMEp05TVEFSBwy5TFJPV05JTkcgRkFDRQYWq+sHSEFSRCBTSEVMTCBGTE9QUFkgRElTSwcEjfIHRUFSVAsF4UwgU1VJVAkNhU5WWSBDSEVDSyBNQVJLDBzA9wdTQUxUSVJFIFdJVEggUk9VTkRFRCBDT1JORVJTCAWhVlhBR09OBxGtVk9SSVpPTlRBTCBFTExJUFNFCAfWU1VSR0xBU1MGDJxWTEFSR0UgU1FVQVJFCAnG6gdUSU4gQ1JPU1MHDtlNRUZUIExBTkUgTUVSR0ULF8joB1BPSU5USU5HIEJBQ0tIQU5EIElOREVYFAWcTElOREVYChHFSy1QT0lOVElORyBQT0lOVEVSFA7DS1NNQUxMIFRSSUFOR0xFFAjBS1RSSUFOR0xFBxGGmQdPV0VSIExFRlQgUE9JTlRFUgwNh5kHUklHSFQgUE9JTlRFUggkoO8HWkVOR0UgQ09OVEFJTklORyBCTEFDSyBTTUFMTCBMT1pFTkdFBg6mVk1FRElVTSBESUFNT05EDQeoVkxPWkVOR0UNDP1LU01BTEwgU1FVQVJFDgX7S1FVQVJFDgPQVlRBUgcK3VdPT04gU0VMRU5BBgORTk5JQgYNsUtQQVJBTExFTE9HUkFNBwbx5wdFTk5BTlQJBaBWVEFHT04GFtROUVVFU1RJT04gTUFSSyBPUk5BTUVOVAYJrUtSRUNUQU5HTEUHHMnoB0lHSFQgUE9JTlRJTkcgQkFDS0hBTkQgSU5ERVgVBZ5MSU5ERVgLEtRWLVBPSU5USU5HIFBFTlRBR09OFga7S09JTlRFUhUOuUtTTUFMTCBUUklBTkdMRRUIt0tUUklBTkdMRQYIhE5TQ0lTU09SUwcJxvwDRVNBTUUgRE9UBwqWTEhPR0kgUElFQ0UHDKtWTUFMTCBMT1pFTkdFDAarS1NRVUFSRQ0D0lZUQVIICrpMSUxJTkcgRkFDRQcJ5ExQQURFIFNVSVQHBaFLUVVBUkUMB7PqByBCVVRUT04NHpXvB0NPTlRBSU5JTkcgQkxBQ0sgTUVESVVNIFNRVUFSRR4Mo0tTTUFMTCBTUVVBUkUeEZTvB1ZFUlkgU01BTEwgU1FVQVJFDRmFR1dJVEggQ0VOVFJFIFZFUlRJQ0FMIExJTkUSDuRPTEVGVFdBUkRTIFRJQ0sTEvFLT1dFUiBMRUZUIFFVQURSQU5UGA7yS1JJR0hUIFFVQURSQU5UEg/lT1JJR0hUV0FSRFMgVElDSxMOoktPVU5ERUQgQ09STkVSUxIT8EtVUFBFUiBMRUZUIFFVQURSQU5UGA7zS1JJR0hUIFFVQURSQU5UEhfrS1ZFUlRJQ0FMIEJJU0VDVElORyBMSU5FBwOGTFRBUgcCo+YHVU4JDaXmByBCRUhJTkQgQ0xPVUQWCqbmByBXSVRIIFJBSU4KCbxMV0lUSCBSQVlTDwuk5gdTTUFMTCBDTE9VRAYJj0xURUxFUEhPTkUHEv7qB09VQ0hUT05FIFRFTEVQSE9ORQcI4kdSQVBFWklVTQgmwU9JQU5HTEUgQ09OVEFJTklORyBTTUFMTCBXSElURSBUUklBTkdMRQcX101XTy1XQVkgTEVGVCBXQVkgVFJBRkZJQwYaxugHVVAgUE9JTlRJTkcgQkFDS0hBTkQgSU5ERVgSBZ1MSU5ERVgIEcr3By1QT0lOVElORyBDSEVWUk9OEg61S1NNQUxMIFRSSUFOR0xFEgizS1RSSUFOR0xFGgnsSyBXSVRIIERPVAYM/lVWRVJUSUNBTCBCQVIPB69WRUxMSVBTRQ8Jr0tSRUNUQU5HTEUYFIacByBXSVRIIEhPUklaT05UQUwgQkFSCQ6eVlkgU01BTEwgU1FVQVJFBRuzTy1GRUFUSEVSRUQgUklHSFRXQVJEUyBBUlJPVwEf6/AHSURFLUhFQURFRCBET1dOV0FS",
+ "RFMgQkFSQiBBUlJPVxYQ+/AHSEVBVlkgQkFSQiBBUlJPVxYQ4/AHTElHSFQgQkFSQiBBUlJPVxYR8/AHTUVESVVNIEJBUkIgQVJST1cWFYPxB1ZFUlkgSEVBVlkgQkFSQiBBUlJPVwwU6PAHTEVGVFdBUkRTIEJBUkIgQVJST1cWEPjwB0hFQVZZIEJBUkIgQVJST1cWEODwB0xJR0hUIEJBUkIgQVJST1cWEfDwB01FRElVTSBCQVJCIEFSUk9XFhWA8QdWRVJZIEhFQVZZIEJBUkIgQVJST1cMFe3wB05PUlRIIEVBU1QgQkFSQiBBUlJPVxcQ/fAHSEVBVlkgQkFSQiBBUlJPVxcQ5fAHTElHSFQgQkFSQiBBUlJPVxcR9fAHTUVESVVNIEJBUkIgQVJST1cXFYXxB1ZFUlkgSEVBVlkgQkFSQiBBUlJPVxIP7PAHV0VTVCBCQVJCIEFSUk9XFxD88AdIRUFWWSBCQVJCIEFSUk9XFxDk8AdMSUdIVCBCQVJCIEFSUk9XFxH08AdNRURJVU0gQkFSQiBBUlJPVxcVhPEHVkVSWSBIRUFWWSBCQVJCIEFSUk9XDBXq8AdSSUdIVFdBUkRTIEJBUkIgQVJST1cXEPrwB0hFQVZZIEJBUkIgQVJST1cXEOLwB0xJR0hUIEJBUkIgQVJST1cXEfLwB01FRElVTSBCQVJCIEFSUk9XFxWC8QdWRVJZIEhFQVZZIEJBUkIgQVJST1cMFe7wB1NPVVRIIEVBU1QgQkFSQiBBUlJPVxcQ/vAHSEVBVlkgQkFSQiBBUlJPVxcQ5vAHTElHSFQgQkFSQiBBUlJPVxcR9vAHTUVESVVNIEJBUkIgQVJST1cXFYbxB1ZFUlkgSEVBVlkgQkFSQiBBUlJPVxIP7/AHV0VTVCBCQVJCIEFSUk9XFxD/8AdIRUFWWSBCQVJCIEFSUk9XFxDn8AdMSUdIVCBCQVJCIEFSUk9XFxH38AdNRURJVU0gQkFSQiBBUlJPVxcVh/EHVkVSWSBIRUFWWSBCQVJCIEFSUk9XDBLp8AdVUFdBUkRTIEJBUkIgQVJST1cUEPnwB0hFQVZZIEJBUkIgQVJST1cUEOHwB0xJR0hUIEJBUkIgQVJST1cUEfHwB01FRElVTSBCQVJCIEFSUk9XFBWB8QdWRVJZIEhFQVZZIEJBUkIgQVJST1cC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9cACUA8B8sACVA8B88ACWA0BqsACRQ4Bq8ACUA4BqcACWA0BmMACSQ4BnMACRQ8BncACUA8BmsACVA8Bm8ACWA4BmcACUA4BlsACVA4Bl8ACWA0Bp8ACTw4BqMACUA4BpcACVA4BpsACWA0BrsACVQ4Bo8ACTw8BpMACUA8BosACWA4Br8ACUA4BscACUg8BsMACWA4BrMACVA4BrcACWA0BtMACWQ4BtcACUA4Bt8ACUg8BtsACWA4BssACVA4Bs8ACWAwCuMUCQ0EOAbnFAlAOAbbFAlQOAbfFAlgNAcLFAkUOAcPFAlAOAcHFAlgNAuPGAkhBDwHkxgJQDwHhxgJUDwHixgJYDgHvxgJFDwHwxgJQDwHtxgJUDwHuxgJYDgHrxgJPDwHsxgJQDwHpxgJUDwHqxgJYDgHyxgJVDwHnxgJPEAHoxgJQEAHlxgJUEAHmxgJYDwHzxgJQDwH1xgJSEAH0xgJYDwHxxgJYDgH4xgJZDwH5xgJQDwH7xgJSEAH6xgJYDwH2xgJUDwH3xgJYDQGwxQJJDgG0xQJFDwG1xQJQDwGyxQJUDwGzxQJYDgGxxQJQDgGuxQJUDgGvxQJYDQG/xQJPDgHAxQJQDgG9xQJUDgG+xQJYDQHGxQJVDgG7xQJPDwG8xQJQDwG6xQJYDgHHxQJQDgHJxQJSDwHIxQJYDgHExQJUDgHFxQJYDQHMxQJZDgHNxQJQDgHPxQJSDwHOxQJYDgHKxQJUDgHLxQJYDAKJwgJEQQ4BisICUA4Bh8ICVA4BiMICWA0Cv8ICREEPAcDCAlAPAb3CAlQPAb7CAlgOAcnCAkUPAcrCAlAPAcjCAlgOAbjCAkkPAbvCAkUQAbzCAlAQAbrCAlgPAbnCAlAPAbbCAlQPAbfCAlgOAcbCAk8PAcfCAlAPAcTCAlQPAcXCAlgOAc3CAlUPAcLCAk8QAcPCAlAQAcHCAlgPAc7CAlAPAdDCAlIQAc/CAlgPAcvCAlQPAczCAlgNAZLCAkUOAZPCAlAOAZHCAlgNAYLCAkkOAYXCAkUPAYbCAlAPAYTCAlgOAYPCAlAOAYDCAlQOAYHCAlgNAY/CAk8OAZDCAlAOAY3CAlQOAY7CAlgNAZbCAlUOAYzCAk8PAYvCAlgOAZfCAlAOAZnCAlIPAZjCAlgOAZTCAlQOAZXCAlgMAZTAAkUNAZPAAlgMAtPBAkZBDgHUwQJQDgHRwQJUDgHSwQJYDQHPwQJJDgHQwQJQDgHNwQJUDgHOwQJYDQHWwQJPDgHXwQJQDgHVwQJYDQHawQJVDgHbwQJQDgHdwQJSDwHcwQJYDgHYwQJUDgHZwQJYDQHgwQJZDgHhwQJQDgHewQJUDgHfwQJYDALkwwJHQQ4B5cMCUA4B4sMCVA4B48MCWA0B8MMCRQ4B8cMCUA4B7sMCVA4B78MCWA0CnMQCR0EPAZ3EAlAPAZrEAlQPAZvEAlgOAajEAkUPAanEAlAPAabEAlQPAafEAlgOAZbEAkkPAZjEAkUQAZnEAlAQAZfEAlgPAZTEAlQPAZXEAlgOAaTEAk8PAaXEAlAPAaLEAlQPAaPEAlgOAazEAlUPAaDEAk8QAaHEAlAQAZ7EAlQQAZ/EAlgPAa3EAlAPAa/EAlIQAa7EAlgPAarEAlQPAavEAlgNAdzDAkkOAeDDAkUPAeHDAlAPAd7DAlQPAd/DAlgOAd3DAlAOAdrDAlQOAdvDAlgNAezDAk8OAe3DAlAOAerDAlQOAevDAlgNAfTDAlUOAejDAk8PAenDAlAPAebDAlQPAefDAlgOAfXDAlAOAffDAlIPAfbDAlgOAfLDAlQOAfPDAlgMAvPEAkhBDgH0xAJQDgHxxAJUDgHyxAJYDQH+xAJFDgH/xAJQDgH9xAJYDQLwxAJJRQ8B78QCWA4B7sQCVA0CoMMCTEEPAaHDAlAPAZ7DAlQPAZ/DAlgOAanDAkUPAarDAlAPAajDAlgOAZnDAkkPAZzDAkUQAZ3DAlAQAZvDAlgPAZrDAlAPAZfDAlQPAZjDAlgOAabDAk8PAafDAlAPAaXDAlgOAa3DAlUPAaPDAk8QAaTDAlAQAaLDAlgPAa7DAlAPAbDDAlIQAa/DAlgPAavDAlQPAazDAlgOAbPDAlkPAbTDAlAPAbbDAlIQAbXDAlgPAbHDAlQPAbLDAlgNAprBAk1BDwGbwQJQDwGYwQJUDwGZwQJYDgGTwQJJDwGWwQJFEAGXwQJQEAGVwQJYDwGUwQJQDwGRwQJUDwGSwQJYDgGhwQJPDwGiwQJQDwGfwQJUDwGgwQJYDgGlwQJVDwGdwQJPEAGewQJQEAGcwQJYDwGmwQJQDwGowQJSEAGnwQJYDwGjwQJUDwGkwQJYDgGqwQJZDwGrwQJQDwGtwQJSEAGswQJYDwGpwQJYDQLywgJOQQ8B88ICUA8B8MICVA8B8cICWA4B+sICRQ8B+8ICUA8B+cICWA4B6sICSQ8B7sICRRAB78ICUBAB7MICVBAB7cICWA8B68ICUA8B6MICVA8B6cICWA4C+MICT1APAfbCAlQPAffCAlgOAvXCAlVPEAH0wgJYDwH8wgJUDQH7xAJPDgH8xAJQDgH5xAJUDgH6xAJYDQL3xAJVTw8B+MQCUA8B9cQCVA8B9sQCWA0C0MQCWEEPAdHEAlAPAc7EAlQPAc/EAlgOAdvEAkUPAdzEAlAPAdrEAlgOAcjEAkkPAczEAkUQAc3EAlAQAcrEAlQQAcvEAlgPAcnEAlAPAcbEAlQPAcfEAlgOAdjEAk8PAdnEAlAPAdbEAlQPAdfEAlgOAtTEAlVPEAHVxAJQEAHSxAJUEAHTxAJYDAGCwAJJDQGGwAJFDgGHwAJQDgGEwAJUDgGFwAJYDQGDwAJQDQGAwAJUDgyVwAJFUkFUSU9OIE1BUksNAYHAAlgMAuLHAkpJDgHmxwJFDwHnxwJQDwHkxwJUDwHlxwJYDgHjxwJQDgHgxwJUDgHhxwJYDQKayAJKSQ8BnsgCRRABn8gCUBABnMgCVBABncgCWA8Bm8gCUA8BmMgCVA8BmcgCWA4BpcgCTw8BpsgCUA8Bo8gCVA8BpMgCWA4BqcgCVQ8BocgCTxABosgCUBABoMgCWA8BqsgCUA8BrMgCUhABq8gCWA8Bp8gCVA8BqMgCWA4Br8gCWQ8BsMgCUA8BrcgCVA8BrsgCWA0B7scCTw4B78cCUA4B7McCVA4B7ccCWA0B8scCVQ4B6scCTw8B68cCUA8B6McCVA8B6ccCWA4B88cCUA4B9ccCUg8B9McCWA4B8McCVA4B8ccCWA0B+McCWQ4B+ccCUA4B+8cCUg8B+scCWA4B9scCVA4B98cCWAwCgcQCS0EOAYLEAlAOAf/DAlQOAYDEAlgNAYzEAkUOAY3EAlAOAYrEAlQOAYvEAlgNAfrDAkkOAf3DAkUPAf7DAlAPAfzDAlgOAfvDAlAOAfjDAlQOAfnDAlgNAYjEAk8OAYnEAlAOAYbEAlQOAYfEAlgNAZDEAlUOAYTEAk8PAYXEAlAPAYPEAlgOAZHEAlAOAZPEAlIPAZLEAlgOAY7EAlQOAY/EAlgMAsHDAkxBDgHCwwJQDgG/wwJUDgHAwwJYDQHMwwJFDgHNwwJQDgHLwwJYDQG5wwJJDgG9wwJFDwG+wwJQDwG7wwJUDwG8wwJYDgG6wwJQDgG3wwJUDgG4wwJYDQHJwwJPDgHKwwJQDgHHwwJUDgHIwwJYDQHQwwJVDgHFwwJPDwHGwwJQDwHDwwJUDwHEwwJYDgHRwwJQDgHTwwJSDwHSwwJYDgHOwwJUDgHPwwJYDQHWwwJZDgHXwwJQDgHZwwJSDwHYwwJYDgHUwwJUDgHVwwJYDAK3wQJNQQ4BuMECUA4BtcECVA4BtsECWA0BwsECRQ4BwcECWA0CtMQCR0EPAbXEAlAPAbLEAlQPAbPEAlgOAb7EAkUPAb/EAlAPAb3EAlgOArHEAklFEAGwxAJYDgG7xAJPDwG8xAJQDwG5xAJUDwG6xAJYDgHCxAJVDwG3xAJPEAG4xAJQEAG2xAJYDwHDxAJQDwHFxAJSEAHExAJYDwHAxAJUDwHBxAJYDQGwwQJJDgGzwQJFDwG0wQJQDwGywQJYDgGxwQJQDgGuwQJUDgGvwQJYDQG/wQJPDgHAwQJQDgG9wQJUDgG+wQJYDQHFwQJVDgG7wQJPDwG8wQJQDwG5wQJUDwG6wQJYDgHGwQJQDgHIwQJSDwHHwQJYDgHDwQJUDgHEwQJYDQHLwQJZDgHMwQJQDgHJwQJUDgHKwQJYDAKFwwJOQQ4BhsMCUA4BhMMCWA0C/8ACQkEPAYDBAlAPAf3AAlQPAf7AAlgOAfjAAkkPAfvAAkUQAfzAAlAQAfrAAlgPAfnAAlAPAfbAAlQPAffAAlgOAYPBAk8P",
+ "AYTBAlAPAYHBAlQPAYLBAlgOAYfBAlUPAYjBAlAPAYrBAlIQAYnBAlgPAYXBAlQPAYbBAlgOAY3BAlkPAY7BAlAPAZDBAlIQAY/BAlgPAYvBAlQPAYzBAlgNAtnCAkRBDwHawgJQDwHXwgJUDwHYwgJYDgHgwgJFDwHhwgJQDwHfwgJYDgHTwgJJDwHWwgJFEAHVwgJYDwHUwgJQDwHRwgJUDwHSwgJYDgHdwgJPDwHewgJQDwHbwgJUDwHcwgJYDgHkwgJVDwHlwgJQDwHnwgJSEAHmwgJYDwHiwgJUDwHjwgJYDQGPwwJFDgGQwwJQDgGOwwJYDQLixAJHQQ8B48QCUA8B4MQCVA8B4cQCWA4B7MQCRQ8B7cQCUA8B68QCWA4C3sQCSUUQAd/EAlAQAd3EAlgOAenEAk8PAerEAlAPAefEAlQPAejEAlgOAubEAlVPEAHkxAJUEAHlxAJYDQH/wgJJDgGCwwJFDwGDwwJQDwGBwwJYDgGAwwJQDgH9wgJUDgH+wgJYDQKzyAJKSQ8Bt8gCRRABuMgCUBABtcgCVBABtsgCWA8BtMgCUA8BscgCVA8BssgCWA4BvcgCTw8BvsgCUA8Bu8gCVA8BvMgCWA4BwMgCVQ8BusgCTxABucgCWA8BwcgCUA8Bw8gCUhABwsgCWA8Bv8gCWA4BxsgCWQ8Bx8gCUA8BycgCUhAByMgCWA8BxMgCVA8BxcgCWA0BjMMCTw4BjcMCUA4BisMCVA4Bi8MCWA0ClscCUkEPAZfHAlAPAZTHAlQPAZXHAlgOAZ3HAkUPAZ7HAlAPAZvHAlQPAZzHAlgOAZnHAk8PAZrHAlAPAZjHAlgOAaHHAlUPAaLHAlAPAaTHAlIQAaPHAlgPAZ/HAlQPAaDHAlgOAafHAlkPAajHAlAPAarHAlIQAanHAlgPAaXHAlQPAabHAlgNAZPDAlUOAYjDAk8PAYnDAlAPAYfDAlgOAZTDAlAOAZbDAlIPAZXDAlgOAZHDAlQOAZLDAlgNAszIAllJDwHQyAJFEAHRyAJQEAHOyAJUEAHPyAJYDwHNyAJQDwHKyAJUDwHLyAJYDgHXyAJPDwHYyAJQDwHVyAJUDwHWyAJYDgHbyAJVDwHTyAJPEAHUyAJQEAHSyAJYDwHcyAJQDwHZyAJUDwHayAJYDQL2xQJaQQ8B98UCUA8B9MUCVA8B9cUCWA4B/cUCRQ8B/MUCWA4B78UCSQ8B8sUCRRAB88UCUBAB8cUCWA8B8MUCUA8B7cUCVA8B7sUCWA4C+8UCT1APAfrFAlgOAf/FAlUPAfnFAk8QAfjFAlgPAYDGAlAPAYLGAlIQAYHGAlgPAf7FAlgOAYXGAlkPAYbGAlAPAYjGAlIQAYfGAlgPAYPGAlQPAYTGAlgMAZHAAk8NAZLAAlANAY/AAlQNAZDAAlgMAsHAAlBBDgHCwAJQDgG/wAJUDgHAwAJYDQG6wAJJDgG9wAJFDwG+wAJQDwG8wAJYDgG7wAJQDgG4wAJUDgG5wAJYDQHIwAJPDgHJwAJQDgHGwAJUDgHHwAJYDQHMwAJVDgHEwAJPDwHFwAJQDwHDwAJYDgHNwAJQDgHPwAJSDwHOwAJYDgHKwAJUDgHLwAJYDQHSwAJZDgHTwAJQDgHVwAJSDwHUwAJYDgHQwAJUDgHRwAJYDAL+xwJRSQ4BgsgCRQ8Bg8gCUA8BgMgCVA8BgcgCWA4B/8cCUA4B/McCVA4B/ccCWA0BisgCTw4Bi8gCUA4BiMgCVA4BicgCWA0BjsgCVQ4BhsgCTw8Bh8gCUA8BhMgCVA8BhcgCWA4Bj8gCUA4BkcgCUg8BkMgCWA4BjMgCVA4BjcgCWA0BlMgCWQ4BlcgCUA4Bl8gCUg8BlsgCWA4BksgCVA4Bk8gCWAwCyMcCUkEOAcnHAlAOAcbHAlQOAcfHAlgNAdLHAkUOAdPHAlAOAdHHAlgNAc/HAk8OAdDHAlAOAc3HAlQOAc7HAlgNAv3GAlJBDwH8xgJYDgGGxwJFDwGHxwJQDwGExwJUDwGFxwJYDgGCxwJPDwGDxwJQDwGAxwJUDwGBxwJYDgGKxwJVDwH/xgJPEAH+xgJYDwGLxwJQDwGNxwJSEAGMxwJYDwGIxwJUDwGJxwJYDgGQxwJZDwGRxwJQDwGTxwJSEAGSxwJYDwGOxwJUDwGPxwJYDQHWxwJVDgHLxwJPDwHMxwJQDwHKxwJYDgHXxwJQDgHZxwJSDwHYxwJYDgHUxwJUDgHVxwJYDQHcxwJZDgHdxwJQDgHfxwJSDwHexwJYDgHaxwJUDgHbxwJYDAKSxgJTQQ4Bk8YCUA4BkMYCVA4BkcYCWA0BnMYCRQ4BncYCUA4Bm8YCWA0CrccCSEEPAa7HAlAPAavHAlQPAazHAlgOAbjHAkUPAbnHAlAPAbbHAlQPAbfHAlgOAbTHAk8PAbXHAlAPAbLHAlQPAbPHAlgOAbzHAlUPAbDHAk8QAbHHAlAQAa/HAlgPAb3HAlAPAb/HAlIQAb7HAlgPAbrHAlQPAbvHAlgOAcLHAlkPAcPHAlAPAcXHAlIQAcTHAlgPAcDHAlQPAcHHAlgNAYvGAkkOAY7GAkUPAY/GAlAPAY3GAlgOAYzGAlAOAYnGAlQOAYrGAlgNAZnGAk8OAZrGAlAOAZfGAlQOAZjGAlgNArPGAlNBDwG0xgJQDwGxxgJUDwGyxgJYDgG6xgJFDwG7xgJQDwG5xgJYDgGsxgJJDwGvxgJFEAGwxgJQEAGuxgJYDwGtxgJQDwGqxgJUDwGrxgJYDgG3xgJPDwG4xgJQDwG1xgJUDwG2xgJYDgG+xgJVDwG/xgJQDwG8xgJUDwG9xgJYDgHCxgJZDwHDxgJQDwHFxgJSEAHExgJYDwHAxgJUDwHBxgJYDQGgxgJVDgGVxgJPDwGWxgJQDwGUxgJYDgGhxgJQDgGjxgJSDwGixgJYDgGexgJUDgGfxgJYDQGmxgJZDgGnxgJQDgGpxgJSDwGoxgJYDgGkxgJUDgGlxgJYDAKjwgJUQQ4BpMICUA4BocICVA4BosICWA0BrsICRQ4Br8ICUA4BrcICWA0BnMICSQ4Bn8ICRQ8BoMICUA8BnsICWA4BncICUA4BmsICVA4Bm8ICWA0Bq8ICTw4BrMICUA4BqcICVA4BqsICWA0BssICVQ4Bp8ICTw8BqMICUA8BpcICVA8BpsICWA4Bs8ICUA4BtcICUg8BtMICWA4BsMICVA4BscICWAwCjcACVU8OAY7AAlAOAYzAAlgMAuzBAlZBDgHtwQJQDgHqwQJUDgHrwQJYDQLzwQJFUA4B8sECWA0B5MECSQ4B6MECRQ8B6cECUA8B5sECVA8B58ECWA4B5cECUA4B4sECVA4B48ECWA0B8MECTw4B8cECUA4B7sECVA4B78ECWA0B9sECVQ4B98ECUA4B+cECUg8B+MECWA4B9MECVA4B9cECWA0B/MECWQ4B/cECUA4B/8ECUg8B/sECWA4B+sECVA4B+8ECWAwCgsUCV0EOAYPFAlAOAYDFAlQOAYHFAlgNAYvFAkUOAYzFAlAOAYrFAlgNAYjFAk8OAYnFAlAOAYfFAlgNAZXAAlUOAYXFAk8PAYbFAlAPAYTFAlgMAt/IAlhJDgHjyAJFDwHkyAJQDwHhyAJUDwHiyAJYDgHgyAJQDgHdyAJUDgHeyAJYDQHpyAJPDgHqyAJQDgHnyAJUDgHoyAJYDQLmyAJVTw8B5cgCWA0B7cgCWQ4B7sgCUA4B8MgCUg8B78gCWA4B68gCVA4B7MgCWAwC88gCWUkOAffIAkUPAfjIAlAPAfXIAlQPAfbIAlgOAfTIAlAOAfHIAlQOAfLIAlgNAf/IAk8OAYDJAlAOAf3IAlQOAf7IAlgNAYPJAlUOAfvIAk8PAfzIAlAPAfnIAlQPAfrIAlgOAYTJAlAOAYbJAlIPAYXJAlgOAYHJAlQOAYLJAlgNAYnJAlkOAYrJAlAOAYzJAlIPAYvJAlgOAYfJAlQOAYjJAlgMApbFAlpBDgGXxQJQDgGUxQJUDgGVxQJYDQGgxQJFDgGhxQJQDgGfxQJYDQLIxgJIQQ8BycYCUA8BxsYCVA8Bx8YCWA4B08YCRQ8B1MYCUA8B0cYCVA8B0sYCWA4Bz8YCTw8B0MYCUA8BzcYCVA8BzsYCWA4B18YCVQ8By8YCTxABzMYCUBABysYCWA8B2MYCUA8B2sYCUhAB2cYCWA8B1cYCVA8B1sYCWA4B3cYCWQ8B3sYCUA8B4MYCUhAB38YCWA8B28YCVA8B3MYCWA0Bj8UCSQ4BksUCRQ8Bk8UCUA8BkcUCWA4BkMUCUA4BjcUCVA4BjsUCWA0BncUCTw4BnsUCUA4Bm8UCVA4BnMUCWA0BpMUCVQ4BmcUCTw8BmsUCUA8BmMUCWA4BpcUCUA4Bp8UCUg8BpsUCWA4BosUCVA4Bo8UCWA0BqsUCWQ4Bq8UCUA4BrcUCUg8BrMUCWA4BqMUCVA4BqcUCWA0C2sUCWkEPAdvFAlAPAdjFAlQPAdnFAlgOAeDFAkUPAeHFAlAPAd/FAlgOAdLFAkkPAdbFAkUQAdfFAlAQAdTFAlQQAdXFAlgPAdPFAlAPAdDFAlQPAdHFAlgOAd3FAk8PAd7FAlAPAdzFAlgOAePFAlUPAeTFAlAPAebFAlIQAeXFAlgPAeLFAlgOAenFAlkPAerFAlAPAezFAlIQAevFAlgPAefFAlQPAejFAlgCBq9MTiBZQU5HAQSA9QdPLVlPABn/RVogTk9UQVRJT04gQkFHIE1FTUJFUlNISVALFuRURE9NQUlOIEFOVElSRVNUUklDVElPTgsUiVNMRUZUIEJJTkRJTkcgQlJBQ0tFVBANh1NJTUFHRSBCUkFDS0VUCxXlVFJBTkdFIEFOVElSRVNUUklDVElPTgwVvlRFTEFUSU9OQUwgQ09NUE9TSVRJT04MFIpTSUdIVCBCSU5ESU5HIEJSQUNLRVQRDYhTSU1BR0UgQlJBQ0tFVAsSn1RTQ0hFTUEgQ09NUE9TSVRJT04SBqBUUElQSU5HEwmhVFJPSkVDVElPTgwDgVNQT1QLCoJTVFlQRSBDT0xPTgEuxrQEQU5BQkFaQVIgU1FVQVJFIENMT1NJTkcgRE9VQkxFLUxJTkVEIEhFQUQgTUFSSxkJwLQESEVBRCBNQVJLExW9tARVU1RFUi1GSU5BTCBMRVRURVIgTEEmAry0BFJBJgK+tARWQSYCu7QEWUEZEbq0BElOSVRJQUwgTEVUVEVSIFJBERSztARGSU5BTCBDT05TT05BTlQgTUFSSxEexbQESU5JVElBTCBET1VCTEUtTElORUQgSEVBRCBNQVJLGQm/tARIRUFEIE1BUksRCam0BExFVFRFUiAtQRgBgLQEQRgCoLQEQkEZAqG0BEhBGAKQtARDQRkCkbQESEEYApu0BERBGQKWtAREQRoCl7QESEEZApy0BEhBGQKltARaQRoCprQESEEYAo20BEdBGQKOtARIQRgCsbQESEEYApK0BEpBGAKLtARLQRkCjLQESEEZA7K0BFNTQRgCrLQETEEYAqK0BE1BGAKdtAROQRkCj7QER0EZApi0BE5BGQKTtARZQRgCnrQEUEEZAp+0BEhBGAKrtARSQRgCsLQEU0EZAq60BEhBGQKvtARTQRgCmbQEVEEZApq0BEhBGQKjtARTQRoCpLQESEEZApS0BFRBGgKVtARIQRgCrbQEVkEYAqq0BFlBGAKotARaQRkCp7QESEEREMO0BE1BUksgRE9VQkxFIFNIQUQWCsS0BExPTkcgVFNIRUcWBMK0BFNIQUQWBcG0BFRTSEVHEQ24tARTSUdOIEFOVVNWQVJBFhS3tARDQU5EUkEgV0lUSCBPUk5BTUVOVBwFtbQEQklORFUhDra0BCBXSVRIIE9STkFNRU5UFga0tARWSVJBTUEYBbm0BFNBUkdBEgjHtARVQkpPSU5FUhERirQEVk9XRUwgTEVOR1RIIE1BUksXB4e0BFNJR04gQUkdAYi0BFUcAYS0BEUcAYG0BEkcAYa0BE8dAYW0BEUcCom0BFJFVkVSU0VEIEkcAYO0BFUdAYK0BEUBCZPzB0VCUkEgRkFDRQIPjUBSTyBXSURUSCBKT0lORVILDv/9A05PLUJSRUFLIFNQQUNFDQiMQE4tSk9JTkVSCwWLQFNQQUNFAgLiV1VTARCQ8gdJUFBFUi1NT1VUSCBGQUNFASm+ngdOQU1FTk5ZIENPTUJJTklORyBBVFRBQ0hJTkcgVkVSVElDQUwgT01FVBMbwZ4HTE9XRVIgVE9OQUwgUkFOR0UgSU5ESUNBVE9SEwykngdNQVJLIEJPUlpBWUEYB7ieB0NIQVNIS0EfCLmeByBQT0xOQVlBGQq/ngdVUlZFRCBPTUVUGBW0ngdERU1FU1RWRU5OWSBaQURFUlpIS0EZCb2eB1ZPRVRPQ0hJRRgVg54HR09SQVpETyBOSVpLTyBPTiBMRUZUKQWPngdSSUdIVCYRgJ4HUyBLUllaSEVNIE9OIExFRlQzBYyeB1JJR0hUIAafngdWWVNPS08YBqmeB0tBQ0hLQRkEwJ4HUllaSB0IrZ4HIE9OIExFRlQZBqieB1VQTkFZQRgFp54HTE9NS0EYFIaeB01BTE8gUE9WWVNIRSBPTiBMRUZUKAWSngdSSUdIVCUSiZ4HUyBLSE9LSExPTSBPTiBMRUZUMwWVngdSSUdIVCgGmZ4HUllaSEVNJgaengdUUkFOTk8YDYSeB05JWktPIE9OIExFRlQhBZCeB1JJR0hUHhGBngdTIEtSWVpIRU0gT04gTEVGVCsFjZ4HUklHSFQYCLqeB09CTEFDSEtPGQGhngdOGQe1ngdUU0VDSEtBGAq2ngdQT0RDSEFTSElFIhW3ngcgV0lUSCBWRVJUSUNBTCBTVFJPS0UbBqaeB1ZFUlRLQRoNh54HVllTSEUgT04gTEVGVCMFk54HUklHSFQgEoqeB1MgS0hPS0hMT00gT04gTEVGVC4Flp4HUklHSFQjBpueB1JZWkhFTSEGnJ4HVFJBTk5PGAWingdSQVZOTxoFrJ4HWlNFS0EYBaueB1NLT0JBGQ67ngdPUk9DSFlBIE5PWkhLQRkNhZ4HUkVETkUgT04gTEVGVCIFkZ4HUklHSFQZE5qeB1RSQU5OTyBNQUxPIFBPVllTSEUYB6OeB1RJS0hBWUEZBbyeB09DSEtBGQyCngdTQVRBIE9OIExFRlQhBY6eB1JJR0hUHgmYngdTIEtSWVpIRU0YBqWeB1VEQVJLQRgOiJ4HVllTT0tPIE9OIExFRlQiBZSeB1JJR0hUHxKLngdTIEtIT0tITE9NIE9OIExFRlQtBZeeB1JJR0hUIgadngdSWVpIRU0YCbOeB1pBREVSWkhLQRkDoJ4HRUxPGgOqngdWT0sTGLCeB1RPTkFMIFJBTkdFIE1BUksgTVJBQ0hOTyQGsZ4HU1ZFVExPJAmyngdUUkVTVkVUTE8JDe+eB05FVU1FIENIQVNIS0ERCNueB0VMWVVTVEtBDxL1ngdERU1FU1RWRU5OWSBLTFlVQ0gRBu2eB1JCSVRTQRAP654HT1VCTEUgWkFQWUFUQVlBEAPBnwdVREEQC9OeB1ZBIFYgQ0hFTE5VDwSDnwdGSVRBDw/engdHT0xVQkNISUsgQk9SWlkZB+CeB01SQUNITlkZBuGeB1NWRVRMWRkF354HVElLSFkaCOKeB1JFU1ZFVExZDwfungdLSEFNSUxPEAXUngdMWVVDSBAFhJ8HT0JZTEEQBNCeB1JZVUsUBtGeByBUSUtIWRICgZ8HWkgQBr+fB1VGSVNNQQ8Gp58HTUVDSElLFRSrnwcgS0xZVUNIRU5FUE9TVE9ZQU5OWR0Hqp8HUE9WT0ROWR0DqZ8HVk9ZFgeonwdQT1ZPRE5ZDwXCnwdORU1LQQ8GwJ8HT0JMQUtPEASvnwdTT0tBFBu0nwcgS0xZVUNIRVZBWUEgTkVQT1NUT1lBTk5B",
+ "WUEhCLOfB1NWRVRMQVlBFhKynwdSWVVLT1ZBWUEgU1ZFVExBWUEVCLCfB1NWRVRMQVlBFQuxnwdUUkVTVkVUTEFZQQ8F3J4HUEFMS0ERBtKeB1JBS0xJVBECw58HVUsQCNieB0VSRVZPREtBGA/ZngcgTkVQT1NUT1lBTk5BWUEQCfCeB09EQ0hBU0hJRREIkJ8HTEtVTElaTVkPE+yeB1JFVkVSU0VEIENIRUxZVVNUS0EQAoKfB09HDyL8ngdTS0FNRVlUU0EgRFZPRUNIRUxOQVlBIEtMWVVDSEVWQVlBJg77ngdORVBPU1RPWUFOTkFZQSYI+Z4HUFJPU1RBWUEmCPqeB1NWRVRMQVlBGRX3ngdLTFlVQ0hFTkVQT1NUT1lBTk5BWUEgDfaeB1ZBWUEgU1ZFVExBWUElB/ieB1RJS0hBWUEZCfGeB01SQUNITkFZQRkI8p4HU1ZFVExBWUEZB/SeB1RJS0hBWUEaCvOeB1JFU1ZFVExBWUEQCP2eB0xPWkhJVElFGAqAnwcgUyBLUllaSEVNGwj+ngdaQVBZQVRPWRkI/54HWkFLUllUT0UQBYafB1RBVFlBFQ+RnwcgTkVQT1NUT1lBTk5BWUEWEo6fB1MgRFZVTVlBIFpBUFlBVFlNSRgHiJ8HS1JZWkhFTR8Lip8HIEkgWkFQWUFUT1kYBY2fB1JPR09NGAiHnwdaQVBZQVRPWSAKiZ8HIEkgS1JZWkhFTSMLj58HUE9EQ0hBU0hJRU0WCYufB1pBS1JZVEFZQR8LjJ8HIFMgWkFQWUFUT1kRBtaeB09QSVRTQRcJ154HIFMgT0NIS09NGBTangdXSVRIIFNPUk9DSFlBIE5PWkhLQRERm58HUkVMQSBEVk9FQ0hFTE5BWUEiDJ6fByBLTFlVQ0hFVkFZQR8LnJ8HT0tSWVpIRVZBWUEgCZ2fB1BPVk9ETkFZQSkMn58HIEtMWVVDSEVWQVlBFgiinwdHUk9NTkFZQR4WoJ8HIFdJVEggU0lOR0xFIFpBUFlBVEFZQRoLpZ8HT0tSWVpIRVZBWUElCqafByBQT1ZPRE5BWUEbCaOfB1BPVk9ETkFZQSQWpJ8HIFdJVEggRE9VQkxFIFpBUFlBVEFZQSoQoZ8HU0lOR0xFIFpBUFlBVEFZQRYVmZ8HS0xZVUNIRU5FUE9TVE9ZQU5OQVlBHQmYnwdQT1ZPRE5BWUEXCbWfB1JZVUtPVkFZQSAJuZ8HIEdST01OQVlBKRa3nwcgV0lUSCBTSU5HTEUgWkFQWUFUQVlBJQu8nwdPS1JZWkhFVkFZQTAKvZ8HIFBPVk9ETkFZQSYJup8HUE9WT0ROQVlBLxa7nwcgV0lUSCBET1VCTEUgWkFQWUFUQVlBNRC4nwdTSU5HTEUgWkFQWUFUQVlBIQm2nwdQT1ZPRE5BWUEhB76fB1RSWUFTS0EZB5SfB1pIRVZBWUEWDpOfB01SQUNITk9USUtIQVlBFg6XnwdORVBPU1RPWUFOTkFZQRYNlZ8HUE9MVVBPVk9ETkFZQRgHlp8HVk9ETkFZQRcHkp8HUk9TVEFZQRYPmp8HVElLSEFZQSBQVVROQVlBFw2snwdSWUFTT0dMQVNOQVlBHAmtnwdQT1ZPRE5BWUEcCa6fB1NUUkVMTkFZQQ8e6J4HVlJBS0hJWUEgS0xZVUNIRVZBWUEgTVJBQ0hOQVlBJAjnngdQUk9TVEFZQSQI6Z4HU1ZFVExBWUEkC+qeB1RSRVNWRVRMQVlBGAnkngdNUkFDSE5BWUEYCOOeB1BST1NUQVlBGAjlngdTVkVUTEFZQRgL5p4HVFJFU1ZFVExBWUEPCNWeB1pBTk9aSEVLEQfdngdQWUFUQVlBEAaFnwdNRVlUU0EJH8SeB1BSSVpOQUsgTU9ESUZJRVIgRElSRUNUSU9OIEZMSVAaBcWeB0tSWVpIGgfCngdMRVZFTC0yIAHDngczGgPGngdST0cBBd/zB09NQklFAQKNQFdKAgT//QNOQlNQAwGMQEoCAotAU1A=",
+ };
+
+ private static final String[] NAMES;
+ private static final int[] CODE_POINTS;
+
+ static {
+ byte[] data = Base64.getDecoder().decode(String.join("", ENCODED));
+ Cursor cursor = new Cursor();
+ int count = readVarint(data, cursor);
+ if (count != ENTRY_COUNT) {
+ throw new IllegalStateException("Invalid generated Perl scalar-name count: " + count);
+ }
+ NAMES = new String[count];
+ CODE_POINTS = new int[count];
+ String previous = "";
+ for (int index = 0; index < count; index++) {
+ int prefixLength = readVarint(data, cursor);
+ int suffixLength = readVarint(data, cursor);
+ int codePoint = readVarint(data, cursor);
+ if (prefixLength > previous.length()
+ || suffixLength > data.length - cursor.offset) {
+ throw new IllegalStateException("Invalid generated Perl scalar-name data");
+ }
+ String suffix = new String(
+ data, cursor.offset, suffixLength, StandardCharsets.US_ASCII);
+ cursor.offset += suffixLength;
+ String name = previous.substring(0, prefixLength) + suffix;
+ NAMES[index] = name;
+ CODE_POINTS[index] = codePoint;
+ previous = name;
+ }
+ if (cursor.offset != data.length) {
+ throw new IllegalStateException("Trailing generated Perl scalar-name data");
+ }
+ }
+
+ public static int codePoint(String name) {
+ if (name == null) return -1;
+ int index = Arrays.binarySearch(NAMES, name);
+ return index < 0 ? -1 : CODE_POINTS[index];
+ }
+
+ static int entryCount() {
+ return NAMES.length;
+ }
+
+ private static int readVarint(byte[] data, Cursor cursor) {
+ int value = 0;
+ int shift = 0;
+ while (cursor.offset < data.length && shift < 35) {
+ int current = data[cursor.offset++] & 0xff;
+ value |= (current & 0x7f) << shift;
+ if ((current & 0x80) == 0) return value;
+ shift += 7;
+ }
+ throw new IllegalStateException("Invalid generated Perl scalar-name varint");
+ }
+
+ private static final class Cursor {
+ private int offset;
+ }
+
+ private PerlUnicodeScalarNameData() {
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexDiagnosticFormatter.java b/src/main/java/org/perlonjava/runtime/regex/RegexDiagnosticFormatter.java
index 34bdb45566..229f19de24 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RegexDiagnosticFormatter.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RegexDiagnosticFormatter.java
@@ -11,7 +11,35 @@ static String marked(String pattern, int characterOffset, String message) {
/** Renders the exact Perl form, including whitespace before the closing delimiter. */
static String markedPerl(String pattern, int characterOffset, String message) {
- return marked(pattern, characterOffset, message, true);
+ return marked(pattern, characterOffset,
+ normalizePerlMessage(pattern, characterOffset, message), true);
+ }
+
+ private static String normalizePerlMessage(
+ String pattern, int characterOffset, String message) {
+ if ("Sequence (?<... not terminated".equals(message)
+ && pattern != null && characterOffset > 0
+ && characterOffset <= pattern.length()) {
+ char first = pattern.charAt(characterOffset - 1);
+ if (first != '_' && !Character.isLetterOrDigit(first)) {
+ return "Group name must start with a non-digit word character";
+ }
+ }
+ return expandUselessModifier(message);
+ }
+
+ private static String expandUselessModifier(String message) {
+ if (message == null || !message.startsWith("Useless (?")
+ || !message.endsWith(")")) {
+ return message;
+ }
+ int modifierIndex = message.length() - 2;
+ char modifier = message.charAt(modifierIndex);
+ if (modifier != 'c' && modifier != 'g' && modifier != 'o') return message;
+ boolean negative = modifierIndex > 9
+ && message.charAt(modifierIndex - 1) == '-';
+ return message + " - " + (negative ? "don't use" : "use") + " /"
+ + (modifier == 'c' ? "gc" : String.valueOf(modifier)) + " modifier";
}
private static String marked(String pattern, int characterOffset, String message,
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java
index d07c13f602..9a0a7cd9e2 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RegexFlags.java
@@ -2,8 +2,6 @@
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
-import static java.util.regex.Pattern.*;
-
/**
* @param isGlobalMatch g flag - match globally (find all occurrences)
* @param keepCurrentPosition c flag - continue matching from last match position
@@ -121,39 +119,6 @@ static boolean hasInlinePreserveModifier(String pattern) {
return false;
}
- public int toPatternFlags() {
- int flags = 0;
-
- // UNIX_LINES ensures that . only excludes \n (not \r, \u0085, etc.)
- // This matches Perl's behavior where . excludes only \n
- flags |= UNIX_LINES;
-
- // /u flag enables Unicode semantics for \w, \d, \s
- // /a flag (ASCII-restrict) disables Unicode semantics
- if (isUnicode && !isAscii) {
- flags |= UNICODE_CHARACTER_CLASS;
- }
-
- if (isCaseInsensitive) {
- flags |= CASE_INSENSITIVE;
- // /a restricts character classes such as \w but retains Unicode
- // case folding. Only /aa forbids ASCII/non-ASCII fold crossings.
- if (!isAsciiStrict) {
- flags |= UNICODE_CASE;
- }
- }
- if (isMultiLine) {
- flags |= MULTILINE;
- }
- if (isDotAll) {
- flags |= DOTALL;
- }
- if (isExtended) {
- flags |= COMMENTS;
- }
- return flags;
- }
-
public RegexFlags with(String positiveFlags, String negativeFlags) {
boolean newFlagN = this.isNonCapturing;
boolean newIsCaseInsensitive = this.isCaseInsensitive;
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexMarkers.java b/src/main/java/org/perlonjava/runtime/regex/RegexMarkers.java
index 6f61e87932..329a1ded59 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RegexMarkers.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RegexMarkers.java
@@ -1,61 +1,66 @@
package org.perlonjava.runtime.regex;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
- * Shared placeholder markers used by the string-interpolation parser to
- * stand in for regex constructs that PerlOnJava cannot compile literally
- * (because they require features unsupported by the underlying Java regex
- * engine — e.g. arbitrary {@code (?{ CODE })} code blocks and
- * {@code (??{ CODE })} recursive/dynamic patterns).
- *
- * The markers are emitted by {@code StringSegmentParser} when a code
- * block can't be constant-folded. {@link RegexPreprocessor} detects them
- * and reports "not implemented":
- *
- * - {@link #CODE_BLOCK} — a hard error under default die mode,
- * or a no-op fallback only when {@link #CODE_BLOCK_NOOP_ENV} is set.
- * Plain {@code JPERL_UNIMPLEMENTED=warn} still reports the unsupported
- * feature without pretending the callback ran.
- * - {@link #RECURSIVE_PATTERN} — a hard error under default die mode,
- * or a warning under {@code JPERL_UNIMPLEMENTED=warn} followed by
- * the soft {@code (?:} fallback so the surrounding pattern still
- * compiles (many CPAN modules build dynamic patterns that happen
- * to work with the empty-group fallback; under warn mode we want
- * tests to continue but the user must see a diagnostic).
- *
- *
- * Why these specific spellings? The preprocessor performs some
- * {@code /i} case-fold expansions (notably for {@code K}↔{@code k}↔
- * Kelvin sign U+212A, {@code µ}↔U+00B5↔U+03BC, and {@code Å}↔
- * U+212B↔{@code å}) by rewriting matching code points into alternations.
- * If the marker contained any of these "problem" letters it would be
- * silently rewritten under {@code /i}, bypassing the detection check and
- * leaving a garbled placeholder embedded in the compiled pattern (observed
- * bug: {@code (?{UNIMPLEMENTED_CODE_BLOC(?:\QK\E|\Qk\E|\QK\E)})}). Keeping
- * the markers free of {@code k}, {@code K}, {@code µ}, {@code å} (and
- * their Unicode counterparts) guarantees the detection check always
- * matches regardless of flags.
+ * Source-only markers that carry frontend regex diagnostics into native Joni
+ * compilation without changing executable pattern text.
*/
public final class RegexMarkers {
/**
- * Environment variable that permits non-constant {@code (?{ CODE })}
- * blocks to compile as empty no-op groups. This is deliberately opt-in:
- * normal PerlOnJava code must fail loudly because callbacks are not
- * executed.
+ * Source-only marker for a literal {@code \\E} encountered without an
+ * active quote or case-modification region. The frontend consumes that
+ * escape, but RuntimeRegex still needs its provenance to emit Perl's
+ * construction-time diagnostic. It is removed before backend compilation.
*/
- public static final String CODE_BLOCK_NOOP_ENV = "JPERL_REGEX_CODE_BLOCK_NOOP";
+ public static final String LITERAL_USELESS_CASE_ESCAPE = "\uFDD0\uFDEF";
- /**
- * Marker for a {@code (?{ CODE })} code block that could not be
- * constant-folded at parse time. Contains no fold-affected letters.
- */
- public static final String CODE_BLOCK = "(?{UNIMPLEMENTED_CODE_BLOC})";
+ private static final String LITERAL_DIAGNOSTIC_PREFIX = "\uFDD0\uFDD2";
+ private static final String LITERAL_DIAGNOSTIC_SUFFIX = "\uFDD3\uFDEF";
+ // Keep identity markers inside the byte domain. Their Latin-1 members
+ // preserve the source's lexical utf8 flag through literal materialization:
+ // byte source stays octets, while `use utf8` source stays Unicode.
+ private static final String LITERAL_IDENTITY_PREFIX = "\u001D\u00FE";
+ private static final String LITERAL_IDENTITY_SUFFIX = "\u00FF\u001C";
+ private static final Pattern LITERAL_DIAGNOSTIC = Pattern.compile(
+ Pattern.quote(LITERAL_DIAGNOSTIC_PREFIX) + "([A-Za-z0-9_-]+)"
+ + Pattern.quote(LITERAL_DIAGNOSTIC_SUFFIX));
+ private static final Pattern LITERAL_IDENTITY = Pattern.compile(
+ Pattern.quote(LITERAL_IDENTITY_PREFIX) + "[0-9]+"
+ + Pattern.quote(LITERAL_IDENTITY_SUFFIX));
- /**
- * Marker for a {@code (??{ CODE })} recursive/dynamic pattern that
- * could not be constant-folded at parse time. Contains no
- * fold-affected letters.
- */
- public static final String RECURSIVE_PATTERN = "(??{UNIMPLEMENTED_RECURSIVE_PATTERN})";
+ /** Distinguish two lexical custom-charname literals without changing matcher source. */
+ public static String literalIdentity(long identity) {
+ return LITERAL_IDENTITY_PREFIX + Math.max(0, identity)
+ + LITERAL_IDENTITY_SUFFIX;
+ }
+
+ /** Encode a frontend diagnostic while retaining the literal regex spelling. */
+ public static String literalDiagnostic(String diagnostic) {
+ return LITERAL_DIAGNOSTIC_PREFIX
+ + Base64.getUrlEncoder().withoutPadding().encodeToString(
+ diagnostic.getBytes(StandardCharsets.UTF_8))
+ + LITERAL_DIAGNOSTIC_SUFFIX;
+ }
+
+ /** Return the first frontend diagnostic carried by a literal pattern. */
+ public static String firstLiteralDiagnostic(String pattern) {
+ if (pattern == null) return null;
+ Matcher matcher = LITERAL_DIAGNOSTIC.matcher(pattern);
+ if (!matcher.find()) return null;
+ return new String(Base64.getUrlDecoder().decode(matcher.group(1)),
+ StandardCharsets.UTF_8);
+ }
+
+ /** Remove all source-only diagnostic markers before backend compilation. */
+ public static String stripLiteralDiagnostics(String pattern) {
+ if (pattern == null) return null;
+ return LITERAL_IDENTITY.matcher(
+ LITERAL_DIAGNOSTIC.matcher(pattern).replaceAll("")).replaceAll("");
+ }
private RegexMarkers() {}
}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java b/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java
deleted file mode 100644
index 2d7cfd83c4..0000000000
--- a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessor.java
+++ /dev/null
@@ -1,1792 +0,0 @@
-package org.perlonjava.runtime.regex;
-
-import com.ibm.icu.lang.UCharacter;
-import org.perlonjava.runtime.operators.WarnDie;
-import org.perlonjava.runtime.runtimetypes.GlobalVariable;
-import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
-import org.perlonjava.runtime.runtimetypes.PerlJavaUnimplementedException;
-import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
-
-import java.util.LinkedHashSet;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static java.lang.Character.isAlphabetic;
-
-/**
- * The RegexPreprocessor class provides functionality to preprocess regular expressions
- * to make them compatible with Java's regex engine. It handles various regex constructs
- * and modifies them as necessary to adhere to Java's regex syntax requirements.
- */
-public class RegexPreprocessor {
-
- // Regex escape rules:
- //
- // \[ as-is
- // \120 becomes: \0120 - Java requires octal sequences to start with zero
- // \0 becomes: \00 - Java requires the extra zero
- // (?#...) inline comment is removed
- // \N{name} named Unicode character or character sequence
- // \N{U+263D} Unicode character
- // \G \G is removed, it is handled separately
- //
- // inside [ ... ]
- // space becomes: "\ " unless the /xx flag is used (flag_xx)
- // \120 becomes: \0120 - Java requires octal sequences to start with zero
- // \0 becomes: \00 - Java requires the extra zero
- // \N{name} named Unicode character or character sequence
- // \N{U+263D} Unicode character
- // [:ascii:] becomes: \p{ASCII}
- // [:^ascii:] becomes: \P{ASCII}
- // \b is moved, Java doesn't support \b inside [...]
- // [xx \b xx] becomes: (?:[xx xx]|\b) - Java doesn't support \b as a character
- //
- // WIP:
- // named capture (? ... ) replace underscore in name
-
- private static final Map SPECIAL_SINGLE_CHAR_FOLDS = Map.of(
- 0x00B5, 0x03BC,
- 0x212A, 0x006B,
- 0x212B, 0x00E5
- );
- private static final Map SPECIAL_SINGLE_CHAR_REVERSE_FOLDS = Map.of(
- 0x03BC, 0x00B5,
- 0x006B, 0x212A,
- 0x00E5, 0x212B
- );
- static int captureGroupCount;
- static boolean deferredUnicodePropertyEncountered;
- static boolean inlinePFlagEncountered;
- /**
- * Tracks named capture groups already emitted in the current pattern.
- * Used to detect duplicate names like `(?a)|(?b)` (legal in Perl,
- * rejected by Java). Duplicates get a synthetic suffix added by
- * {@link #handleNamedCapture}; {@link CaptureNameEncoder#decodeGroupName}
- * strips the suffix back off when reporting names to user code via
- * `%+` / `%-`.
- */
- static java.util.Set seenNamedCaptures = new java.util.HashSet<>();
- static java.util.Map> emittedNamedCaptures = new java.util.LinkedHashMap<>();
- static int duplicateNameCounter;
- static List warningsOnUse = new ArrayList<>();
- private static final ThreadLocal EMIT_WARNINGS = ThreadLocal.withInitial(() -> true);
-
- static boolean shouldEmitWarnings() {
- return EMIT_WARNINGS.get();
- }
-
- static void recordWarningOnUse(String message) {
- warningsOnUse.add(message);
- }
-
- static List getWarningsOnUse() {
- return new ArrayList<>(warningsOnUse);
- }
-
- static void markDeferredUnicodePropertyEncountered() {
- deferredUnicodePropertyEncountered = true;
- }
-
- static boolean hadDeferredUnicodePropertyEncountered() {
- return deferredUnicodePropertyEncountered;
- }
-
- static boolean hadInlinePFlag() {
- return inlinePFlagEncountered;
- }
-
- /**
- * Preprocesses a given regex string to make it compatible with Java's regex engine.
- * This involves handling various constructs and escape sequences that Java does not
- * natively support or requires in a different format.
- *
- * @param s The regex string to preprocess.
- * @param regexFlags The regex flags to use.
- * @return A preprocessed regex string compatible with Java's regex engine.
- * @throws PerlCompilerException If there are unmatched parentheses in the regex.
- */
- static String preProcessRegex(String s, RegexFlags regexFlags) {
- return preProcessRegex(s, regexFlags, true);
- }
-
- static String preProcessRegex(String s, RegexFlags regexFlags, boolean emitWarnings) {
- boolean previousEmitWarnings = EMIT_WARNINGS.get();
- EMIT_WARNINGS.set(emitWarnings);
- try {
- return preProcessRegexInternal(s, regexFlags);
- } finally {
- EMIT_WARNINGS.set(previousEmitWarnings);
- }
- }
-
- private static String preProcessRegexInternal(String s, RegexFlags regexFlags) {
- if (s == null) {
- s = "";
- }
- captureGroupCount = 0;
- deferredUnicodePropertyEncountered = false;
- inlinePFlagEncountered = false;
- seenNamedCaptures.clear();
- emittedNamedCaptures.clear();
- duplicateNameCounter = 0;
- warningsOnUse.clear();
-
- s = removeUnderscoresFromEscapes(s);
- s = normalizeQuantifiers(s);
-
- // Expand multi-character case folds when case-insensitive flag is set
- if (regexFlags.isCaseInsensitive() && !regexFlags.isAsciiStrict()) {
- s = expandMultiCharFolds(materializeFoldableHexEscapes(s), regexFlags);
- }
-
- StringBuilder sb = new StringBuilder();
- handleRegex(s, 0, sb, regexFlags, false);
- return sb.toString();
- }
-
- private static int findRegularCharacterClassEnd(String pattern, int classStart) {
- boolean escaped = false;
- for (int i = classStart + 1; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
- if (escaped) {
- escaped = false;
- if (ch == 'c' && i + 1 < pattern.length()) {
- i++;
- }
- continue;
- }
- if (ch == '\\') {
- escaped = true;
- continue;
- }
- // A POSIX character class such as [[:blank:]] has its own
- // bracket pair inside the surrounding regular class. Do not
- // mistake the POSIX closing bracket for the end of the outer
- // class; otherwise /i preprocessing case-folds syntax letters
- // (notably the k in "blank") as literal class contents.
- if (ch == '[' && i + 1 < pattern.length()
- && (pattern.charAt(i + 1) == ':'
- || pattern.charAt(i + 1) == '.'
- || pattern.charAt(i + 1) == '=')) {
- char delimiter = pattern.charAt(i + 1);
- String terminator = String.valueOf(delimiter) + ']';
- int nestedEnd = pattern.indexOf(terminator, i + 2);
- if (nestedEnd >= 0) {
- i = nestedEnd + 1;
- continue;
- }
- }
- if (ch == ']') {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Expand characters with multi-character case folds into alternations.
- * For example: ß → (?:ß|ss|SS|Ss|sS)
- * This is needed because Java's UNICODE_CASE flag doesn't handle multi-char folds.
- */
- private static String expandMultiCharFolds(String pattern, RegexFlags regexFlags) {
- StringBuilder result = new StringBuilder();
- int i = 0;
- int len = pattern.length();
- boolean inCharClass = false;
- boolean escaped = false;
-
- while (i < len) {
- char ch = pattern.charAt(i);
-
- // Skip extended character classes (?[...]]) entirely - they handle their own case folding
- if (!escaped && ch == '(' && i + 2 < len && pattern.charAt(i + 1) == '?' && pattern.charAt(i + 2) == '[') {
- // Find the end of the extended char class
- int depth = 1;
- int j = i + 3; // Start after (?[
- while (j < len && depth > 0) {
- char c = pattern.charAt(j);
- if (c == '\\' && j + 1 < len) {
- // Skip escaped character
- j += 2;
- continue;
- }
- if (c == ']' && j + 1 < len && pattern.charAt(j + 1) == ')') {
- depth--;
- if (depth == 0) {
- break;
- }
- }
- if (c == '(' && j + 2 < len && pattern.charAt(j + 1) == '?' && pattern.charAt(j + 2) == '[') {
- depth++;
- }
- j++;
- }
- // Append (?[ ... ]) - the whole extended char class unchanged
- result.append(pattern, i, j + 2);
- i = j + 2;
- continue;
- }
-
- // POSIX/collating/equivalence-class tokens nested inside a
- // regular character class are regex syntax, not literal class
- // contents. Preserve the complete token before considering
- // Unicode case-fold expansion of individual characters.
- if (!escaped && inCharClass && ch == '[' && i + 1 < len
- && (pattern.charAt(i + 1) == ':'
- || pattern.charAt(i + 1) == '.'
- || pattern.charAt(i + 1) == '=')) {
- char delimiter = pattern.charAt(i + 1);
- int nestedEnd = pattern.indexOf(String.valueOf(delimiter) + ']', i + 2);
- if (nestedEnd >= 0) {
- result.append(pattern, i, nestedEnd + 2);
- i = nestedEnd + 2;
- continue;
- }
- }
-
- // Track if we're inside a character class [...]
- if (!escaped) {
- if (ch == '[') {
- int classEnd = findRegularCharacterClassEnd(pattern, i);
- if (classEnd > i) {
- String classExpansion = expandMultiCharFoldClass(
- pattern.substring(i, classEnd + 1), regexFlags);
- if (classExpansion != null) {
- result.append(classExpansion);
- i = classEnd + 1;
- continue;
- }
- }
- inCharClass = true;
- } else if (ch == ']') {
- inCharClass = false;
- }
- }
-
- // Skip the name of a named capture group / backreference. Group
- // names are syntactic identifiers, not pattern text, so the fold
- // expansion must not touch them — otherwise `(?...)` under /i
- // becomes `(?...)` and Java rejects the resulting name.
- if (!escaped && !inCharClass && ch == '(' && i + 2 < len
- && pattern.charAt(i + 1) == '?'
- && (pattern.charAt(i + 2) == '<' || pattern.charAt(i + 2) == '\''
- || (pattern.charAt(i + 2) == 'P' && i + 3 < len
- && (pattern.charAt(i + 3) == '<' || pattern.charAt(i + 3) == '\'' || pattern.charAt(i + 3) == '=')))) {
- // (?... | (?'name'... | (?P... | (?P'name'... | (?P=name)
- int nameStart;
- char closer;
- if (pattern.charAt(i + 2) == 'P') {
- if (pattern.charAt(i + 3) == '=') {
- // (?P=name) — name terminates at ')'
- nameStart = i + 4;
- closer = ')';
- } else {
- nameStart = i + 4;
- closer = pattern.charAt(i + 3) == '<' ? '>' : '\'';
- }
- } else {
- nameStart = i + 3;
- closer = pattern.charAt(i + 2) == '<' ? '>' : '\'';
- }
- // Reject lookbehind: (?<= or (?' && nameStart < len
- && (pattern.charAt(nameStart) == '=' || pattern.charAt(nameStart) == '!')) {
- // Not a named capture; fall through to normal handling.
- } else {
- int closeIdx = pattern.indexOf(closer, nameStart);
- if (closeIdx > nameStart) {
- // Append the literal `(?` (or variant) verbatim, no folding.
- result.append(pattern, i, closeIdx + 1);
- i = closeIdx + 1;
- continue;
- }
- }
- }
-
- // Handle escape sequences
- if (ch == '\\' && !escaped) {
- escaped = true;
- result.append(ch);
- i++;
- continue;
- }
-
- // If this is an escaped character or we're in a char class, don't expand
- if (escaped || inCharClass) {
- if (!escaped && inCharClass) {
- int codePoint = pattern.codePointAt(i);
- String specialClassExpansion = expandSpecialSingleCharFoldInCharClass(codePoint);
- if (specialClassExpansion != null) {
- result.append(specialClassExpansion);
- i += Character.charCount(codePoint);
- continue;
- }
- }
-
- // Skip over \p{...}, \P{...}, \x{...}, \o{...} constructs without case folding
- // Note: \N{...} is handled separately below - it needs case-folding for special chars
- if (escaped && (ch == 'p' || ch == 'P' || ch == 'x' || ch == 'o')
- && i + 1 < len && pattern.charAt(i + 1) == '{') {
- result.append(ch);
- i++;
- // Now append everything up to and including the closing '}'
- while (i < len && pattern.charAt(i) != '}') {
- result.append(pattern.charAt(i));
- i++;
- }
- if (i < len) {
- result.append(pattern.charAt(i)); // append '}'
- i++;
- }
- escaped = false;
- continue;
- }
-
- // Handle \N{...} specially - resolve and apply case-folding if needed
- if (escaped && ch == 'N' && i + 1 < len && pattern.charAt(i + 1) == '{') {
- int endBrace = pattern.indexOf('}', i + 2);
- if (endBrace != -1) {
- String charName = pattern.substring(i + 2, endBrace).trim();
- try {
- int codePoint = UnicodeResolver.getCodePointFromName(charName);
- // Check if this character needs special case-fold expansion
- String charClassExpansion = expandSpecialSingleCharFoldInCharClass(codePoint);
- if (charClassExpansion != null) {
- // Remove the backslash that was already appended
- result.setLength(result.length() - 1);
- if (inCharClass) {
- // Inside regular [...], append chars directly
- result.append(charClassExpansion);
- } else {
- // Not inside [...], wrap in character class
- // This handles extended char class context like (?[ \N{KELVIN SIGN} ])
- result.append('[').append(charClassExpansion).append(']');
- }
- } else {
- // No special expansion needed, keep the original \N{...}
- result.append(ch);
- for (int j = i + 1; j <= endBrace; j++) {
- result.append(pattern.charAt(j));
- }
- }
- i = endBrace + 1;
- escaped = false;
- continue;
- } catch (IllegalArgumentException e) {
- // Unknown character name, pass through as-is
- result.append(ch);
- i++;
- escaped = false;
- continue;
- }
- }
- }
-
- result.append(ch);
- escaped = false;
- i++;
- continue;
- }
-
- // Check if this character has a multi-char fold
- int codePoint = pattern.codePointAt(i);
- if (!regexFlags.isAsciiStrict() && MultiCharFoldMapper.hasMultiCharFold(codePoint)) {
- // Expand it (e.g., ß → (?:ß|ss|SS|Ss|sS))
- String expansion = MultiCharFoldMapper.expandToAlternation(codePoint);
- result.append(expansion);
- i += Character.charCount(codePoint);
- } else {
- // Check if we're at the start of a reverse fold sequence (e.g., ss → ß)
- boolean foundReverseFold = false;
- // Try longest sequences first (3, then 2 characters)
- for (int seqLen = 3; seqLen >= 2 && !foundReverseFold; seqLen--) {
- if (i + seqLen <= len) {
- String sequence = pattern.substring(i, i + seqLen);
- if (!regexFlags.isAsciiStrict() && MultiCharFoldMapper.hasReverseFold(sequence)) {
- // Expand to include both the sequence and its reverse fold
- result.append("(?:");
- result.append(sequence);
- for (int reverseFoldChar : MultiCharFoldMapper.getReverseFolds(sequence)) {
- result.append("|");
- result.appendCodePoint(reverseFoldChar);
- }
- result.append(")");
- i += seqLen;
- foundReverseFold = true;
- }
- }
- }
-
- if (!foundReverseFold) {
- String specialExpansion = regexFlags.isAsciiStrict() ? null
- : MultiCharFoldMapper.expandSimpleFoldToAlternation(codePoint);
- if (specialExpansion == null && !regexFlags.isAsciiStrict()) {
- specialExpansion = expandSpecialSingleCharFold(codePoint);
- }
- if (specialExpansion != null) {
- result.append(specialExpansion);
- } else {
- result.appendCodePoint(codePoint);
- }
- i += Character.charCount(codePoint);
- }
- }
-
- escaped = false;
- }
-
- return result.toString();
- }
-
- private static String expandMultiCharFoldClass(String charClass, RegexFlags regexFlags) {
- if (regexFlags.isAsciiStrict() || charClass.length() < 3 || charClass.charAt(1) == '^') return null;
-
- LinkedHashSet folds = new LinkedHashSet<>();
- LinkedHashSet simpleVariants = new LinkedHashSet<>();
- boolean escaped = false;
- for (int i = 1; i < charClass.length() - 1; ) {
- int codePoint = charClass.codePointAt(i);
- if (!escaped && codePoint == '\\') {
- escaped = true;
- i++;
- continue;
- }
- if (escaped) {
- escaped = false;
- i += Character.charCount(codePoint);
- continue;
- }
- if (codePoint == '-' || codePoint == '[' || codePoint == ']') return null;
- String fold = MultiCharFoldMapper.getMultiCharFold(codePoint);
- if (fold != null) folds.add(Pattern.quote(fold));
- simpleVariants.addAll(MultiCharFoldMapper.getSimpleFoldVariants(codePoint));
- i += Character.charCount(codePoint);
- }
- if (folds.isEmpty() && simpleVariants.isEmpty()) return null;
-
- StringBuilder expandedClass = new StringBuilder(charClass.substring(0, charClass.length() - 1));
- simpleVariants.forEach(expandedClass::appendCodePoint);
- expandedClass.append(']');
- if (folds.isEmpty()) return expandedClass.toString();
-
- StringBuilder expansion = new StringBuilder("(?:").append(expandedClass);
- for (String fold : folds) expansion.append('|').append(fold);
- return expansion.append(')').toString();
- }
-
- /**
- * Materialize only hex escapes relevant to full case folds. Keeping all
- * other escapes intact avoids turning escaped regex metacharacters into
- * syntax while allowing sequences such as \x{73}\x{73} to be recognized
- * as the reverse fold of sharp-s.
- */
- private static String materializeFoldableHexEscapes(String pattern) {
- StringBuilder result = new StringBuilder(pattern.length());
- for (int i = 0; i < pattern.length(); ) {
- if (pattern.charAt(i) == '\\' && i + 3 < pattern.length()
- && pattern.charAt(i + 1) == 'x' && pattern.charAt(i + 2) == '{') {
- int close = pattern.indexOf('}', i + 3);
- if (close > i + 3) {
- try {
- int codePoint = Integer.parseInt(pattern.substring(i + 3, close), 16);
- if (Character.isValidCodePoint(codePoint)
- && (MultiCharFoldMapper.hasMultiCharFold(codePoint)
- || MultiCharFoldMapper.hasSimpleFold(codePoint)
- || MultiCharFoldMapper.isFoldComponent(codePoint))) {
- result.appendCodePoint(codePoint);
- i = close + 1;
- continue;
- }
- } catch (NumberFormatException ignored) {
- // Leave malformed escapes to the normal regex parser.
- }
- }
- }
- int codePoint = pattern.codePointAt(i);
- result.appendCodePoint(codePoint);
- i += Character.charCount(codePoint);
- }
- return result.toString();
- }
-
- private static String expandSpecialSingleCharFold(int codePoint) {
- // Compute full case fold for this code point.
- int folded = UCharacter.foldCase(codePoint, true);
-
- // Trigger expansion only if this code point participates in one of the known problematic folds.
- // We key off the folded form so that e.g. 'k' and 'K' will match Kelvin sign under /i.
- if (!SPECIAL_SINGLE_CHAR_FOLDS.containsKey(codePoint)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(folded)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(codePoint)) {
- return null;
- }
-
- LinkedHashSet variants = new LinkedHashSet<>();
- variants.add(codePoint);
- variants.add(folded);
-
- Integer reverse = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(folded);
- if (reverse != null) {
- variants.add(reverse);
- }
- Integer reverse2 = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(codePoint);
- if (reverse2 != null) {
- variants.add(reverse2);
- }
-
- // Include upper/lower variants of all participating code points.
- // This keeps behavior stable even if the Java regex engine doesn't map the special ones.
- LinkedHashSet expanded = new LinkedHashSet<>();
- for (Integer cp : variants) {
- expanded.add(cp);
- expanded.add(UCharacter.toLowerCase(cp));
- expanded.add(UCharacter.toUpperCase(cp));
- expanded.add(UCharacter.foldCase(cp, true));
- }
-
- StringBuilder sb = new StringBuilder("(?:");
- boolean first = true;
- for (Integer cp : expanded) {
- if (!first) {
- sb.append("|");
- }
- first = false;
- sb.append(Pattern.quote(new String(Character.toChars(cp))));
- }
- sb.append(")");
- return sb.toString();
- }
-
- private static String expandSpecialSingleCharFoldInCharClass(int codePoint) {
- int folded = UCharacter.foldCase(codePoint, true);
-
- if (!SPECIAL_SINGLE_CHAR_FOLDS.containsKey(codePoint)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(folded)
- && !SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.containsKey(codePoint)) {
- return null;
- }
-
- LinkedHashSet variants = new LinkedHashSet<>();
- variants.add(codePoint);
- variants.add(folded);
-
- Integer reverse = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(folded);
- if (reverse != null) {
- variants.add(reverse);
- }
- Integer reverse2 = SPECIAL_SINGLE_CHAR_REVERSE_FOLDS.get(codePoint);
- if (reverse2 != null) {
- variants.add(reverse2);
- }
-
- LinkedHashSet expanded = new LinkedHashSet<>();
- for (Integer cp : variants) {
- expanded.add(cp);
- expanded.add(UCharacter.toLowerCase(cp));
- expanded.add(UCharacter.toUpperCase(cp));
- expanded.add(UCharacter.foldCase(cp, true));
- }
-
- StringBuilder sb = new StringBuilder();
- for (Integer cp : expanded) {
- appendCharClassLiteral(sb, cp);
- }
- return sb.toString();
- }
-
- private static void appendCharClassLiteral(StringBuilder sb, int codePoint) {
- // Escape only the few metacharacters with special meaning inside [...].
- if (codePoint == '\\' || codePoint == ']' || codePoint == '-') {
- sb.append('\\');
- }
- sb.appendCodePoint(codePoint);
- }
-
- /**
- * Remove underscores from \x{...} and \o{...} escape sequences.
- * Perl allows underscores in numeric literals for readability, but Java doesn't.
- * Example: \x{_1_0000} becomes \x{10000}
- */
- private static String removeUnderscoresFromEscapes(String pattern) {
- StringBuilder result = new StringBuilder();
- int i = 0;
- int len = pattern.length();
-
- while (i < len) {
- if (i + 2 < len && pattern.charAt(i) == '\\' &&
- (pattern.charAt(i + 1) == 'x' || pattern.charAt(i + 1) == 'o') &&
- pattern.charAt(i + 2) == '{') {
- // Found \x{ or \o{
- result.append(pattern.charAt(i)); // \
- result.append(pattern.charAt(i + 1)); // x or o
- result.append('{');
- i += 3;
-
- // Copy content until }, removing underscores
- while (i < len && pattern.charAt(i) != '}') {
- char ch = pattern.charAt(i);
- if (ch != '_') {
- result.append(ch);
- }
- i++;
- }
-
- if (i < len) {
- result.append('}'); // Closing brace
- i++;
- }
- } else {
- result.append(pattern.charAt(i));
- i++;
- }
- }
-
- return result.toString();
- }
-
- /**
- * Normalize quantifiers to Java regex format.
- * Perl allows {,n} to mean {0,n} (omitting minimum).
- * Perl allows spaces inside quantifiers like {2, 5} or { , 2 }.
- * Java requires explicit {0,n} and no spaces (unless in /x mode).
- * Example: {,2} becomes {0,2}, { , 2 } becomes {0,2}
- */
- private static String normalizeQuantifiers(String pattern) {
- StringBuilder result = new StringBuilder();
- int i = 0;
- int len = pattern.length();
- boolean inCharClass = false;
- boolean escaped = false;
-
- while (i < len) {
- char ch = pattern.charAt(i);
-
- if (escaped) {
- result.append(ch);
- escaped = false;
- i++;
- continue;
- }
-
- if (ch == '\\') {
- result.append(ch);
- escaped = true;
- i++;
- continue;
- }
-
- if (ch == '[') {
- inCharClass = true;
- result.append(ch);
- i++;
- continue;
- }
-
- if (ch == ']' && inCharClass) {
- inCharClass = false;
- result.append(ch);
- i++;
- continue;
- }
-
- if (ch == '{' && !inCharClass) {
- // Potential quantifier
- int start = i;
- i++;
- StringBuilder quantifier = new StringBuilder();
-
- // Read the quantifier content
- while (i < len && pattern.charAt(i) != '}') {
- quantifier.append(pattern.charAt(i));
- i++;
- }
-
- if (i < len && pattern.charAt(i) == '}') {
- // We have a complete quantifier
- String content = quantifier.toString().trim();
-
- // Check if it's a {,n} pattern or has spaces
- if (content.matches("\\s*,\\s*\\d+\\s*")) {
- // {,n} pattern - add 0 at the start
- content = content.replaceAll("\\s*,\\s*(\\d+)\\s*", "0,$1");
- result.append('{').append(content).append('}');
- i++;
- } else if (content.contains(",") && content.matches(".*\\s+.*")) {
- // Has comma and spaces - remove spaces
- content = content.replaceAll("\\s+", "");
- result.append('{').append(content).append('}');
- i++;
- } else {
- // Normal quantifier, keep as-is
- result.append('{').append(quantifier).append('}');
- i++;
- }
- } else {
- // Unclosed {, not a quantifier - keep as-is
- result.append('{').append(quantifier);
- }
- } else {
- result.append(ch);
- i++;
- }
- }
-
- return result.toString();
- }
-
- /**
- * Preprocesses a given regex string to make it compatible with Java's regex engine.
- * This involves handling various constructs and escape sequences that Java does not
- * natively support or requires in a different format.
- *
- * @param s The regex string to preprocess.
- * @param offset The position in the string.
- * @param sb The string builder to append the preprocessed regex to.
- * @param regexFlags The regex flags to use.
- * @param stopAtClosingParen A flag indicating whether to stop processing at the first closing parenthesis.
- * @return The position in the string after the preprocessed regex.
- * @throws PerlCompilerException If there are unmatched parentheses in the regex.
- */
- static int handleRegex(String s, int offset, StringBuilder sb, RegexFlags regexFlags, boolean stopAtClosingParen) {
- final int length = s.length();
- boolean lastWasQuantifiable = false; // Track if the last thing can be quantified
-
- // Remove \G from the pattern string for Java compilation
- if (s.startsWith("\\G", offset)) {
- offset += 2;
- // Also strip any quantifier that followed \G (e.g. \G? in (\G?[ac])?)
- // Since \G is a zero-width assertion, \G? is effectively nothing
- if (offset < length) {
- char next = s.charAt(offset);
- if (next == '?' || next == '*' || next == '+') {
- offset++;
- }
- }
- }
-
- while (offset < length) {
- final int c = s.codePointAt(offset);
- boolean isQuantifier = false;
-
- switch (c) {
- case '*':
- case '+':
- case '?':
- // Check if the previous item can be quantified
- if (!lastWasQuantifiable) {
- // Distinguish "Nested quantifiers" from "Quantifier follows nothing":
- // If the last char in sb is a quantifier, this is a nested quantifier
- if (sb.length() > 0) {
- char lc = sb.charAt(sb.length() - 1);
- boolean esc = sb.length() >= 2 && sb.charAt(sb.length() - 2) == '\\';
- if (!esc && (lc == '*' || lc == '+' || lc == '?' || lc == '}')) {
- regexError(s, offset + 1, "Nested quantifiers");
- }
- }
- regexError(s, offset + 1, "Quantifier follows nothing");
- }
-
- // Check if this might be a possessive quantifier
- boolean isPossessive = offset + 1 < length && s.charAt(offset + 1) == '+';
-
- // Check if this might be a non-greedy quantifier
- boolean isNonGreedy = offset + 1 < length && s.charAt(offset + 1) == '?';
-
- // Check for nested quantifiers (but not possessive or non-greedy)
- if (!isPossessive && !isNonGreedy && sb.length() > 0) {
- char lastChar = sb.charAt(sb.length() - 1);
- // Also check if the previous character was a backslash (escaped)
- boolean isEscaped = sb.length() >= 2 && sb.charAt(sb.length() - 2) == '\\';
- if (!isEscaped && (lastChar == '*' || lastChar == '+' || lastChar == '?')) {
- regexError(s, offset + 1, "Nested quantifiers");
- }
- }
-
- sb.append(Character.toChars(c));
-
- // If possessive or non-greedy, also append the following character
- if (isPossessive) {
- sb.append('+');
- offset++; // Skip the extra +
- lastWasQuantifiable = false; // Can't quantify a possessive quantifier
- } else if (isNonGreedy) {
- sb.append('?');
- offset++; // Skip the extra ?
- lastWasQuantifiable = false; // Can't quantify a non-greedy quantifier
- } else {
- lastWasQuantifiable = false; // Regular quantifier
- }
- break;
-
- case '\\': // Handle escape sequences
- offset = RegexPreprocessorHelper.handleEscapeSequences(s, sb, c, offset, regexFlags);
- lastWasQuantifiable = true;
- break;
-
- case '[': // Handle character classes
- offset = handleCharacterClass(s, regexFlags.isExtendedWhitespace(), sb, c, offset);
- lastWasQuantifiable = true; // Character classes can be quantified
- break;
-
- case '(':
- offset = handleParentheses(s, offset, length, sb, c, regexFlags, stopAtClosingParen);
- lastWasQuantifiable = true;
- break;
-
- case ')':
- if (stopAtClosingParen) {
- return offset;
- }
- regexError(s, offset, "Unmatched ) in regex");
- break;
-
- case '#':
- if (regexFlags.isExtended() || regexFlags.isExtendedWhitespace()) {
- // Consume comment to end of line
- while (offset < length) {
- if (s.codePointAt(offset) == '\n') {
- break;
- }
- offset++;
- }
- break;
- } else {
- sb.append(Character.toChars(c));
- lastWasQuantifiable = true;
- break;
- }
-
- case '{':
- // Only treat '{' as a quantifier if the previous item is quantifiable.
- // Otherwise, '{' must be a literal (Perl allows literal braces).
- if (!lastWasQuantifiable) {
- // Literal '{' (not a quantifier), escape for Java regex
- sb.append("\\{");
- lastWasQuantifiable = true;
- } else {
- // Parse as quantifier (may return literal braces if invalid quantifier)
- int[] quantResult = handleQuantifier(s, offset, sb);
- offset = quantResult[0];
- boolean wasLiteral = quantResult[1] != 0;
-
- if (wasLiteral) {
- // Braces were treated as literal (e.g., {} or {,})
- // The escaped \} is quantifiable
- lastWasQuantifiable = true;
- } else {
- // Valid quantifier
- // Check for possessive quantifier {n,m}+
- if (offset + 1 < length && s.charAt(offset + 1) == '+') {
- sb.append('+');
- offset++; // Skip the extra +
- }
- // Check for non-greedy quantifier {n,m}?
- else if (offset + 1 < length && s.charAt(offset + 1) == '?') {
- sb.append('?');
- offset++; // Skip the extra ?
- }
-
- isQuantifier = true;
- lastWasQuantifiable = false; // Can't quantify a quantifier
- }
- }
- break;
-
- case '^':
- sb.append(Character.toChars(c));
- lastWasQuantifiable = false; // Can't quantify anchors
- break;
-
- case '|':
- sb.append(Character.toChars(c));
- lastWasQuantifiable = false; // Next item starts fresh
- break;
-
- default: // Append normal characters
- sb.append(Character.toChars(c));
- lastWasQuantifiable = true;
- break;
- }
-
- // Check for nested quantifiers
- if (isQuantifier && offset + 1 < length) {
- char nextChar = s.charAt(offset + 1);
- // Don't flag *+, ++, ?+ as nested quantifiers (they're possessive)
- // Don't flag *?, +?, ??, }? as nested quantifiers (they're non-greedy)
- boolean isModifier = (nextChar == '+' || nextChar == '?');
- if (!isModifier && (nextChar == '*' || nextChar == '+' || nextChar == '?')) {
- regexError(s, offset + 2, "Nested quantifiers");
- }
- // For '{', only flag as nested if it's actually a valid quantifier
- if (nextChar == '{' && isValidQuantifierAt(s, offset + 1)) {
- regexError(s, offset + 2, "Nested quantifiers");
- }
- }
-
- offset += Character.charCount(c);
- }
-
- return offset;
- }
-
- static void regexError(String s, int offset, String errMsg) {
- if (offset > s.length()) {
- offset = s.length();
- }
-
-
- // "Error message in regex; marked by <-- HERE in m/regex <-- HERE remaining/"
- String before = s.substring(0, offset);
- String after = s.substring(offset);
-
- // When marker is at the end, no space before <-- HERE
- String marker = after.isEmpty() ? " <-- HERE" : " <-- HERE ";
-
- throw new PerlCompilerException(errMsg + " in regex; marked by <-- HERE in m/" +
- before + marker + after + "/");
- }
-
- private static int handleParentheses(String s, int offset, int length, StringBuilder sb, int c, RegexFlags regexFlags, boolean stopAtClosingParen) {
- // Check for incomplete (?
- if (offset + 1 >= length) {
- regexError(s, offset + 1, "Sequence (? incomplete");
- }
-
- int c2 = s.codePointAt(offset + 1);
-
- // Check for (*...) verb patterns FIRST, before checking (?
- if (c2 == '*') {
- // Java-backend compatibility for (*...) control verbs such as
- // (*FAIL). Alpha assertions are routed to Joni before preprocessing.
-
- // Find the verb name (up to ':' or ')')
- int verbNameEnd = offset + 2;
- while (verbNameEnd < length) {
- int cp = s.codePointAt(verbNameEnd);
- if (cp == ':' || cp == ')') break;
- verbNameEnd++;
- }
- String verbName = s.substring(offset + 2, verbNameEnd);
-
- // Perl's (*FAIL) / (*F) is a zero-width assertion that can never
- // succeed. Java's empty negative lookahead has exactly that
- // declarative behavior and preserves surrounding backtracking.
- if ((verbName.equals("FAIL") || verbName.equals("F"))
- && verbNameEnd < length && s.codePointAt(verbNameEnd) == ')') {
- sb.append("(?!)");
- return verbNameEnd;
- }
-
- // Find the end of the verb for error reporting
- int verbEnd = offset + 2;
- while (verbEnd < length && s.codePointAt(verbEnd) != ')') {
- verbEnd++;
- }
- if (verbEnd < length) {
- verbEnd++; // Include the closing paren
- }
-
- // Extract the verb name for error reporting
- String verb = s.substring(offset, Math.min(verbEnd, length));
-
- // Replace with empty non-capturing group as placeholder
- sb.append("(?:)");
-
- // Throw error that can be caught by JPERL_UNIMPLEMENTED=warn
- regexUnimplemented(s, offset + 2,
- "Regex control verb " + verb + " not implemented");
-
- return verbEnd; // Skip past the entire verb construct
- } else if (c2 == '?') {
- if (offset + 2 >= length) {
- // Marker should be after the ?
- regexError(s, offset + 2, "Sequence (? incomplete");
- }
-
- int c3 = s.codePointAt(offset + 2);
-
- // For sequences that need a 4th character
- int c4 = (offset + 3 < length) ? s.codePointAt(offset + 3) : -1;
-
- if (c3 == '#') {
- // Remove inline comments (?# ... )
- offset = handleSkipComment(offset, s, length);
- // Comment is completely removed - don't append anything, just return
- return offset; // offset points to ')', caller will increment past it
- } else if (c3 == '@') {
- // Handle (?@...) which is not implemented
- regexUnimplemented(s, offset + 3, "Sequence (?@...) not implemented");
- } else if (c3 == '{') {
- // Check if this is our special unimplemented marker
- if (s.startsWith(RegexMarkers.CODE_BLOCK, offset)) {
- if (!regexFlags.allowEvalGroup()) {
- if (allowRegexCodeBlockNoop()) {
- regexUnimplementedSoft(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- } else if (isUnimplementedWarnMode()) {
- regexUnimplemented(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- } else {
- regexError(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- }
- }
- sb.append("(?:");
- offset = offset + RegexMarkers.CODE_BLOCK.length() - 1;
- } else {
- // Handle (?{ ... }) code blocks - try constant folding
- offset = handleCodeBlock(s, offset, length, sb, regexFlags);
- }
- } else if (c3 == '?' && c4 == '{') {
- // Check if this is the unimplemented marker for (??{...}).
- // Under JPERL_UNIMPLEMENTED=warn, warn and fall through to the
- // existing non-constant handling (which appends "(?:"); under
- // die mode, abort with a clean diagnostic. Either way the user
- // sees the issue — silent substitution would be a lie.
- if (s.startsWith(RegexMarkers.RECURSIVE_PATTERN, offset)) {
- regexUnimplementedSoft(s, offset + 3,
- "(??{...}) recursive/dynamic regex patterns not implemented");
- if (isUnimplementedWarnMode()) {
- // The marker includes the source construct's closing
- // parenthesis. Emit a complete empty-group fallback
- // and return its closing position so the dynamic
- // construct is not parsed a second time below.
- sb.append("(?:)");
- offset += RegexMarkers.RECURSIVE_PATTERN.length() - 1;
- return offset;
- }
- }
- // Handle (??{ ... }) recursive/dynamic regex patterns
- // These insert a regex pattern at runtime based on code execution
-
- // Skip the (??{ part to find the code content
- int codeStart = offset + 4;
- int codeOffset = findRegexCodeBlockClosingBrace(s, codeStart);
- if (codeOffset < 0) {
- regexError(s, offset + 3,
- "Unmatched '{' in (??{...}) dynamic pattern");
- }
- // codeOffset points at the closing '}'
- String codeBlock = s.substring(codeStart, codeOffset).trim();
- offset = codeOffset + 1; // Skip past '}'
-
- // For simple constant expressions, inline the value as a regex pattern.
- // (??{1}) means "evaluate 1 and use result as pattern" → matches literal "1"
- // (??{"[x]"}) → matches character class [x]
- if (isSimpleConstant(codeBlock)) {
- String value = evaluateSimpleConstant(codeBlock);
- if (value != null) {
- // Insert the constant value as a non-capturing group pattern.
- // Run through handleRegex to process any regex constructs
- // (e.g. (?[...]) from regex_sets transformation).
- sb.append("(?:");
- handleRegex(value, 0, sb, regexFlags, false);
- } else {
- // Fallback: empty non-capturing group
- sb.append("(?:");
- }
- } else {
- // Non-constant: replace with empty non-capturing group
- sb.append("(?:");
- }
-
- // offset now points at ')' closing the (??{...}) construct
- // Fall through to common ')' handling at end of handleParentheses
- } else if (c3 == ';') {
- // (?;...) is not recognized - marker should be after ;
- regexError(s, offset + 3, "Sequence (?;...) not recognized");
- } else if (c3 == '\\') {
- // (?\...) is not recognized - marker should be after \
- regexError(s, offset + 3, "Sequence (?\\...) not recognized");
- } else if (c3 == '<' && (isAlphabetic(c4) || c4 == '_')) {
- // Handle named capture (? ... ) - name can start with letter or underscore
- offset = handleNamedCapture(c3, s, offset, length, sb, regexFlags);
- } else if (c3 == '<') {
- // Invalid character after (?<
- if (offset + 3 < length) {
- // For (?<;x, the marker should be after the invalid character (the ;)
- regexError(s, offset + 4, "Group name must start with a non-digit word character");
- } else {
- // Pattern ends after (?<
- regexError(s, offset + 3, "Sequence (?<... not terminated");
- }
- } else if (c3 == '\'') {
- // Handle named capture (?'name' ... )
- offset = handleNamedCapture(c3, s, offset, length, sb, regexFlags);
- } else if (c3 == '[') {
- // Handle extended bracketed character class (?[...])
- return ExtendedCharClass.handleExtendedCharacterClass(s, offset, sb, regexFlags);
- } else if ((c3 >= 'a' && c3 <= 'z') || c3 == '-' || c3 == '^' || c3 == ':' || c3 == ')') {
- // Handle (?modifiers: ... ) construct and non-capturing groups
- return RegexPreprocessorHelper.handleFlagModifiers(s, offset, sb, regexFlags);
- } else if (c3 == ':') {
- // Handle non-capturing group (?:...)
- sb.append("(?:");
- offset = handleRegex(s, offset + 3, sb, regexFlags, true);
- } else if (c3 == '=') {
- // Positive lookahead (?=...)
- sb.append("(?=");
- offset = handleRegex(s, offset + 3, sb, regexFlags, true);
- } else if (c3 == '!') {
- // Negative lookahead (?!...)
- sb.append("(?!");
- offset = handleRegex(s, offset + 3, sb, regexFlags, true);
- } else if (c3 == '>') {
- // Atomic group (?>...) - non-backtracking group
- sb.append("(?>");
- offset = handleRegex(s, offset + 3, sb, regexFlags, true);
- } else if (Character.isDigit(c3)) {
- // Recursive subpattern reference (?1), (?2), etc.
- // These refer to the subpattern with that number and are recursive
- regexUnimplemented(s, offset + 2, "Sequence (?" + ((char) c3) + "...) not recognized");
- } else if (c3 == '&') {
- // Named group recursion (?&name) - Perl feature not yet implemented
- regexUnimplemented(s, offset + 2, "Sequence (?&...) not recognized");
- } else {
- // Unknown sequence - show the actual character
- String seq = "(?";
- if (offset + 2 < length) {
- seq += Character.toString((char) s.codePointAt(offset + 2));
- }
- regexError(s, offset + 2, "Sequence " + seq + "...) not recognized");
- }
- } else {
- // Regular parenthesis
- offset = handleRegularParentheses(s, offset, length, sb, regexFlags);
- }
-
- // Ensure the closing parenthesis is consumed
- if (offset >= length || s.codePointAt(offset) != ')') {
- // Change this error message based on what we're looking for
- regexError(s, offset - 1, "Unmatched (");
- }
- sb.append(')');
- return offset;
- }
-
- private static int handleRegularParentheses(String s, int offset, int length, StringBuilder sb, RegexFlags regexFlags) {
- if (regexFlags.isNonCapturing()) {
- // Check if it's already a non-capturing group
- boolean isNonCapturing = offset + 1 < length &&
- s.charAt(offset + 1) == '?' &&
- s.charAt(offset + 2) == ':';
-
- // If not already non-capturing and not a special construct, make it non-capturing
- if (!isNonCapturing && s.charAt(offset + 1) != '?') {
- sb.append("(?:");
- } else {
- sb.append('(');
- if (!isNonCapturing) {
- captureGroupCount++; // Only increment for capturing groups
- }
- }
- } else {
- sb.append('(');
- captureGroupCount++; // Increment counter for capturing groups
- }
-
- return handleRegex(s, offset + 1, sb, regexFlags, true);
- }
-
- private static int handleNamedCapture(int c, String s, int offset, int length, StringBuilder sb, RegexFlags regexFlags) {
- int start = offset + 3; // Skip past '(?<'
- int end = c == '<'
- ? s.indexOf('>', start)
- : s.indexOf('\'', start);
- if (end == -1) {
- regexError(s, offset, "Unterminated named capture in regex");
- }
- String name = s.substring(start, end);
- // Encode underscores for Java regex compatibility
- String encodedName = CaptureNameEncoder.encodeGroupName(name);
- String originalEncodedName = encodedName;
- // Perl allows the same capture group name to appear in multiple
- // alternation branches (e.g. `(?\d+)|(?foo)`). Java's regex
- // engine rejects duplicates outright, so we suffix subsequent
- // occurrences with a synthetic marker; CaptureNameEncoder.decodeGroupName
- // strips the marker back off when reporting names to user code.
- if (!seenNamedCaptures.add(encodedName)) {
- encodedName = encodedName + CaptureNameEncoder.DUPLICATE_MARKER + (duplicateNameCounter++);
- }
- emittedNamedCaptures.computeIfAbsent(originalEncodedName, k -> new java.util.ArrayList<>()).add(encodedName);
- sb.append("(?<").append(encodedName).append(">");
- captureGroupCount++; // Increment counter for capturing groups
- return handleRegex(s, end + 1, sb, regexFlags, true); // Process content inside the group
- }
-
- static java.util.List namedBackreferenceTargets(String perlName) {
- String encodedName = CaptureNameEncoder.encodeGroupName(perlName);
- java.util.List emitted = emittedNamedCaptures.get(encodedName);
- if (emitted == null || emitted.isEmpty()) {
- return java.util.Collections.singletonList(encodedName);
- }
- return emitted;
- }
-
- private static int handleCharacterClass(String s, boolean flag_xx, StringBuilder sb, int c, int offset) {
- final int length = s.length();
- int classStart = sb.length();
- sb.append(Character.toChars(c)); // Append the '['
- offset++;
-
-
- // Check if the bracket is properly closed
- int bracketEnd = offset;
- int depth = 1;
- boolean inEscape = false;
-
- while (bracketEnd < length && depth > 0) {
- char ch = s.charAt(bracketEnd);
- if (inEscape) {
- inEscape = false;
- // \cX consumes the next character (control-character escape),
- // even if that character is ']' or '\' (e.g., \c], \c\).
- if (ch == 'c' && bracketEnd + 1 < length) {
- bracketEnd++; // skip the control-char target
- }
- } else if (ch == '\\') {
- inEscape = true;
- } else if (ch == '[') {
- // Don't increment depth - [ is just a literal inside a character class
- } else if (ch == ']') {
- depth--;
- }
- bracketEnd++;
- }
-
- if (depth > 0) {
- regexError(s, offset, "Unmatched [");
- }
-
- // Check for POSIX syntax at the start of character class
- if (offset < length && s.charAt(offset) == '[') {
- // Check for [[=...=]] or [[.....]]
- if (offset + 1 < length) {
- char nextChar = s.charAt(offset + 1);
- if (nextChar == '=' || nextChar == '.') {
- // Look for closing syntax
- int searchPos = offset + 2;
- while (searchPos < length - 1) {
- if (s.charAt(searchPos) == nextChar && s.charAt(searchPos + 1) == ']') {
- // Found complete POSIX syntax
- String syntaxType = nextChar == '=' ? "[= =]" : "[. .]";
- regexError(s, searchPos + 2, "POSIX syntax " + syntaxType + " is reserved for future extensions");
- }
- searchPos++;
- }
- }
- }
- }
-
- StringBuilder rejected = new StringBuilder();
- offset = RegexPreprocessorHelper.handleRegexCharacterClassEscape(offset, s, sb, length, flag_xx, rejected);
- String processedClass = sb.substring(classStart);
- if (processedClass.startsWith("[^")) {
- sb.setLength(classStart);
- sb.append("(?:\\x{FFFD}<[0-9A-F]+>|");
- sb.append(processedClass);
- sb.append(')');
- }
- // Note: rejected is kept for future use but currently \b is handled by direct substitution to \x08
- return offset;
- }
-
-// public static void hexPrinter(String emojiString) {
-// // Print each code point as a hex value
-// System.out.println("String: <<" + emojiString + ">>");
-// System.out.println("Hexadecimal representation:");
-// emojiString.codePoints()
-// .forEach(codePoint -> System.out.printf("U+%04X ", codePoint));
-//
-// // Optional: Print raw UTF-16 encoding
-// System.out.println("\nUTF-16 Encoding:");
-// for (char c : emojiString.toCharArray()) {
-// System.out.printf("\\u%04X ", (int) c);
-// }
-// System.out.println("\n\n");
-// }
-
-// private static String generateGraphemeClusterRegex() {
-// return "(?x: # Free-spacing mode\n" +
-// " # Basic grapheme cluster\n" +
-// " \\P{M}\\p{M}*\n" +
-// " |\n" +
-//
-// " \\uD83D\\uDC4B\\uD83C\\uDFFB" + // Special case
-// " |\n" +
-// " \\uD83C \\uDDFA \\uD83C \\uDDF8" + // Special case
-// " |\n" +
-//
-// " # Regional indicators for flags\n" +
-// " (?:[\uD83C][\uDDE6-\uDDFF]){2}\n" +
-// " |\n" +
-// " # Emoji with modifiers and ZWJ sequences\n" +
-// " (?:[\uD83C-\uDBFF\uDC00-\uDFFF]|[\u2600-\u27BF])\n" +
-// " (?:[\uD83C][\uDFFB-\uDFFF])?\n" +
-// " (?:\u200D\n" +
-// " (?:[\uD83C-\uDBFF\uDC00-\uDFFF]|[\u2600-\u27BF])\n" +
-// " (?:[\uD83C][\uDFFB-\uDFFF])?\n" +
-// " )*\n" +
-// " (?:[\uFE00-\uFE0F])?\n" +
-// ")";
-// }
-
- /**
- * Handles predefined character classes within the regex.
- *
- * @param offset The current offset in the regex string.
- * @param s The regex string.
- * @param sb The StringBuilder to append processed characters.
- * @param length The length of the regex string.
- * @return The updated offset after processing the character class.
- */
- static int handleCharacterClass(int offset, String s, StringBuilder sb, int length) {
- // Check for POSIX syntax [= =] or [. .]
- if (offset + 3 < length && s.charAt(offset) == '[' &&
- (s.charAt(offset + 1) == '=' || s.charAt(offset + 1) == '.')) {
- char syntaxChar = s.charAt(offset + 1);
- // Look for closing syntax
- int closePos = offset + 2;
- while (closePos < length - 1) {
- if (s.charAt(closePos) == syntaxChar && s.charAt(closePos + 1) == ']') {
- // Found POSIX syntax
- String syntaxType = syntaxChar == '=' ? "[= =]" : "[. .]";
- // Point to after the ']' that closes the POSIX syntax
- regexError(s, closePos + 2, "POSIX syntax " + syntaxType + " is reserved for future extensions");
- }
- closePos++;
- }
- }
- int endDelimiter = s.indexOf(":]", offset);
- if (endDelimiter > -1) {
- String potentialClass = s.substring(offset, endDelimiter + 2);
- String mapping = CharacterClassMapper.getMappedClass(potentialClass);
- if (mapping != null) {
- sb.append(mapping);
- return endDelimiter + 1;
- }
- }
- sb.append("\\[");
- return offset;
- }
-
- /**
- * Skips over inline comments within the regex.
- *
- * @param offset The current offset in the regex string.
- * @param s The regex string.
- * @param length The length of the regex string.
- * @return The updated offset after skipping the comment.
- */
- private static int handleSkipComment(int offset, String s, int length) {
- // comment (?# ... )
- int offset3 = offset;
- while (offset3 < length) {
- final int c3 = s.codePointAt(offset3);
- switch (c3) {
- case ')':
- return offset3;
- case '\\':
- offset3++;
- break;
- default:
- break;
- }
- offset3++;
- }
- return offset; // possible error - end of comment not found
- }
-
- // Lookbehind errors don't show position
- static void regexErrorNoPosition(String errMsg) {
- throw new PerlCompilerException(errMsg);
- }
-
- static void regexErrorSimple(String s, String errMsg) {
- throw new PerlCompilerException(errMsg + " in regex m/" + s + "/");
- }
-
- /**
- * Throws PerlJavaUnimplementedException with proper error formatting including marker and context.
- * Used for regex features that are valid Perl but not yet implemented in PerlOnJava.
- */
- static void regexUnimplemented(String s, int offset, String errMsg) {
- if (offset > s.length()) {
- offset = s.length();
- }
-
- // "Error message in regex; marked by <-- HERE in m/regex <-- HERE remaining/"
- String before = s.substring(0, offset);
- String after = s.substring(offset);
-
- // When marker is at the end, no space before <-- HERE
- String marker = after.isEmpty() ? " <-- HERE" : " <-- HERE ";
-
- throw new PerlJavaUnimplementedException(errMsg + " in regex; marked by <-- HERE in m/" +
- before + marker + after + "/");
- }
-
- /**
- * Soft variant of {@link #regexUnimplemented}: under
- * {@code JPERL_UNIMPLEMENTED=warn} the caller emits a best-effort fallback
- * construct (e.g. an empty non-capturing group) and this method warns so
- * the user still sees the issue, letting compilation of the surrounding
- * pattern continue. Under the default die mode it behaves identically to
- * {@code regexUnimplemented} and throws, aborting the regex compile.
- *
- * The intent of {@code JPERL_UNIMPLEMENTED=warn} is to let test runs
- * keep going past unsupported features, not to silently hide them. Using
- * this helper (instead of silent fallback) ensures the user always sees a
- * diagnostic.
- */
- static void regexUnimplementedSoft(String s, int offset, String errMsg) {
- if (!isUnimplementedWarnMode()) {
- regexUnimplemented(s, offset, errMsg);
- return;
- }
-
- if (offset > s.length()) {
- offset = s.length();
- }
- String before = s.substring(0, offset);
- String after = s.substring(offset);
- String marker = after.isEmpty() ? " <-- HERE" : " <-- HERE ";
-
- String message = errMsg + " in regex; marked by <-- HERE in m/" +
- before + marker + after + "/\n";
- if (shouldEmitWarnings()) {
- WarnDie.warn(new RuntimeScalar(message), new RuntimeScalar());
- }
- }
-
- private static boolean isUnimplementedWarnMode() {
- return "warn".equals(
- GlobalVariable.getGlobalHash("main::ENV")
- .get("JPERL_UNIMPLEMENTED").toString());
- }
-
- private static int findClosingBrace(String s, int start, int length) {
- int depth = 1;
- int pos = start;
- while (pos < length && depth > 0) {
- char ch = s.charAt(pos);
- if (ch == '{') {
- depth++;
- } else if (ch == '}') {
- depth--;
- if (depth == 0) {
- return pos;
- }
- } else if (ch == '\\' && pos + 1 < length) {
- pos++; // Skip escaped character
- }
- pos++;
- }
- return -1; // Not found
- }
-
- // Find matching parenthesis
- private static int findMatchingParen(String s, int start, int length) {
- int depth = 1;
- int pos = start + 1; // Start after the opening paren
- while (pos < length && depth > 0) {
- char ch = s.charAt(pos);
- if (ch == '(') {
- depth++;
- } else if (ch == ')') {
- depth--;
- if (depth == 0) {
- return pos;
- }
- } else if (ch == '\\' && pos + 1 < length) {
- pos++; // Skip escaped character
- }
- pos++;
- }
- return -1; // Not found
- }
-
-
- /**
- * Handle a potential quantifier starting with '{'.
- * Returns int[2]: [0] = new offset, [1] = 1 if literal braces (not a quantifier), 0 if valid quantifier
- */
- private static int[] handleQuantifier(String s, int offset, StringBuilder sb) {
- int start = offset; // Position of '{'
- int end = s.indexOf('}', start);
-
- if (end == -1) {
- // No closing brace - treat as literal (escape for Java regex)
- sb.append("\\{");
- return new int[]{offset, 1}; // literal
- }
-
- String quantifier = s.substring(start + 1, end);
- String[] parts = quantifier.split(",", -1);
-
- // A valid quantifier must have at least one number: {n}, {n,}, or {n,m}
- // Perl treats {}, {,}, and non-numeric content as literal braces
- boolean hasFirstNumber = false;
- boolean hasSecondNumber = false;
- boolean isValid = true;
-
- try {
- // Check first number
- if (!parts[0].isEmpty()) {
- // Check for leading zeros
- if (parts[0].length() > 1 && parts[0].charAt(0) == '0') {
- regexError(s, start + 1, "Invalid quantifier in {,}");
- }
-
- long n = Long.parseLong(parts[0]);
- if (n > Integer.MAX_VALUE) {
- regexError(s, start + 1, "Quantifier in {,} bigger than " + Integer.MAX_VALUE);
- }
- hasFirstNumber = true;
- }
-
- // Check second number if present
- if (parts.length > 1 && !parts[1].isEmpty()) {
- // Check for leading zeros
- if (parts[1].length() > 1 && parts[1].charAt(0) == '0') {
- int commaPos = quantifier.indexOf(',');
- regexError(s, start + 1 + commaPos + 1, "Invalid quantifier in {,}");
- }
-
- long m = Long.parseLong(parts[1]);
- if (m > Integer.MAX_VALUE) {
- int commaPos = quantifier.indexOf(',');
- regexError(s, start + 1 + commaPos, "Quantifier in {,} bigger than " + Integer.MAX_VALUE);
- }
- hasSecondNumber = true;
- }
- } catch (NumberFormatException e) {
- // Not a valid quantifier - contains non-numeric content
- isValid = false;
- }
-
- // Valid quantifier forms: {n}, {n,}, {n,m}, {,m}
- // Invalid (literal): {}, {,}, {abc}, etc.
- if (!isValid || (!hasFirstNumber && !hasSecondNumber)) {
- // Not a valid quantifier - treat opening brace as literal (escape for Java regex).
- // Don't consume content up to '}' — it may contain regex metacharacters
- // (like parentheses, character classes, etc.) that need proper processing.
- sb.append("\\{");
- return new int[]{start, 1}; // literal, offset stays at '{' so caller increments past it
- }
-
- // Valid quantifier - pass through to Java
- sb.append(s, start, end + 1);
- return new int[]{end, 0}; // valid quantifier
- }
-
- /**
- * Check if position offset in string s starts a valid quantifier (for nested quantifier detection).
- * A valid quantifier is {n}, {n,}, {n,m}, or {,m} where n and m are numbers.
- */
- private static boolean isValidQuantifierAt(String s, int offset) {
- if (offset >= s.length() || s.charAt(offset) != '{') {
- return false;
- }
-
- int end = s.indexOf('}', offset);
- if (end == -1) {
- return false;
- }
-
- String content = s.substring(offset + 1, end);
- if (content.isEmpty()) {
- return false; // {} is not a valid quantifier
- }
-
- String[] parts = content.split(",", -1);
- boolean hasFirstNumber = false;
- boolean hasSecondNumber = false;
-
- try {
- if (!parts[0].isEmpty()) {
- Long.parseLong(parts[0]);
- hasFirstNumber = true;
- }
- if (parts.length > 1 && !parts[1].isEmpty()) {
- Long.parseLong(parts[1]);
- hasSecondNumber = true;
- }
- } catch (NumberFormatException e) {
- return false;
- }
-
- // Valid if at least one number is present
- return hasFirstNumber || hasSecondNumber;
- }
-
-
-
- /**
- * Handles (?{...}) code blocks in regex patterns.
- * For simple constant expressions, replaces them with empty named capture groups.
- * This allows pack.t tests to work without full (?{...}) implementation.
- *
- * @param s The regex string
- * @param offset Current position (at '(' of '(?{')
- * @param length Length of the regex string
- * @param sb StringBuilder to append the processed regex
- * @param regexFlags The regex flags
- * @return New offset after processing the code block
- */
- private static int handleCodeBlock(String s, int offset, int length, StringBuilder sb, RegexFlags regexFlags) {
- // Find the matching closing brace
- int codeStart = offset + 3; // Skip '(?{'
- int codeEnd = findRegexCodeBlockClosingBrace(s, codeStart);
-
- if (codeEnd < 0) {
- regexError(s, offset + 2, "Unmatched '{' in (?{...}) code block");
- }
-
- // Extract the code block content
- String codeBlock = s.substring(codeStart, codeEnd).trim();
-
- // Try to detect simple constant expressions
- // This handles the common cases in pack.t without full parser integration
- if (isSimpleConstant(codeBlock)) {
- // Replace with an empty named capture group
- // This allows the regex to compile and match correctly
- // Generate a unique name for this code block capture
- // Java regex requires capture group names to start with a letter
- String captureName = "codeblock" + captureGroupCount++;
-
- // Append a named capture group that matches empty string
- // This allows us to store the constant value without affecting the match
- sb.append("(?<").append(captureName).append(">");
-
- // Return offset pointing to the ')' so handleGroup can consume it
- // codeEnd points to the '}', the next char should be ')'
- if (codeEnd + 1 < length && s.charAt(codeEnd + 1) == ')') {
- return codeEnd + 1; // Point to ')' for handleGroup to consume
- }
- return codeEnd + 1; // Just skip past '}' if no ')' found
- }
-
- if (!regexFlags.allowEvalGroup()) {
- if (allowRegexCodeBlockNoop()) {
- regexUnimplementedSoft(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- } else if (isUnimplementedWarnMode()) {
- regexUnimplemented(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- } else {
- regexError(s, offset + 2,
- "Eval-group not allowed at runtime, use re 'eval'");
- }
- }
- sb.append("(?:");
-
- // Return offset pointing to the ')' so handleGroup can consume it
- if (codeEnd + 1 < length && s.charAt(codeEnd + 1) == ')') {
- return codeEnd + 1; // Point to ')' for handleGroup to consume
- }
- return codeEnd + 1; // Just skip past '}' if no ')' found
- }
-
- /**
- * Find the brace that closes a regex code block while ignoring braces in
- * ordinary single- and double-quoted Perl strings. Runtime string patterns
- * still pass through this compatibility parser, so values such as
- * {@code (??{"{"})} must not make the source brace depth unbalanced.
- */
- private static int findRegexCodeBlockClosingBrace(String source, int codeStart) {
- int depth = 1;
- char quote = 0;
- for (int i = codeStart; i < source.length(); i++) {
- char ch = source.charAt(i);
- if (ch == '\\' && i + 1 < source.length()) {
- i++;
- continue;
- }
- if (quote != 0) {
- if (ch == quote) quote = 0;
- continue;
- }
- if (ch == '\'' || ch == '"') {
- quote = ch;
- } else if (ch == '{') {
- depth++;
- } else if (ch == '}' && --depth == 0) {
- return i;
- }
- }
- return -1;
- }
-
- private static boolean allowRegexCodeBlockNoop() {
- return "1".equals(GlobalVariable.getGlobalHash("main::ENV")
- .get(RegexMarkers.CODE_BLOCK_NOOP_ENV).toString());
- }
-
- /**
- * {{ ... }}
- * Handles the common patterns used in pack.t tests.
- *
- * @param codeBlock The code block content (without (?{ and }))
- * @return true if this is a simple constant we can handle
- */
- private static boolean isSimpleConstant(String codeBlock) {
- // Handle common constant patterns from pack.t:
- // - undef
- // - 'string'
- // - "string"
- // - numbers (integer or float)
- // - simple arithmetic (e.g., 1.36514538e37)
-
- if (codeBlock.equals("undef")) {
- return true;
- }
-
- // String literals
- if ((codeBlock.startsWith("'") && codeBlock.endsWith("'")) ||
- (codeBlock.startsWith("\"") && codeBlock.endsWith("\""))) {
- return true;
- }
-
- // Numeric literals (including scientific notation)
- if (codeBlock.matches("[+-]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?")) {
- return true;
- }
-
- // Simple arithmetic expressions (for pack.t compatibility)
- // Just check if it looks like a number-only expression
- return codeBlock.matches("[\\d\\s+\\-*/().eE]+");
- }
-
- /**
- * Evaluates a simple constant code block expression and returns its string value.
- * Used by (??{...}) to inline constant patterns.
- *
- * @param codeBlock The code block content
- * @return The string value, or null if it can't be evaluated
- */
- private static String evaluateSimpleConstant(String codeBlock) {
- if (codeBlock.equals("undef")) {
- return "";
- }
-
- // String literals — strip quotes and return content
- if (codeBlock.startsWith("\"") && codeBlock.endsWith("\"")) {
- return codeBlock.substring(1, codeBlock.length() - 1);
- }
- if (codeBlock.startsWith("'") && codeBlock.endsWith("'")) {
- return codeBlock.substring(1, codeBlock.length() - 1);
- }
-
- // Numeric literals and simple arithmetic — return as-is (matches literal digits)
- if (codeBlock.matches("[+-]?\\d+(\\.\\d+)?([eE][+-]?\\d+)?")) {
- return codeBlock;
- }
-
- // Simple arithmetic — too complex to evaluate, return null
- return null;
- }
-}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessorHelper.java b/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessorHelper.java
deleted file mode 100644
index f14c2c1737..0000000000
--- a/src/main/java/org/perlonjava/runtime/regex/RegexPreprocessorHelper.java
+++ /dev/null
@@ -1,1330 +0,0 @@
-package org.perlonjava.runtime.regex;
-
-import org.perlonjava.runtime.operators.WarnDie;
-import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.perlonjava.runtime.regex.UnicodeResolver.translateUnicodeProperty;
-
-public class RegexPreprocessorHelper {
- static final String ASCII_WHITESPACE_CLASS = "[\\t\\n\\u000B\\f\\r\\x20]";
- static final String ASCII_NON_WHITESPACE_CLASS = "[^\\t\\n\\u000B\\f\\r\\x20]";
- static final String UNICODE_WHITESPACE_CLASS = "[\\t\\n\\u000B\\f\\r\\x20\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
- static final String UNICODE_NON_WHITESPACE_CLASS = "[^\\t\\n\\u000B\\f\\r\\x20\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
-
- static int handleEscapeSequences(String s, StringBuilder sb, int c, int offset, RegexFlags regexFlags) {
- sb.append(Character.toChars(c)); // This appends the backslash
- final int length = s.length();
-
- offset++;
- if (offset >= length) {
- return offset;
- }
-
- char nextChar = s.charAt(offset);
-
- // Check for numeric backreferences vs octal escapes
- // Perl disambiguation rules (from perlrebackslash):
- // 1. If the backslash is followed by a single digit, it's a backreference.
- // 2. If the first digit following the backslash is a 0, it's an octal escape.
- // 3. If the number N (in decimal) and Perl has already seen N capture groups,
- // it's a backreference. Otherwise, it's an octal escape.
- //
- // Examples:
- // \100 with 100 capture groups -> backreference to group 100
- // \100 with 0 capture groups -> octal 100 = '@'
- // \037 with 0 capture groups -> octal 037 = unit separator (used in Archive::Tar)
- // \9 is always a backreference (single digit rule)
- boolean isOctalNotBackref = false;
- if (nextChar >= '1' && nextChar <= '9') {
- // Parse all consecutive digits to get the full number
- int endDigits = offset;
- while (endDigits < length && s.charAt(endDigits) >= '0' && s.charAt(endDigits) <= '9') {
- endDigits++;
- }
- String digitStr = s.substring(offset, endDigits);
- int refNum = Integer.parseInt(digitStr);
-
- // Rule 3: If refNum > captureGroupCount, it's an octal escape (if valid octal)
- if (refNum > RegexPreprocessor.captureGroupCount) {
- // Check if all digits are octal (0-7)
- boolean allOctal = true;
- for (int i = 0; i < digitStr.length(); i++) {
- char d = digitStr.charAt(i);
- if (d > '7') {
- allOctal = false;
- break;
- }
- }
- if (allOctal && digitStr.length() >= 2) {
- // This is an octal escape, not a backreference
- isOctalNotBackref = true;
- }
- }
- }
-
- if (!isOctalNotBackref && nextChar >= '1' && nextChar <= '9') {
- // This is a backreference like \1, \2, etc.
- int refNum = nextChar - '0';
-
- // Check if we have ANY capture groups at all
- // If there are no groups, this is always an error
- // But if there are groups, allow forward references
- if (RegexPreprocessor.captureGroupCount == 0) {
- sb.setLength(sb.length() - 1); // Remove the backslash
- RegexPreprocessor.regexError(s, offset + 1, "Reference to nonexistent group");
- }
- // Forward references are allowed when there are capture groups
- // Perl allows forward references like (\3|b)\2(a) where \3 refers to group 3
- // which hasn't been captured yet. This is valid and the reference just won't match
- // until group 3 is actually captured.
-
- sb.append(nextChar);
- return offset;
- }
- if (nextChar == 'k' && offset + 1 < length && s.charAt(offset + 1) == '\'') {
- // Handle \k'name' backreference (Perl syntax)
- offset += 2; // Skip past \k'
- int endQuote = s.indexOf('\'', offset);
- if (endQuote != -1) {
- String name = s.substring(offset, endQuote);
- // Convert to Java syntax \k
- appendNamedBackreference(sb, name);
- return endQuote; // Return position at closing quote
- } else {
- RegexPreprocessor.regexError(s, offset - 2, "Unterminated \\k'...' backreference");
- }
- }
- if (nextChar == 'k' && offset + 1 < length && s.charAt(offset + 1) == '<') {
- // Handle \k backreference (also valid Perl syntax)
- offset += 2; // Skip past \k<
- int endAngle = s.indexOf('>', offset);
- if (endAngle != -1) {
- String name = s.substring(offset, endAngle);
- appendNamedBackreference(sb, name);
- return endAngle; // Return position at closing >
- } else {
- RegexPreprocessor.regexError(s, offset - 2, "Unterminated \\k<...> backreference");
- }
- }
- if (nextChar == 'g') {
- if (offset + 1 < length && s.charAt(offset + 1) == '{') {
- // Handle \g{name} backreference
- offset += 2; // Skip past \g{
- int endBrace = s.indexOf('}', offset);
- if (endBrace != -1) {
- String ref = s.substring(offset, endBrace).trim(); // Trim whitespace
-
- // Check if it's numeric (including negative)
- try {
- int groupNum = Integer.parseInt(ref);
- if (groupNum == 0) {
- RegexPreprocessor.regexError(s, offset - 2, "Reference to invalid group 0");
- } else if (groupNum < 0) {
- // Handle relative backreference
- int absoluteRef = RegexPreprocessor.captureGroupCount + groupNum + 1;
- if (absoluteRef > 0) {
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("\\").append(absoluteRef);
- } else {
- RegexPreprocessor.regexError(s, offset - 2, "Reference to nonexistent or unclosed group");
- }
- } else {
- // Positive numeric reference
- // If there are no groups at all, this is an error
- if (RegexPreprocessor.captureGroupCount == 0) {
- RegexPreprocessor.regexError(s, offset - 2, "Reference to nonexistent group");
- }
- // Otherwise allow forward references
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("\\").append(groupNum);
- }
- } catch (NumberFormatException e) {
- // It's a named reference.
- appendNamedBackreference(sb, ref);
- }
- offset = endBrace;
- }
- } else if (offset + 1 < length && Character.isDigit(s.charAt(offset + 1))) {
- // Handle \g0, \g1, etc.
- int start = offset + 1;
- int end = start;
- while (end < length && Character.isDigit(s.charAt(end))) {
- end++;
- }
- String numStr = s.substring(start, end);
- int groupNum = Integer.parseInt(numStr);
-
- if (groupNum == 0) {
- RegexPreprocessor.regexError(s, offset, "Reference to invalid group 0");
- } else if (RegexPreprocessor.captureGroupCount == 0) {
- // No groups at all - this is an error
- RegexPreprocessor.regexError(s, offset, "Reference to nonexistent group");
- }
- // Otherwise allow forward references - they're valid in Perl
-
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("\\").append(groupNum);
- return end - 1; // -1 because the main loop will increment
- } else if (offset + 1 < length && s.charAt(offset + 1) == '-') {
- // Handle \g-1, \g-2, etc.
- int start = offset + 1;
- int end = start + 1; // Skip the minus
- while (end < length && Character.isDigit(s.charAt(end))) {
- end++;
- }
- String numStr = s.substring(start, end);
- int relativeRef = Integer.parseInt(numStr);
- int absoluteRef = RegexPreprocessor.captureGroupCount + relativeRef + 1;
-
- if (absoluteRef > 0) {
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("\\").append(absoluteRef);
- } else {
- RegexPreprocessor.regexError(s, offset, "Reference to nonexistent group");
- }
- return end - 1;
- }
- } else if (nextChar == 's' || nextChar == 'S') {
- sb.setLength(sb.length() - 1); // Remove the backslash
- // Default regexes must keep byte-string \s ASCII-only. Explicit /u
- // compiles the primary pattern with Unicode whitespace, and the
- // UTF-8 target variant is widened after preprocessing.
- boolean unicodeWhitespace = regexFlags.isUnicode() && !regexFlags.isAscii();
- if (nextChar == 's') {
- sb.append(unicodeWhitespace ? UNICODE_WHITESPACE_CLASS : ASCII_WHITESPACE_CLASS);
- } else {
- sb.append(unicodeWhitespace ? UNICODE_NON_WHITESPACE_CLASS : ASCII_NON_WHITESPACE_CLASS);
- }
- return offset;
- } else if (nextChar == 'h') {
- // \h - horizontal whitespace
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("[\\t\\x20\\xA0\\x{1680}\\x{2000}-\\x{200A}\\x{202F}\\x{205F}\\x{3000}]");
- return offset;
- } else if (nextChar == 'H') {
- // \H - not horizontal whitespace
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("[^\\t\\x20\\xA0\\x{1680}\\x{2000}-\\x{200A}\\x{202F}\\x{205F}\\x{3000}]");
- return offset;
- } else if (nextChar == 'v') {
- // \v - vertical whitespace
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("[\\n\\x0B\\f\\r\\x85\\x{2028}\\x{2029}]");
- return offset;
- } else if (nextChar == 'V') {
- // \V - not vertical whitespace
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append("[^\\n\\x0B\\f\\r\\x85\\x{2028}\\x{2029}]");
- return offset;
- } else if (nextChar == 'K') {
- // Syntax-aware admission routes real KEEP assertions to Joni. A
- // \K reaching the Java preprocessor is literal, for example in a
- // character class or a \Q...\E region.
- sb.setLength(sb.length() - 1);
- sb.append('K');
- return offset;
- } else if ((nextChar == 'b' || nextChar == 'B') && offset + 1 < length && s.charAt(offset + 1) == '{') {
- // Handle \b{...} and \B{...} boundary assertions
- boolean negated = (nextChar == 'B');
- offset += 2; // Skip past \b{ or \B{
- int endBrace = s.indexOf('}', offset);
- if (endBrace != -1) {
- String boundaryType = s.substring(offset, endBrace).trim();
- sb.setLength(sb.length() - 1); // Remove the backslash
-
- if (negated) {
- // Negated boundaries - wrap in negative lookaround
- sb.append("(?!");
- }
-
- switch (boundaryType) {
- case "gcb":
- // Grapheme cluster boundary
- // This is a simplified version - a full implementation would be more complex
- sb.append("(?= '0' && c2 <= '7') {
- // Potential octal sequence - parse it
- int octalValue = c2 - '0';
- int octalLength = 1;
-
- // Read up to 2 more octal digits
- for (int i = 1; i <= 2 && offset + i < length; i++) {
- int nextDigit = s.codePointAt(offset + i);
- if (nextDigit >= '0' && nextDigit <= '7') {
- octalValue = octalValue * 8 + (nextDigit - '0');
- octalLength++;
- } else {
- break;
- }
- }
-
- // Check if value is > 255 (requires hex conversion)
- if (octalValue > 255) {
- // Convert to hex for Java regex
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append(String.format("\\x{%X}", octalValue));
- offset += octalLength - 1; // -1 because caller will increment
- } else if (octalValue <= 255 && octalLength == 3) {
- // Standard 3-digit octal - convert to hex for Java regex
- // Using \x{hex} avoids issues with \0mnn parsing and ensures
- // all 3 digits are consumed (e.g., \177 → \x{7F})
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append(String.format("\\x{%X}", octalValue));
- offset += octalLength - 1; // -1 because caller will increment
- } else if (c2 == '0' && octalLength == 1) {
- // Single \0 becomes \00
- sb.append('0');
- sb.append('0');
- } else {
- // Short octal or single digit, pass through
- sb.append(Character.toChars(c2));
- }
- } else if (c2 == '8' || c2 == '9') {
- // \8 and \9 are not valid octals - treat as literal digits
- sb.setLength(sb.length() - 1); // Remove the backslash
- sb.append(Character.toChars(c2));
- } else if (c2 == 'x' && offset + 1 < length && s.charAt(offset + 1) == '{') {
- // \x{...} hex escape - parse and normalize the hex value.
- // Perl stops at the first non-hex character (after removing underscores).
- offset += 2; // Skip past x{
- int endBrace = -1;
- for (int i = offset; i < length; i++) {
- if (s.charAt(i) == '}') {
- endBrace = i;
- break;
- }
- }
- if (endBrace != -1) {
- String hexStr = s.substring(offset, endBrace).trim().replace("_", "");
- // Extract valid hex prefix
- int validLen = 0;
- for (int i = 0; i < hexStr.length(); i++) {
- char ch = hexStr.charAt(i);
- if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
- validLen++;
- } else {
- break;
- }
- }
- int value;
- if (validLen == 0) {
- value = 0; // No valid hex digits → \x00
- } else {
- value = Integer.parseInt(hexStr.substring(0, validLen), 16);
- }
- sb.append(String.format("x{%X}", value));
- offset = endBrace;
- } else {
- // No closing brace - pass through as-is
- sb.append('x');
- }
- // offset now points to '}', caller will increment
- } else if (c2 == 'x') {
- // Bare \xNN (no braces) - Perl takes up to 2 hex digits.
- // If fewer than 2 valid hex digits, stop at first non-hex char.
- // Java's Pattern requires exactly 2 hex digits for \xHH, so normalize.
- int hexVal = 0;
- int hexDigits = 0;
- int pos = offset + 1; // position after 'x'
- while (hexDigits < 2 && pos < length) {
- char ch = s.charAt(pos);
- if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
- hexVal = hexVal * 16 + Character.digit(ch, 16);
- hexDigits++;
- pos++;
- } else {
- break;
- }
- }
- if (hexDigits == 2) {
- // Standard \xHH - pass through (Java handles it natively)
- sb.append('x');
- sb.append(s.charAt(offset + 1));
- sb.append(s.charAt(offset + 2));
- offset += 2;
- } else {
- // 0 or 1 hex digits - use \x{H} format for Java
- sb.append(String.format("x{%X}", hexVal));
- offset = pos - 1; // -1 because caller will increment
- }
- } else if (c2 == 'e') {
- // Perl \e is ESC (0x1B); Java regex has no \e alias
- sb.setLength(sb.length() - 1);
- sb.append("\\x1B");
- } else if (c2 == 'a') {
- // Perl \a is BEL (0x07)
- sb.setLength(sb.length() - 1);
- sb.append("\\x07");
- } else if (c2 == 'u') {
- // Perl's regex engine does not treat backslash-u plus four
- // digits as a Unicode escape (code points use hex braces). In a runtime pattern the
- // unrecognized backslash is passed through and `u` is matched
- // literally. Joni otherwise interprets that sequence Java-style,
- // changing diagnostic-text patterns into a NUL
- // match instead of matching the diagnostic text.
- sb.setLength(sb.length() - 1);
- sb.append('u');
- } else {
- // Other escape sequences, pass through
- sb.append(Character.toChars(c2));
- }
- }
- return offset;
- }
-
- private static void appendNamedBackreference(StringBuilder sb, String perlName) {
- java.util.List targets = RegexPreprocessor.namedBackreferenceTargets(perlName);
- sb.setLength(sb.length() - 1); // Remove the already-appended backslash
- if (targets.size() == 1) {
- sb.append("\\k<").append(targets.get(0)).append(">");
- return;
- }
-
- sb.append("(?:");
- for (int i = 0; i < targets.size(); i++) {
- if (i > 0) {
- sb.append('|');
- }
- sb.append("\\k<").append(targets.get(i)).append(">");
- }
- sb.append(')');
- }
-
- /**
- * Handles escape sequences within character classes.
- *
- * @param offset The current offset in the regex string.
- * @param s The regex string.
- * @param sb The StringBuilder to append processed characters.
- * @param length The length of the regex string.
- * @param flag_xx A flag indicating whether to treat spaces as tokens.
- * @param rejected A StringBuilder to collect rejected sequences.
- * @return The updated offset after processing the character class.
- */
- static int handleRegexCharacterClassEscape(int offset, String s, StringBuilder sb, int length, boolean flag_xx,
- StringBuilder rejected) {
- // inside [ ... ]
- // space becomes: "\ " unless the /xx flag is used (flag_xx)
- // \120 becomes: \0120 - Java requires octal sequences to start with zero
- // \0 becomes: \00 - Java requires the extra zero
- // [:ascii:] becomes: \p{ASCII}
- // [:^ascii:] becomes: \P{ASCII}
- // \b is rejected, Java doesn't support \b inside [...]
- boolean first = true;
- boolean afterCaret = false;
- int lastChar = -1; // Track last character for range validation
- boolean wasEscape = false; // Track if last char was from escape sequence
-
- // POSIX syntax checking
- if (offset < length && s.charAt(offset) == '[') {
- // Check for [[=...=]] or [[.....]]
- if (offset + 1 < length) {
- char nextChar = s.charAt(offset + 1);
- if (nextChar == '=' || nextChar == '.') {
- // Look for closing syntax
- int searchPos = offset + 2;
- while (searchPos < length - 1) {
- if (s.charAt(searchPos) == nextChar && s.charAt(searchPos + 1) == ']') {
- // Found complete POSIX syntax
- String syntaxType = nextChar == '=' ? "[= =]" : "[. .]";
- // IMPORTANT: Use searchPos + 2, not offset + 2
- RegexPreprocessor.regexError(s, searchPos + 2, "POSIX syntax " + syntaxType + " is reserved for future extensions");
- }
- searchPos++;
- }
- }
- }
- }
-
- while (offset < length) {
- final int c = s.codePointAt(offset);
- switch (c) {
- case ']':
- // Special case: ] immediately after [ or [^ is a literal ]
- if (first || afterCaret) {
- sb.append("\\]");
- first = false;
- afterCaret = false;
- lastChar = ']';
- wasEscape = false;
- break;
- } else {
- sb.append(Character.toChars(c));
- return offset;
- }
- case '^':
- if (first) {
- afterCaret = true;
- } else {
- // A second caret is the first ordinary member of a
- // negated class, as in [^^]. Only ']' immediately
- // after the leading negation caret is literal; leaving
- // afterCaret set made the real closing bracket literal
- // and consumed the rest of the pattern as class text.
- afterCaret = false;
- }
- sb.append(Character.toChars(c));
- first = false;
- lastChar = -1; // Reset for special chars
- wasEscape = false;
- break;
- case '-':
- // Check for invalid range
- if (lastChar != -1 && !wasEscape && offset + 1 < length) {
- int nextPos = offset + 1;
- int nextChar = s.codePointAt(nextPos);
-
- // Skip if next is ], then it's a literal -
- if (nextChar != ']') {
- // Check if next is start of POSIX class like [:alpha:]
- // In that case, the hyphen is literal, not a range
- if (nextChar == '[' && nextPos + 1 < length && s.charAt(nextPos + 1) == ':') {
- // Next is a POSIX class, hyphen is literal
- sb.append(Character.toChars(c));
- first = false;
- afterCaret = false;
- lastChar = -1;
- wasEscape = false;
- break;
- }
-
- // Handle escaped next character
- int rangeEndCharCount = 1; // How many chars to skip for range end
- if (nextChar == '\\' && nextPos + 1 < length) {
- nextChar = s.codePointAt(nextPos + 1);
- rangeEndCharCount = 2; // Escaped char is 2 chars
- // Special handling for escape sequences
- if (nextChar == 'b' || nextChar == 'N' || nextChar == 'p' || nextChar == 'P') {
- // These are special escapes, can't be in range
- nextChar = -1;
- } else if (nextChar == 'x' && nextPos + 2 < length && s.charAt(nextPos + 2) == '{') {
- // Parse \x{NNNN} as range endpoint
- int endBrace = s.indexOf('}', nextPos + 3);
- if (endBrace != -1) {
- String hex = s.substring(nextPos + 3, endBrace).trim().replace("_", "");
- // Extract valid hex prefix (Perl stops at first non-hex char)
- int vLen = 0;
- for (int i = 0; i < hex.length(); i++) {
- char ch = hex.charAt(i);
- if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
- vLen++;
- } else {
- break;
- }
- }
- if (vLen == 0) {
- nextChar = 0;
- } else {
- nextChar = Integer.parseInt(hex.substring(0, vLen), 16);
- }
- rangeEndCharCount = endBrace - nextPos + 1;
- }
- } else if (nextChar == 'o' && nextPos + 2 < length && s.charAt(nextPos + 2) == '{') {
- // Parse \o{NNNN} as range endpoint
- int endBrace = s.indexOf('}', nextPos + 3);
- if (endBrace != -1) {
- String oct = s.substring(nextPos + 3, endBrace).trim().replace("_", "");
- try {
- nextChar = Integer.parseInt(oct, 8);
- rangeEndCharCount = endBrace - nextPos + 1;
- } catch (NumberFormatException e) {
- nextChar = -1;
- }
- }
- } else if (nextChar >= '0' && nextChar <= '7') {
- // Parse bare octal escape (\NNN) as range endpoint
- // e.g., \237 → octal 237 = 159
- int octalVal = nextChar - '0';
- int digits = 1;
- for (int k = 2; k <= 3 && nextPos + k < length; k++) {
- int d = s.codePointAt(nextPos + k);
- if (d >= '0' && d <= '7') {
- octalVal = octalVal * 8 + (d - '0');
- digits++;
- } else {
- break;
- }
- }
- if (digits >= 2) {
- // Multi-digit octal: use computed value
- nextChar = octalVal;
- rangeEndCharCount = 1 + digits; // backslash + digits
- }
- // Single digit \N stays as-is (rangeEndCharCount = 2)
- }
- }
-
- if (nextChar != -1) {
- if (nextChar < lastChar) {
- String rangeStart = Character.toString(lastChar);
- String rangeEnd = Character.toString(nextChar);
- RegexPreprocessor.regexError(s, offset + 2,
- "Invalid [] range \"" + rangeStart + "-" + rangeEnd + "\" in regex");
- }
- // Valid range - append the dash and range end, then skip past range end
- sb.append(Character.toChars(c)); // Append the '-'
- // Append and skip the range end character so it won't be processed again
- // This prevents the range end from being used as a range start
- for (int i = 0; i < rangeEndCharCount; i++) {
- sb.append(s.charAt(nextPos + i));
- }
- offset += rangeEndCharCount; // Skip past range end
- first = false;
- afterCaret = false;
- lastChar = -1; // Range complete, next dash is literal or starts new range
- wasEscape = false;
- break;
- }
- }
- }
- sb.append(Character.toChars(c));
- first = false;
- afterCaret = false;
- lastChar = -1; // Reset after dash
- wasEscape = false;
- break;
- case '[':
- // Check if this could be a POSIX character class like [:ascii:]
- if (offset + 1 < length && s.charAt(offset + 1) == ':') {
- // This might be a POSIX character class
- offset = RegexPreprocessor.handleCharacterClass(offset, s, sb, length);
- first = false;
- afterCaret = false;
- lastChar = -1; // POSIX classes can't be range endpoints
- wasEscape = false;
- } else {
- // It's just a literal [ inside a character class
- sb.append("\\["); // Escape it for Java regex
- first = false;
- afterCaret = false;
- lastChar = '[';
- wasEscape = false;
- }
- break;
- case '\\': // Handle escape sequences
- sb.append(Character.toChars(c));
- offset++;
- wasEscape = true;
- if (offset < length && (s.charAt(offset) == 'p' || s.charAt(offset) == 'P')
- && offset + 1 < length && s.charAt(offset + 1) == '{') {
- // Handle \p{...} and \P{...} inside character class
- // Translate Perl Unicode property names to Java-compatible patterns
- boolean pNegated = (s.charAt(offset) == 'P');
- int pEndBrace = s.indexOf('}', offset + 2);
- if (pEndBrace != -1) {
- String property = s.substring(offset + 2, pEndBrace).trim();
- try {
- String translatedProperty = UnicodeResolver.translateUnicodeProperty(
- property, pNegated);
- // Remove the backslash that was already appended
- sb.setLength(sb.length() - 1);
- // Append the translated property (e.g., a character class pattern from ICU4J)
- sb.append(translatedProperty);
- offset = pEndBrace;
- } catch (IllegalArgumentException e) {
- // If translation fails, pass through as-is and let Java handle it
- // (works for standard Java properties like \p{L}, \p{IsAlphabetic}, etc.)
- sb.append(Character.toChars(s.charAt(offset)));
- }
- } else {
- sb.append(Character.toChars(s.charAt(offset)));
- }
- lastChar = -1; // Unicode properties can't be range endpoints
- } else if (offset < length && s.charAt(offset) == 'K') {
- // KEEP is not active inside a class. Preserve Perl's
- // literal K while emitting Java-compatible syntax.
- sb.setLength(sb.length() - 1);
- sb.append('K');
- lastChar = 'K';
- } else if (offset < length && s.charAt(offset) == 'N') {
- if (offset + 1 < length && s.charAt(offset + 1) == '{') {
- // Handle \N{...} constructs
- offset += 2; // Skip past \N{
- int endBrace = s.indexOf('}', offset);
- if (endBrace != -1) {
- String content = s.substring(offset, endBrace).trim();
- // Check if content is a quantifier
- if (content.matches("\\d+(?:\\s*,\\s*\\d*)?")) {
- // Can't use quantifiers inside character class
- RegexPreprocessor.regexError(s, offset - 2, "Quantifier \\N{" + content + "} not allowed inside character class");
- } else {
- // The class scanner has already copied the
- // leading backslash. Append only the escape
- // body so the result is \x{...}, not
- // \\x{...} (a class containing literal
- // backslash/x/braces characters).
- int codePoint = UnicodeResolver.getCodePointFromName(content);
- sb.append("x{").append(Integer.toHexString(codePoint)).append('}');
- offset = endBrace;
- }
- } else {
- RegexPreprocessor.regexError(s, offset, "Unmatched brace in \\N{name} construct");
- }
- } else {
- // Plain \N - but inside character class we can't use [^\n]
- // We need to handle this differently - maybe reject it
- RegexPreprocessor.regexError(s, offset - 1, "\\N (non-newline) not supported inside character class");
- }
- lastChar = -1; // Can't use \N in ranges
- } else if (offset + 1 < length && s.charAt(offset) == 'o' && s.charAt(offset + 1) == '{') {
- // Handle \o{...} octal escape construct in character class
- offset += 2; // Skip past \o{
- int endBrace = s.indexOf('}', offset);
- if (endBrace != -1) {
- String octalStr = s.substring(offset, endBrace).trim();
- // Remove underscores (Perl allows them in number literals)
- octalStr = octalStr.replace("_", "");
- try {
- // Parse as octal and convert to hex
- int value = Integer.parseInt(octalStr, 8);
- sb.append(String.format("x{%X}", value));
- offset = endBrace;
- lastChar = value;
- } catch (NumberFormatException e) {
- RegexPreprocessor.regexError(s, offset, "Invalid octal number in \\o{...}");
- }
- } else {
- RegexPreprocessor.regexError(s, offset, "Missing right brace on \\o{}");
- }
- } else if (offset + 1 < length && s.charAt(offset) == 'x' && s.charAt(offset + 1) == '{') {
- // Handle \x{...} hex escape construct in character class
- offset += 2; // Skip past x{
- int endBrace = s.indexOf('}', offset);
- if (endBrace != -1) {
- String hexStr = s.substring(offset, endBrace).trim();
- // Remove underscores (Perl allows them in number literals)
- hexStr = hexStr.replace("_", "");
- // Extract valid hex prefix (Perl stops at first non-hex char)
- int validLen = 0;
- for (int i = 0; i < hexStr.length(); i++) {
- char ch = hexStr.charAt(i);
- if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
- validLen++;
- } else {
- break;
- }
- }
- int value;
- if (validLen == 0) {
- value = 0; // No valid hex digits → \x00
- } else {
- value = Integer.parseInt(hexStr.substring(0, validLen), 16);
- }
- sb.append(String.format("x{%X}", value));
- offset = endBrace;
- lastChar = value;
- } else {
- RegexPreprocessor.regexError(s, offset, "Missing right brace on \\x{}");
- }
- } else if (s.codePointAt(offset) == 'x') {
- // Bare \xNN inside a character class has the same
- // up-to-two-hex-digit semantics as it does outside a
- // class. Consume the digits here so they do not become
- // independent range endpoints (for example, Perl's
- // [\x00-\ ] was previously misread as [0- ]).
- int hexValue = 0;
- int hexDigits = 0;
- int pos = offset + 1;
- while (hexDigits < 2 && pos < length) {
- char ch = s.charAt(pos);
- if ((ch >= '0' && ch <= '9')
- || (ch >= 'a' && ch <= 'f')
- || (ch >= 'A' && ch <= 'F')) {
- hexValue = hexValue * 16 + Character.digit(ch, 16);
- hexDigits++;
- pos++;
- } else {
- break;
- }
- }
- if (hexDigits == 2) {
- sb.append('x');
- sb.append(s.charAt(offset + 1));
- sb.append(s.charAt(offset + 2));
- offset += 2;
- } else {
- sb.append(String.format("x{%X}", hexValue));
- offset = pos - 1;
- }
- lastChar = hexValue;
- } else if (s.codePointAt(offset) == 'b') {
- // \b inside character class = backspace in Perl
- // Java doesn't support \b in [...], so convert to \x08
- // Remove the \ that was already appended to sb
- sb.setLength(sb.length() - 1);
- // Use \x08 directly in the class (works for ranges too)
- sb.append("\\x08");
- // Don't increment offset here - the outer loop will do it
- lastChar = 0x08; // Backspace character for range validation
- first = false;
- afterCaret = false;
- } else if (s.codePointAt(offset) == 'c' && offset + 1 < length) {
- // \cX control-character escape: consume the next character
- // and convert to a hex escape so Java doesn't mis-parse
- // sequences like \c\, \c[, \c] (where the next char is
- // special inside a character class).
- // Perl semantics: ord(uc(X)) XOR 0x40, low 8 bits.
- int ctrl = s.codePointAt(offset + 1);
- if (ctrl >= 'a' && ctrl <= 'z') {
- ctrl = ctrl - 'a' + 'A'; // upper-case ASCII letter
- }
- int value = (ctrl ^ 0x40) & 0xFF;
- // Remove the backslash that was already appended
- sb.setLength(sb.length() - 1);
- sb.append(String.format("\\x{%X}", value));
- offset++; // skip past the control-char target (outer loop bumps past it)
- lastChar = value;
- first = false;
- afterCaret = false;
- wasEscape = true;
- break;
- } else if (s.codePointAt(offset) == 'Q' || s.codePointAt(offset) == 'E') {
- warnUnrecognizedCharacterClassEscape((char) s.codePointAt(offset));
- sb.append(Character.toChars(s.codePointAt(offset)));
- lastChar = s.codePointAt(offset);
- } else {
- int c2 = s.codePointAt(offset);
- if (c2 >= '0' && c2 <= '7') {
- // Potential octal sequence - parse it
- int octalValue = c2 - '0';
- int octalLength = 1;
-
- // Read up to 2 more octal digits
- for (int i = 1; i <= 2 && offset + i < length; i++) {
- int nextDigit = s.codePointAt(offset + i);
- if (nextDigit >= '0' && nextDigit <= '7') {
- octalValue = octalValue * 8 + (nextDigit - '0');
- octalLength++;
- } else {
- break;
- }
- }
-
- // Check if value is > 255 (requires hex conversion)
- if (octalValue > 255) {
- // Convert to hex for Java regex
- sb.append(String.format("x{%X}", octalValue));
- offset += octalLength - 1; // -1 because outer loop will increment
- lastChar = octalValue;
- } else if (octalValue <= 255 && octalLength == 3) {
- // Standard 3-digit octal - convert to hex for Java regex
- // Using \x{hex} avoids issues with \0mnn parsing and ensures
- // correct range validation (e.g., [\177-\237])
- sb.append(String.format("x{%X}", octalValue));
- offset += octalLength - 1; // -1 because outer loop will increment
- lastChar = octalValue;
- } else if (c2 == '0' && octalLength == 1) {
- // Single \0 becomes \00
- sb.append('0');
- sb.append('0');
- lastChar = 0;
- } else {
- // Short octal (1-2 digits) — prepend 0 for Java
- // In Perl, \1-\7 inside [] are octal; in Java, \N is a backreference
- sb.append('0');
- for (int j = 0; j < octalLength; j++) {
- sb.append(Character.toChars(s.codePointAt(offset + j)));
- }
- offset += octalLength - 1;
- lastChar = octalValue;
- }
- } else if (c2 == '8' || c2 == '9') {
- // \8 and \9 are not valid octals - treat as literal digits
- // Remove the backslash that was already appended
- sb.setLength(sb.length() - 1);
- sb.append(Character.toChars(c2));
- lastChar = c2;
- } else if (flag_xx && (c2 == ' ' || c2 == '\t')) {
- // Java COMMENTS mode drops escaped horizontal
- // whitespace in character classes. Spell it as hex
- // so a Perl /xx literal survives Java compilation.
- sb.setLength(sb.length() - 1);
- sb.append(c2 == ' ' ? "\\x20" : "\\x09");
- lastChar = c2;
- } else {
- // Other escape sequences
- sb.append(Character.toChars(c2));
- // Remember the actual character for range validation
- if (c2 != 'p' && c2 != 'P') { // Skip property escapes
- lastChar = c2;
- } else {
- lastChar = -1;
- }
- }
- }
- first = false;
- afterCaret = false;
- break;
- case ' ':
- if (flag_xx) {
- // Perl /xx ignores unescaped horizontal whitespace even
- // inside a bracketed class. Do not let it affect whether
- // a following ^ is the class negator or whether - is an
- // endpoint. Java COMMENTS mode also ignores it, but only
- // after our class parser has already made those decisions.
- break;
- } else {
- // make this space a "token", even inside /x
- sb.append("\\ ");
- }
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case '\t':
- if (flag_xx) {
- break;
- } else {
- sb.append("\\t");
- }
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case '\n':
- sb.append("\\n");
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case '\r':
- sb.append("\\r");
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case '\f':
- sb.append("\\f");
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case 0x0B:
- sb.append("\\x{B}");
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- case '(', ')', '*', '<', '>', '\'', '"', '`', '@', '#', '=', '&':
- // NOTE: '?' is deliberately NOT in this list. None of these
- // characters need to be escaped inside a Java regex character
- // class, so the backslash is purely cosmetic — except that a
- // backslashed '?' combines with a preceding `\c` to form
- // `\c\?`, which Java then parses as `\c\` (control-backslash
- // = U+001C) followed by a literal '?', silently corrupting
- // patterns like `[\n\t\c?]` (Perl: matches LF, TAB, DEL;
- // pre-fix: also matched U+001C). See
- // dev/modules/json_test_parity.md and t/99_binary.t in the
- // CPAN JSON distribution for a motivating example.
- sb.append('\\');
- sb.append(Character.toChars(c));
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- default:
- sb.append(Character.toChars(c));
- first = false;
- afterCaret = false;
- lastChar = c;
- wasEscape = false;
- break;
- }
- offset++;
- }
- return offset;
- }
-
- private static void warnUnrecognizedCharacterClassEscape(char escape) {
- String message = "Unrecognized escape \\" + escape + " in character class passed through in regex";
- RegexPreprocessor.recordWarningOnUse(message);
- if (!RegexPreprocessor.shouldEmitWarnings()) {
- return;
- }
- WarnDie.warnWithCategory(
- new RuntimeScalar(message),
- new RuntimeScalar(""),
- "regexp");
- }
-
- static int handleFlagModifiers(String s, int offset, StringBuilder sb, RegexFlags regexFlags) {
- int start = offset + 2; // Skip past '(?'
- int colonPos = s.indexOf(':', start);
- int closeParen = s.indexOf(')', start);
-
- if (closeParen == -1) {
- RegexPreprocessor.regexError(s, offset, "Unterminated ( in regex");
- }
-
- int flagsEnd = (colonPos == -1 || closeParen < colonPos) ? closeParen : colonPos;
- String flags = s.substring(start, flagsEnd);
-
- // Check for invalid (?^- pattern
- if (flags.startsWith("^-")) {
- RegexPreprocessor.regexError(s, start + 1, "Sequence (?^-...) not recognized");
- }
-
- // Check for invalid (?^d pattern
- if (flags.startsWith("^d")) {
- RegexPreprocessor.regexError(s, start + 2, "Sequence (?^d...) not recognized");
- }
-
- // Split into positive and negative parts
- String[] parts = flags.split("-", 2);
- String positiveFlags = parts[0];
- String negativeFlags = parts.length > 1 ? parts[1] : "";
-
- warnForUselessMatchOnlyFlags(positiveFlags, false);
- warnForUselessMatchOnlyFlags(negativeFlags, true);
-
- // Check for mutually exclusive modifiers
- if (positiveFlags.contains("l") && positiveFlags.contains("u")) {
- // Find position of second flag
- int lPos = positiveFlags.indexOf('l');
- int uPos = positiveFlags.indexOf('u');
- int errorPos = Math.max(lPos, uPos);
- RegexPreprocessor.regexError(s, start + errorPos + 1, "Regexp modifiers \"l\" and \"u\" are mutually exclusive");
- }
-
- // Check for d and a being mutually exclusive
- if (positiveFlags.contains("d") && positiveFlags.contains("a")) {
- // Find position of second flag
- int dPos = positiveFlags.indexOf('d');
- int aPos = positiveFlags.indexOf('a');
- int errorPos = Math.max(dPos, aPos);
- RegexPreprocessor.regexError(s, start + errorPos + 1, "Regexp modifiers \"d\" and \"a\" are mutually exclusive");
- }
-
- // Check for duplicate modifiers
- if (positiveFlags.indexOf('l') != positiveFlags.lastIndexOf('l')) {
- int secondL = positiveFlags.lastIndexOf('l');
- RegexPreprocessor.regexError(s, start + secondL + 1, "Regexp modifier \"l\" may not appear twice");
- }
-
- // Check for 'a' appearing more than twice
- int aCount = positiveFlags.length() - positiveFlags.replace("a", "").length();
- if (aCount > 2) {
- // Find position of third 'a'
- int pos = -1;
- for (int i = 0; i < 3; i++) {
- pos = positiveFlags.indexOf('a', pos + 1);
- }
- RegexPreprocessor.regexError(s, start + pos + 1, "Regexp modifier \"a\" may appear a maximum of twice");
- }
-
- // Check for modifiers in negative part that shouldn't be there
- if (negativeFlags.contains("l")) {
- int lPos = negativeFlags.indexOf('l');
- RegexPreprocessor.regexError(s, start + parts[0].length() + 1 + lPos + 1, "Regexp modifier \"l\" may not appear after the \"-\"");
- }
-
- // Handle caret case
- if (!positiveFlags.isEmpty() && positiveFlags.charAt(0) == '^') {
- positiveFlags = positiveFlags.substring(1);
- Set negSet = new HashSet<>();
- for (char c : (negativeFlags + "imnsx").toCharArray()) {
- negSet.add(c);
- }
- // Remove any negative flags that appear in positive flags
- for (char c : positiveFlags.toCharArray()) {
- negSet.remove(c);
- }
- negativeFlags = negSet.stream()
- .map(String::valueOf)
- .collect(Collectors.joining());
- }
-
- RegexFlags newFlags = regexFlags.with(positiveFlags, negativeFlags);
-
- if (positiveFlags.indexOf('p') >= 0) {
- RegexPreprocessor.inlinePFlagEncountered = true;
- }
-
- if (positiveFlags.isEmpty()
- && negativeFlags.isEmpty()
- && (colonPos == -1 || closeParen < colonPos)) {
- offset = RegexPreprocessor.handleRegex(s, closeParen + 1, sb, newFlags, true);
- if (offset < s.length()) {
- offset--;
- }
- return offset;
- }
-
- // Handle `n` flags
- if (positiveFlags.indexOf('n') >= 0) {
- positiveFlags = positiveFlags.replace("n", "");
- }
- if (negativeFlags.indexOf('n') >= 0) {
- negativeFlags = negativeFlags.replace("n", "");
- }
-
- // Filter flags to only include Java-supported ones
- positiveFlags = filterSupportedFlags(positiveFlags);
- negativeFlags = filterSupportedFlags(negativeFlags);
-
- // Build the new flag string
- sb.append("(?");
- sb.append(positiveFlags);
- if (!negativeFlags.isEmpty()) {
- sb.append('-').append(negativeFlags);
- }
-
- if (colonPos == -1 || closeParen < colonPos) {
- // Case: `(?flags)pattern`
- sb.append(")");
- offset = RegexPreprocessor.handleRegex(s, closeParen + 1, sb, newFlags, true);
- // The closing parenthesis, if any, is consumed by the caller
- if (offset < s.length()) {
- offset--;
- }
- return offset;
- }
-
- // Case: `(?flags:pattern)`
- sb.append(":");
- offset = RegexPreprocessor.handleRegex(s, colonPos + 1, sb, newFlags, true);
-
- // Ensure the closing parenthesis is consumed
- if (offset >= s.length() || s.codePointAt(offset) != ')') {
- RegexPreprocessor.regexError(s, offset, "Unterminated ( in regex");
- }
- sb.append(')');
- return offset;
- }
-
- private static void warnForUselessMatchOnlyFlags(String flags, boolean negative) {
- if (!RegexPreprocessor.shouldEmitWarnings()) {
- return;
- }
- boolean suppressNegativeG = negative && flags.indexOf('c') >= 0;
- for (int i = 0; i < flags.length(); i++) {
- char flag = flags.charAt(i);
- if (flag != 'c' && flag != 'g' && flag != 'o') {
- continue;
- }
- if (negative && flag == 'g' && suppressNegativeG) {
- continue;
- }
- String prefix = negative ? "?-" : "?";
- WarnDie.warn(
- new RuntimeScalar("Useless (" + prefix + flag + ")"),
- new RuntimeScalar(""));
- }
- }
-
- /**
- * Filters out regex flags that are not supported by Java's regex engine.
- *
- * @param flags The flags string to filter
- * @return The filtered flags string containing only Java-supported flags
- */
- private static String filterSupportedFlags(String flags) {
- // Java supports: i, m, s, x, u, d
- // We need to remove: a, l, p, and any other Perl-specific flags
- StringBuilder filtered = new StringBuilder();
- for (char c : flags.toCharArray()) {
- switch (c) {
- case 'i': // case-insensitive
- case 'm': // multi-line
- case 's': // single-line (dotall)
- case 'x': // extended/comments
- case 'u': // Unicode case
- case 'd': // Unix lines
- filtered.append(c);
- break;
- // Skip unsupported flags
- case 'a': // ASCII
- case 'l': // locale
- case 'p': // preserve
- case 'n': // no capture (already handled separately)
- // These are Perl flags not supported in Java
- break;
- default:
- // Unknown flag - you might want to warn or error here
- break;
- }
- }
- return filtered.toString();
- }
-
-}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RegexQuoteMeta.java b/src/main/java/org/perlonjava/runtime/regex/RegexQuoteMeta.java
index e4cca7bbf2..4d6b77ec2e 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RegexQuoteMeta.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RegexQuoteMeta.java
@@ -1,7 +1,9 @@
package org.perlonjava.runtime.regex;
+import org.perlonjava.runtime.WarningBitsRegistry;
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
+import org.perlonjava.runtime.runtimetypes.WarningFlags;
import java.util.ArrayList;
import java.util.List;
@@ -10,6 +12,7 @@ public class RegexQuoteMeta {
private static final ThreadLocal> WARNINGS_ON_USE = ThreadLocal.withInitial(ArrayList::new);
private static final ThreadLocal CALL_SITE_WARNING_STATE = new ThreadLocal<>();
private static final ThreadLocal CALL_SITE_WARNING_BITS = new ThreadLocal<>();
+ private static final ThreadLocal PARSER_WARNING_BITS = new ThreadLocal<>();
private static final ThreadLocal MATCH_TARGET_NAME = new ThreadLocal<>();
public static void setCallSiteWarningState(int state) {
@@ -24,6 +27,20 @@ public static String getCallSiteWarningBits() {
return CALL_SITE_WARNING_BITS.get();
}
+ public static boolean isCallSiteWarningExplicitlyDisabled() {
+ Integer state = CALL_SITE_WARNING_STATE.get();
+ return state != null && state < 0;
+ }
+
+ public static void setParserWarningBits(String bits) {
+ if (bits == null) PARSER_WARNING_BITS.remove();
+ else PARSER_WARNING_BITS.set(bits);
+ }
+
+ public static String getParserWarningBits() {
+ return PARSER_WARNING_BITS.get();
+ }
+
public static void setMatchTargetName(String name) {
MATCH_TARGET_NAME.set(name);
}
@@ -129,15 +146,44 @@ private static void warnUnrecognizedCharClassEscape(char c) {
/** Emit a regex-construction warning using the lexical state captured at the quote site. */
public static void warnAtConstruction(String message) {
+ warnAtConstruction(message, false);
+ }
+
+ static void warnAtConstruction(String message, boolean strictDefault) {
// This warning belongs to construction of the interpolated pattern.
// Retaining it on the cached RuntimeRegex re-emits it for every match
// and can leak a warning-enabled compilation into a no-warnings use.
Integer state = CALL_SITE_WARNING_STATE.get();
+ RuntimeScalar warning = new RuntimeScalar(message);
+ RuntimeScalar where = new RuntimeScalar("");
+ String warningBits = CALL_SITE_WARNING_BITS.get();
+ if (warningBits == null) {
+ // Synthetic/interpolated regex wrappers do not always have a
+ // dedicated quote opcode. Use the active statement mask rather
+ // than a stale warning-state value from an earlier qr//.
+ warningBits = WarningBitsRegistry.getRuntimeWarningBits();
+ }
+ if (warningBits != null) {
+ String category = warningCategory(message);
+ boolean enabled = WarningFlags.isEnabledInBits(warningBits, category);
+ if (!enabled && !isCallSiteWarningExplicitlyDisabled()) {
+ enabled = strictDefault
+ || (WarningFlags.isGlobalWarningVariableEnabled()
+ && !WarningFlags.isWarningSuppressedAtRuntime(category));
+ }
+ if (!enabled) {
+ return;
+ }
+ if (WarningFlags.isFatalInBits(warningBits, category)) {
+ WarnDie.die(warning, where);
+ } else {
+ WarnDie.warn(warning, where);
+ }
+ return;
+ }
if (state != null && state == 0) {
return;
}
- RuntimeScalar warning = new RuntimeScalar(message);
- RuntimeScalar where = new RuntimeScalar("");
if (state != null && state == 2) {
WarnDie.die(warning, where);
} else if (state != null) {
@@ -146,4 +192,26 @@ public static void warnAtConstruction(String message) {
WarnDie.warnWithCategory(warning, where, "regexp");
}
}
+
+ /** Perl assigns a few lexer diagnostics to broader warning categories. */
+ static String warningCategory(String message) {
+ if (message != null && message.startsWith("Useless use of \\E")) {
+ return "misc";
+ }
+ if (message != null && message.contains("is more clearly written simply as")) {
+ return "syntax";
+ }
+ if (message != null && message.startsWith("Variable length ")
+ && message.contains("lookbehind with capturing is experimental")) {
+ return "experimental::vlb";
+ }
+ if (message != null
+ && (message.startsWith(
+ "The Unicode property wildcards feature is experimental")
+ || message.startsWith(
+ "Using just the single character results returned by \\p{} in (?[...])"))) {
+ return "experimental::uniprop_wildcards";
+ }
+ return "regexp";
+ }
}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
index fbeeb788a8..606c484648 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegex.java
@@ -3,11 +3,14 @@
import org.joni.exception.SyntaxException;
import org.perlonjava.backend.bytecode.InterpreterState;
import org.perlonjava.runtime.WarningBitsRegistry;
+import org.perlonjava.runtime.NamedCharacterExpansion;
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
import org.perlonjava.runtime.operators.PerlUtfString;
import org.perlonjava.runtime.operators.Time;
import org.perlonjava.runtime.operators.StringOperators;
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.perlmodule.Utf8;
+import org.perlonjava.runtime.perlmodule.Warnings;
import org.perlonjava.runtime.runtimetypes.*;
import java.nio.charset.StandardCharsets;
@@ -23,10 +26,10 @@
import java.util.regex.PatternSyntaxException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
import static org.perlonjava.runtime.regex.RegexFlags.fromModifiers;
import static org.perlonjava.runtime.regex.RegexFlags.validateModifiers;
-import static org.perlonjava.runtime.regex.RegexPreprocessor.preProcessRegex;
import static org.perlonjava.runtime.regex.RegexQuoteMeta.escapeQ;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.getScalarInt;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
@@ -39,6 +42,11 @@
*/
public class RuntimeRegex extends RuntimeBase implements RuntimeScalarReference {
+ /** Returns optimization facts selected by the primary compiled Joni pattern. */
+ public org.joni.Regex.OptimizationInfo getOptimizationInfo() {
+ return recursivePattern == null ? null : recursivePattern.engineRegex().getOptimizationInfo();
+ }
+
/**
* Keep a literal target's SV stable across repeated execution of one regex call site.
* Perl stores pos() on the literal SV in the compiled optree; rematerializing it on
@@ -64,10 +72,6 @@ public static RuntimeScalar stabilizeLiteralTarget(RuntimeScalar literal, int ca
// Debug flag for regex compilation (set at class load time)
private static final boolean DEBUG_REGEX = System.getenv("DEBUG_REGEX") != null;
- // Constants for regex pattern flags
- private static final int CASE_INSENSITIVE = Pattern.CASE_INSENSITIVE;
- private static final int MULTILINE = Pattern.MULTILINE;
- private static final int DOTALL = Pattern.DOTALL;
private static final Pattern USER_DEFINED_PROPERTY_PATTERN =
Pattern.compile("\\\\([pP])\\{((?:[A-Za-z_][A-Za-z0-9_]*::)*(?:Is|In)[A-Za-z_][A-Za-z0-9_]*)}");
private static final Pattern MALFORMED_USER_DEFINED_PROPERTY_PATTERN =
@@ -82,73 +86,6 @@ public static RuntimeScalar stabilizeLiteralTarget(RuntimeScalar literal, int ca
private static final Set REPORTED_DEBUG_COMPILATIONS =
ConcurrentHashMap.newKeySet();
- private static String makeDelimitedTokenRepetitionsPossessive(String pattern) {
- Deque groups = new ArrayDeque<>();
- List insertions = new ArrayList<>();
- boolean escaped = false;
- boolean inClass = false;
-
- for (int i = 0; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
- if (escaped) {
- escaped = false;
- continue;
- }
- if (ch == '\\') {
- escaped = true;
- continue;
- }
- if (ch == '[') {
- inClass = true;
- continue;
- }
- if (ch == ']' && inClass) {
- inClass = false;
- continue;
- }
- if (inClass) continue;
-
- if (ch == '(') {
- groups.push(new DelimitedGroup(i));
- } else if (ch == '|' && !groups.isEmpty()) {
- groups.peek().hasAlternation = true;
- } else if (ch == ')' && !groups.isEmpty()) {
- DelimitedGroup group = groups.pop();
- if (!group.hasAlternation || i + 1 >= pattern.length()
- || pattern.charAt(i + 1) != '*') {
- continue;
- }
-
- String body = pattern.substring(group.start, i + 1);
- char delimiter = body.contains("[^\"") || body.contains("[^\\\"") ? '"'
- : body.contains("[^'") || body.contains("[^\\'") ? '\'' : 0;
- if (delimiter == 0) continue;
-
- int suffix = i + 2;
- while (suffix < pattern.length() && pattern.charAt(suffix) == ')') suffix++;
- if (suffix < pattern.length() && pattern.charAt(suffix) == delimiter) {
- insertions.add(i + 2);
- }
- }
- }
-
- if (insertions.isEmpty()) return pattern;
- StringBuilder stackSafe = new StringBuilder(pattern);
- for (int i = insertions.size() - 1; i >= 0; i--) {
- stackSafe.insert(insertions.get(i).intValue(), '+');
- }
- return stackSafe.toString();
- }
-
- private static final class DelimitedGroup {
- final int start;
- boolean hasAlternation;
-
- DelimitedGroup(int start) {
- this.start = start;
- }
- }
-
private static RuntimeRegexState state() {
return PerlRuntime.current().regexState;
}
@@ -175,35 +112,23 @@ static void updateControlVerbVariables(String mark, String error) {
}
}
}
- // Compiled regex pattern (for byte strings - ASCII-only \w, \d)
- public Pattern pattern;
- // Compiled regex pattern for Unicode strings (Unicode \w, \d)
- public Pattern patternUnicode;
- // Marker-free variants used for ordinary strings that cannot contain PerlOnJava's
- // internal U+FFFD scalar markers.
- Pattern patternNoInternalMarkers;
- Pattern patternUnicodeNoInternalMarkers;
- // "Notempty" variant patterns for zero-length match guard retry.
- // In Perl, after a zero-length /gc match at position P, the next attempt
- // stays at P but uses NOTEMPTY. Java lacks this option, so an opaque region
- // starting at P plus a trailing one-character lookbehind requires the
- // selected alternative to consume input.
- Pattern notemptyPattern;
- Pattern notemptyPatternUnicode;
JoniRegexPattern recursivePattern;
JoniRegexPattern recursivePatternUnicode;
JoniRegexPattern recursivePatternBytes;
private JoniRegexPattern.NamedCharacterCache namedCharacterCache;
+ private NamedCharacterExpansion.SourceMode namedCharacterSourceMode;
+ private RuntimeScalar namedCharacterTranslator;
+ private int trustedCalloutCount;
+ private String compiledRegexCacheKey;
+ // Bare Is*/In* callbacks resolve in the package where the regex was
+ // constructed, even when qr// is first matched later or in an ithread.
+ private String userPropertyPackage = "main";
List executableCallbacks = List.of();
private boolean executableCallbacksReleased;
- int patternFlags;
- int patternFlagsUnicode;
public String patternString;
// Source scalar provenance needed when a compiled qr// is reused under
// lexical use bytes: byte-backed source characters are already octets.
private boolean patternByteBacked;
- String javaPatternString; // Preprocessed Java-compatible pattern for recompilation
- private String requiredLiteral;
boolean hasPreservesMatch = false; // True if /p was used (outer or inline (?p))
// Indicates if \G assertion is used (set from regexFlags during compilation)
private boolean useGAssertion = false;
@@ -227,39 +152,52 @@ static void updateControlVerbVariables(String mark, String error) {
// 0 = off, 1 = debug, 2 = debugcolor. Captured at the regex call site.
private int lexicalDebugMode;
private boolean lexicalReStrict;
- private static final String DYNAMIC_PATTERN_ERROR =
- "\u0000(??{...}) recursive regex patterns not implemented (dynamic pattern)";
-
public RuntimeRegex() {
this.regexFlags = null;
}
/**
* Creates a tracked copy of this RuntimeRegex for use as a qr// value.
- * The copy shares compiled Pattern objects but has its own refCount = 0,
+ * The copy shares compiled native regex objects but has its own refCount = 0,
* enabling proper reference counting when assigned to user variables.
* This mirrors Perl 5 where qr// always creates a new SV wrapper around
* the shared compiled regex.
*/
public RuntimeRegex cloneTracked() {
+ return cloneTrackedWithCallbacks(this.executableCallbacks);
+ }
+
+ /**
+ * Clone a compiled qr// at an ithread graph boundary.
+ *
+ * The native patterns are immutable and remain shared, but executable
+ * callback CODE objects own Perl lexical pads. Route those CODE objects
+ * through the graph cloner so callbacks share the same child-side lexical
+ * identities as ordinary CODE references in the snapshot.
+ */
+ public RuntimeRegex cloneTrackedForThread(
+ Function callbackCloner) {
+ List callbacks = new ArrayList<>(executableCallbacks.size());
+ for (RuntimeRegexCallback callback : executableCallbacks) {
+ callbacks.add(callbackCloner.apply(callback));
+ }
+ return cloneTrackedWithCallbacks(callbacks);
+ }
+
+ private RuntimeRegex cloneTrackedWithCallbacks(List callbacks) {
RuntimeRegex copy = new RuntimeRegex();
- copy.pattern = this.pattern;
- copy.patternUnicode = this.patternUnicode;
- copy.patternNoInternalMarkers = this.patternNoInternalMarkers;
- copy.patternUnicodeNoInternalMarkers = this.patternUnicodeNoInternalMarkers;
- copy.notemptyPattern = this.notemptyPattern;
- copy.notemptyPatternUnicode = this.notemptyPatternUnicode;
copy.recursivePattern = this.recursivePattern;
copy.recursivePatternUnicode = this.recursivePatternUnicode;
copy.recursivePatternBytes = this.recursivePatternBytes;
copy.namedCharacterCache = this.namedCharacterCache;
- copy.setExecutableCallbacks(this.executableCallbacks);
- copy.patternFlags = this.patternFlags;
- copy.patternFlagsUnicode = this.patternFlagsUnicode;
+ copy.namedCharacterSourceMode = this.namedCharacterSourceMode;
+ copy.namedCharacterTranslator = copyScalarOrNull(this.namedCharacterTranslator);
+ copy.trustedCalloutCount = this.trustedCalloutCount;
+ copy.compiledRegexCacheKey = this.compiledRegexCacheKey;
+ copy.userPropertyPackage = this.userPropertyPackage;
+ copy.setExecutableCallbacks(callbacks);
copy.patternString = this.patternString;
copy.patternByteBacked = this.patternByteBacked;
- copy.javaPatternString = this.javaPatternString;
- copy.requiredLiteral = this.requiredLiteral;
copy.hasPreservesMatch = this.hasPreservesMatch;
copy.useGAssertion = this.useGAssertion;
copy.regexFlags = this.regexFlags;
@@ -299,37 +237,18 @@ public void releaseExecutableCallbacks() {
for (RuntimeRegexCallback callback : executableCallbacks) callback.releaseOwner();
}
- /** Select the byte or Unicode compiled pattern for a particular target scalar. */
- public Pattern selectPattern(RuntimeScalar string) {
- Pattern selected = this.pattern;
- if (this.patternUnicode != null && this.patternUnicode != this.pattern) {
- if (this.regexFlags != null && this.regexFlags.isAscii()) {
- selected = this.pattern;
- } else if (hasInlineAsciiModifier(this.patternString)) {
- selected = this.pattern;
- } else if (Utf8.isUtf8(string)) {
- selected = this.patternUnicode;
- }
- }
- return selected;
- }
-
/**
* Create a backend-neutral matcher for callers that implement Perl
* operations around regex matches (for example {@code split}).
*/
public RegexMatcher matcher(RuntimeScalar string, String input) {
- if (recursivePattern != null) {
- return selectRecursivePattern(string).matcher(input, executableCallbacks, string);
- }
- Pattern selected = selectPattern(string, input);
- return new JavaRegexMatcher(selected.matcher(new RegexTimeoutCharSequence(input)));
+ return selectRecursivePattern(string).matcher(input, executableCallbacks, string);
}
private JoniRegexPattern selectRecursivePattern(RuntimeScalar string) {
boolean byteDefaultSemantics = patternByteBacked && regexFlags != null
- && regexFlags.isCaseInsensitive() && !regexFlags.isUnicode()
- && !regexFlags.isAscii();
+ && !regexFlags.isUnicode()
+ && !regexFlags.isAscii() && !hasInlineUnicodeCaseFoldModifier(patternString);
if ((bytesSubstitution || byteDefaultSemantics) && recursivePatternBytes != null
&& !Utf8.isUtf8(string)) {
return recursivePatternBytes;
@@ -346,115 +265,113 @@ public String sourcePattern() {
return patternString;
}
- private Pattern selectPattern(RuntimeScalar string, String inputStr) {
- Pattern selected = selectPattern(string);
- if (!couldContainInternalScalarMarker(inputStr)) {
- if (selected == this.patternUnicode && this.patternUnicodeNoInternalMarkers != null) {
- return this.patternUnicodeNoInternalMarkers;
- }
- if (selected == this.pattern && this.patternNoInternalMarkers != null) {
- return this.patternNoInternalMarkers;
- }
- }
- return selected;
- }
-
- private static boolean couldContainInternalScalarMarker(String inputStr) {
- return inputStr != null && inputStr.indexOf('\uFFFD') >= 0;
- }
-
- private static Pattern compileWithoutInternalMarkerAlternations(String javaPattern, int patternFlags) {
- String stripped = stripInternalMarkerAlternations(javaPattern);
- if (stripped.equals(javaPattern)) {
- return null;
- }
- return Pattern.compile(stripped, patternFlags);
- }
-
- private static final String INTERNAL_MARKER_ALTERNATION = "(?:\\x{FFFD}<[0-9A-F]+>|";
-
- private static String stripInternalMarkerAlternations(String javaPattern) {
- int markerStart = javaPattern.indexOf(INTERNAL_MARKER_ALTERNATION);
- if (markerStart < 0) {
- return javaPattern;
- }
-
- StringBuilder stripped = new StringBuilder(javaPattern.length());
- int offset = 0;
- while (markerStart >= 0) {
- stripped.append(javaPattern, offset, markerStart);
- int altStart = markerStart + INTERNAL_MARKER_ALTERNATION.length();
- int close = findInternalMarkerAlternationClose(javaPattern, altStart);
- if (close < 0) {
- stripped.append(javaPattern, markerStart, altStart);
- offset = altStart;
- } else {
- stripped.append(javaPattern, altStart, close);
- offset = close + 1;
- }
- markerStart = javaPattern.indexOf(INTERNAL_MARKER_ALTERNATION, offset);
- }
- stripped.append(javaPattern, offset, javaPattern.length());
- return stripped.toString();
- }
-
- private static int findInternalMarkerAlternationClose(String pattern, int altStart) {
- boolean escaped = false;
- boolean inCharacterClass = false;
- int nestedGroups = 0;
-
- for (int i = altStart; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
- if (escaped) {
- escaped = false;
- continue;
- }
- if (ch == '\\') {
- escaped = true;
+ private void emitWarningsOnUse() {
+ // These warnings belong to the regex use site, not the earlier qr//
+ // construction site. The active Perl code supplies the baseline lexical
+ // warning bits. Each retained diagnostic keeps its Perl warning
+ // category; WarnDie additionally applies a narrower runtime lexical
+ // scope at the individual match site.
+ String activeCodeBits = WarningBitsRegistry.getCurrent();
+ String retainedRuntimeWarningBits = WarningBitsRegistry.getRuntimeWarningBits();
+ String retainedCallSiteWarningBits = RegexQuoteMeta.getCallSiteWarningBits();
+ boolean retainedWarnFlagSet = Warnings.isWarnFlagSet();
+ for (String warning : warningsOnUse) {
+ String category = RegexQuoteMeta.warningCategory(warning);
+ if ("experimental::uniprop_wildcards".equals(category)) {
+ // The feature notice is default-on, while the positioned
+ // single-character diagnostic follows the lexical warning
+ // bits (or re strict). Snapshot those bits before warning
+ // callbacks can temporarily replace the active Perl scope.
+ boolean defaultOnFeatureNotice = warning.startsWith(
+ "The Unicode property wildcards feature is experimental");
+ boolean categoryEnabled = retainedRuntimeWarningBits != null
+ && WarningFlags.isEnabledInBits(
+ retainedRuntimeWarningBits, category)
+ || retainedCallSiteWarningBits != null
+ && WarningFlags.isEnabledInBits(
+ retainedCallSiteWarningBits, category)
+ || activeCodeBits != null
+ && WarningFlags.isEnabledInBits(activeCodeBits, category);
+ boolean enabledAtCallSite = WarningFlags.areWarningsForcedOn()
+ || retainedWarnFlagSet || categoryEnabled;
+ if ((defaultOnFeatureNotice || lexicalReStrict || enabledAtCallSite)
+ && !WarningFlags.areWarningsForcedOff()
+ && !WarningFlags.isWarningSuppressedAtRuntime(category)) {
+ RuntimeScalar message = new RuntimeScalar(warning);
+ if (retainedRuntimeWarningBits != null
+ && WarningFlags.isFatalInBits(
+ retainedRuntimeWarningBits, category)
+ || retainedCallSiteWarningBits != null
+ && WarningFlags.isFatalInBits(
+ retainedCallSiteWarningBits, category)
+ || activeCodeBits != null
+ && WarningFlags.isFatalInBits(activeCodeBits, category)) {
+ WarnDie.die(message, RuntimeScalarCache.scalarEmptyString);
+ } else {
+ WarnDie.warn(message, RuntimeScalarCache.scalarEmptyString);
+ }
+ }
continue;
}
- if (inCharacterClass) {
- if (ch == ']') {
- inCharacterClass = false;
+ if ("experimental::vlb".equals(category)) {
+ // Perl's experimental VLB warning is default-on,
+ // independent of both `use warnings` and `use re 'strict'`.
+ // The narrower lexical category can still suppress it
+ // explicitly.
+ if (!WarningFlags.areWarningsForcedOff()
+ && !WarningFlags.isWarningSuppressedAtRuntime(category)) {
+ String warningBits = RegexQuoteMeta.getCallSiteWarningBits();
+ RuntimeScalar message = new RuntimeScalar(warning);
+ if (warningBits != null
+ && WarningFlags.isFatalInBits(warningBits, category)) {
+ WarnDie.die(message, RuntimeScalarCache.scalarEmptyString);
+ } else {
+ WarnDie.warn(message, RuntimeScalarCache.scalarEmptyString);
+ }
}
continue;
}
- if (ch == '[') {
- inCharacterClass = true;
+ boolean defaultOnExtendedClassDeprecation = warning.startsWith(
+ "Use of unescaped '#' in [] is deprecated under /xx")
+ || warning.startsWith(
+ "Use of literal vertical space in [] is deprecated under /xx");
+ if (defaultOnExtendedClassDeprecation
+ && !WarningFlags.areWarningsForcedOff()
+ && !WarningFlags.isWarningSuppressedAtRuntime("regexp")
+ && !WarningFlags.isWarningSuppressedAtRuntime("deprecated")) {
+ WarnDie.warn(new RuntimeScalar(warning),
+ RuntimeScalarCache.scalarEmptyString);
continue;
}
- if (ch == '(') {
- nestedGroups++;
+ if (lexicalReStrict
+ && !WarningFlags.areWarningsForcedOff()
+ && !WarningFlags.isWarningSuppressedAtRuntime(category)
+ && !WarningFlags.isWarningSuppressedAtRuntime("regexp")
+ && !WarningFlags.isWarningSuppressedAtRuntime("digit")) {
+ String warningBits = WarningBitsRegistry.getRuntimeWarningBits();
+ if (warningBits == null) {
+ warningBits = activeCodeBits;
+ }
+ RuntimeScalar message = new RuntimeScalar(warning);
+ if (warningBits != null
+ && WarningFlags.isFatalInBits(warningBits, category)) {
+ WarnDie.die(message, RuntimeScalarCache.scalarEmptyString);
+ } else {
+ // `use re 'strict'` makes its retained regex diagnostics
+ // default-on. This is independent of $^W, including the
+ // deliberately localized unset value used by reg_mesg.t.
+ WarnDie.warn(message, RuntimeScalarCache.scalarEmptyString);
+ }
continue;
}
- if (ch == ')') {
- if (nestedGroups == 0) {
- return i;
- }
- nestedGroups--;
+ if (!WarningFlags.areWarningsForcedOn()
+ && InterpreterState.current() == null
+ && activeCodeBits != null
+ && !WarningFlags.isEnabledInBits(activeCodeBits, category)) {
+ continue;
}
- }
- return -1;
- }
-
- private void emitWarningsOnUse() {
- // These warnings belong to the regex use site, not the earlier qr//
- // construction site. The active Perl code supplies the baseline lexical
- // warning bits; WarnDie additionally applies a narrower runtime
- // `no warnings 'regexp'` scope at the individual match site.
- String activeCodeBits = WarningBitsRegistry.getCurrent();
- boolean activeCodeEnablesRegexp = activeCodeBits == null
- || WarningFlags.isEnabledInBits(activeCodeBits, "regexp");
- if (!WarningFlags.areWarningsForcedOn()
- && InterpreterState.current() == null
- && !activeCodeEnablesRegexp) {
- return;
- }
- if (warningsOnUse.contains(DYNAMIC_PATTERN_ERROR)) {
- throw new PerlJavaUnimplementedException(DYNAMIC_PATTERN_ERROR.substring(1));
- }
- for (String warning : warningsOnUse) {
- WarnDie.warnWithCategory(new RuntimeScalar(warning), RuntimeScalarCache.scalarEmptyString, "regexp");
+ WarnDie.warnWithCategory(new RuntimeScalar(warning),
+ RuntimeScalarCache.scalarEmptyString, category);
}
}
@@ -531,8 +448,25 @@ private static RuntimeRegex compile(String patternString, String modifiers, int
private static RuntimeRegex compile(String patternString, String modifiers, int lexicalDebugMode,
int trustedCalloutCount, boolean patternByteBacked,
boolean lexicalReStrict) {
- RuntimeScalar namedCharacterTranslator =
- org.perlonjava.runtime.HintHashRegistry.getCompileTimeHint("charnames");
+ return compile(patternString, modifiers, lexicalDebugMode, trustedCalloutCount,
+ patternByteBacked, lexicalReStrict, null);
+ }
+
+ private static RuntimeRegex compile(String patternString, String modifiers, int lexicalDebugMode,
+ int trustedCalloutCount, boolean patternByteBacked,
+ boolean lexicalReStrict, String sourceDiagnosticPattern) {
+ return compile(patternString, modifiers, lexicalDebugMode, trustedCalloutCount,
+ patternByteBacked, lexicalReStrict, sourceDiagnosticPattern, null);
+ }
+
+ private static RuntimeRegex compile(String patternString, String modifiers, int lexicalDebugMode,
+ int trustedCalloutCount, boolean patternByteBacked,
+ boolean lexicalReStrict, String sourceDiagnosticPattern,
+ NamedCharacterExpansionMap preResolvedNamedCharacters) {
+ String userPropertyPackage = currentUserPropertyPackage();
+ RuntimeScalar namedCharacterTranslator = preResolvedNamedCharacters == null
+ ? org.perlonjava.runtime.HintHashRegistry.getCompileTimeHint("charnames")
+ : null;
modifiers = stripInternalMarkers(modifiers);
// Dynamic/interpolated qr// compilation can begin during ordinary
// execution, outside the Perl compiler lock. User-defined Unicode
@@ -540,11 +474,26 @@ private static RuntimeRegex compile(String patternString, String modifiers, int
// before entering the process-wide regex compiler monitor. Calls made
// during source compilation remain deferred by UnicodeResolver.
RegexFlags preloadFlags = fromModifiers(modifiers, patternString);
- UnicodeResolver.preloadUserDefinedProperties(
- patternString, preloadFlags.isCaseInsensitive());
- return compileSynchronized(patternString, modifiers, lexicalDebugMode,
- trustedCalloutCount, false, patternByteBacked, lexicalReStrict,
- namedCharacterTranslator);
+ String compileModifiers = modifiers;
+ return UnicodeResolver.withUserPropertyPackage(userPropertyPackage, () -> {
+ UnicodeResolver.preloadUserDefinedProperties(
+ patternString, preloadFlags.isCaseInsensitive(),
+ preloadFlags.isExtended());
+ return compileSynchronized(patternString, compileModifiers, lexicalDebugMode,
+ trustedCalloutCount, false, patternByteBacked, lexicalReStrict,
+ namedCharacterTranslator,
+ preResolvedNamedCharacters == null ? null
+ : new JoniRegexPattern.NamedCharacterCache(
+ preResolvedNamedCharacters),
+ preResolvedNamedCharacters == null ? null
+ : preResolvedNamedCharacters.sourceMode(),
+ false, sourceDiagnosticPattern);
+ });
+ }
+
+ private static String currentUserPropertyPackage() {
+ String packageName = InterpreterState.currentPackage.get().toString();
+ return packageName == null || packageName.isBlank() ? "main" : packageName;
}
/** User properties execute Perl code and therefore cannot be validated while compiling a CV. */
@@ -564,10 +513,36 @@ public static boolean requiresRuntimeUnicodePropertyResolution(String patternStr
* JPERL_UNIMPLEMENTED policy for those patterns.
*/
public static void validateLiteralSyntax(String patternString, String modifiers) {
+ validateLiteralSyntax(patternString, modifiers,
+ org.perlonjava.runtime.HintHashRegistry
+ .getCompileTimeHint("charnames"));
+ }
+
+ /** Validate literal syntax with the lexical charnames policy captured by its AST. */
+ public static void validateLiteralSyntax(
+ String patternString, String modifiers,
+ RuntimeScalar namedCharacterTranslator) {
+ validateLiteralSyntax(patternString, modifiers,
+ namedCharacterTranslator, null);
+ }
+
+ /** Validate literal syntax with its captured charnames source provenance. */
+ public static void validateLiteralSyntax(
+ String patternString, String modifiers,
+ RuntimeScalar namedCharacterTranslator,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode) {
+ validateLiteralSyntaxResult(patternString, modifiers,
+ namedCharacterTranslator, namedCharacterSourceMode);
+ }
+
+ private static RuntimeRegex validateLiteralSyntaxResult(
+ String patternString, String modifiers,
+ RuntimeScalar namedCharacterTranslator,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode) {
try {
- compileSynchronized(patternString, stripInternalMarkers(modifiers),
+ return compileSynchronized(patternString, stripInternalMarkers(modifiers),
debugMode(modifiers), 0, true, false, reStrictMode(modifiers),
- org.perlonjava.runtime.HintHashRegistry.getCompileTimeHint("charnames"));
+ namedCharacterTranslator, null, namedCharacterSourceMode, false, null);
} catch (PerlJavaUnimplementedException unsupported) {
String message = unsupported.getMessage();
if (message != null && (message.contains("premature end of char-class")
@@ -581,38 +556,131 @@ public static void validateLiteralSyntax(String patternString, String modifiers)
|| message.contains("Illegal repetition"))) {
throw new PerlCompilerException(message);
}
+ return null;
}
}
+ /**
+ * Validate one lexical literal and retain the custom translator's immutable
+ * results for compiled-CV and ithread use.
+ */
+ public static NamedCharacterExpansionMap validateLiteralSyntaxAndCapture(
+ String patternString, String modifiers,
+ RuntimeScalar namedCharacterTranslator,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode,
+ NamedCharacterExpansionMap.LiteralIdentity literalIdentity,
+ NamedCharacterExpansionMap.CallableIdentity callableIdentity,
+ String diagnosticPatternString) {
+ try {
+ RuntimeRegex regex = validateLiteralSyntaxResult(patternString, modifiers,
+ namedCharacterTranslator, namedCharacterSourceMode);
+ return regex == null ? new NamedCharacterExpansionMap(
+ literalIdentity, callableIdentity, Map.of())
+ : regex.namedCharacterCache.snapshot(literalIdentity, callableIdentity);
+ } catch (PerlCompilerException exception) {
+ throw new PerlCompilerException(remapLiteralDiagnosticSource(
+ exception.getMessage(), patternString, diagnosticPatternString));
+ }
+ }
+
+ /** Validate masked callback syntax while retaining its same-width source in diagnostics. */
+ public static void validateLiteralSyntax(
+ String patternString, String modifiers, String diagnosticPatternString) {
+ validateLiteralSyntax(patternString, modifiers,
+ org.perlonjava.runtime.HintHashRegistry
+ .getCompileTimeHint("charnames"),
+ null, diagnosticPatternString);
+ }
+
+ /** Validate masked callback syntax with the literal's lexical charname context. */
+ public static void validateLiteralSyntax(
+ String patternString, String modifiers,
+ RuntimeScalar namedCharacterTranslator,
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode,
+ String diagnosticPatternString) {
+ try {
+ validateLiteralSyntax(patternString, modifiers,
+ namedCharacterTranslator, namedCharacterSourceMode);
+ } catch (PerlCompilerException exception) {
+ throw new PerlCompilerException(remapLiteralDiagnosticSource(
+ exception.getMessage(), patternString, diagnosticPatternString));
+ }
+ }
+
+ private static String remapLiteralDiagnosticSource(
+ String message, String compiledPattern, String diagnosticPattern) {
+ if (message == null || compiledPattern == null || diagnosticPattern == null
+ || compiledPattern.length() != diagnosticPattern.length()
+ || compiledPattern.equals(diagnosticPattern)) {
+ return message;
+ }
+ String prefix = " in regex; marked by <-- HERE in m/";
+ String marker = " <-- HERE ";
+ int sourceStart = message.indexOf(prefix);
+ if (sourceStart < 0) return message;
+ sourceStart += prefix.length();
+ int markerStart = message.indexOf(marker, sourceStart);
+ if (markerStart < sourceStart) return message;
+ int offset = markerStart - sourceStart;
+ if (offset > diagnosticPattern.length()) return message;
+ int sourceEnd = markerStart + marker.length()
+ + compiledPattern.length() - offset;
+ if (sourceEnd > message.length()) return message;
+ return message.substring(0, sourceStart)
+ + diagnosticPattern.substring(0, offset)
+ + marker
+ + diagnosticPattern.substring(offset)
+ + message.substring(sourceEnd);
+ }
+
private static synchronized RuntimeRegex compileSynchronized(
String patternString, String modifiers, int lexicalDebugMode,
int trustedCalloutCount, boolean literalSyntaxValidation,
boolean patternByteBacked, boolean lexicalReStrict,
RuntimeScalar namedCharacterTranslator) {
+ return compileSynchronized(patternString, modifiers, lexicalDebugMode,
+ trustedCalloutCount, literalSyntaxValidation, patternByteBacked,
+ lexicalReStrict, namedCharacterTranslator, null, null, false, null);
+ }
+
+ private static synchronized RuntimeRegex compileSynchronized(
+ String patternString, String modifiers, int lexicalDebugMode,
+ int trustedCalloutCount, boolean literalSyntaxValidation,
+ boolean patternByteBacked, boolean lexicalReStrict,
+ RuntimeScalar namedCharacterTranslator, String sourceDiagnosticPattern) {
+ return compileSynchronized(patternString, modifiers, lexicalDebugMode,
+ trustedCalloutCount, literalSyntaxValidation, patternByteBacked,
+ lexicalReStrict, namedCharacterTranslator, null, null, false,
+ sourceDiagnosticPattern);
+ }
+
+ private static synchronized RuntimeRegex compileSynchronized(
+ String patternString, String modifiers, int lexicalDebugMode,
+ int trustedCalloutCount, boolean literalSyntaxValidation,
+ boolean patternByteBacked, boolean lexicalReStrict,
+ RuntimeScalar namedCharacterTranslator,
+ JoniRegexPattern.NamedCharacterCache existingNamedCharacterCache,
+ NamedCharacterExpansion.SourceMode existingNamedCharacterSourceMode,
+ boolean forceRecompile, String sourceDiagnosticPattern) {
// Debug logging
if (DEBUG_REGEX) {
System.err.println("RuntimeRegex.compile: pattern=" + patternString + " modifiers=" + modifiers);
System.err.println(" caller stack: " + Thread.currentThread().getStackTrace()[2]);
}
- String originalPatternString = patternString;
- String compilePatternString = patternString;
- boolean hasDynamicPattern = compilePatternString != null
- && compilePatternString.contains(RegexMarkers.RECURSIVE_PATTERN);
- boolean warnOnUnimplemented = "warn".equals(
- GlobalVariable.getGlobalHash("main::ENV")
- .get("JPERL_UNIMPLEMENTED").toString());
- boolean hasDeferredDynamicPattern = hasDynamicPattern && !warnOnUnimplemented;
- boolean hasWarnDynamicFallback = hasDynamicPattern && warnOnUnimplemented;
- if (hasDeferredDynamicPattern || hasWarnDynamicFallback) {
- // Perl permits qr// construction before the dynamic callback is needed.
- // Default mode keeps a never-matching placeholder and reports the hard
- // error on use. Warn mode retains the historical compatibility fallback
- // that ignores the unsupported dynamic component after warning.
- compilePatternString = compilePatternString.replace(
- RegexMarkers.RECURSIVE_PATTERN,
- hasWarnDynamicFallback ? "(?:)" : "(?!)");
+ boolean literalUselessCaseEscape = patternString != null
+ && patternString.contains(RegexMarkers.LITERAL_USELESS_CASE_ESCAPE);
+ String literalFrontendDiagnostic =
+ RegexMarkers.firstLiteralDiagnostic(patternString);
+ String originalPatternString = RegexMarkers.stripLiteralDiagnostics(patternString);
+ if (literalUselessCaseEscape) {
+ originalPatternString = originalPatternString.replace(
+ RegexMarkers.LITERAL_USELESS_CASE_ESCAPE, "");
}
+ String compilePatternString = originalPatternString;
+ String displayDiagnosticPattern = sourceDiagnosticPattern == null
+ ? originalPatternString
+ : RegexMarkers.stripLiteralDiagnostics(sourceDiagnosticPattern);
List quoteMetaWarningsOnUse = new ArrayList<>();
if (compilePatternString != null && compilePatternString.contains("\\Q")) {
// Interpolated-pattern warnings are lexical diagnostics for each
@@ -621,9 +689,7 @@ private static synchronized RuntimeRegex compileSynchronized(
quoteMetaWarningsOnUse = RegexQuoteMeta.getWarningsOnUse();
}
- // Dynamic patterns compile differently in normal and warn modes. Do not
- // let a placeholder cached in one mode leak into the other. Lexical
- // regex debugging also changes the compiled representation.
+ // Lexical regex debugging changes the compiled representation.
// A lexical charname translator may return a different expansion for
// each compilation of the same spelling. Literal syntax validation is
// the first leg of one logical compilation: refresh the raw-source
@@ -634,19 +700,20 @@ private static synchronized RuntimeRegex compileSynchronized(
&& patternString != null
&& patternString.contains("\\N{");
boolean effectivePatternByteBacked = patternByteBacked
- && !hasUnicodePromotingPatternSyntax(patternString);
+ && !hasUnicodePromotingPatternSyntax(originalPatternString);
String cacheKey = patternString + "/" + modifiers
+ "#debug=" + lexicalDebugMode
+ "#callouts=" + trustedCalloutCount
- + "#backend=" + RegexBackendPolicy.cacheTag()
+ "#bytepattern=" + effectivePatternByteBacked
+ "#strict=" + lexicalReStrict
+ + (requiresRuntimeUnicodePropertyResolution(patternString)
+ ? "#propertyPackage="
+ + UnicodeResolver.activeUserPropertyPackage() : "")
+ (namedCharacterTranslator == null ? "" : "#charnames="
- + namedCharacterTranslator.toString())
- + (hasDynamicPattern ? (warnOnUnimplemented ? "\0warn" : "\0defer") : "");
+ + namedCharacterTranslator.toString());
// Check if the regex is already cached
- RuntimeRegex regex = refreshLexicalNamedCharacter
+ RuntimeRegex regex = refreshLexicalNamedCharacter || forceRecompile
? null : state().compiledRegexCache.get(cacheKey);
if (regex == null) {
if (DEBUG_REGEX) {
@@ -654,23 +721,34 @@ private static synchronized RuntimeRegex compileSynchronized(
}
regex = new RuntimeRegex();
regex.patternByteBacked = effectivePatternByteBacked;
- regex.namedCharacterCache =
- new JoniRegexPattern.NamedCharacterCache(namedCharacterTranslator);
+ regex.namedCharacterTranslator = copyScalarOrNull(namedCharacterTranslator);
+ regex.namedCharacterCache = existingNamedCharacterCache == null
+ ? new JoniRegexPattern.NamedCharacterCache(regex.namedCharacterTranslator)
+ : existingNamedCharacterCache;
+ regex.trustedCalloutCount = trustedCalloutCount;
+ regex.compiledRegexCacheKey = cacheKey;
+ regex.userPropertyPackage = UnicodeResolver.activeUserPropertyPackage();
regex.lexicalDebugMode = lexicalDebugMode;
regex.lexicalReStrict = lexicalReStrict;
// Note: flags /e /ee are processed at parse time, in parseRegexReplace()
- validateModifiers(modifiers);
-
regex.regexFlags = fromModifiers(modifiers, compilePatternString);
regex.useGAssertion = regex.regexFlags.useGAssertion();
- regex.patternFlags = regex.regexFlags.toPatternFlags();
+ NamedCharacterExpansion.SourceMode namedCharacterSourceMode =
+ existingNamedCharacterSourceMode != null
+ ? existingNamedCharacterSourceMode
+ : patternByteBacked
+ ? NamedCharacterExpansion.SourceMode.BYTE
+ : NamedCharacterExpansion.SourceMode.UNICODE;
+ regex.namedCharacterSourceMode = namedCharacterSourceMode;
LeftBraceIssue leftBraceIssue = unescapedLeftBraceIssue(
originalPatternString);
String sourcePolicyWarning = null;
- String constructionPolicyWarning = null;
+ String constructionPolicyWarning = literalUselessCaseEscape
+ ? "Useless use of \\E"
+ : null;
if (leftBraceIssue != null) {
String message = leftBraceIssue.alwaysFatal || lexicalReStrict
? "Unescaped left brace in regex is illegal here"
@@ -682,140 +760,76 @@ private static synchronized RuntimeRegex compileSynchronized(
}
sourcePolicyWarning = diagnostic;
}
- NonHexIssue nonHexIssue = nonHexEscapeIssue(originalPatternString);
- if (nonHexIssue != null) {
- if (lexicalReStrict) {
- throw new PerlCompilerException(RegexDiagnosticFormatter.markedPerl(
- originalPatternString, nonHexIssue.offset + 1,
- "Non-hex character"));
- }
- if (!nonHexIssue.braced) {
- char invalid = originalPatternString.charAt(nonHexIssue.offset);
- char digit = originalPatternString.charAt(nonHexIssue.offset - 1);
- String message = "Non-hex character '" + invalid
- + "' terminates \\x early. Resolved as \"\\x0"
- + digit + invalid + "\"";
- constructionPolicyWarning = RegexDiagnosticFormatter.markedPerl(
- originalPatternString, nonHexIssue.offset, message);
- }
- }
-
- // Always compute Unicode flags - we need the Unicode variant for when
- // the input string contains non-ASCII characters (auto-Unicode detection)
- // Only skip Unicode variant if /a flag is explicitly used
- if (!regex.regexFlags.isAscii()) {
- regex.patternFlagsUnicode = regex.patternFlags | Pattern.UNICODE_CHARACTER_CLASS;
- } else {
- regex.patternFlagsUnicode = regex.patternFlags;
- }
- String javaPattern = null;
try {
- boolean usesRecursiveBackend = org.perlonjava.runtime.NamedCharacterExpansion
- .usesCustomTranslator(namedCharacterTranslator)
- && JoniRegexPattern.containsNamedCharacterEscape(compilePatternString)
- || RegexBackendPolicy.useJoni(
- compilePatternString, regex.regexFlags);
- if (usesRecursiveBackend
- && compilePatternString.contains("(?&")
- && (compilePatternString.contains("(?<=")
- || compilePatternString.contains("(?(quoteMetaWarningsOnUse);
if (sourcePolicyWarning != null) {
regex.inlineModifierWarnings.add(sourcePolicyWarning);
- regex.warningsOnUse.add(sourcePolicyWarning);
}
if (constructionPolicyWarning != null) {
regex.inlineModifierWarnings.add(constructionPolicyWarning);
}
- if (hasDeferredDynamicPattern) {
- regex.warningsOnUse.add(DYNAMIC_PATTERN_ERROR);
- } else if (hasWarnDynamicFallback) {
- regex.warningsOnUse.add(
- "(??{...}) recursive/dynamic regex patterns not implemented\n");
- }
- if (usesRecursiveBackend) {
- regex.recursivePattern = new JoniRegexPattern(compilePatternString,
+ regex.recursivePattern = new JoniRegexPattern(compilePatternString,
regex.regexFlags, trustedCalloutCount,
!regex.regexFlags.isUnicode(), false, false,
- regex.namedCharacterCache);
+ regex.namedCharacterCache, namedCharacterSourceMode,
+ lexicalReStrict);
regex.recursivePatternUnicode = regex.regexFlags.isAscii()
? regex.recursivePattern
: new JoniRegexPattern(compilePatternString,
regex.regexFlags, trustedCalloutCount, false,
- false, false, regex.namedCharacterCache);
- if (effectivePatternByteBacked && regex.regexFlags.isCaseInsensitive()
- && !regex.regexFlags.isUnicode() && !regex.regexFlags.isAscii()) {
- regex.recursivePatternBytes = new JoniRegexPattern(
- compilePatternString, regex.regexFlags, trustedCalloutCount,
- true, true, true, regex.namedCharacterCache);
- }
+ false, false, regex.namedCharacterCache,
+ namedCharacterSourceMode, lexicalReStrict);
regex.deferredUserDefinedUnicodeProperties =
regex.recursivePattern.hasDeferredUserDefinedUnicodeProperty()
|| regex.recursivePatternUnicode
.hasDeferredUserDefinedUnicodeProperty();
- regex.inlineModifierWarnings.addAll(normalizeNonHexWarningCase(
- regex.recursivePattern.compileWarnings(),
- originalPatternString, nonHexIssue));
+ if (effectivePatternByteBacked && !regex.regexFlags.isUnicode()
+ && !regex.regexFlags.isAscii()
+ && !regex.deferredUserDefinedUnicodeProperties
+ && !regex.recursivePattern.hasUserDefinedUnicodeProperty()
+ && !regex.recursivePatternUnicode
+ .hasUserDefinedUnicodeProperty()) {
+ try {
+ regex.recursivePatternBytes = new JoniRegexPattern(
+ compilePatternString, regex.regexFlags,
+ trustedCalloutCount, true, true, true,
+ regex.namedCharacterCache,
+ namedCharacterSourceMode, lexicalReStrict);
+ } catch (RuntimeException byteVariantFailure) {
+ // The Unicode variant above is authoritative. Some
+ // otherwise valid property/set programs cannot be
+ // represented by the ISO-8859-1 optimization; use
+ // the Unicode matcher instead of rejecting them.
+ regex.recursivePatternBytes = null;
+ }
+ }
+ regex.inlineModifierWarnings.addAll(
+ regex.recursivePattern.compileWarnings());
+ if (lexicalReStrict) {
+ for (String warning : regex.inlineModifierWarnings) {
+ if (warning.startsWith("False [] range")) {
+ throw new PerlCompilerException(warning);
+ }
+ if (warning.startsWith("Unrecognized escape ")
+ && warning.contains(
+ " in character class passed through")) {
+ throw new PerlCompilerException(warning.replaceFirst(
+ " in character class passed through",
+ " in character class"));
+ }
+ }
+ }
regex.warningsOnUse.addAll(regex.inlineModifierWarnings);
- regex.hasPreservesMatch = regex.regexFlags.preservesMatch()
- || RegexFlags.hasInlinePreserveModifier(compilePatternString);
- } else {
- regex.deferredUserDefinedUnicodeProperties = RegexPreprocessor.hadDeferredUnicodePropertyEncountered();
- regex.hasPreservesMatch = regex.regexFlags.preservesMatch()
- || RegexFlags.hasInlinePreserveModifier(compilePatternString)
- || RegexPreprocessor.hadInlinePFlag();
- regex.warningsOnUse.addAll(RegexPreprocessor.getWarningsOnUse());
- }
-
+ regex.warningsOnUse.addAll(
+ unicodePropertyWildcardWarnings(originalPatternString));
+ regex.hasPreservesMatch = regex.regexFlags.preservesMatch()
+ || RegexFlags.hasInlinePreserveModifier(compilePatternString);
regex.patternString = originalPatternString;
- regex.javaPatternString = javaPattern;
- regex.requiredLiteral = usesRecursiveBackend
- ? null
- : findTopLevelRequiredLiteral(compilePatternString, regex.regexFlags);
-
- // Compile the regex pattern for byte strings (ASCII-only \w, \d, \s)
- regex.pattern = Pattern.compile(javaPattern, regex.patternFlags);
- regex.patternNoInternalMarkers = compileWithoutInternalMarkerAlternations(javaPattern, regex.patternFlags);
-
- // Compile the Unicode variant for Unicode strings
- // Only compile separately if the flags differ (saves memory when /a or /u is used)
- if (!usesRecursiveBackend && regex.patternFlagsUnicode != regex.patternFlags) {
- String javaPatternUnicode = preProcessRegex(compilePatternString, regex.regexFlags.with("u", "a"), false);
- // Fix POSIX [:punct:] for Unicode mode: Java's UNICODE_CHARACTER_CLASS flag
- // changes \p{Punct} from ASCII punct+symbols to only \p{P} (Unicode Punctuation).
- // Perl's [:punct:] should match both Punctuation and Symbols in Unicode mode.
- javaPatternUnicode = javaPatternUnicode.replace("\\p{Punct}", "[\\p{P}\\p{S}]")
- .replace("\\P{Punct}", "[^\\p{P}\\p{S}]");
- regex.patternUnicode = Pattern.compile(javaPatternUnicode, regex.patternFlagsUnicode);
- regex.patternUnicodeNoInternalMarkers =
- compileWithoutInternalMarkerAlternations(javaPatternUnicode, regex.patternFlagsUnicode);
- } else {
- regex.patternUnicode = regex.pattern;
- regex.patternUnicodeNoInternalMarkers = regex.patternNoInternalMarkers;
- }
// Check if pattern has code block captures for $^R optimization
// Code blocks are encoded as named captures like (?)
- Map namedGroups = regex.pattern.namedGroups();
+ Map namedGroups = regex.recursivePattern.namedGroups();
if (namedGroups != null) {
for (String groupName : namedGroups.keySet()) {
if (CaptureNameEncoder.isCodeBlockCapture(groupName)) {
@@ -825,37 +839,47 @@ private static synchronized RuntimeRegex compileSynchronized(
}
}
- // Compile a "notempty" variant for /g patterns. With an opaque
- // region starting at the retry position, the trailing lookbehind
- // succeeds only when the selected alternative consumed input.
- if (!usesRecursiveBackend && regex.regexFlags.isGlobalMatch() && javaPattern != null) {
- try {
- String notemptyJava = "(?:" + javaPattern + ")(?<=[\\s\\S])";
- regex.notemptyPattern = Pattern.compile(notemptyJava, regex.patternFlags);
- if (regex.patternFlagsUnicode != regex.patternFlags) {
- String javaPatternUnicode = preProcessRegex(compilePatternString, regex.regexFlags.with("u", "a"), false);
- String notemptyUnicode = javaPatternUnicode
- .replace("\\p{Punct}", "[\\p{P}\\p{S}]")
- .replace("\\P{Punct}", "[^\\p{P}\\p{S}]");
- notemptyUnicode = "(?:" + notemptyUnicode + ")(?<=[\\s\\S])";
- regex.notemptyPatternUnicode = Pattern.compile(notemptyUnicode, regex.patternFlagsUnicode);
- } else {
- regex.notemptyPatternUnicode = regex.notemptyPattern;
- }
- } catch (Exception ignore) {
- // If notempty compilation fails, fall back to bumpalong
- regex.notemptyPattern = null;
- regex.notemptyPatternUnicode = null;
+ } catch (Exception e) {
+ if (literalFrontendDiagnostic != null) {
+ String backendDiagnostic = e.getMessage();
+ if (backendDiagnostic != null
+ && backendDiagnostic.startsWith("Unknown charname '")
+ && literalFrontendDiagnostic.startsWith("Unknown charname '")) {
+ // Literal source validation and native compilation are
+ // two legs of one compile. Perl reports the lexical
+ // source diagnostic once, not again as a backend error.
+ throw new PerlCompilerException(literalFrontendDiagnostic + "\n");
}
+ if (backendDiagnostic != null
+ && backendDiagnostic.startsWith("Unknown charname '")
+ && !backendDiagnostic.endsWith(" in regex")) {
+ backendDiagnostic += " in regex";
+ }
+ throw new PerlCompilerException(literalFrontendDiagnostic
+ + "\n" + backendDiagnostic);
}
- } catch (Exception e) {
if (e instanceof PatternSyntaxException syntaxError
&& "Illegal character range".equals(syntaxError.getDescription())) {
throw new PerlCompilerException("Invalid [] range");
}
if ("invalid backref number/name".equals(e.getMessage())
|| "invalid backref number".equals(e.getMessage())) {
- throw new PerlCompilerException("Reference to nonexistent group");
+ int offset = nonexistentNumericBackreferenceEnd(originalPatternString);
+ String message = "Reference to nonexistent group";
+ throw new PerlCompilerException(offset >= 0
+ ? RegexDiagnosticFormatter.markedPerl(
+ originalPatternString, offset, message)
+ : message + " in regex");
+ }
+ if (e instanceof IllegalArgumentException
+ && e.getMessage() != null
+ && e.getMessage().startsWith("Unknown charname '")
+ && !e.getMessage().endsWith(" in regex")) {
+ throw new PerlCompilerException(e.getMessage() + " in regex");
+ }
+ if (e instanceof IllegalArgumentException
+ && isNamedCharacterDiagnostic(e.getMessage())) {
+ throw new PerlCompilerException(e.getMessage());
}
String invalidProperty = invalidUnicodePropertyName(e.getMessage());
if (invalidProperty != null) {
@@ -863,8 +887,19 @@ private static synchronized RuntimeRegex compileSynchronized(
throw new PerlCompilerException(
"Illegal user-defined property name \"" + invalidProperty + "\"");
}
- throw new PerlCompilerException(
- "Can't find Unicode property definition \"" + invalidProperty + "\"");
+ String diagnostic = failedUnicodePropertyDiagnostic(
+ displayDiagnosticPattern, invalidProperty);
+ throw new PerlCompilerException(diagnostic != null
+ ? diagnostic
+ : "Can't find Unicode property definition \""
+ + invalidProperty.trim() + "\"");
+ }
+ if (isUnicodePropertyResolutionFailure(e.getMessage())) {
+ String diagnostic = failedUnicodePropertyDiagnostic(
+ displayDiagnosticPattern, null);
+ if (diagnostic != null) {
+ throw new PerlCompilerException(diagnostic);
+ }
}
// Joni reports malformed patterns with SyntaxException (including its
// ValueException subclass). These are real compile errors, not missing
@@ -873,7 +908,16 @@ private static synchronized RuntimeRegex compileSynchronized(
&& containsExecutableSource(originalPatternString,
regex.regexFlags.isExtended());
if (e instanceof SyntaxException && !validatesExecutableSource) {
- String message = e.getMessage();
+ String message = ((SyntaxException) e).getDiagnosticMessage();
+ if (message != null && message.startsWith("end pattern")
+ && originalPatternString != null
+ && originalPatternString.endsWith("\\")) {
+ throw new PerlCompilerException("Trailing \\ in regex m/"
+ + originalPatternString + "/");
+ }
+ if ("unmatched close parenthesis".equals(message)) {
+ message = "Unmatched )";
+ }
if ("Empty \\N{}".equals(message)) {
throw new PerlCompilerException("Unknown charname ''");
}
@@ -886,11 +930,20 @@ && containsExecutableSource(originalPatternString,
}
throw new PerlCompilerException(diagnostic);
}
+ if ("Lookbehind longer than 255 not implemented in regex".equals(message)) {
+ throw new PerlCompilerException(message + " m/"
+ + originalPatternString + "/");
+ }
int bytePosition = ((SyntaxException) e).getPatternPosition();
if (bytePosition != SyntaxException.UNKNOWN_PATTERN_POSITION) {
int characterPosition = utf8ByteOffsetToCharacterOffset(
- compilePatternString, bytePosition);
- if ("undefined group option".equals(message)
+ displayDiagnosticPattern, bytePosition);
+ int unmatched = unmatchedOpeningParenthesis(originalPatternString);
+ if ("Reference to nonexistent named group".equals(message)
+ && unmatched >= 0) {
+ message = "Unmatched (";
+ characterPosition = unmatched + 1;
+ } else if ("undefined group option".equals(message)
&& originalPatternString != null
&& characterPosition >= 3
&& originalPatternString.regionMatches(
@@ -899,15 +952,13 @@ && containsExecutableSource(originalPatternString,
} else if ("too big number for repeat range".equals(message)) {
message = "Quantifier in {,} bigger than 2147483646";
} else if ("end pattern with unmatched parenthesis".equals(message)) {
- int unmatched = ordinaryUnmatchedOpeningParenthesis(
- originalPatternString);
if (unmatched >= 0) {
message = "Unmatched (";
characterPosition = unmatched + 1;
}
}
throw new PerlCompilerException(RegexDiagnosticFormatter.markedPerl(
- originalPatternString, characterPosition, message));
+ displayDiagnosticPattern, characterPosition, message));
}
throw new PerlCompilerException(message);
}
@@ -934,16 +985,15 @@ && containsExecutableSource(originalPatternString,
if (GlobalVariable.getGlobalHash("main::ENV").get("JPERL_UNIMPLEMENTED").toString().equals("warn")) {
String base = unimplEx.getMessage();
// Include original and preprocessed patterns to aid debugging
- String patternInfo = " [pattern='" + (originalPatternString == null ? "" : originalPatternString) + "'" +
- (javaPattern != null ? ", java='" + javaPattern + "'" : "") + "]";
+ String patternInfo = " [pattern='" + (originalPatternString == null ? "" : originalPatternString) + "']";
String errorMessage = base + patternInfo;
// Ensure error message ends with newline to prevent running into test output
if (!errorMessage.endsWith("\n")) {
errorMessage += "\n";
}
WarnDie.warn(new RuntimeScalar(errorMessage), new RuntimeScalar());
- regex.pattern = Pattern.compile(Character.toString(0) + "ERROR" + Character.toString(0), Pattern.DOTALL);
- regex.patternUnicode = regex.pattern; // Error pattern - same for both
+ regex.recursivePattern = new JoniRegexPattern("(?!)", regex.regexFlags);
+ regex.recursivePatternUnicode = regex.recursivePattern;
// Ensure patternString is set so downstream code doesn't NPE
if (regex.patternString == null) {
regex.patternString = originalPatternString != null ? originalPatternString : "";
@@ -953,6 +1003,13 @@ && containsExecutableSource(originalPatternString,
}
}
+ // Parse the complete alphanumeric modifier suffix before reporting
+ // it. Perl can diagnose a malformed pattern and an invalid numeric
+ // modifier from the same quote-like construct; compiling the
+ // pattern first preserves the more specific native syntax error.
+ // A valid pattern still reaches the ordinary modifier diagnostic.
+ validateModifiers(modifiers);
+
// Cache the result if the cache is not full
if (state().compiledRegexCache.size() < MAX_REGEX_CACHE_SIZE
|| state().compiledRegexCache.containsKey(cacheKey)) {
@@ -986,6 +1043,50 @@ private static String invalidUnicodePropertyName(String message) {
return end < 0 ? null : message.substring(start, end);
}
+ private static boolean isUnicodePropertyResolutionFailure(String message) {
+ if (message == null) return false;
+ return message.startsWith("Invalid or unsupported Unicode property:")
+ || message.startsWith("Unsupported General_Category value:")
+ || message.startsWith("Unsupported Numeric_Value value:")
+ || message.startsWith("Unsupported obsolete Unicode property:");
+ }
+
+ /** Preserve Perl's original spelling and point immediately after its closing brace. */
+ private static String failedUnicodePropertyDiagnostic(
+ String pattern, String engineProperty) {
+ if (pattern == null) return null;
+ Matcher property = Pattern.compile("\\\\[pP]\\{([^}]*)}").matcher(pattern);
+ while (property.find()) {
+ String original = property.group(1).trim();
+ if (engineProperty == null
+ || original.equals(engineProperty.trim())
+ || looseUnicodePropertyDiagnosticName(original).equals(
+ looseUnicodePropertyDiagnosticName(engineProperty))) {
+ return RegexDiagnosticFormatter.markedPerl(
+ pattern, property.end(),
+ "Can't find Unicode property definition \"" + original + "\"");
+ }
+ }
+ return null;
+ }
+
+ private static String looseUnicodePropertyDiagnosticName(String property) {
+ return property == null ? "" : property.replaceAll("[\\s_-]+", "")
+ .toLowerCase(java.util.Locale.ROOT);
+ }
+
+ private static List unicodePropertyWildcardWarnings(String pattern) {
+ if (pattern == null || !pattern.contains("(?[")) return List.of();
+ Matcher property = Pattern.compile(
+ "\\\\[pP]\\{\\s*(?:name|na)\\s*(?:=|:(?!:))\\s*/[^}]*/\\s*}",
+ Pattern.CASE_INSENSITIVE).matcher(pattern);
+ if (!property.find()) return List.of();
+ return List.of(
+ "The Unicode property wildcards feature is experimental",
+ RegexDiagnosticFormatter.markedPerl(pattern, property.end(),
+ "Using just the single character results returned by \\p{} in (?[...])"));
+ }
+
private static int utf8ByteOffsetToCharacterOffset(String pattern, int byteOffset) {
if (pattern == null || byteOffset <= 0) return 0;
byte[] bytes = pattern.getBytes(StandardCharsets.UTF_8);
@@ -993,8 +1094,76 @@ private static int utf8ByteOffsetToCharacterOffset(String pattern, int byteOffse
return new String(bytes, 0, boundedOffset, StandardCharsets.UTF_8).length();
}
- /** Locate an unmatched ordinary group opener, excluding Perl's specialized {@code (?...} forms. */
- private static int ordinaryUnmatchedOpeningParenthesis(String pattern) {
+ /** Locate the end of the numeric escape responsible for an invalid-backref error. */
+ private static int nonexistentNumericBackreferenceEnd(String pattern) {
+ if (pattern == null) return -1;
+
+ int captureCount = 0;
+ boolean inClass = false;
+ for (int i = 0; i < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ if (c == '\\') {
+ i++;
+ continue;
+ }
+ if (c == '[') {
+ inClass = true;
+ continue;
+ }
+ if (c == ']' && inClass) {
+ inClass = false;
+ continue;
+ }
+ if (c != '(' || inClass) continue;
+ if (i + 1 >= pattern.length() || pattern.charAt(i + 1) != '?') {
+ captureCount++;
+ continue;
+ }
+ if (i + 2 >= pattern.length()) continue;
+ char kind = pattern.charAt(i + 2);
+ if (kind == '\''
+ || (kind == '<' && i + 3 < pattern.length()
+ && pattern.charAt(i + 3) != '='
+ && pattern.charAt(i + 3) != '!')
+ || (kind == 'P' && i + 3 < pattern.length()
+ && pattern.charAt(i + 3) == '<')) {
+ captureCount++;
+ }
+ }
+
+ inClass = false;
+ for (int i = 0; i + 1 < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ if (c == '[') {
+ inClass = true;
+ continue;
+ }
+ if (c == ']' && inClass) {
+ inClass = false;
+ continue;
+ }
+ if (c != '\\') continue;
+ char first = pattern.charAt(i + 1);
+ if (inClass || first < '1' || first > '9') {
+ i++;
+ continue;
+ }
+ int end = i + 2;
+ long number = first - '0';
+ while (end < pattern.length()
+ && pattern.charAt(end) >= '0' && pattern.charAt(end) <= '9') {
+ number = Math.min(Integer.MAX_VALUE,
+ number * 10 + pattern.charAt(end) - '0');
+ end++;
+ }
+ if (number > captureCount) return end;
+ i = end - 1;
+ }
+ return -1;
+ }
+
+ /** Locate the first unmatched group opener after otherwise balanced groups are removed. */
+ private static int unmatchedOpeningParenthesis(String pattern) {
if (pattern == null) return -1;
Deque openings = new ArrayDeque<>();
boolean escaped = false;
@@ -1024,13 +1193,7 @@ private static int ordinaryUnmatchedOpeningParenthesis(String pattern) {
openings.pop();
}
}
- while (!openings.isEmpty()) {
- int opening = openings.removeLast();
- if (opening + 1 >= pattern.length() || pattern.charAt(opening + 1) != '?') {
- return opening;
- }
- }
- return -1;
+ return openings.isEmpty() ? -1 : openings.getLast();
}
private static LeftBraceIssue unescapedLeftBraceIssue(String pattern) {
@@ -1137,93 +1300,6 @@ private static final class LeftBraceIssue {
}
}
- private static NonHexIssue nonHexEscapeIssue(String pattern) {
- if (pattern == null || pattern.contains("(?[")) return null;
- for (int i = 0; i + 2 < pattern.length(); i++) {
- if (pattern.charAt(i) != '\\' || pattern.charAt(i + 1) != 'x'
- || (i > 0 && pattern.charAt(i - 1) == '\\')) {
- continue;
- }
- int cursor = i + 2;
- if (pattern.charAt(cursor) != '{') {
- if (isHexDigit(pattern.charAt(cursor))
- && cursor + 1 < pattern.length()
- && Character.isLetterOrDigit(pattern.charAt(cursor + 1))
- && !isHexDigit(pattern.charAt(cursor + 1))) {
- return new NonHexIssue(cursor + 1, false);
- }
- continue;
- }
- int close = pattern.indexOf('}', cursor + 1);
- if (close < 0) continue;
- cursor++;
- while (cursor < close && pattern.charAt(cursor) == ' ') cursor++;
- int digits = cursor;
- while (cursor < close) {
- if (isHexDigit(pattern.charAt(cursor))) {
- cursor++;
- continue;
- }
- if (pattern.charAt(cursor) == '_' && cursor > digits
- && cursor + 1 < close
- && isHexDigit(pattern.charAt(cursor + 1))) {
- cursor++;
- continue;
- }
- break;
- }
- if (cursor == digits || cursor == close) {
- i = close;
- continue;
- }
- if (pattern.charAt(cursor) != ' ') return new NonHexIssue(cursor, true);
- int whitespace = cursor;
- while (cursor < close && pattern.charAt(cursor) == ' ') cursor++;
- if (cursor < close) return new NonHexIssue(whitespace, true);
- i = close;
- }
- return null;
- }
-
- private static final class NonHexIssue {
- final int offset;
- final boolean braced;
-
- NonHexIssue(int offset, boolean braced) {
- this.offset = offset;
- this.braced = braced;
- }
- }
-
- private static List normalizeNonHexWarningCase(
- List warnings, String pattern, NonHexIssue issue) {
- if (issue == null || !issue.braced || warnings.isEmpty()) return warnings;
- int opening = pattern.lastIndexOf('{', issue.offset);
- if (opening < 0) return warnings;
- String sourceDigits = pattern.substring(opening + 1, issue.offset).trim();
- if (sourceDigits.isEmpty()) return warnings;
- List normalized = new ArrayList<>(warnings.size());
- String prefix = "Resolved as \"\\x{";
- for (String warning : warnings) {
- int valueStart = warning.indexOf(prefix);
- int valueEnd = valueStart < 0 ? -1 : warning.indexOf('}', valueStart + prefix.length());
- if (valueEnd >= 0) {
- String resolved = warning.substring(valueStart + prefix.length(), valueEnd);
- if (resolved.equalsIgnoreCase(sourceDigits)) {
- warning = warning.substring(0, valueStart + prefix.length())
- + sourceDigits + warning.substring(valueEnd);
- }
- }
- normalized.add(warning);
- }
- return normalized;
- }
-
- private static boolean isHexDigit(char ch) {
- return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f'
- || ch >= 'A' && ch <= 'F';
- }
-
private static int debugMode(String modifiers) {
if (modifiers == null) return 0;
if (modifiers.indexOf(INTERNAL_DEBUGCOLOR_MARKER) >= 0) return 2;
@@ -1245,10 +1321,14 @@ private void emitCompileDebugTrace() {
if (lexicalDebugMode == 0) return;
registerDebugLifecycle();
String patternDescription = patternString == null ? "" : patternString;
+ String optimizerDescription = recursivePattern == null
+ ? "" : recursivePattern.optimizerDebugDescription();
debugWrite("Compiling REx \"" + patternDescription + "\"\n"
+ "Final program:\n"
- + " 1: JAVA_PATTERN <" + patternDescription + "> (2)\n"
- + " 2: END (0)\n");
+ + " 1: JONI_PATTERN <" + patternDescription + "> (2)\n"
+ + " 2: END (0)\n"
+ + (optimizerDescription.isEmpty()
+ ? "" : optimizerDescription + "\n"));
}
public void emitExecutionDebugTrace(String input) {
@@ -1300,44 +1380,53 @@ private static RuntimeRegex ensureCompiledForRuntime(RuntimeRegex regex) {
if (!regex.deferredUserDefinedUnicodeProperties) {
return regex;
}
-
- // Recompile once, now that runtime may have defined user properties.
- // To avoid infinite loops if recompilation still can't resolve, clear the flag first.
- regex.deferredUserDefinedUnicodeProperties = false;
-
- // Evict the old cached entry so compile() will actually recompile
- // instead of returning the stale regex with deferred placeholders.
- String cacheKey = regex.patternString + "/" + (regex.regexFlags == null ? "" : regex.regexFlags.toFlagString());
- // Deferred Unicode properties cannot contain executable callouts, so
- // this is the same zero-callout key used by compile(). Keep the suffix
- // in sync with compileSynchronized(): leaving the placeholder cached
- // makes a later qr/\\p{Property}/ reuse its match-any stand-in.
- state().compiledRegexCache.remove(cacheKey + "#debug=" + regex.lexicalDebugMode
- + "#callouts=0#backend=" + RegexBackendPolicy.cacheTag()
- + "#bytepattern=" + regex.patternByteBacked
- + "#strict=" + regex.lexicalReStrict);
+ RegexFlags originalFlags = regex.regexFlags;
// User property subs can execute arbitrary Perl and block. Resolve them
// before compile() takes its process-wide monitor; only simultaneous
// definitions of the same property coordinate in PerlThreadRegistry.
- UnicodeResolver.preloadDeferredUserDefinedProperties(regex.patternString,
- regex.regexFlags != null && regex.regexFlags.isCaseInsensitive());
- RuntimeRegex recompiled = compile(regex.patternString,
- regex.regexFlags == null ? "" : regex.regexFlags.toFlagString(),
- regex.lexicalDebugMode, 0, regex.patternByteBacked,
- regex.lexicalReStrict);
- regex.pattern = recompiled.pattern;
- regex.patternUnicode = recompiled.patternUnicode;
+ RuntimeRegex recompiled = UnicodeResolver.withUserPropertyPackage(
+ regex.userPropertyPackage, () -> {
+ UnicodeResolver.preloadDeferredUserDefinedProperties(
+ regex.patternString,
+ regex.regexFlags != null
+ && regex.regexFlags.isCaseInsensitive(),
+ regex.regexFlags != null && regex.regexFlags.isExtended());
+
+ // The placeholder was cached under its original lexical
+ // compilation context. Package and lexical charnames
+ // provenance must both be reused for recompilation.
+ if (regex.compiledRegexCacheKey != null) {
+ state().compiledRegexCache.remove(regex.compiledRegexCacheKey);
+ }
+ return compileSynchronized(regex.patternString,
+ regex.regexFlags == null
+ ? "" : regex.regexFlags.toFlagString(),
+ regex.lexicalDebugMode, regex.trustedCalloutCount, false,
+ regex.patternByteBacked, regex.lexicalReStrict,
+ regex.namedCharacterTranslator, regex.namedCharacterCache,
+ regex.namedCharacterSourceMode, true, null);
+ });
+ if (recompiled.deferredUserDefinedUnicodeProperties) {
+ throw new PerlCompilerException(
+ "Deferred user-defined Unicode property remained unresolved at runtime");
+ }
regex.recursivePattern = recompiled.recursivePattern;
regex.recursivePatternUnicode = recompiled.recursivePatternUnicode;
regex.recursivePatternBytes = recompiled.recursivePatternBytes;
- regex.patternNoInternalMarkers = recompiled.patternNoInternalMarkers;
- regex.patternUnicodeNoInternalMarkers = recompiled.patternUnicodeNoInternalMarkers;
- regex.patternFlags = recompiled.patternFlags;
- regex.regexFlags = recompiled.regexFlags;
- regex.useGAssertion = recompiled.useGAssertion;
+ regex.namedCharacterCache = recompiled.namedCharacterCache;
+ regex.namedCharacterSourceMode = recompiled.namedCharacterSourceMode;
+ regex.namedCharacterTranslator =
+ copyScalarOrNull(recompiled.namedCharacterTranslator);
+ regex.trustedCalloutCount = recompiled.trustedCalloutCount;
+ regex.compiledRegexCacheKey = recompiled.compiledRegexCacheKey;
+ regex.userPropertyPackage = recompiled.userPropertyPackage;
+ // Compilation-only flag spelling omits operation state such as /c,
+ // /g, /o, and m?PAT?. Those flags belong to the original regex object
+ // and must survive the native-pattern refresh unchanged.
+ regex.regexFlags = originalFlags;
+ regex.useGAssertion = originalFlags != null && originalFlags.useGAssertion();
regex.deferredUserDefinedUnicodeProperties = recompiled.deferredUserDefinedUnicodeProperties;
- regex.requiredLiteral = recompiled.requiredLiteral;
regex.warningsOnUse = new ArrayList<>(recompiled.warningsOnUse);
regex.inlineModifierWarnings = new ArrayList<>(recompiled.inlineModifierWarnings);
regex.lexicalDebugMode = recompiled.lexicalDebugMode;
@@ -1345,309 +1434,8 @@ private static RuntimeRegex ensureCompiledForRuntime(RuntimeRegex regex) {
return regex;
}
- private static String findTopLevelRequiredLiteral(String pattern, RegexFlags flags) {
- if (pattern == null || pattern.isEmpty() || flags == null || flags.isCaseInsensitive()
- || pattern.contains("(?i") || pattern.contains("(?[") || hasTopLevelAlternation(pattern)) {
- return null;
- }
-
- int depth = 0;
- boolean inCharClass = false;
- boolean escaped = false;
- for (int i = 0; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
-
- if (escaped) {
- if (depth == 0 && !inCharClass && isEscapedRequiredLiteral(ch)
- && !isOptionalQuantifiedAt(pattern, i + 1)) {
- return Character.toString(ch);
- }
- if (!isEscapedRequiredLiteral(ch)) {
- i = skipEscapePayload(pattern, i);
- }
- escaped = false;
- continue;
- }
-
- if (ch == '\\') {
- escaped = true;
- continue;
- }
-
- if (inCharClass) {
- if (ch == '[' && i + 1 < pattern.length() && isPosixBracketStart(pattern.charAt(i + 1))) {
- int posixEnd = findPosixBracketEnd(pattern, i);
- if (posixEnd >= 0) {
- i = posixEnd;
- }
- continue;
- }
- if (ch == ']') {
- inCharClass = false;
- }
- continue;
- }
-
- if (ch == '[') {
- inCharClass = true;
- continue;
- }
-
- if (ch == '(') {
- depth++;
- continue;
- }
-
- if (ch == ')' && depth > 0) {
- depth--;
- continue;
- }
-
- if (depth != 0) {
- continue;
- }
-
- if (ch == '{') {
- int quantifierEnd = findQuantifierEnd(pattern, i);
- if (quantifierEnd >= 0) {
- i = quantifierEnd;
- continue;
- }
- }
-
- if ((flags.isExtended() || flags.isExtendedWhitespace()) && Character.isWhitespace(ch)) {
- continue;
- }
- if ((flags.isExtended() || flags.isExtendedWhitespace()) && ch == '#') {
- while (i < pattern.length() && pattern.charAt(i) != '\n') {
- i++;
- }
- continue;
- }
-
- if (!isTopLevelLiteralMetaCharacter(ch) && !isOptionalQuantifiedAt(pattern, i + 1)) {
- return Character.toString(ch);
- }
- }
-
- return null;
- }
-
- private static boolean isPosixBracketStart(char ch) {
- return ch == ':' || ch == '=' || ch == '.';
- }
-
- private static int findPosixBracketEnd(String pattern, int bracketOffset) {
- if (bracketOffset + 1 >= pattern.length()) {
- return -1;
- }
- char delimiter = pattern.charAt(bracketOffset + 1);
- if (!isPosixBracketStart(delimiter)) {
- return -1;
- }
- for (int i = bracketOffset + 2; i + 1 < pattern.length(); i++) {
- if (pattern.charAt(i) == delimiter && pattern.charAt(i + 1) == ']') {
- return i + 1;
- }
- }
- return -1;
- }
-
- private static int findQuantifierEnd(String pattern, int offset) {
- int close = pattern.indexOf('}', offset + 1);
- if (close < 0) {
- return -1;
- }
-
- String body = pattern.substring(offset + 1, close).trim();
- if (body.isEmpty()) {
- return -1;
- }
-
- int comma = body.indexOf(',');
- if (comma < 0) {
- return isAsciiDigits(body) ? close : -1;
- }
-
- if (body.indexOf(',', comma + 1) >= 0) {
- return -1;
- }
-
- String min = body.substring(0, comma).trim();
- String max = body.substring(comma + 1).trim();
- if (!min.isEmpty() && !isAsciiDigits(min)) {
- return -1;
- }
- if (min.isEmpty() && max.isEmpty()) {
- return -1;
- }
- return max.isEmpty() || isAsciiDigits(max) ? close : -1;
- }
-
- private static boolean isAsciiDigits(String s) {
- if (s.isEmpty()) {
- return false;
- }
- for (int i = 0; i < s.length(); i++) {
- char ch = s.charAt(i);
- if (ch < '0' || ch > '9') {
- return false;
- }
- }
- return true;
- }
-
- private static boolean hasTopLevelAlternation(String pattern) {
- int depth = 0;
- boolean inCharClass = false;
- boolean escaped = false;
- for (int i = 0; i < pattern.length(); i++) {
- char ch = pattern.charAt(i);
- if (escaped) {
- escaped = false;
- continue;
- }
- if (ch == '\\') {
- escaped = true;
- continue;
- }
- if (inCharClass) {
- if (ch == '[' && i + 1 < pattern.length() && isPosixBracketStart(pattern.charAt(i + 1))) {
- int posixEnd = findPosixBracketEnd(pattern, i);
- if (posixEnd >= 0) {
- i = posixEnd;
- }
- continue;
- }
- if (ch == ']') {
- inCharClass = false;
- }
- continue;
- }
- if (ch == '[') {
- inCharClass = true;
- continue;
- }
- if (ch == '(') {
- depth++;
- continue;
- }
- if (ch == ')' && depth > 0) {
- depth--;
- continue;
- }
- if (ch == '|' && depth == 0) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isEscapedRequiredLiteral(char ch) {
- return switch (ch) {
- case 'A', 'B', 'C', 'D', 'G', 'H', 'K', 'N', 'P', 'R', 'S', 'V', 'W', 'X', 'Z',
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'k', 'n', 'o', 'p', 'r', 's', 't', 'v', 'w', 'x', 'z',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> false;
- default -> true;
- };
- }
-
- private static int skipEscapePayload(String pattern, int escapeNameOffset) {
- char ch = pattern.charAt(escapeNameOffset);
- if ((ch == 'p' || ch == 'P' || ch == 'x' || ch == 'o' || ch == 'N' || ch == 'g')
- && escapeNameOffset + 1 < pattern.length()
- && pattern.charAt(escapeNameOffset + 1) == '{') {
- int close = pattern.indexOf('}', escapeNameOffset + 2);
- return close >= 0 ? close : escapeNameOffset;
- }
- if ((ch == 'b' || ch == 'B')
- && escapeNameOffset + 1 < pattern.length()
- && pattern.charAt(escapeNameOffset + 1) == '{') {
- int close = pattern.indexOf('}', escapeNameOffset + 2);
- return close >= 0 ? close : escapeNameOffset;
- }
- if (ch == 'c' && escapeNameOffset + 1 < pattern.length()) {
- return escapeNameOffset + 1;
- }
- if (ch == 'x') {
- int end = skipHexDigits(pattern, escapeNameOffset + 1, 2);
- return end > escapeNameOffset + 1 ? end - 1 : escapeNameOffset;
- }
- if ((ch == 'p' || ch == 'P')
- && escapeNameOffset + 1 < pattern.length()
- && Character.isLetter(pattern.charAt(escapeNameOffset + 1))) {
- return escapeNameOffset + 1;
- }
- if (ch == 'g' && escapeNameOffset + 1 < pattern.length()) {
- int pos = escapeNameOffset + 1;
- if (pattern.charAt(pos) == '-') {
- pos++;
- }
- int end = skipAsciiDigits(pattern, pos);
- return end > pos ? end - 1 : escapeNameOffset;
- }
- if (ch >= '0' && ch <= '9') {
- int end = skipAsciiDigits(pattern, escapeNameOffset);
- return end > escapeNameOffset ? end - 1 : escapeNameOffset;
- }
- if (ch == 'k' && escapeNameOffset + 1 < pattern.length()) {
- char opener = pattern.charAt(escapeNameOffset + 1);
- char closer = opener == '<' ? '>' : (opener == '\'' ? '\'' : 0);
- if (closer != 0) {
- int close = pattern.indexOf(closer, escapeNameOffset + 2);
- return close >= 0 ? close : escapeNameOffset;
- }
- }
- return escapeNameOffset;
- }
-
- private static int skipAsciiDigits(String s, int offset) {
- int pos = offset;
- while (pos < s.length()) {
- char ch = s.charAt(pos);
- if (ch < '0' || ch > '9') {
- break;
- }
- pos++;
- }
- return pos;
- }
-
- private static int skipHexDigits(String s, int offset, int maxDigits) {
- int pos = offset;
- int digits = 0;
- while (pos < s.length() && digits < maxDigits) {
- if (Character.digit(s.charAt(pos), 16) < 0) {
- break;
- }
- pos++;
- digits++;
- }
- return pos;
- }
-
- private static boolean isTopLevelLiteralMetaCharacter(char ch) {
- return ch == '^' || ch == '$' || ch == '.' || ch == '*' || ch == '+'
- || ch == '?' || ch == '{' || ch == '}';
- }
-
- private static boolean isOptionalQuantifiedAt(String pattern, int offset) {
- if (offset >= pattern.length()) {
- return false;
- }
- char ch = pattern.charAt(offset);
- if (ch == '?' || ch == '*') {
- return true;
- }
- if (ch != '{') {
- return false;
- }
- int close = pattern.indexOf('}', offset + 1);
- if (close < 0) {
- return false;
- }
- String body = pattern.substring(offset + 1, close).trim();
- return body.equals("0") || body.startsWith("0,") || body.startsWith(",");
+ private static RuntimeScalar copyScalarOrNull(RuntimeScalar value) {
+ return value == null ? null : new RuntimeScalar(value);
}
/**
@@ -1717,6 +1505,22 @@ public static RuntimeScalar applyUnicodeStringsFeatureToModifiers(RuntimeScalar
}
public static RuntimeScalar getQuotedRegex(RuntimeScalar patternString, RuntimeScalar modifiers) {
+ NamedCharacterExpansionMap metadata =
+ patternString instanceof RuntimeRegexLiteralScalar literal
+ ? literal.namedCharacterExpansions() : null;
+ return getQuotedRegex(patternString, modifiers, metadata);
+ }
+
+ /** Attach CV-owned immutable charname results without changing pattern text. */
+ public static RuntimeScalar attachNamedCharacterExpansions(
+ RuntimeScalar patternString, NamedCharacterExpansionMap metadata) {
+ return metadata == null ? patternString
+ : new RuntimeRegexLiteralScalar(patternString, metadata);
+ }
+
+ public static RuntimeScalar getQuotedRegex(
+ RuntimeScalar patternString, RuntimeScalar modifiers,
+ NamedCharacterExpansionMap preResolvedNamedCharacters) {
String rawModifierStr = modifiers.toString();
int callSiteDebugMode = debugMode(rawModifierStr);
String modifierStr = stripInternalMarkers(rawModifierStr);
@@ -1748,7 +1552,9 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
if (patternString.value instanceof RuntimeRegexTemplate template) {
RuntimeRegex regex = compile(template.pattern(), rawModifierStr, callSiteDebugMode,
- template.callbacks().size(), template.byteBackedPattern()).cloneTracked();
+ template.callbacks().size(), template.byteBackedPattern(),
+ reStrictMode(rawModifierStr), RuntimeRegexTemplate.displayPattern(
+ template.pattern(), template.callbacks())).cloneTracked();
regex.setExecutableCallbacks(template.callbacks());
return new RuntimeScalar(regex).propagateTaint(patternString);
}
@@ -1764,14 +1570,17 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
// Create a new regex with merged flags
RuntimeRegex regex = new RuntimeRegex();
- regex.pattern = originalRegex.pattern;
- regex.patternUnicode = originalRegex.patternUnicode;
regex.recursivePattern = originalRegex.recursivePattern;
regex.recursivePatternUnicode = originalRegex.recursivePatternUnicode;
regex.recursivePatternBytes = originalRegex.recursivePatternBytes;
+ regex.namedCharacterCache = originalRegex.namedCharacterCache;
+ regex.namedCharacterSourceMode = originalRegex.namedCharacterSourceMode;
+ regex.namedCharacterTranslator =
+ copyScalarOrNull(originalRegex.namedCharacterTranslator);
+ regex.trustedCalloutCount = originalRegex.trustedCalloutCount;
+ regex.compiledRegexCacheKey = originalRegex.compiledRegexCacheKey;
+ regex.userPropertyPackage = originalRegex.userPropertyPackage;
regex.setExecutableCallbacks(originalRegex.executableCallbacks);
- regex.patternNoInternalMarkers = originalRegex.patternNoInternalMarkers;
- regex.patternUnicodeNoInternalMarkers = originalRegex.patternUnicodeNoInternalMarkers;
regex.patternString = originalRegex.patternString;
regex.patternByteBacked = originalRegex.patternByteBacked;
regex.deferredUserDefinedUnicodeProperties =
@@ -1786,7 +1595,6 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
regex.regexFlags = mergeRegexFlags(originalRegex.regexFlags, modifierStr, originalRegex.patternString);
regex.hasPreservesMatch = regex.hasPreservesMatch || regex.regexFlags.preservesMatch();
regex.useGAssertion = regex.regexFlags.useGAssertion();
- regex.patternFlags = regex.regexFlags.toPatternFlags();
regex.refCount = 0; // Track for proper weak ref handling
return new RuntimeScalar(regex).propagateTaint(patternString);
@@ -1809,14 +1617,17 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
// Create a new regex with merged flags
RuntimeRegex regex = new RuntimeRegex();
- regex.pattern = originalRegex.pattern;
- regex.patternUnicode = originalRegex.patternUnicode;
regex.recursivePattern = originalRegex.recursivePattern;
regex.recursivePatternUnicode = originalRegex.recursivePatternUnicode;
regex.recursivePatternBytes = originalRegex.recursivePatternBytes;
+ regex.namedCharacterCache = originalRegex.namedCharacterCache;
+ regex.namedCharacterSourceMode = originalRegex.namedCharacterSourceMode;
+ regex.namedCharacterTranslator =
+ copyScalarOrNull(originalRegex.namedCharacterTranslator);
+ regex.trustedCalloutCount = originalRegex.trustedCalloutCount;
+ regex.compiledRegexCacheKey = originalRegex.compiledRegexCacheKey;
+ regex.userPropertyPackage = originalRegex.userPropertyPackage;
regex.setExecutableCallbacks(originalRegex.executableCallbacks);
- regex.patternNoInternalMarkers = originalRegex.patternNoInternalMarkers;
- regex.patternUnicodeNoInternalMarkers = originalRegex.patternUnicodeNoInternalMarkers;
regex.patternString = originalRegex.patternString;
regex.patternByteBacked = originalRegex.patternByteBacked;
regex.deferredUserDefinedUnicodeProperties =
@@ -1831,7 +1642,6 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
regex.regexFlags = mergeRegexFlags(originalRegex.regexFlags, modifierStr, originalRegex.patternString);
regex.hasPreservesMatch = regex.hasPreservesMatch || regex.regexFlags.preservesMatch();
regex.useGAssertion = regex.regexFlags.useGAssertion();
- regex.patternFlags = regex.regexFlags.toPatternFlags();
regex.refCount = 0; // Track for proper weak ref handling
return new RuntimeScalar(regex).propagateTaint(patternString, overloadedResult);
@@ -1854,7 +1664,9 @@ && containsExecutableSource(patternString.toString(), modifierStr.indexOf('x') >
// so the cached RuntimeRegex is not corrupted by refCount changes)
RuntimeRegex compiled = compile(patternString.toString(), rawModifierStr,
callSiteDebugMode, 0,
- patternString.type == RuntimeScalarType.BYTE_STRING).cloneTracked();
+ patternString.type == RuntimeScalarType.BYTE_STRING,
+ reStrictMode(rawModifierStr), null,
+ preResolvedNamedCharacters).cloneTracked();
return new RuntimeScalar(compiled).propagateTaint(patternString);
}
@@ -1862,6 +1674,14 @@ static boolean containsExecutableSource(String pattern) {
return containsExecutableSource(pattern, false);
}
+ private static boolean isNamedCharacterDiagnostic(String message) {
+ return "Invalid character in \\N{...}".equals(message)
+ || "Zero length \\N{}".equals(message)
+ || "Invalid hexadecimal number in \\N{U+...}".equals(message)
+ || (message != null && message.startsWith(
+ "charnames alias definitions may not contain "));
+ }
+
static boolean containsExecutableSource(String pattern, boolean extended) {
if (pattern == null || pattern.isEmpty()) return false;
boolean escaped = false;
@@ -1943,7 +1763,7 @@ public static RuntimeScalar markSyntacticQuoteConstruction(RuntimeScalar quotedR
RuntimeRegex regex = resolveRegex(quotedRegex);
regex.quoteConstruction = true;
for (String warning : regex.inlineModifierWarnings) {
- RegexQuoteMeta.warnAtConstruction(warning);
+ RegexQuoteMeta.warnAtConstruction(warning, regex.lexicalReStrict);
regex.warningsOnUse.remove(warning);
}
regex.inlineModifierWarnings.clear();
@@ -1967,7 +1787,7 @@ private static void validateTaintedPatternSecurity(RuntimeScalar patternString)
}
String property = matcher.group(2);
- String qualified = property.contains("::") ? property : "main::" + property;
+ String qualified = UnicodeResolver.qualifyUserPropertyName(property);
if (GlobalVariable.isGlobalCodeRefDefined(qualified)) {
throw new PerlCompilerException(
"Insecure user-defined property \"" + property + "\" in regex");
@@ -1987,6 +1807,15 @@ private static void validateTaintedPatternSecurity(RuntimeScalar patternString)
* @return A RuntimeScalar representing the compiled regex.
*/
public static RuntimeScalar getQuotedRegex(RuntimeScalar patternString, RuntimeScalar modifiers, int callsiteId) {
+ NamedCharacterExpansionMap metadata =
+ patternString instanceof RuntimeRegexLiteralScalar literal
+ ? literal.namedCharacterExpansions() : null;
+ return getQuotedRegex(patternString, modifiers, callsiteId, metadata);
+ }
+
+ public static RuntimeScalar getQuotedRegex(
+ RuntimeScalar patternString, RuntimeScalar modifiers, int callsiteId,
+ NamedCharacterExpansionMap preResolvedNamedCharacters) {
String rawModifierStr = modifiers.toString();
String modifierStr = stripInternalMarkers(rawModifierStr);
@@ -2001,13 +1830,14 @@ public static RuntimeScalar getQuotedRegex(RuntimeScalar patternString, RuntimeS
}
// Compile the regex and cache it
- RuntimeScalar result = getQuotedRegex(patternString, modifiers);
+ RuntimeScalar result = getQuotedRegex(
+ patternString, modifiers, preResolvedNamedCharacters);
state().optimizedRegexCache.put(callsiteId, result);
return result;
}
// No /o or m?PAT? modifier, use normal compilation
- return getQuotedRegex(patternString, modifiers);
+ return getQuotedRegex(patternString, modifiers, preResolvedNamedCharacters);
}
/**
@@ -2031,14 +1861,17 @@ public static RuntimeScalar getReplacementRegex(RuntimeScalar patternString, Run
RuntimeRegex regex = new RuntimeRegex();
// Always start with the resolved regex properties
- regex.pattern = resolvedRegex.pattern;
- regex.patternUnicode = resolvedRegex.patternUnicode;
regex.recursivePattern = resolvedRegex.recursivePattern;
regex.recursivePatternUnicode = resolvedRegex.recursivePatternUnicode;
regex.recursivePatternBytes = resolvedRegex.recursivePatternBytes;
+ regex.namedCharacterCache = resolvedRegex.namedCharacterCache;
+ regex.namedCharacterSourceMode = resolvedRegex.namedCharacterSourceMode;
+ regex.namedCharacterTranslator =
+ copyScalarOrNull(resolvedRegex.namedCharacterTranslator);
+ regex.trustedCalloutCount = resolvedRegex.trustedCalloutCount;
+ regex.compiledRegexCacheKey = resolvedRegex.compiledRegexCacheKey;
+ regex.userPropertyPackage = resolvedRegex.userPropertyPackage;
regex.executableCallbacks = resolvedRegex.executableCallbacks;
- regex.patternNoInternalMarkers = resolvedRegex.patternNoInternalMarkers;
- regex.patternUnicodeNoInternalMarkers = resolvedRegex.patternUnicodeNoInternalMarkers;
regex.patternString = resolvedRegex.patternString;
regex.patternByteBacked = resolvedRegex.patternByteBacked;
regex.deferredUserDefinedUnicodeProperties =
@@ -2047,7 +1880,6 @@ public static RuntimeScalar getReplacementRegex(RuntimeScalar patternString, Run
regex.hasPreservesMatch = resolvedRegex.hasPreservesMatch;
regex.quoteConstruction = resolvedRegex.quoteConstruction;
regex.useGAssertion = resolvedRegex.useGAssertion;
- regex.patternFlags = resolvedRegex.patternFlags;
regex.hasCodeBlockCaptures = resolvedRegex.hasCodeBlockCaptures;
regex.warningsOnUse = new ArrayList<>(resolvedRegex.warningsOnUse);
regex.inlineModifierWarnings = new ArrayList<>(resolvedRegex.inlineModifierWarnings);
@@ -2075,19 +1907,24 @@ public static RuntimeScalar getReplacementRegex(RuntimeScalar patternString, Run
newFlags.toFlagString(), regex.lexicalDebugMode,
resolvedRegex.executableCallbacks.size(),
resolvedRegex.patternByteBacked, regex.lexicalReStrict);
- regex.pattern = recompiledRegex.pattern;
- regex.patternUnicode = recompiledRegex.patternUnicode;
regex.recursivePattern = recompiledRegex.recursivePattern;
regex.recursivePatternUnicode = recompiledRegex.recursivePatternUnicode;
regex.recursivePatternBytes = recompiledRegex.recursivePatternBytes;
+ regex.namedCharacterCache = recompiledRegex.namedCharacterCache;
+ regex.namedCharacterSourceMode = recompiledRegex.namedCharacterSourceMode;
+ regex.namedCharacterTranslator =
+ copyScalarOrNull(recompiledRegex.namedCharacterTranslator);
+ regex.trustedCalloutCount = recompiledRegex.trustedCalloutCount;
+ regex.compiledRegexCacheKey = recompiledRegex.compiledRegexCacheKey;
+ regex.userPropertyPackage = recompiledRegex.userPropertyPackage;
regex.executableCallbacks = resolvedRegex.executableCallbacks;
- regex.patternNoInternalMarkers = recompiledRegex.patternNoInternalMarkers;
- regex.patternUnicodeNoInternalMarkers = recompiledRegex.patternUnicodeNoInternalMarkers;
regex.patternString = recompiledRegex.patternString;
+ regex.patternByteBacked = recompiledRegex.patternByteBacked;
regex.regexFlags = recompiledRegex.regexFlags;
+ regex.deferredUserDefinedUnicodeProperties =
+ recompiledRegex.deferredUserDefinedUnicodeProperties;
regex.hasPreservesMatch = recompiledRegex.hasPreservesMatch;
regex.useGAssertion = recompiledRegex.useGAssertion;
- regex.patternFlags = recompiledRegex.patternFlags;
regex.hasCodeBlockCaptures = recompiledRegex.hasCodeBlockCaptures;
regex.warningsOnUse = new ArrayList<>(recompiledRegex.warningsOnUse);
regex.inlineModifierWarnings = new ArrayList<>(
@@ -2099,7 +1936,6 @@ public static RuntimeScalar getReplacementRegex(RuntimeScalar patternString, Run
regex.regexFlags = newFlags;
regex.hasPreservesMatch = regex.hasPreservesMatch || newFlags.preservesMatch();
regex.useGAssertion = newFlags.useGAssertion();
- regex.patternFlags = newFlags.toPatternFlags();
}
}
@@ -2140,7 +1976,8 @@ public static RuntimeScalar getBytesReplacementRegex(RuntimeScalar patternString
: patternString.type == RuntimeScalarType.BYTE_STRING;
regex.recursivePatternBytes = new JoniRegexPattern(sourcePattern,
regex.regexFlags, regex.executableCallbacks.size(), true,
- true, byteBackedPattern, regex.namedCharacterCache);
+ true, byteBackedPattern, regex.namedCharacterCache,
+ regex.lexicalReStrict);
}
return result;
}
@@ -2152,13 +1989,39 @@ private static boolean containsNonAscii(String value) {
return false;
}
- /** Perl named characters and wide numeric escapes make an ASCII source Unicode. */
+ /** Syntax with intrinsic Unicode semantics makes an ASCII source Unicode. */
private static boolean hasUnicodePromotingPatternSyntax(String pattern) {
if (pattern == null) return false;
boolean quoted = false;
+ int characterClassDepth = 0;
+ boolean[] classAtStart = new boolean[pattern.length() + 1];
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
if (ch > 0xff) return true;
+ if (!quoted && ch == '[') {
+ if (characterClassDepth > 0) {
+ // A nested opener (including the '[' in a POSIX class)
+ // consumes the parent class's first member position.
+ classAtStart[characterClassDepth - 1] = false;
+ }
+ classAtStart[characterClassDepth] = true;
+ characterClassDepth++;
+ continue;
+ }
+ if (!quoted && ch == ']' && characterClassDepth > 0) {
+ if (classAtStart[characterClassDepth - 1]) {
+ // A close bracket immediately after '[' or '[^' is a
+ // literal member, not the end of the character class.
+ classAtStart[characterClassDepth - 1] = false;
+ continue;
+ }
+ characterClassDepth--;
+ continue;
+ }
+ if (!quoted && characterClassDepth > 0
+ && classAtStart[characterClassDepth - 1]) {
+ if (ch != '^') classAtStart[characterClassDepth - 1] = false;
+ }
if (ch != '\\' || i + 1 >= pattern.length()) continue;
char escape = pattern.charAt(++i);
@@ -2171,6 +2034,9 @@ private static boolean hasUnicodePromotingPatternSyntax(String pattern) {
continue;
}
if (quoted || escape == '\\') continue;
+ if ((escape == 'p' || escape == 'P') && characterClassDepth == 0) {
+ return true;
+ }
if (escape == 'N' && i + 1 < pattern.length()
&& pattern.charAt(i + 1) == '{') {
return true;
@@ -2340,6 +2206,39 @@ private static void updateNumberedCaptureGroups(RegexMatcher matcher) {
/**
* Direct regex matching without timeout wrapper (fast path).
*/
+ /**
+ * Resolve Perl's empty-pattern reuse without retaining Java matcher state.
+ * A changed modifier set needs a complete native recompilation; otherwise a
+ * tracked wrapper around the last native compiled form is sufficient.
+ */
+ private static RuntimeRegex emptyPatternReuse(RuntimeRegex previous,
+ RegexFlags flags,
+ int lexicalDebugMode,
+ boolean lexicalReStrict,
+ RuntimeScalar replacement) {
+ RuntimeRegex reused;
+ if (previous != null && (flags == null || flags.equals(previous.regexFlags))) {
+ reused = previous.cloneTracked();
+ } else {
+ String source = previous == null ? "" : previous.patternString;
+ String modifiers = flags == null ? "" : flags.toFlagString();
+ int debugMode = lexicalDebugMode != 0
+ ? lexicalDebugMode
+ : (previous == null ? 0 : previous.lexicalDebugMode);
+ reused = compile(source, modifiers, debugMode, 0,
+ previous != null && previous.patternByteBacked,
+ lexicalReStrict || (previous != null && previous.lexicalReStrict));
+ }
+ reused.regexFlags = flags == null ? reused.regexFlags : flags;
+ reused.hasPreservesMatch = reused.hasPreservesMatch
+ || (flags != null && flags.preservesMatch());
+ reused.lexicalDebugMode = lexicalDebugMode != 0 ? lexicalDebugMode : reused.lexicalDebugMode;
+ reused.lexicalReStrict = lexicalReStrict || reused.lexicalReStrict;
+ reused.useGAssertion = flags != null && flags.useGAssertion();
+ reused.replacement = replacement;
+ return reused;
+ }
+
private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeScalar string, int ctx) {
RuntimeRegexState regexState = state();
RuntimeRegex regex = resolveRegex(quotedRegex);
@@ -2354,51 +2253,19 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
if (regexState.lastSuccessfulPattern != null) {
// Use the pattern from last successful match
// But keep the current flags (especially /g and /i)
- Pattern pattern = regexState.lastSuccessfulPattern.pattern;
- // Re-apply current flags if they differ
- if (originalFlags != null && !originalFlags.equals(regexState.lastSuccessfulPattern.regexFlags)) {
- // Need to recompile with current flags using preprocessed pattern
- int newFlags = originalFlags.toPatternFlags();
- String recompilePattern = regexState.lastSuccessfulPattern.javaPatternString != null
- ? regexState.lastSuccessfulPattern.javaPatternString : regexState.lastSuccessfulPattern.patternString;
- pattern = Pattern.compile(recompilePattern, newFlags);
- }
- // Create a temporary regex with the right pattern and current flags
- RuntimeRegex tempRegex = new RuntimeRegex();
- tempRegex.pattern = pattern;
- tempRegex.patternUnicode = regexState.lastSuccessfulPattern.patternUnicode;
- tempRegex.recursivePattern = regexState.lastSuccessfulPattern.recursivePattern;
- tempRegex.recursivePatternUnicode =
- regexState.lastSuccessfulPattern.recursivePatternUnicode;
- tempRegex.executableCallbacks = regexState.lastSuccessfulPattern.executableCallbacks;
- tempRegex.patternNoInternalMarkers = regexState.lastSuccessfulPattern.patternNoInternalMarkers;
- tempRegex.patternUnicodeNoInternalMarkers = regexState.lastSuccessfulPattern.patternUnicodeNoInternalMarkers;
- tempRegex.patternString = regexState.lastSuccessfulPattern.patternString;
- tempRegex.javaPatternString = regexState.lastSuccessfulPattern.javaPatternString;
- tempRegex.requiredLiteral = regexState.lastSuccessfulPattern.requiredLiteral;
- tempRegex.hasPreservesMatch = regexState.lastSuccessfulPattern.hasPreservesMatch || (originalFlags != null && originalFlags.preservesMatch());
- tempRegex.warningsOnUse = new ArrayList<>(regexState.lastSuccessfulPattern.warningsOnUse);
- tempRegex.inlineModifierWarnings = new ArrayList<>(
- regexState.lastSuccessfulPattern.inlineModifierWarnings);
- tempRegex.lexicalDebugMode = regex.lexicalDebugMode != 0
- ? regex.lexicalDebugMode
- : regexState.lastSuccessfulPattern.lexicalDebugMode;
- tempRegex.lexicalReStrict = regex.lexicalReStrict
- || regexState.lastSuccessfulPattern.lexicalReStrict;
- tempRegex.regexFlags = originalFlags;
- tempRegex.useGAssertion = originalFlags != null && originalFlags.useGAssertion();
- regex = tempRegex;
+ regex = emptyPatternReuse(regexState.lastSuccessfulPattern, originalFlags,
+ regex.lexicalDebugMode, regex.lexicalReStrict, null);
+ } else {
+ regex = emptyPatternReuse(null, originalFlags,
+ regex.lexicalDebugMode, regex.lexicalReStrict, null);
}
- // If no previous pattern, the empty pattern matches empty string at start (default behavior)
}
regex.emitWarningsOnUse();
// Debug logging
if (DEBUG_REGEX) {
- String description = regex.recursivePattern != null
- ? regex.recursivePattern.patternDescription()
- : regex.pattern.pattern();
+ String description = regex.recursivePattern.patternDescription();
System.err.println("matchRegexDirect: pattern=" + description +
" input=" + string.toString() + " ctx=" + ctx);
}
@@ -2417,23 +2284,8 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
String inputStr = string.toString();
regex.emitNonUnicodePropertyWarning(string, inputStr);
regex.emitExecutionDebugTrace(inputStr);
- CharSequence matchInput = new RegexTimeoutCharSequence(inputStr);
- RegexMatcher matcher;
- if (regex.recursivePattern != null) {
- matcher = regex.selectRecursivePattern(string)
- .matcher(inputStr, regex.executableCallbacks, string);
- } else {
- Pattern pattern = regex.selectPattern(string, inputStr);
-
- // Workaround for Java MULTILINE quirk: Java's Pattern.MULTILINE changes ^ to only
- // match after line terminators, so "^" fails on empty strings. In Perl, /m makes ^
- // and $ match at line boundaries AND at start/end of string. Since empty strings have
- // no line breaks, MULTILINE is irrelevant and we can safely strip it.
- if (inputStr.isEmpty() && (pattern.flags() & Pattern.MULTILINE) != 0) {
- pattern = Pattern.compile(pattern.pattern(), pattern.flags() & ~Pattern.MULTILINE);
- }
- matcher = new JavaRegexMatcher(pattern.matcher(matchInput));
- }
+ RegexMatcher matcher = regex.selectRecursivePattern(string)
+ .matcher(inputStr, regex.executableCallbacks, string);
// hexPrinter(inputStr);
@@ -2476,7 +2328,7 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
string, posScalar.getInt(), patternKey)) {
// First, try the notempty variant at the SAME position (Perl behavior)
RegexMatcher notemptyMatcher = findNonEmptyGlobalRetry(
- regex, string, inputStr, matchInput, startPos);
+ regex, string, inputStr, startPos);
boolean notemptySucceeded = notemptyMatcher != null;
if (notemptySucceeded) {
matcher = notemptyMatcher;
@@ -2507,26 +2359,6 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
nativeGlobalPosition = matcher.setGlobalPosition(startPos);
}
- if (regex.requiredLiteral != null && !inputStr.contains(regex.requiredLiteral)) {
- if (DEBUG_REGEX) {
- System.err.println(" required literal prefilter failed: " + regex.requiredLiteral);
- }
- if (regex.regexFlags.isGlobalMatch() && !regex.regexFlags.keepCurrentPosition() && posScalar != null) {
- posScalar.set(scalarUndef);
- }
- regexState.globalMatchString = null;
- regexState.lastMatchedString = null;
- regexState.lastMatchStart = -1;
- regexState.lastMatchEnd = -1;
- if (ctx == RuntimeContextType.LIST) {
- return new RuntimeList();
- } else if (ctx == RuntimeContextType.SCALAR) {
- return RuntimeScalarCache.scalarFalse;
- } else {
- return scalarUndef;
- }
- }
-
// Start matching from the current position if defined
// (skip if notempty variant already found a match - region() would reset the matcher)
if (isPosDefined && !skipFirstFind) {
@@ -2660,7 +2492,7 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
}
if (zeroLengthMatch) {
RegexMatcher notemptyMatcher = findNonEmptyGlobalRetry(
- regex, string, inputStr, matchInput, startPos);
+ regex, string, inputStr, startPos);
if (notemptyMatcher != null) {
matcher = notemptyMatcher;
skipFirstFind = true;
@@ -2679,8 +2511,8 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
WarnDie.warn(new RuntimeScalar(e.getMessage() + "\n"), RuntimeScalarCache.scalarEmptyString);
found = false;
} catch (StackOverflowError e) {
- // Java's backtracking engine uses the native stack for some nested
- // quantifiers. Perl reports an unsuccessful match when its own
+ // Joni uses the native stack for some nested quantifiers. Perl
+ // reports an unsuccessful match when its own
// recursion ceiling is reached; do not let the JVM error abort the
// whole program for the equivalent pathological failure case.
found = false;
@@ -2753,13 +2585,13 @@ private static RuntimeBase matchRegexDirect(RuntimeScalar quotedRegex, RuntimeSc
}
/**
- * Avoid Java Pattern recursion for ExifTool's XMP element scan:
+ * Avoid deep matcher recursion for ExifTool's XMP element scan:
* {@code <(/?)x:xmpmeta([-\w:.\x80-\xff]*)(.*?(/?))>}.
*
- * On Google extended-XMP payloads, Java's backtracking engine can
- * recurse deeply enough to throw {@link StackOverflowError}, while Perl
- * handles the scan iteratively. This narrow path preserves the captures
- * ExifTool uses while keeping other regexes on the normal engine.
+ * On Google extended-XMP payloads, a generic backtracking scan can
+ * recurse deeply enough to throw {@link StackOverflowError}. This narrow
+ * path preserves the captures ExifTool uses while keeping other regexes on
+ * the normal Joni engine.
*/
private static RuntimeBase tryFastXmpMetaElementScan(RuntimeRegex regex,
RuntimeScalar string,
@@ -2856,14 +2688,11 @@ private static RuntimeBase tryFastXmpMetaElementScan(RuntimeRegex regex,
private static boolean isXmpMetaElementScanPattern(RuntimeRegex regex) {
String perlPattern = "<(/?)x:xmpmeta([-\\w:.\\x80-\\xff]*)(.*?(/?))>";
- if (perlPattern.equals(regex.patternString)) {
- return true;
- }
- return regex.pattern != null && perlPattern.equals(regex.pattern.pattern());
+ return perlPattern.equals(regex.patternString);
}
/**
- * Avoid Java Pattern recursion for ExifTool's generic XML element scan:
+ * Avoid deep matcher recursion for ExifTool's generic XML element scan:
* {@code <([?/]?)([-\w:.\x80-\xff]+|!--)([^>]*)>}.
*/
private static RuntimeBase tryFastXmlElementScan(RuntimeRegex regex,
@@ -2929,10 +2758,7 @@ private static RuntimeBase tryFastXmlElementScan(RuntimeRegex regex,
private static boolean isXmlElementScanPattern(RuntimeRegex regex) {
String perlPattern = "<([?/]?)([-\\w:.\\x80-\\xff]+|!--)([^>]*)>";
- if (perlPattern.equals(regex.patternString)) {
- return true;
- }
- return regex.pattern != null && perlPattern.equals(regex.pattern.pattern());
+ return perlPattern.equals(regex.patternString);
}
private static XmlElementMatch findXmlElement(String inputStr, int startPos) {
@@ -3117,14 +2943,6 @@ private static RuntimeBase matchRegexWithTimeout(RuntimeScalar quotedRegex, Runt
* @param ctx The context LIST, SCALAR, VOID.
* @return A RuntimeScalar or RuntimeList.
*/
- private static Pattern compileNonEmptySubstitutionPattern(Pattern pattern) {
- try {
- return Pattern.compile("(?:" + pattern.pattern() + ")(?<=[\\s\\S])", pattern.flags());
- } catch (RuntimeException e) {
- return null;
- }
- }
-
private static int bumpGlobalMatchPosition(String inputStr, int offset) {
if (offset >= inputStr.length()) {
return inputStr.length() + 1;
@@ -3135,45 +2953,16 @@ private static int bumpGlobalMatchPosition(String inputStr, int offset) {
private static RegexMatcher findNonEmptyGlobalRetry(RuntimeRegex regex,
RuntimeScalar string,
String inputStr,
- CharSequence matchInput,
int startPos) {
- RegexMatcher retryMatcher;
- boolean nativeNotEmpty;
- if (regex.recursivePattern != null) {
- retryMatcher = regex.selectRecursivePattern(string)
- .matcher(inputStr, regex.executableCallbacks, string);
- nativeNotEmpty = true;
- } else {
- Pattern notemptyPattern = regex.notemptyPattern;
- if (regex.notemptyPatternUnicode != null
- && regex.notemptyPatternUnicode != regex.notemptyPattern
- && !(regex.regexFlags != null && regex.regexFlags.isAscii())
- && !hasInlineAsciiModifier(regex.patternString)
- && Utf8.isUtf8(string)) {
- notemptyPattern = regex.notemptyPatternUnicode;
- }
- if (notemptyPattern == null) {
- // A compiled qr// can be created without /g and interpolated
- // later into a global match. Build its retry form lazily from
- // the selected byte/Unicode Java pattern.
- notemptyPattern = compileNonEmptySubstitutionPattern(
- regex.selectPattern(string, inputStr));
- if (notemptyPattern == null) {
- return null;
- }
- }
- retryMatcher = new JavaRegexMatcher(notemptyPattern.matcher(matchInput));
- nativeNotEmpty = false;
- }
+ RegexMatcher retryMatcher = regex.selectRecursivePattern(string)
+ .matcher(inputStr, regex.executableCallbacks, string);
retryMatcher.region(startPos, inputStr.length());
retryMatcher.useAnchoringBounds(false);
if (regex.useGAssertion) {
retryMatcher.setGlobalPosition(startPos);
}
- boolean found = nativeNotEmpty
- ? retryMatcher.findNotEmpty()
- : retryMatcher.find();
+ boolean found = retryMatcher.findNotEmpty();
return found
&& retryMatcher.consumedStart() == startPos
&& retryMatcher.end() > startPos
@@ -3262,69 +3051,20 @@ public static RuntimeBase replaceRegex(RuntimeScalar quotedRegex, RuntimeScalar
if (state().lastSuccessfulPattern != null) {
// Use the pattern from last successful match
// But keep the current replacement and flags (especially /g and /i)
- Pattern pattern = state().lastSuccessfulPattern.pattern;
- // Re-apply current flags if they differ
- if (originalFlags != null && !originalFlags.equals(state().lastSuccessfulPattern.regexFlags)) {
- // Need to recompile with current flags using preprocessed pattern
- int newFlags = originalFlags.toPatternFlags();
- String recompilePattern = state().lastSuccessfulPattern.javaPatternString != null
- ? state().lastSuccessfulPattern.javaPatternString : state().lastSuccessfulPattern.patternString;
- pattern = Pattern.compile(recompilePattern, newFlags);
- }
- // Create a temporary regex with the right pattern and current flags
- RuntimeRegex tempRegex = new RuntimeRegex();
- tempRegex.pattern = pattern;
- tempRegex.patternUnicode = state().lastSuccessfulPattern.patternUnicode;
- tempRegex.recursivePattern = state().lastSuccessfulPattern.recursivePattern;
- tempRegex.recursivePatternUnicode =
- state().lastSuccessfulPattern.recursivePatternUnicode;
- tempRegex.executableCallbacks = state().lastSuccessfulPattern.executableCallbacks;
- tempRegex.patternNoInternalMarkers = state().lastSuccessfulPattern.patternNoInternalMarkers;
- tempRegex.patternUnicodeNoInternalMarkers = state().lastSuccessfulPattern.patternUnicodeNoInternalMarkers;
- tempRegex.patternString = state().lastSuccessfulPattern.patternString;
- tempRegex.javaPatternString = state().lastSuccessfulPattern.javaPatternString;
- tempRegex.hasPreservesMatch = state().lastSuccessfulPattern.hasPreservesMatch || (originalFlags != null && originalFlags.preservesMatch());
- tempRegex.warningsOnUse = new ArrayList<>(state().lastSuccessfulPattern.warningsOnUse);
- tempRegex.inlineModifierWarnings = new ArrayList<>(
- state().lastSuccessfulPattern.inlineModifierWarnings);
- tempRegex.regexFlags = originalFlags;
- tempRegex.useGAssertion = originalFlags != null && originalFlags.useGAssertion();
- tempRegex.replacement = replacement;
- regex = tempRegex;
+ regex = emptyPatternReuse(state().lastSuccessfulPattern, originalFlags,
+ regex.lexicalDebugMode, regex.lexicalReStrict, replacement);
} else {
// No previous regex - use empty pattern (matches empty string at start)
// This matches Perl's behavior: s//x/ inserts 'x' at the beginning
- RuntimeRegex tempRegex = new RuntimeRegex();
- int flags = originalFlags != null ? originalFlags.toPatternFlags() : 0;
- tempRegex.pattern = Pattern.compile("", flags);
- tempRegex.patternUnicode = tempRegex.pattern; // Empty pattern - same for both
- tempRegex.patternString = "";
- tempRegex.regexFlags = originalFlags;
- tempRegex.useGAssertion = originalFlags != null && originalFlags.useGAssertion();
- tempRegex.replacement = replacement;
- regex = tempRegex;
+ regex = emptyPatternReuse(null, originalFlags,
+ regex.lexicalDebugMode, regex.lexicalReStrict, replacement);
}
}
regex.emitWarningsOnUse();
- CharSequence matchInput = new RegexTimeoutCharSequence(inputStr);
- Pattern pattern = null;
- RegexMatcher matcher;
- if (regex.recursivePattern != null) {
- matcher = regex.selectRecursivePattern(inputValue)
- .matcher(inputStr, regex.executableCallbacks, inputValue);
- } else {
- pattern = regex.selectPattern(inputValue, inputStr);
- if (inputStr.isEmpty() && (pattern.flags() & Pattern.MULTILINE) != 0) {
- pattern = Pattern.compile(pattern.pattern(), pattern.flags() & ~Pattern.MULTILINE);
- }
- matcher = new JavaRegexMatcher(pattern.matcher(matchInput));
- }
- Pattern nonEmptySubstitutionPattern = pattern != null
- && regex.regexFlags != null && regex.regexFlags.isGlobalMatch()
- ? compileNonEmptySubstitutionPattern(pattern)
- : null;
+ RegexMatcher matcher = regex.selectRecursivePattern(inputValue)
+ .matcher(inputStr, regex.executableCallbacks, inputValue);
int searchStart = 0;
int globalPosition = 0;
boolean nativeGlobalPosition = false;
@@ -3439,18 +3179,14 @@ public static RuntimeBase replaceRegex(RuntimeScalar quotedRegex, RuntimeScalar
int zeroLengthOffset = matcher.end();
boolean consumedNonEmptyRetry = false;
- if ((nonEmptySubstitutionPattern != null || regex.recursivePattern != null)
- && zeroLengthOffset <= inputStr.length()) {
- RegexMatcher retryMatcher = nonEmptySubstitutionPattern != null
- ? new JavaRegexMatcher(nonEmptySubstitutionPattern.matcher(matchInput))
- : regex.selectRecursivePattern(inputValue)
- .matcher(inputStr, regex.executableCallbacks, inputValue);
+ if (zeroLengthOffset <= inputStr.length()) {
+ RegexMatcher retryMatcher = regex.selectRecursivePattern(inputValue)
+ .matcher(inputStr, regex.executableCallbacks, inputValue);
// The synthetic (?<=[\s\S]) suffix relies on opaque bounds
// so a zero-length match at the region start is rejected.
setSubstitutionRegion(retryMatcher, zeroLengthOffset, inputStr.length(), false);
if (nativeGlobalPosition) retryMatcher.setGlobalPosition(zeroLengthOffset);
- boolean retryFound = nonEmptySubstitutionPattern != null
- ? retryMatcher.find() : retryMatcher.findNotEmpty();
+ boolean retryFound = retryMatcher.findNotEmpty();
if (retryFound
&& retryMatcher.start() == zeroLengthOffset
&& retryMatcher.end() > zeroLengthOffset) {
@@ -3781,31 +3517,48 @@ private static boolean hasUnicodeChars(String s) {
}
/**
- * Check if a pattern contains inline ASCII modifier (?a...).
- * When present, we should use ASCII matching even for UTF-8 strings with non-ASCII content.
- *
- * @param pattern The pattern string to check
- * @return true if the pattern contains inline (?a...) modifier
+ * A scoped {@code /u}, {@code /a}, or {@code /aa} group must use the UTF-8
+ * Joni variant even when its containing qr// originated in a byte scalar.
+ * The byte variant carries {@code PERL_BYTE_PATTERN}, whose global
+ * case-fold policy deliberately excludes Latin-1 folds; that policy cannot
+ * be relaxed by a nested charset group.
*/
- private static boolean hasInlineAsciiModifier(String pattern) {
- if (pattern == null) {
- return false;
- }
- // Check for (?a...) inline modifier - matches (?a, (?a:, (?ai, (?ia, etc.
- // The 'a' must appear in the modifier position after (?
- int idx = 0;
- while ((idx = pattern.indexOf("(?", idx)) >= 0) {
- idx += 2;
- // Scan modifier characters until we hit : or )
- while (idx < pattern.length()) {
- char c = pattern.charAt(idx);
- if (c == 'a') {
- return true; // Found inline ASCII modifier
- }
- if (c == ':' || c == ')' || c == '-' || c == '<' || c == '=' || c == '!' || c == '{' || c == '#') {
- break; // End of modifier section
+ private static boolean hasInlineUnicodeCaseFoldModifier(String pattern) {
+ if (pattern == null) return false;
+
+ boolean escaped = false;
+ boolean inClass = false;
+ for (int i = 0; i + 2 < pattern.length(); i++) {
+ char current = pattern.charAt(i);
+ if (escaped) {
+ escaped = false;
+ continue;
+ }
+ if (current == '\\') {
+ escaped = true;
+ continue;
+ }
+ if (current == '[') {
+ inClass = true;
+ continue;
+ }
+ if (current == ']' && inClass) {
+ inClass = false;
+ continue;
+ }
+ if (inClass || current != '(' || pattern.charAt(i + 1) != '?') continue;
+
+ boolean negated = false;
+ for (int j = i + 2; j < pattern.length(); j++) {
+ char modifier = pattern.charAt(j);
+ if (modifier == ':' || modifier == ')') break;
+ if (modifier == '-') {
+ negated = true;
+ continue;
}
- idx++;
+ if (modifier == '^') continue;
+ if (modifier < 'a' || modifier > 'z') break;
+ if ((modifier == 'u' || modifier == 'a') && !negated) return true;
}
}
return false;
diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexCallback.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexCallback.java
index feb45966c5..4547dfefcf 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexCallback.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexCallback.java
@@ -4,6 +4,8 @@
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
import org.perlonjava.runtime.operators.WarnDie;
+import java.util.function.Function;
+
/** A parser-created executable segment in a regex template. */
public final class RuntimeRegexCallback {
public enum Kind { BLOCK, CONDITION, DYNAMIC }
@@ -13,16 +15,18 @@ public enum Kind { BLOCK, CONDITION, DYNAMIC }
final String sourceLocation;
final String lexicalPackage;
final String source;
+ final boolean uninitializedWarningsEnabled;
private int ownerCount;
private RuntimeRegexCallback(
RuntimeCode code, Kind kind, String sourceLocation, String lexicalPackage,
- String source) {
+ String source, boolean uninitializedWarningsEnabled) {
this.code = code;
this.kind = kind;
this.sourceLocation = sourceLocation;
this.lexicalPackage = lexicalPackage;
this.source = source;
+ this.uninitializedWarningsEnabled = uninitializedWarningsEnabled;
}
synchronized void retainOwner() {
@@ -43,6 +47,14 @@ synchronized void releaseOwner() {
}
}
+ /** Exact metadata copy for an ithread graph clone with a child-owned CODE pad. */
+ public RuntimeRegexCallback cloneForThread(
+ Function codeCloner) {
+ return new RuntimeRegexCallback(
+ codeCloner.apply(code), kind, sourceLocation, lexicalPackage,
+ source, uninitializedWarningsEnabled);
+ }
+
public static RuntimeScalar wrap(RuntimeScalar codeRef, String kindName) {
return wrap(codeRef, kindName, null);
}
@@ -54,6 +66,12 @@ public static RuntimeScalar wrap(
public static RuntimeScalar wrap(
RuntimeScalar codeRef, String kindName, String lexicalPackage, String source) {
+ return wrap(codeRef, kindName, lexicalPackage, source, true);
+ }
+
+ public static RuntimeScalar wrap(
+ RuntimeScalar codeRef, String kindName, String lexicalPackage, String source,
+ boolean uninitializedWarningsEnabled) {
if (!(codeRef.value instanceof RuntimeCode code)) {
throw new IllegalArgumentException("regex callback is not a code reference");
}
@@ -68,6 +86,6 @@ public static RuntimeScalar wrap(
return new RuntimeScalar(new RuntimeRegexCallback(
code, Kind.valueOf(kindName), sourceLocation,
lexicalPackage != null ? lexicalPackage : code.packageName,
- source));
+ source, uninitializedWarningsEnabled));
}
}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexLiteralScalar.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexLiteralScalar.java
new file mode 100644
index 0000000000..6e7bdb1c07
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexLiteralScalar.java
@@ -0,0 +1,19 @@
+package org.perlonjava.runtime.regex;
+
+import org.perlonjava.runtime.NamedCharacterExpansionMap;
+import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
+
+/** A pattern scalar carrying immutable lexical regex metadata from its CV. */
+public final class RuntimeRegexLiteralScalar extends RuntimeScalar {
+ private final NamedCharacterExpansionMap namedCharacterExpansions;
+
+ RuntimeRegexLiteralScalar(
+ RuntimeScalar pattern, NamedCharacterExpansionMap namedCharacterExpansions) {
+ super(pattern);
+ this.namedCharacterExpansions = namedCharacterExpansions;
+ }
+
+ public NamedCharacterExpansionMap namedCharacterExpansions() {
+ return namedCharacterExpansions;
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexSourceCompiler.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexSourceCompiler.java
index 50b3b0851b..f6992a83ae 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexSourceCompiler.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexSourceCompiler.java
@@ -37,8 +37,28 @@ private RuntimeRegexSourceCompiler() {}
static RuntimeScalar compile(RuntimeScalar pattern, String modifiers) {
RuntimeCode owner = RuntimeCode.getActiveCodeAt(0);
- Map cells = owner == null
- ? Map.of() : RuntimeCode.snapshotActiveLexicals(owner);
+ Map cells = new LinkedHashMap<>();
+ if (owner != null) {
+ cells.putAll(RuntimeCode.snapshotActiveLexicals(owner));
+ }
+ // Eval STRING code enters with its outer lexical cells preloaded in
+ // captured registers. Those registers have no declaration opcode in
+ // the eval body, so they are not added to the active-frame map. Merge
+ // them explicitly before strict-vars validation and callback capture;
+ // a live active binding wins whenever both routes provide a cell.
+ if (owner instanceof InterpretedCode interpreted
+ && interpreted.capturedVars != null) {
+ for (Map.Entry entry
+ : interpreted.variableRegistry.entrySet()) {
+ int capturedIndex = entry.getValue() - 3;
+ if (capturedIndex >= 0
+ && capturedIndex < interpreted.capturedVars.length
+ && interpreted.capturedVars[capturedIndex] != null) {
+ cells.putIfAbsent(
+ entry.getKey(), interpreted.capturedVars[capturedIndex]);
+ }
+ }
+ }
Map orderedCells = new LinkedHashMap<>();
for (Map.Entry entry : cells.entrySet()) {
String name = entry.getKey();
@@ -52,12 +72,13 @@ static RuntimeScalar compile(RuntimeScalar pattern, String modifiers) {
String publicModifiers = modifiers.replace("E", "").replace("T", "")
.replace(String.valueOf(RuntimeRegex.INTERNAL_DEBUG_MARKER), "")
- .replace(String.valueOf(RuntimeRegex.INTERNAL_DEBUGCOLOR_MARKER), "");
+ .replace(String.valueOf(RuntimeRegex.INTERNAL_DEBUGCOLOR_MARKER), "")
+ .replace(String.valueOf(RuntimeRegex.INTERNAL_RE_STRICT_MARKER), "");
// qr// accepts pattern modifiers, not operation modifiers such as the
// trailing marker used for m?PAT?. Reapply the complete operation flag
// set to the compiled qr object below.
String sourceModifiers = publicModifiers.replaceAll("[gcr?op]", "");
- String source = "qr~" + pattern.toString().replace("~", "\\~")
+ String source = "qr~" + escapeDelimiter(pattern.toString(), '~')
+ "~" + sourceModifiers;
// Perl compiles each executable runtime pattern as a distinct eval,
// so diagnostics and warnings use an independent (eval N) filename.
@@ -74,7 +95,13 @@ static RuntimeScalar compile(RuntimeScalar pattern, String modifiers) {
symbolTable.addVariable("this", "", null);
symbolTable.addVariable("@_", "our", null);
symbolTable.addVariable("wantarray", "", null);
- symbolTable.enableStrictOption(HINT_RE_EVAL);
+ // Runtime callback source is an eval at the regex construction
+ // site. Preserve that site's complete $^H state so strict subs,
+ // vars, refs, bytes, and related lexical policy validate the
+ // callback while it is compiled, rather than deferring malformed
+ // code until the first match executes.
+ symbolTable.setStrictOptions(
+ WarningBitsRegistry.getCallSiteHints() | HINT_RE_EVAL);
symbolTable.enableLexicalRegexModifiers(
publicModifiers.replaceAll("[^imsx]", ""));
String warningBits = RegexQuoteMeta.getCallSiteWarningBits();
@@ -117,17 +144,36 @@ static RuntimeScalar compile(RuntimeScalar pattern, String modifiers) {
Node ast = new Parser(context, tokens).parse();
InterpretedCode code = new BytecodeCompiler(
sourceName, sourceLine, errors, registry).compile(ast, context);
- code = code.withCapturedVars(orderedCells.values().toArray(new RuntimeBase[0]));
+ int highestCapturedRegister = 2;
+ for (int compiledRegister : code.variableRegistry.values()) {
+ highestCapturedRegister = Math.max(
+ highestCapturedRegister, compiledRegister);
+ }
+ RuntimeBase[] capturedCells =
+ new RuntimeBase[highestCapturedRegister - 2];
+ for (Map.Entry entry : orderedCells.entrySet()) {
+ Integer compiledRegister = code.variableRegistry.get(entry.getKey());
+ if (compiledRegister != null && compiledRegister >= 3) {
+ capturedCells[compiledRegister - 3] = entry.getValue();
+ }
+ }
+ code = code.withCapturedVars(capturedCells);
if (owner != null) {
code.__SUB__ = owner.__SUB__ != null ? owner.__SUB__ : new RuntimeScalar(owner);
}
RuntimeList result = code.apply(new RuntimeArray(), RuntimeContextType.SCALAR);
RuntimeScalar compiled = result.scalar().propagateTaint(pattern);
- if (compiled.value instanceof RuntimeRegex && !modifiers.isEmpty()) {
+ // Pattern modifiers were already parsed by the synthetic qr// source
+ // above. Reapplying them through getQuotedRegex() merges flags again;
+ // that loses the second 'a' of /aa because ordinary flag merging
+ // deduplicates modifier characters. Only operation modifiers belong
+ // to the outer runtime regexp operation.
+ String operationModifiers = publicModifiers.replaceAll("[nimsxpadeul]", "");
+ if (compiled.value instanceof RuntimeRegex && !operationModifiers.isEmpty()) {
RuntimeScalar unmodified = compiled;
compiled = RuntimeRegex.getQuotedRegex(
- unmodified, new RuntimeScalar(modifiers)).propagateTaint(pattern);
+ unmodified, new RuntimeScalar(operationModifiers)).propagateTaint(pattern);
if (compiled.value != unmodified.value
&& unmodified.value instanceof RuntimeRegex temporary) {
temporary.releaseExecutableCallbacks();
@@ -139,6 +185,22 @@ static RuntimeScalar compile(RuntimeScalar pattern, String modifiers) {
}
}
+ /** Quote a non-paired qr delimiter without changing regex backslash parity. */
+ private static String escapeDelimiter(String pattern, char delimiter) {
+ StringBuilder escaped = new StringBuilder(pattern.length());
+ int precedingBackslashes = 0;
+ for (int i = 0; i < pattern.length(); i++) {
+ char current = pattern.charAt(i);
+ if (current == delimiter && (precedingBackslashes & 1) == 0) {
+ escaped.append('\\');
+ }
+ escaped.append(current);
+ precedingBackslashes = current == '\\'
+ ? precedingBackslashes + 1 : 0;
+ }
+ return escaped.toString();
+ }
+
static RuntimeScalar compileTemplate(RuntimeScalar original,
RuntimeRegexTemplate template,
String modifiers) {
diff --git a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexTemplate.java b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexTemplate.java
index 3b5c6978a0..4a9d11604a 100644
--- a/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexTemplate.java
+++ b/src/main/java/org/perlonjava/runtime/regex/RuntimeRegexTemplate.java
@@ -14,19 +14,18 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
/** Runtime interpolation result that keeps executable regex callbacks out of strings. */
public final class RuntimeRegexTemplate {
- private static final Pattern CALLOUT_ID = Pattern.compile(
- "\\(\\?\\{=(CALL|DYNAMIC):(\\d+)\\}\\)");
+ private static final char SLOT_START = '\u001e';
+ private static final char SLOT_END = '\u001f';
private final String pattern;
private final List callbacks;
private final boolean byteBackedPattern;
+ private record CalloutSlot(int start, int end, char kind, int id) {}
+
/**
* Deferred array interpolation. Keeping the joined operands separate until
* the enclosing regex template is assembled lets a blessed element's dot
@@ -99,23 +98,25 @@ public static RuntimeScalar build(RuntimeList parts) {
int id = callbacks.size();
callbacks.add(callback);
if (callback.kind == RuntimeRegexCallback.Kind.CONDITION) {
- pattern.append("?{=CALL:").append(id).append("})");
+ appendSlot(pattern, 'C', id);
} else if (callback.kind == RuntimeRegexCallback.Kind.DYNAMIC) {
- pattern.append("(?{=DYNAMIC:").append(id).append("})");
+ appendSlot(pattern, 'D', id);
} else {
- pattern.append("(?{=CALL:").append(id).append("})");
+ appendSlot(pattern, 'B', id);
}
} else if (scalar.value instanceof RuntimeRegex regex
&& !regex.executableCallbacks.isEmpty()) {
- appendEmbeddedRegex(pattern, callbacks, regex.toExecutableString(), regex.executableCallbacks);
+ appendEmbeddedRegex(pattern, callbacks,
+ regex.toExecutableString(), regex.executableCallbacks);
} else if (scalar.value instanceof RuntimeRegexTemplate template) {
- appendEmbeddedRegex(pattern, callbacks, template.pattern, template.callbacks);
+ appendEmbeddedRegex(pattern, callbacks,
+ template.pattern, template.callbacks);
} else {
- pattern.append(scalar);
+ appendUntrusted(pattern, scalar.toString());
}
}
RuntimeScalar result = callbacks.isEmpty()
- ? patternScalar(pattern.toString(), byteBackedPattern)
+ ? patternScalar(collapseSlotEscapes(pattern.toString()), byteBackedPattern)
: new RuntimeScalar(new RuntimeRegexTemplate(
pattern.toString(), callbacks, byteBackedPattern));
result.tainted = tainted;
@@ -282,22 +283,71 @@ private static void appendEmbeddedRegex(StringBuilder pattern,
String embeddedPattern,
List embeddedCallbacks) {
int offset = callbacks.size();
- Matcher matcher = CALLOUT_ID.matcher(embeddedPattern);
- StringBuilder remapped = new StringBuilder();
- while (matcher.find()) {
- String kind = matcher.group(1);
- int oldId = Integer.parseInt(matcher.group(2));
+ int cursor = 0;
+ for (CalloutSlot slot : calloutSlots(embeddedPattern)) {
+ pattern.append(embeddedPattern, cursor, slot.start());
+ int oldId = slot.id();
if (oldId < 0 || oldId >= embeddedCallbacks.size()) {
throw new IllegalArgumentException("Invalid embedded regex callout ID " + oldId);
}
- matcher.appendReplacement(remapped,
- "(?{=" + kind + ":" + (offset + oldId) + "})");
+ appendSlot(pattern, slot.kind(), offset + oldId);
+ cursor = slot.end();
}
- matcher.appendTail(remapped);
- pattern.append(remapped);
+ pattern.append(embeddedPattern, cursor, embeddedPattern.length());
callbacks.addAll(embeddedCallbacks);
}
+ private static void appendSlot(StringBuilder pattern, char kind, int id) {
+ pattern.append(SLOT_START).append(kind).append(id).append(SLOT_END);
+ }
+
+ private static void appendUntrusted(StringBuilder pattern, String text) {
+ for (int i = 0; i < text.length(); i++) {
+ char ch = text.charAt(i);
+ pattern.append(ch);
+ if (ch == SLOT_START) pattern.append(ch);
+ }
+ }
+
+ private static List calloutSlots(String executablePattern) {
+ List slots = new ArrayList<>();
+ for (int i = 0; i < executablePattern.length(); i++) {
+ if (executablePattern.charAt(i) != SLOT_START) continue;
+ if (i + 1 < executablePattern.length()
+ && executablePattern.charAt(i + 1) == SLOT_START) {
+ i++;
+ continue;
+ }
+ int start = i;
+ if (++i >= executablePattern.length()) {
+ throw malformedCalloutSlot();
+ }
+ char kind = executablePattern.charAt(i);
+ if (kind != 'B' && kind != 'C' && kind != 'D') {
+ throw malformedCalloutSlot();
+ }
+ int digitStart = ++i;
+ while (i < executablePattern.length()
+ && executablePattern.charAt(i) >= '0'
+ && executablePattern.charAt(i) <= '9') i++;
+ if (i == digitStart || i >= executablePattern.length()
+ || executablePattern.charAt(i) != SLOT_END) {
+ throw malformedCalloutSlot();
+ }
+ try {
+ slots.add(new CalloutSlot(start, i + 1, kind,
+ Integer.parseInt(executablePattern.substring(digitStart, i))));
+ } catch (NumberFormatException ignored) {
+ throw malformedCalloutSlot();
+ }
+ }
+ return slots;
+ }
+
+ private static IllegalArgumentException malformedCalloutSlot() {
+ return new IllegalArgumentException("Malformed internal regex callout slot");
+ }
+
String pattern() {
return pattern;
}
@@ -317,21 +367,24 @@ boolean byteBackedPattern() {
* Perl expressions.
*/
MaskedCallouts maskCallouts() {
- Matcher matcher = CALLOUT_ID.matcher(pattern);
StringBuilder masked = new StringBuilder();
StringBuilder syntheticPrefix = new StringBuilder();
List placeholders = new ArrayList<>();
List markers = new ArrayList<>();
+ int cursor = 0;
int id = 0;
- while (matcher.find()) {
+ for (CalloutSlot slot : calloutSlots(pattern)) {
+ int maskStart = slot.kind() == 'C' && slot.start() > 0
+ && pattern.charAt(slot.start() - 1) == '('
+ ? slot.start() - 1 : slot.start();
+ masked.append(pattern, cursor, maskStart);
String token = "POJ_INTERNAL_CALLOUT_PLACEHOLDER_" + id++ + "_END";
while (pattern.contains(token)) token += "_";
// In (?(?{...})yes|no), the marker itself is the condition. Use a
// temporary named-capture condition so the runtime regex parser
// preserves the branches without interpreting the trusted marker
// as Perl source. The empty capture is removed by restore().
- boolean callbackCondition = matcher.start() >= 2
- && pattern.startsWith("(?", matcher.start() - 2);
+ boolean callbackCondition = slot.kind() == 'C';
String placeholder = token;
if (callbackCondition) {
String name = "POJ_INTERNAL_CALLOUT_CONDITION_" + id;
@@ -340,10 +393,11 @@ MaskedCallouts maskCallouts() {
placeholder = "(<" + name + ">)";
}
placeholders.add(placeholder);
- markers.add(matcher.group());
- matcher.appendReplacement(masked, Matcher.quoteReplacement(placeholder));
+ markers.add(pattern.substring(maskStart, slot.end()));
+ masked.append(placeholder);
+ cursor = slot.end();
}
- matcher.appendTail(masked);
+ masked.append(pattern, cursor, pattern.length());
return new MaskedCallouts(syntheticPrefix + masked.toString(),
syntheticPrefix.toString(), placeholders, markers);
}
@@ -355,42 +409,90 @@ boolean containsRuntimeExecutableSource(String modifiers) {
static String offsetCalloutIds(String executablePattern, int offset,
int callbackCount) {
- if (offset == 0 || callbackCount == 0) return executablePattern;
- Matcher matcher = CALLOUT_ID.matcher(executablePattern);
StringBuilder remapped = new StringBuilder();
- while (matcher.find()) {
- int oldId = Integer.parseInt(matcher.group(2));
+ int cursor = 0;
+ for (CalloutSlot slot : calloutSlots(executablePattern)) {
+ remapped.append(executablePattern, cursor, slot.start());
+ int oldId = slot.id();
if (oldId < 0 || oldId >= callbackCount) {
throw new IllegalArgumentException("Invalid runtime regex callout ID " + oldId);
}
- matcher.appendReplacement(remapped, Matcher.quoteReplacement(
- "(?{=" + matcher.group(1) + ":" + (offset + oldId) + "})"));
+ appendSlot(remapped, slot.kind(), offset + oldId);
+ cursor = slot.end();
}
- matcher.appendTail(remapped);
+ remapped.append(executablePattern, cursor, executablePattern.length());
return remapped.toString();
}
+ static String materializeTrustedCallouts(String executablePattern, int callbackCount) {
+ if (callbackCount == 0 || executablePattern.indexOf(SLOT_START) < 0) {
+ return executablePattern;
+ }
+ StringBuilder materialized = new StringBuilder(executablePattern.length() + 16);
+ int cursor = 0;
+ for (CalloutSlot slot : calloutSlots(executablePattern)) {
+ appendCollapsedSlotEscapes(materialized,
+ executablePattern.substring(cursor, slot.start()));
+ if (slot.id() < 0 || slot.id() >= callbackCount) {
+ throw new IllegalArgumentException(
+ "Invalid runtime regex callout ID " + slot.id());
+ }
+ if (slot.kind() == 'C') {
+ materialized.append("?{=CALL:").append(slot.id()).append("})");
+ } else {
+ materialized.append("(?{=")
+ .append(slot.kind() == 'D' ? "DYNAMIC:" : "CALL:")
+ .append(slot.id()).append("})");
+ }
+ cursor = slot.end();
+ }
+ appendCollapsedSlotEscapes(materialized,
+ executablePattern.substring(cursor));
+ return materialized.toString();
+ }
+
+ private static void appendCollapsedSlotEscapes(StringBuilder target, String text) {
+ for (int i = 0; i < text.length(); i++) {
+ char ch = text.charAt(i);
+ target.append(ch);
+ if (ch == SLOT_START && i + 1 < text.length()
+ && text.charAt(i + 1) == SLOT_START) i++;
+ }
+ }
+
+ private static String collapseSlotEscapes(String text) {
+ StringBuilder collapsed = new StringBuilder(text.length());
+ appendCollapsedSlotEscapes(collapsed, text);
+ return collapsed.toString();
+ }
+
static String displayPattern(String executablePattern,
List callbacks) {
if (executablePattern == null || executablePattern.isEmpty()) {
return executablePattern;
}
- Matcher matcher = CALLOUT_ID.matcher(executablePattern);
StringBuilder display = new StringBuilder();
- while (matcher.find()) {
- int callbackId = Integer.parseInt(matcher.group(2));
+ int cursor = 0;
+ for (CalloutSlot slot : calloutSlots(executablePattern)) {
+ int textEnd = slot.kind() == 'C' && slot.start() > cursor
+ && executablePattern.charAt(slot.start() - 1) == '('
+ ? slot.start() - 1 : slot.start();
+ appendCollapsedSlotEscapes(display,
+ executablePattern.substring(cursor, textEnd));
+ int callbackId = slot.id();
String replacement = callbackId >= 0 && callbackId < callbacks.size()
&& callbacks.get(callbackId).source != null
? callbacks.get(callbackId).source
- : "DYNAMIC".equals(matcher.group(1)) ? "(??{})" : "(?{})";
- matcher.appendReplacement(display, Matcher.quoteReplacement(replacement));
+ : slot.kind() == 'D' ? "(??{})" : "(?{})";
+ display.append(replacement);
+ cursor = slot.end();
}
- matcher.appendTail(display);
+ appendCollapsedSlotEscapes(display, executablePattern.substring(cursor));
return display.toString();
}
@Override
public String toString() {
- return pattern;
+ return displayPattern(pattern, callbacks);
}
}
diff --git a/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java b/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java
index 039f614db6..8ab6b5da41 100644
--- a/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java
+++ b/src/main/java/org/perlonjava/runtime/regex/UnicodeResolver.java
@@ -4,26 +4,33 @@
import com.ibm.icu.lang.UProperty;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.util.ValueIterator;
+import org.jcodings.Encoding;
import org.joni.CharacterPropertyResolver;
import org.joni.PerlPropertyValueMatcher;
+import org.joni.WideScalarCodec;
import org.perlonjava.app.scriptengine.PerlLanguageProvider;
import org.perlonjava.runtime.runtimetypes.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
+import java.util.ArrayDeque;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.regex.Pattern;
public class UnicodeResolver {
+ private record PropertyScanState(boolean caseInsensitive, boolean extended) {}
private static final String USER_PROPERTY_RANGE_ENCODING =
"\u0000POJ_USER_RANGES_V1:";
+ private static final ThreadLocal USER_PROPERTY_PACKAGE =
+ ThreadLocal.withInitial(() -> "main");
private static final Pattern USER_DEFINED_PROPERTY_NAME = Pattern.compile(
- "^(?:[A-Za-z_][A-Za-z0-9_]*::)*(?:Is|In)[A-Za-z_][A-Za-z0-9_]*$");
+ "^(?:[A-Za-z_][A-Za-z0-9_]*::)*(?:Is|In)[A-Za-z0-9_]+$");
private static final UnicodeSet[] PERL_DECOMPOSITION_TYPE_SETS =
buildPerlDecompositionTypeSets();
private static final UnicodeSet PERL_UNICODE_BASE_SET =
@@ -96,6 +103,96 @@ public class UnicodeResolver {
private static final UnicodeSet PERL_COMPOSITION_EXCLUSION_SET =
buildPerlCompositionExclusionSet();
+ /**
+ * Perl's native (*script_run:...) predicate, evaluated against the pinned
+ * Script and Script_Extensions data used by ordinary property matching.
+ */
+ static boolean isPerlScriptRun(byte[] bytes, int start, int end, Encoding encoding,
+ WideScalarCodec wideScalarCodec) {
+ String[] scripts = PerlUnicodeScriptData.canonicalValues();
+ boolean[] possibleScripts = null;
+ boolean japanesePossible = true;
+ int unknownCount = 0;
+ int characterCount = 0;
+ int digitSet = -1;
+
+ for (int p = start; p < end;) {
+ int codePoint;
+ int next;
+ WideScalarCodec.Decoded wide = wideScalarCodec == null
+ ? null : wideScalarCodec.decode(bytes, p, end, encoding);
+ if (wide != null) {
+ if (wide.value() > Character.MAX_CODE_POINT || wide.end() <= p || wide.end() > end) {
+ return false;
+ }
+ codePoint = (int) wide.value();
+ next = wide.end();
+ } else {
+ int length = encoding.length(bytes, p, end);
+ if (length <= 0 || p + length > end) return false;
+ codePoint = encoding.mbcToCode(bytes, p, end);
+ next = p + length;
+ }
+ p = next;
+ characterCount++;
+
+ if (PerlUnicodeScriptData.scriptSet("Unknown").contains(codePoint)) {
+ unknownCount++;
+ if (unknownCount > 1 || characterCount > 1) return false;
+ continue;
+ }
+ if (unknownCount != 0) return false;
+
+ if (Character.getType(codePoint) == Character.DECIMAL_DIGIT_NUMBER) {
+ int digit = Character.digit(codePoint, 10);
+ int thisDigitSet = digit < 0 ? codePoint : codePoint - digit;
+ if (digitSet >= 0 && digitSet != thisDigitSet) return false;
+ digitSet = thisDigitSet;
+ }
+
+ boolean[] forCharacter = new boolean[scripts.length];
+ boolean hasScript = false;
+ for (int i = 0; i < scripts.length; i++) {
+ String script = scripts[i];
+ if (script.equals("Common") || script.equals("Inherited")
+ || script.equals("Unknown")) continue;
+ if (PerlUnicodeScriptData.scriptExtensionsSet(script).contains(codePoint)) {
+ forCharacter[i] = true;
+ hasScript = true;
+ }
+ }
+ // Plain Common/Inherited characters do not constrain the run;
+ // Common characters with Script_Extensions (for example certain
+ // punctuation) do constrain it through the generated sets above.
+ if (!hasScript && (PerlUnicodeScriptData.scriptSet("Common").contains(codePoint)
+ || PerlUnicodeScriptData.scriptSet("Inherited").contains(codePoint))) {
+ continue;
+ }
+ if (!hasScript) return false;
+ boolean characterJapanese = forCharacter[scriptIndex(scripts, "Han")]
+ || forCharacter[scriptIndex(scripts, "Hiragana")]
+ || forCharacter[scriptIndex(scripts, "Katakana")];
+ japanesePossible &= characterJapanese;
+ if (possibleScripts == null) {
+ possibleScripts = forCharacter;
+ } else {
+ for (int i = 0; i < possibleScripts.length; i++) {
+ possibleScripts[i] &= forCharacter[i];
+ }
+ }
+ }
+ if (possibleScripts == null) return true;
+ for (boolean possible : possibleScripts) if (possible) return true;
+ // UTS #39's Highly Restrictive profile treats the Japanese script
+ // combination (Han, Hiragana, Katakana) as one permitted run.
+ return japanesePossible;
+ }
+
+ private static int scriptIndex(String[] scripts, String name) {
+ for (int i = 0; i < scripts.length; i++) if (scripts[i].equals(name)) return i;
+ return -1;
+ }
+
/**
* Cache for user-defined property subroutine results.
* Perl only calls user-defined property subs once per unique name and caches the result.
@@ -114,6 +211,31 @@ private static String userPropertyCacheKey(String subName, boolean caseInsensiti
return subName + (caseInsensitive ? "\u0000i" : "\u0000s");
}
+ private static String userPropertyAttemptKey(String subName, boolean caseInsensitive) {
+ return userPropertyCacheKey(subName, caseInsensitive) + "\u0000checked";
+ }
+
+ static String activeUserPropertyPackage() {
+ String packageName = USER_PROPERTY_PACKAGE.get();
+ return packageName == null || packageName.isBlank() ? "main" : packageName;
+ }
+
+ static T withUserPropertyPackage(String packageName, Supplier action) {
+ String previous = USER_PROPERTY_PACKAGE.get();
+ USER_PROPERTY_PACKAGE.set(
+ packageName == null || packageName.isBlank() ? "main" : packageName);
+ try {
+ return action.get();
+ } finally {
+ USER_PROPERTY_PACKAGE.set(previous);
+ }
+ }
+
+ static String qualifyUserPropertyName(String property) {
+ return property.contains("::")
+ ? property : activeUserPropertyPackage() + "::" + property;
+ }
+
/**
* Retrieves the Unicode code point for a given character name.
* Supports:
@@ -126,6 +248,10 @@ private static String userPropertyCacheKey(String subName, boolean caseInsensiti
* @throws IllegalArgumentException If the name is invalid, not found, or is a named sequence.
*/
public static int getCodePointFromName(String name) {
+ String trimmed = name.strip();
+ if (trimmed.regionMatches(true, 0, "U+", 0, 2)) {
+ name = trimmed;
+ }
int codePoint;
if (name.startsWith("U+")) {
try {
@@ -134,59 +260,13 @@ public static int getCodePointFromName(String name) {
throw new IllegalArgumentException("Invalid Unicode code point: " + name);
}
} else {
- // First try common Perl charnames module aliases
- // These are short names that Perl's charnames module provides
- // for commonly used control characters and spaces
- codePoint = switch (name) {
- // C0 control character abbreviations (Unicode name aliases)
- case "NUL", "NULL" -> 0x0000;
- case "SOH", "START OF HEADING" -> 0x0001;
- case "STX", "START OF TEXT" -> 0x0002;
- case "ETX", "END OF TEXT" -> 0x0003;
- case "EOT", "END OF TRANSMISSION" -> 0x0004;
- case "ENQ", "ENQUIRY" -> 0x0005;
- case "ACK", "ACKNOWLEDGE" -> 0x0006;
- case "BEL", "ALERT" -> 0x0007;
- case "BS", "BACKSPACE" -> 0x0008;
- case "HT", "TAB", "CHARACTER TABULATION", "HORIZONTAL TABULATION" -> 0x0009;
- case "LF", "LINE FEED", "LINE FEED (LF)" -> 0x000A;
- case "VT", "LINE TABULATION", "VERTICAL TABULATION" -> 0x000B;
- case "FF", "FORM FEED", "FORM FEED (FF)" -> 0x000C;
- case "CR", "CARRIAGE RETURN", "CARRIAGE RETURN (CR)" -> 0x000D;
- case "SO", "SHIFT OUT" -> 0x000E;
- case "SI", "SHIFT IN" -> 0x000F;
- case "DLE", "DATA LINK ESCAPE" -> 0x0010;
- case "DC1", "DEVICE CONTROL ONE" -> 0x0011;
- case "DC2", "DEVICE CONTROL TWO" -> 0x0012;
- case "DC3", "DEVICE CONTROL THREE" -> 0x0013;
- case "DC4", "DEVICE CONTROL FOUR" -> 0x0014;
- case "NAK", "NEGATIVE ACKNOWLEDGE" -> 0x0015;
- case "SYN", "SYNCHRONOUS IDLE" -> 0x0016;
- case "ETB", "END OF TRANSMISSION BLOCK" -> 0x0017;
- case "CAN", "CANCEL" -> 0x0018;
- case "EOM", "END OF MEDIUM" -> 0x0019;
- case "SUB", "SUBSTITUTE" -> 0x001A;
- case "ESC", "ESCAPE" -> 0x001B;
- case "FS", "INFORMATION SEPARATOR FOUR", "FILE SEPARATOR" -> 0x001C;
- case "GS", "INFORMATION SEPARATOR THREE", "GROUP SEPARATOR" -> 0x001D;
- case "RS", "INFORMATION SEPARATOR TWO", "RECORD SEPARATOR" -> 0x001E;
- case "US", "INFORMATION SEPARATOR ONE", "UNIT SEPARATOR" -> 0x001F;
- case "SP", "SPACE" -> 0x0020;
- case "DEL", "DELETE" -> 0x007F;
-
- // Other control characters and special characters
- case "NEL", "NEXT LINE", "NEXT LINE (NEL)" -> 0x0085;
- case "BOM", "BYTE ORDER MARK" -> 0xFEFF;
-
- // Spaces (Perl allows both hyphenated and non-hyphenated forms)
- case "NBSP", "NO-BREAK SPACE" -> 0x00A0;
- case "ZWSP", "ZERO WIDTH SPACE" -> 0x200B;
- case "ZWNJ", "ZERO WIDTH NON-JOINER" -> 0x200C;
- case "ZWJ", "ZERO WIDTH JOINER" -> 0x200D;
-
- // Try ICU4J's official Unicode name lookup
- default -> UCharacter.getCharFromName(name);
- };
+ // Perl's generated table is authoritative for canonical scalar
+ // names and all aliases represented by duplicate code-point rows.
+ codePoint = PerlUnicodeScalarNameData.codePoint(name);
+ if (codePoint == -1) {
+ // Preserve ICU's algorithmic CJK/Hangul and loose-name fallback.
+ codePoint = UCharacter.getCharFromName(name);
+ }
if (codePoint == -1) {
// Check if this is a named sequence (multi-character sequence)
@@ -746,11 +826,7 @@ private static String tryUserDefinedProperty(
String property, Set recursionSet, boolean caseInsensitive,
boolean qualifyBareDiagnosticName) {
// Build the full subroutine name
- String subName = property;
- if (!subName.contains("::")) {
- // Try in main package
- subName = "main::" + subName;
- }
+ String subName = qualifyUserPropertyName(property);
// The runtime-family coordinator also keys properties by their fully
// qualified sub name. Keep recursion tracking in that same canonical
@@ -867,29 +943,116 @@ private static IllegalArgumentException propertyDefinitionDie(
* regex compiler. Property subs are arbitrary Perl and may block; keeping
* them outside that compiler monitor lets unrelated property names proceed.
*/
- static void preloadUserDefinedProperties(String pattern, boolean caseInsensitive) {
- preloadUserDefinedProperties(pattern, caseInsensitive, false);
+ static void preloadUserDefinedProperties(
+ String pattern, boolean caseInsensitive, boolean extended) {
+ preloadUserDefinedProperties(pattern, caseInsensitive, extended, false);
}
static void preloadDeferredUserDefinedProperties(
- String pattern, boolean caseInsensitive) {
- preloadUserDefinedProperties(pattern, caseInsensitive, true);
+ String pattern, boolean caseInsensitive, boolean extended) {
+ preloadUserDefinedProperties(pattern, caseInsensitive, extended, true);
}
private static void preloadUserDefinedProperties(
- String pattern, boolean caseInsensitive,
+ String pattern, boolean caseInsensitive, boolean extended,
boolean qualifyBareDiagnosticName) {
if (pattern == null || pattern.isEmpty()) return;
- for (int slash = pattern.indexOf('\\'); slash >= 0;
- slash = pattern.indexOf('\\', slash + 1)) {
- int precedingSlashes = 0;
- for (int cursor = slash - 1; cursor >= 0 && pattern.charAt(cursor) == '\\'; cursor--) {
- precedingSlashes++;
+ boolean quoted = false;
+ int characterClassDepth = 0;
+ PropertyScanState scanState = new PropertyScanState(caseInsensitive, extended);
+ ArrayDeque groupStates = new ArrayDeque<>();
+ for (int slash = 0; slash < pattern.length(); slash++) {
+ char current = pattern.charAt(slash);
+ if (!quoted && current == '[') {
+ characterClassDepth++;
+ continue;
+ }
+ if (!quoted && current == ']' && characterClassDepth > 0) {
+ characterClassDepth--;
+ continue;
}
- if ((precedingSlashes & 1) != 0 || slash + 3 >= pattern.length()) continue;
+ if (!quoted && characterClassDepth == 0 && current == '('
+ && slash + 2 < pattern.length()
+ && pattern.charAt(slash + 1) == '?'
+ && pattern.charAt(slash + 2) == '#') {
+ slash += 3;
+ while (slash < pattern.length() && pattern.charAt(slash) != ')') {
+ if (pattern.charAt(slash) == '\\' && slash + 1 < pattern.length()) {
+ slash++;
+ }
+ slash++;
+ }
+ continue;
+ }
+ if (!quoted && characterClassDepth == 0 && current == '(') {
+ PropertyScanState previousState = scanState;
+ int optionEnd = slash + 2;
+ boolean negative = false;
+ boolean sawOption = false;
+ boolean localCaseInsensitive = scanState.caseInsensitive();
+ boolean localExtended = scanState.extended();
+ while (optionEnd < pattern.length()) {
+ char option = pattern.charAt(optionEnd);
+ if (option == '-') {
+ if (negative) break;
+ negative = true;
+ optionEnd++;
+ continue;
+ }
+ if (option == ':' || option == ')') break;
+ if ("adgilimnopsux".indexOf(option) < 0) break;
+ sawOption = true;
+ if (option == 'i') localCaseInsensitive = !negative;
+ if (option == 'x') localExtended = !negative;
+ optionEnd++;
+ }
+ if (sawOption && optionEnd < pattern.length()
+ && (pattern.charAt(optionEnd) == ':'
+ || pattern.charAt(optionEnd) == ')')) {
+ scanState = new PropertyScanState(
+ localCaseInsensitive, localExtended);
+ if (pattern.charAt(optionEnd) == ':') {
+ groupStates.push(previousState);
+ }
+ slash = optionEnd;
+ continue;
+ }
+ groupStates.push(scanState);
+ continue;
+ }
+ if (!quoted && characterClassDepth == 0 && current == ')') {
+ if (!groupStates.isEmpty()) scanState = groupStates.pop();
+ continue;
+ }
+ if (!quoted && scanState.extended()
+ && characterClassDepth == 0 && current == '#') {
+ while (slash < pattern.length() && pattern.charAt(slash) != '\n') slash++;
+ continue;
+ }
+ if (current != '\\' || slash + 1 >= pattern.length()) continue;
char marker = pattern.charAt(slash + 1);
+ if (marker == 'Q' && !quoted) {
+ quoted = true;
+ slash++;
+ continue;
+ }
+ if (marker == 'E' && quoted) {
+ quoted = false;
+ slash++;
+ continue;
+ }
+ if (quoted) {
+ slash++;
+ continue;
+ }
+ if (marker == '\\') {
+ slash++;
+ continue;
+ }
+ if (slash + 3 >= pattern.length()) continue;
+
if ((marker != 'p' && marker != 'P') || pattern.charAt(slash + 2) != '{') continue;
int end = pattern.indexOf('}', slash + 3);
if (end < 0) return;
@@ -897,18 +1060,30 @@ private static void preloadUserDefinedProperties(
String property = pattern.substring(slash + 3, end).trim();
if (property.startsWith("^")) property = property.substring(1).trim();
if (isUserDefinedPropertyName(property)) {
- if (qualifyBareDiagnosticName && !property.contains("::")) {
- deferredUserProperties().add("main::" + property);
+ String subName = qualifyUserPropertyName(property);
+ if (qualifyBareDiagnosticName) {
+ deferredUserProperties().add(subName);
}
// Preloading is an optimization for callbacks that are already
// available. Perl permits qr// to contain a forward reference
- // to a user property; RegexPreprocessor represents that with a
- // placeholder and recompiles on first use. Calling the complete
- // translator here would turn that intentional forward reference
- // into an early "unsupported property" fatal before the
- // placeholder path can run.
- tryUserDefinedProperty(property, new LinkedHashSet<>(), caseInsensitive,
+ // to a user property; RuntimeRegex retains that deferred marker
+ // and ensureCompiledForRuntime() recompiles on first use. Calling
+ // the complete translator here would turn that intentional
+ // forward reference into an early "unsupported property" fatal
+ // before the deferred runtime path can run.
+ String resolved = tryUserDefinedProperty(
+ property, new LinkedHashSet<>(), scanState.caseInsensitive(),
qualifyBareDiagnosticName);
+ if (qualifyBareDiagnosticName && resolved == null
+ && !PerlLanguageProvider.COMPILE_LOCK.isHeldByCurrentThread()) {
+ // Record a completed negative lookup separately for each
+ // fold mode. A later /i compile must still defer if only
+ // the case-sensitive callback result (or absence) has
+ // previously been observed.
+ deferredUserProperties().add(
+ userPropertyAttemptKey(
+ subName, scanState.caseInsensitive()));
+ }
}
slash = end;
}
@@ -1020,6 +1195,23 @@ && isPerlAllProperty(property, looseIsValue)) {
UserUnicodePropertyResult.decode(userProp).unicodePattern(),
negated);
}
+ if (mustDeferPotentialUserDefinedProperty(
+ property, caseInsensitive)) {
+ // A forward In*/Is* callback must outrank a same-spelled
+ // built-in property once runtime compilation can see the
+ // subroutine. Signal the Joni adapter to retain its
+ // deferred match-any placeholder instead of falling
+ // through to Block/Script/binary lookup under the compile
+ // lock.
+ throw new IllegalArgumentException(
+ "Deferred user-defined Unicode property " + property);
+ }
+ if (isRepeatedUserDefinedPropertyPrefix(property)) {
+ String qualified = qualifyUserPropertyName(property);
+ throw new PerlCompilerException(
+ "Unknown user-defined property name \\p{"
+ + qualified + "}");
+ }
String looseUserProperty = loosePropertyName(property);
if (looseUserProperty.startsWith("isxposix")
|| looseUserProperty.startsWith("isposix")) {
@@ -1252,20 +1444,22 @@ static boolean isUserDefinedPropertyName(String property) {
return property != null && USER_DEFINED_PROPERTY_NAME.matcher(property).matches();
}
- static boolean mustPreserveUserDefinedProperty(
- String property, boolean caseInsensitive) {
- if (!isUserDefinedPropertyName(property)) return false;
- String subName = property.contains("::") ? property : "main::" + property;
- if (PerlLanguageProvider.COMPILE_LOCK.isHeldByCurrentThread()) return true;
- PerlRuntime runtime = PerlRuntime.currentOrNull();
- return runtime != null
- && (runtime.regexState().deferredUserUnicodeProperties.contains(subName)
- || runtime.regexState().userUnicodePropertyCache.containsKey(
- userPropertyCacheKey(subName, caseInsensitive)));
+ private static boolean isRepeatedUserDefinedPropertyPrefix(String property) {
+ int separator = property.lastIndexOf("::");
+ String localName = separator < 0 ? property : property.substring(separator + 2);
+ return localName.startsWith("Is_Is_") || localName.startsWith("In_In_");
}
static boolean isPerlBuiltInPropertyAlias(String property) {
if (property == null) return false;
+ if (isUserDefinedPropertyName(property)
+ && (mustDeferPotentialUserDefinedProperty(property, false)
+ || mustDeferPotentialUserDefinedProperty(property, true))) {
+ // A colliding In*/Is* callback inside a standard or extended
+ // character class must take the ordinary deferred-user-property
+ // path, not the built-in class shortcut.
+ return false;
+ }
property = canonicalPerlPosixPropertyAlias(property);
return isPerlSpecialPropertyAlias(property.trim())
|| !normalizePerlIsPropertyAssignment(property).equals(property)
@@ -1284,8 +1478,34 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
String property, boolean inCharacterClass, boolean caseInsensitive) {
if (property == null) return null;
property = property.trim();
- if (isUserDefinedPropertyName(property)
- && PerlRuntime.currentOrNull() != null) {
+ if (PERL_REJECTED_OBSOLETE_BINARY_ALIASES.contains(
+ loosePropertyName(property))) {
+ throw new IllegalArgumentException(
+ "Unsupported obsolete Unicode property: " + property);
+ }
+ if (property.matches(
+ "(?i)(?:sc|script|scx|script[-_ ]?extensions)\\s*(?:=|:)\\s*")) {
+ throw new IllegalArgumentException(
+ "Unicode property wildcard not terminated");
+ }
+ if (isPerlIsPrefixedNumericWildcard(property)) {
+ throw new IllegalArgumentException(
+ "Is-prefixed Numeric_Value properties do not accept wildcard values");
+ }
+ if (isPerlIsPrefixedJoiningGroupWildcard(property)) {
+ throw new IllegalArgumentException(
+ "Can't find Unicode property definition for Is-prefixed Joining_Group wildcard");
+ }
+ if (isPerlIsPrefixedBlockWildcard(property)) {
+ throw new IllegalArgumentException(
+ "Can't find Unicode property definition for Is-prefixed Block wildcard");
+ }
+ if (isPerlIsPrefixedScriptWildcard(property)) {
+ throw new IllegalArgumentException(
+ "Can't find Unicode property definition for Is-prefixed Script wildcard");
+ }
+ boolean userDefined = isUserDefinedPropertyName(property);
+ if (userDefined && PerlRuntime.currentOrNull() != null) {
String encoded = tryUserDefinedProperty(
property, new LinkedHashSet<>(), caseInsensitive);
if (encoded != null) {
@@ -1297,6 +1517,15 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
false);
}
}
+ if (userDefined && mustDeferPotentialUserDefinedProperty(
+ property, caseInsensitive)) {
+ return null;
+ }
+ if (userDefined && isRepeatedUserDefinedPropertyPrefix(property)) {
+ String qualified = qualifyUserPropertyName(property);
+ throw new PerlCompilerException(
+ "Unknown user-defined property name \\p{" + qualified + "}");
+ }
property = canonicalPerlPosixPropertyAlias(property);
property = normalizePerlIsPropertyAssignment(property);
int assignment = propertyValueDelimiter(property);
@@ -1341,10 +1570,6 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
return bareSet == null ? null : joniPropertyResult(
bareSet, perlUnicodeOnlyWideRanges(property, null), true);
}
- if (PerlUnicodeScriptData.canonicalValue(looseIsValue) != null
- || PerlUnicodeBlockData.set(looseIsValue) != null) {
- return null;
- }
UnicodeSet bareSet = resolvePerlBuiltInPropertyAlias(property);
return bareSet == null ? null : joniPropertyResult(
bareSet, perlUnicodeOnlyWideRanges(property, looseIsValue), true);
@@ -1354,29 +1579,43 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
PerlUnicodePropertyWildcard propertyWildcard =
resolvePerlUnicodePropertyWildcard(property);
if (propertyWildcard != null) {
- if (!propertyWildcard.caseFold && inCharacterClass) return null;
return joniPropertyResult(
propertyWildcard.set,
propertyWildcard.wideRanges,
propertyWildcard.caseFold);
}
+ if (perlNumericWildcardBody(value) != null) {
+ UnicodeSet wildcardSet;
+ try {
+ wildcardSet = resolvePerlBuiltInPropertyAlias(property);
+ } catch (IllegalArgumentException pinnedUnsupported) {
+ wildcardSet = resolveIcuEnumeratedPropertyWildcard(name, value);
+ if (wildcardSet == null) throw pinnedUnsupported;
+ }
+ if (wildcardSet == null) {
+ wildcardSet = resolveIcuEnumeratedPropertyWildcard(name, value);
+ }
+ if (wildcardSet != null) {
+ return joniPropertyResult(
+ wildcardSet, isGeneralCategoryProperty(name));
+ }
+ }
boolean caseFold;
PerlBinaryBooleanAssignment binaryAssignment =
perlBinaryBooleanAssignment(property);
+ UnicodeSet genericAssignment = binaryAssignment == null
+ ? resolveIcuPropertyAssignment(property) : null;
if (binaryAssignment != null) {
- // False remains a semantic negation and is translated by the
- // frontend so outer \P, /i folding, and signed-wide membership stay
- // symmetric. True can use the native positive range result here.
- if (!binaryAssignment.value) return null;
- caseFold = true;
+ // A false assignment is a complement after applying /i to the
+ // positive property. Folding the complement itself would add the
+ // excluded positive members back into the native class.
+ caseFold = binaryAssignment.value;
} else if (isGeneralCategoryProperty(name)) {
caseFold = true;
} else if (PerlUnicodeBlockData.isPropertyAlias(name)) {
- if (perlBlockWildcardBody(value) != null) return null;
caseFold = false;
} else if (PerlUnicodeScriptData.isScriptPropertyAlias(name)
|| PerlUnicodeScriptData.isScriptExtensionsPropertyAlias(name)) {
- if (perlNumericWildcardBody(value) != null) return null;
caseFold = false;
} else if (isCanonicalCombiningClassProperty(name)
|| PerlUnicodeBidiClassData.isPropertyAlias(name)
@@ -1385,7 +1624,6 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
caseFold = false;
} else if (PerlUnicodeNumericValueData.isPropertyAlias(name)
|| PerlUnicodeJoiningGroupData.isPropertyAlias(name)) {
- if (perlNumericWildcardBody(value) != null) return null;
caseFold = false;
} else if (isPerlWordBreakProperty(name)) {
if (resolvePerlWordBreakProperty(property) == null) return null;
@@ -1393,35 +1631,111 @@ static CharacterPropertyResolver.Result resolveJoniProperty(
} else if (isPerlSentenceBreakProperty(name)) {
if (resolvePerlSentenceBreakProperty(property) == null) return null;
caseFold = false;
+ } else if (isPerlLineBreakProperty(name)) {
+ if (resolvePerlLineBreakProperty(property) == null) return null;
+ caseFold = false;
} else if (isPerlVerticalOrientationProperty(name)) {
if (resolvePerlVerticalOrientationProperty(property) == null) return null;
caseFold = false;
} else if (isPerlAgeProperty(name)) {
- if (isPerlAgeWildcard(value)) return null;
+ caseFold = false;
+ } else if (genericAssignment != null) {
caseFold = false;
} else {
return null;
}
- // Joni currently folds a complete bracket expression as one class.
- // Keep no-fold families translated by the adapter inside brackets until
- // the AST can retain per-property fold policy through class composition.
- if (!caseFold && inCharacterClass) return null;
-
- UnicodeSet set = binaryAssignment == null
- ? resolvePerlBuiltInPropertyAlias(property)
- : resolveStandardPropertyAsSet(
- binaryAssignment.propertyName, new LinkedHashSet<>());
+ UnicodeSet set;
+ if (binaryAssignment != null) {
+ set = resolveStandardPropertyAsSet(
+ binaryAssignment.propertyName, new LinkedHashSet<>());
+ if (!binaryAssignment.value) {
+ set = new UnicodeSet(set);
+ if (caseInsensitive) {
+ set.closeOver(UnicodeSet.CASE_INSENSITIVE);
+ }
+ set.complement();
+ }
+ } else if (isPerlLineBreakProperty(name)) {
+ set = resolvePerlLineBreakProperty(property);
+ } else {
+ set = resolvePerlBuiltInPropertyAlias(property);
+ if (set == null) set = genericAssignment;
+ }
if (set == null) return null;
- long[] wideRanges = isPerlVerticalOrientationDefault(property)
+ long[] wideRanges = binaryAssignment != null && !binaryAssignment.value
+ ? new long[] {1, 0x110000L, Long.MAX_VALUE}
+ : isPerlVerticalOrientationDefault(property)
? new long[] {1, 0x110000L, Long.MAX_VALUE}
: null;
return joniPropertyResult(set, wideRanges, caseFold);
}
+ /** Resolves runtime-neutral ICU property/value pairs not needing Perl policy. */
+ private static UnicodeSet resolveIcuPropertyAssignment(String property) {
+ int assignment = propertyValueDelimiter(property);
+ if (assignment <= 0 || assignment == property.length() - 1) return null;
+ String name = property.substring(0, assignment).trim();
+ String value = property.substring(assignment + 1).trim();
+ try {
+ return new UnicodeSet().applyPropertyAlias(name, value);
+ } catch (IllegalArgumentException unsupported) {
+ return null;
+ }
+ }
+
+ /** Resolves an anchored Perl wildcard across an ICU enumerated property. */
+ private static UnicodeSet resolveIcuEnumeratedPropertyWildcard(
+ String propertyName, String wildcardValue) {
+ PerlPropertyValueMatcher wildcard =
+ compilePerlUnicodePropertyWildcard(wildcardValue);
+ if (wildcard == null) return null;
+ final int property;
+ try {
+ property = UCharacter.getPropertyEnum(propertyName.trim());
+ } catch (IllegalArgumentException unsupported) {
+ return null;
+ }
+
+ UnicodeSet result = new UnicodeSet();
+ int minimum = UCharacter.getIntPropertyMinValue(property);
+ int maximum = UCharacter.getIntPropertyMaxValue(property);
+ for (int value = minimum; value <= maximum; value++) {
+ boolean matches = false;
+ for (int nameChoice = UProperty.NameChoice.SHORT;
+ !matches && nameChoice <= UProperty.NameChoice.LONG;
+ nameChoice++) {
+ String alias = UCharacter.getPropertyValueName(
+ property, value, nameChoice);
+ matches = alias != null
+ && (wildcard.matchesPropertyValue(alias)
+ || wildcard.matchesPropertyValue(
+ loosePropertyName(alias)));
+ }
+ if (matches) result.addAll(
+ new UnicodeSet().applyIntPropertyValue(property, value));
+ }
+ if (result.isEmpty()) {
+ throw new IllegalArgumentException(
+ "No Unicode property value wildcard matches "
+ + propertyName.trim());
+ }
+ return result.freeze();
+ }
+
private static UnicodeSet resolvePerlCaseFoldableBareProperty(
String property, boolean caseInsensitive) {
+ try {
+ int propertyEnum = UCharacter.getPropertyEnum(property);
+ if (propertyEnum == UProperty.UPPERCASE
+ || propertyEnum == UProperty.LOWERCASE) {
+ return resolveStandardPropertyAsSet(
+ property, new LinkedHashSet<>());
+ }
+ } catch (IllegalArgumentException unsupportedAlias) {
+ // Perl-only compatibility aliases are handled below.
+ }
return switch (property) {
case "Uppercase", "XPosixLower", "XPosixUpper" ->
resolveStandardPropertyAsSet(
@@ -1471,6 +1785,38 @@ private static CharacterPropertyResolver.Result joniPropertyResult(
return new CharacterPropertyResolver.Result(ranges, wideRanges, caseFold);
}
+ static boolean mustDeferPotentialUserDefinedProperty(
+ String property, boolean caseInsensitive) {
+ if (!PerlLanguageProvider.COMPILE_LOCK.isHeldByCurrentThread()) {
+ return false;
+ }
+ PerlRuntime runtime = PerlRuntime.currentOrNull();
+ if (runtime == null) return false;
+ String subName = qualifyUserPropertyName(property);
+ if (runtime.regexState().userUnicodePropertyCache.containsKey(
+ userPropertyCacheKey(subName, caseInsensitive))) {
+ return false;
+ }
+ return !runtime.regexState().deferredUserUnicodeProperties.contains(
+ userPropertyAttemptKey(subName, caseInsensitive));
+ }
+
+ static boolean hasCachedUserDefinedProperty(String property) {
+ PerlRuntime runtime = PerlRuntime.currentOrNull();
+ if (runtime == null) return false;
+ String subName = qualifyUserPropertyName(property);
+ return runtime.regexState().userUnicodePropertyCache.containsKey(
+ userPropertyCacheKey(subName, false))
+ || runtime.regexState().userUnicodePropertyCache.containsKey(
+ userPropertyCacheKey(subName, true));
+ }
+
+ static boolean hasDefinedUserPropertySub(String property) {
+ if (PerlRuntime.currentOrNull() == null) return false;
+ String subName = qualifyUserPropertyName(property);
+ return GlobalVariable.isGlobalCodeRefDefined(subName);
+ }
+
private static boolean isPerlSpecialPropertyAlias(String property) {
return switch (property) {
case "lb=cr", "lb=CR",
@@ -1861,6 +2207,32 @@ private static boolean isPerlSentenceBreakProperty(String property) {
};
}
+ private static UnicodeSet resolvePerlLineBreakProperty(String property) {
+ int assignment = propertyValueDelimiter(property);
+ if (assignment <= 0 || assignment == property.length() - 1
+ || !isPerlLineBreakProperty(property.substring(0, assignment))) {
+ return null;
+ }
+ String value = property.substring(assignment + 1);
+ // ICU exposes Line_Break's data for matching but not through its
+ // property-value enum lookup. Preserve the canonical CR value aliases
+ // that Perl accepts, matching the existing frontend property adapter.
+ if (switch (loosePropertyName(value)) {
+ case "cr", "carriagereturn" -> true;
+ default -> false;
+ }) {
+ return new UnicodeSet('\r', '\r');
+ }
+ return unicodePropertyValueSet(UProperty.LINE_BREAK, value);
+ }
+
+ private static boolean isPerlLineBreakProperty(String property) {
+ return switch (loosePropertyName(property)) {
+ case "lb", "linebreak" -> true;
+ default -> false;
+ };
+ }
+
private static UnicodeSet resolvePerlVerticalOrientationProperty(String property) {
int assignment = propertyValueDelimiter(property);
if (assignment <= 0 || assignment == property.length() - 1
@@ -1917,6 +2289,9 @@ private static PerlUnicodePropertyWildcard resolvePerlUnicodePropertyWildcard(
if (isIcuBinaryPropertyAlias(name)) {
return resolvePerlBinaryPropertyWildcard(name, wildcard);
}
+ if (resolvePerlMissingBaseAlias(name) != null) {
+ return resolvePerlBinaryPropertyWildcard(name, wildcard);
+ }
if (isPerlWordBreakProperty(name)) {
return resolvePerlEnumeratedPropertyWildcard(
UProperty.WORD_BREAK, wildcard, "Word_Break",
@@ -2003,10 +2378,7 @@ private static PerlPropertyValueMatcher compilePerlUnicodePropertyWildcard(
String value) {
String body = perlNumericWildcardBody(value);
if (body == null) return null;
- if (body.indexOf('*') >= 0) {
- throw new IllegalArgumentException(
- "quantifier '*' is not allowed in Unicode property value wildcard");
- }
+ rejectForbiddenPerlUnicodePropertyWildcardSyntax(body);
try {
return PerlPropertyValueMatcher.compile(body);
} catch (RuntimeException invalidPattern) {
@@ -2031,14 +2403,19 @@ private static boolean isPerlNameProperty(String name) {
return trimmed.equals("name") || trimmed.equals("na");
}
+ static boolean isPerlStringProperty(String property) {
+ if (property == null) return false;
+ int assignment = propertyValueDelimiter(property);
+ return assignment > 0 && assignment < property.length() - 1
+ && isPerlNameProperty(property.substring(0, assignment))
+ && perlSlashWildcardBody(property.substring(assignment + 1)) == null;
+ }
+
private static PerlUnicodePropertyWildcard resolvePerlNamePropertyWildcard(
String propertyName, String value) {
String body = perlSlashWildcardBody(value);
if (body == null) return null;
- if (body.indexOf('*') >= 0) {
- throw new IllegalArgumentException(
- "quantifier '*' is not allowed in Unicode property value wildcard");
- }
+ rejectForbiddenPerlUnicodePropertyWildcardSyntax(body);
final PerlPropertyValueMatcher wildcard;
try {
@@ -2095,7 +2472,8 @@ private static PerlBinaryBooleanAssignment perlBinaryBooleanAssignment(
String name = normalized.substring(0, assignment);
Boolean value = perlBooleanPropertyValue(
normalized.substring(assignment + 1));
- if (value == null || !isIcuBinaryPropertyAlias(name)) return null;
+ if (value == null || !isIcuBinaryPropertyAlias(name)
+ && resolvePerlMissingBaseAlias(name) == null) return null;
return new PerlBinaryBooleanAssignment(name, value);
}
@@ -2119,6 +2497,8 @@ private static UnicodeSet resolvePerlMissingBaseAlias(String alias) {
case "horizspace" -> PERL_HORIZONTAL_SPACE_SET;
case "kehnomirror" -> PerlUnicodeUnikemetData.noMirror();
case "kehnorotate" -> PerlUnicodeUnikemetData.noRotate();
+ case "perlidstart" -> perlIdStartSet();
+ case "perlidcont" -> perlIdContinueSet();
default -> null;
};
}
@@ -2163,16 +2543,58 @@ private static PerlBarePropertyAlias resolvePerlPosixCompatibilityAlias(
PERL_ASCII_SPACE_SET, false);
case "perlword" -> new PerlBarePropertyAlias(
PERL_ASCII_WORD_SET, false);
- case "xperlspace", "spaceperl" -> new PerlBarePropertyAlias(
+ case "xperlspace", "spaceperl", "xposixspace" -> new PerlBarePropertyAlias(
PERL_UNICODE_SPACE_SET, false);
case "whitespace" -> new PerlBarePropertyAlias(
PERL_UNICODE_SPACE_SET, false);
+ case "xposixalnum" -> new PerlBarePropertyAlias(
+ new UnicodeSet().applyPropertyAlias("Alphabetic", "True")
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Nd"))
+ .freeze(), false);
+ case "xposixalpha" -> new PerlBarePropertyAlias(
+ new UnicodeSet().applyPropertyAlias("Alphabetic", "True")
+ .freeze(), false);
+ case "xposixblank" -> new PerlBarePropertyAlias(
+ PERL_UNICODE_SPACE_SET, false);
+ case "xposixcntrl" -> new PerlBarePropertyAlias(
+ PerlUnicodeGeneralCategoryData.resolve("Cc"), false);
+ case "xposixdigit" -> new PerlBarePropertyAlias(
+ PerlUnicodeGeneralCategoryData.resolve("Nd"), false);
case "xposixgraph" -> new PerlBarePropertyAlias(
PERL_POSIX_GRAPH_SET, false);
case "xposixprint", "print" -> new PerlBarePropertyAlias(
PERL_POSIX_PRINT_SET, false);
+ case "xposixpunct" -> new PerlBarePropertyAlias(
+ PerlUnicodeGeneralCategoryData.resolve("P"), false);
+ case "xposixword" -> new PerlBarePropertyAlias(
+ PERL_WORD_SET, false);
case "xposixxdigit", "xdigit" -> new PerlBarePropertyAlias(
PERL_POSIX_XDIGIT_SET, false);
+ case "posixalnum" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('0', '9').add('A', 'Z').add('a', 'z')
+ .freeze(), false);
+ case "posixalpha" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('A', 'Z').add('a', 'z').freeze(), false);
+ case "posixblank" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('\t').add(' ').freeze(), false);
+ case "posixcntrl" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add(0, 0x1f).add(0x7f).freeze(), false);
+ case "posixdigit" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('0', '9').freeze(), false);
+ case "posixgraph" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('!', '~').freeze(), false);
+ case "posixprint" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add(' ', '~').freeze(), false);
+ case "posixpunct" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('!', '/').add(':', '@')
+ .add('[', '`').add('{', '~').freeze(), false);
+ case "posixspace" -> new PerlBarePropertyAlias(
+ PERL_ASCII_SPACE_SET, false);
+ case "posixword" -> new PerlBarePropertyAlias(
+ PERL_ASCII_WORD_SET, false);
+ case "posixxdigit" -> new PerlBarePropertyAlias(
+ new UnicodeSet().add('0', '9').add('A', 'F').add('a', 'f')
+ .freeze(), false);
default -> null;
};
}
@@ -2223,6 +2645,24 @@ private static UnicodeSet buildPerlHorizontalSpaceSet() {
.freeze();
}
+ private static UnicodeSet perlIdStartSet() {
+ return new UnicodeSet()
+ .applyPropertyAlias("XID_Start", "True")
+ .add('_')
+ .freeze();
+ }
+
+ private static UnicodeSet perlIdContinueSet() {
+ return new UnicodeSet()
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("L"))
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Nl"))
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Nd"))
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Mn"))
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Mc"))
+ .addAll(PerlUnicodeGeneralCategoryData.resolve("Pc"))
+ .freeze();
+ }
+
private static UnicodeSet buildPerlUnicodeSpaceSet() {
return new UnicodeSet()
.add(0x0009, 0x000D).add(0x0020).add(0x0085).add(0x00A0)
@@ -2636,6 +3076,10 @@ private static UnicodeSet resolvePerlNumericValue(String value) {
private static String perlNumericWildcardBody(String value) {
String trimmed = value.trim();
+ if (trimmed.length() > 2 && trimmed.startsWith(":")
+ && trimmed.endsWith(":")) {
+ return trimmed.substring(1, trimmed.length() - 1);
+ }
if (trimmed.startsWith("/\\A") && trimmed.endsWith("\\z/")
&& trimmed.length() > 6) {
return "\\A" + trimmed.substring(3, trimmed.length() - 3) + "\\z";
@@ -2647,6 +3091,63 @@ private static String perlNumericWildcardBody(String value) {
return null;
}
+ /**
+ * Perl deliberately permits only a restricted regex dialect inside a
+ * Unicode property-value wildcard. Diagnose forbidden constructs against
+ * the wildcard body itself, matching the source Perl uses for its marker.
+ */
+ private static void rejectForbiddenPerlUnicodePropertyWildcardSyntax(
+ String body) {
+ int offset = body.indexOf('*');
+ if (offset >= 0) {
+ throw propertyWildcardSyntaxError(body, offset + 1,
+ "Use of quantifier '*' is not allowed in Unicode property wildcard subpatterns");
+ }
+ offset = body.indexOf("\\G");
+ if (offset >= 0) {
+ throw propertyWildcardSyntaxError(body, offset + 2,
+ "Use of '\\G' is not allowed in Unicode property wildcard subpatterns");
+ }
+ for (String escape : new String[] {"\\pS", "\\PS"}) {
+ offset = body.indexOf(escape);
+ if (offset >= 0) {
+ throw propertyWildcardSyntaxError(body, offset + escape.length(),
+ "Use of '" + escape
+ + "' is not allowed in Unicode property wildcard subpatterns");
+ }
+ }
+ java.util.regex.Matcher modifier = Pattern.compile(
+ "\\(\\?(-?[A-Za-z]+)\\)").matcher(body);
+ while (modifier.find()) {
+ String modifiers = modifier.group(1);
+ String prohibited = null;
+ int marker = modifier.end();
+ if (modifiers.startsWith("-m")) {
+ prohibited = "-m";
+ marker = modifier.end() - 1;
+ } else {
+ for (char candidate : new char[] {'g', 'a', 'u', 'd', 'l'}) {
+ if (modifiers.indexOf(candidate) >= 0) {
+ prohibited = String.valueOf(candidate);
+ if (candidate == 'g') marker = modifier.end() - 1;
+ break;
+ }
+ }
+ }
+ if (prohibited != null) {
+ throw propertyWildcardSyntaxError(body, marker,
+ "Use of modifier '" + prohibited
+ + "' is not allowed in Unicode property wildcard subpatterns");
+ }
+ }
+ }
+
+ private static PerlCompilerException propertyWildcardSyntaxError(
+ String body, int offset, String message) {
+ return new PerlCompilerException(
+ RegexDiagnosticFormatter.markedPerl(body, offset, message));
+ }
+
private static short perlNumericValueIndex(String value) {
try {
if (value.indexOf('/') >= 0) {
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/DestroyDispatch.java b/src/main/java/org/perlonjava/runtime/runtimetypes/DestroyDispatch.java
index a57cca866c..4865f8695d 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/DestroyDispatch.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/DestroyDispatch.java
@@ -149,10 +149,13 @@ public static boolean classNeedsWalkerGate(int blessId) {
public static boolean classHasDestroy(int blessId, String className) {
LifecycleRuntimeState state = state();
int idx = Math.abs(blessId);
- if (state.destroyClasses.get(idx)) return true;
+ if (state.destroyClassesChecked.get(idx)) {
+ return state.destroyClasses.get(idx);
+ }
// First time for this class — check hierarchy.
// findMethodInHierarchy already falls through to AUTOLOAD if no explicit DESTROY exists.
RuntimeScalar m = InheritanceResolver.findMethodInHierarchy("DESTROY", className, null, 0);
+ state.destroyClassesChecked.set(idx);
if (m != null) {
state.destroyClasses.set(idx);
// Activate the mortal mechanism now that we know DESTROY classes exist
@@ -168,6 +171,7 @@ public static boolean classHasDestroy(int blessId, String className) {
*/
public static void invalidateCache() {
state().destroyClasses.clear();
+ state().destroyClassesChecked.clear();
state().destroyMethodCache.clear();
}
@@ -270,6 +274,15 @@ public static void callDestroy(RuntimeBase referent) {
}
} else if (referent instanceof org.perlonjava.runtime.regex.RuntimeRegex regex) {
regex.releaseExecutableCallbacks();
+ if (referent.blessId == 0) {
+ int regexpBlessId = NameNormalizer.getBlessId("Regexp");
+ if (classHasDestroy(regexpBlessId, "Regexp")) {
+ // qr// is implicitly in class Regexp. Keep live wrappers
+ // unblessed for selective-lifetime compatibility, and
+ // expose the implicit class only at final destruction.
+ referent.blessId = regexpBlessId;
+ }
+ }
}
String className = NameNormalizer.getBlessStr(referent.blessId);
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java
index 377b44115e..7f1206c0ac 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java
@@ -18,6 +18,7 @@ public class ErrorMessageUtil {
private int tokenIndex;
private int lastLineNumber;
private volatile int[] physicalLineNumbers;
+ private volatile SourceDirectiveIndex sourceDirectiveIndex;
/**
* Constructs an ErrorMessageUtil with the specified file name and list of tokens.
@@ -42,6 +43,7 @@ public ErrorMessageUtil(String fileName, List tokens) {
public void updateTokens(List newTokens) {
this.tokens = newTokens;
this.physicalLineNumbers = null;
+ this.sourceDirectiveIndex = null;
}
/**
@@ -518,91 +520,165 @@ private int[] buildPhysicalLineNumbers() {
}
public SourceLocation getSourceLocationAccurate(int index) {
- String currentFileName = originalFileName;
- int lineNumber = 1;
+ if (index < 0 || tokens == null || tokens.isEmpty()) {
+ return new SourceLocation(originalFileName, 1);
+ }
- boolean atBeginningOfLine = true;
- int i = 0;
- while (i <= index && i < tokens.size()) {
- LexerToken tok = tokens.get(i);
- if (tok.type == LexerTokenType.EOF) {
- break;
+ int boundedIndex = Math.min(index, tokens.size() - 1);
+ int physicalLine = getLineNumberAccurate(boundedIndex);
+ SourceDirectiveIndex directives = sourceDirectiveIndex();
+ int directive = directives.floor(boundedIndex);
+ if (directive < 0) {
+ return new SourceLocation(originalFileName, physicalLine);
+ }
+ int logicalLine = directives.logicalLines[directive]
+ + physicalLine - directives.physicalLines[directive];
+ return new SourceLocation(directives.fileNames[directive], logicalLine);
+ }
+
+ private SourceDirectiveIndex sourceDirectiveIndex() {
+ SourceDirectiveIndex result = sourceDirectiveIndex;
+ if (result == null) {
+ synchronized (this) {
+ result = sourceDirectiveIndex;
+ if (result == null) {
+ result = buildSourceDirectiveIndex();
+ sourceDirectiveIndex = result;
+ }
}
+ }
+ return result;
+ }
- if (tok.type == LexerTokenType.NEWLINE) {
- lineNumber++;
+ private SourceDirectiveIndex buildSourceDirectiveIndex() {
+ ArrayList tokenIndexes = new ArrayList<>();
+ ArrayList physicalLines = new ArrayList<>();
+ ArrayList logicalLines = new ArrayList<>();
+ ArrayList fileNames = new ArrayList<>();
+ String currentFileName = originalFileName;
+ int physicalLine = 1;
+ boolean atBeginningOfLine = true;
+
+ for (int i = 0; i < tokens.size(); i++) {
+ LexerToken token = tokens.get(i);
+ if (token.type == LexerTokenType.EOF) break;
+ if (token.type == LexerTokenType.NEWLINE) {
+ physicalLine++;
atBeginningOfLine = true;
- i++;
continue;
}
-
- if (atBeginningOfLine && tok.type == LexerTokenType.OPERATOR && tok.text.equals("#")) {
- int j = i + 1;
- while (j < tokens.size() && tokens.get(j).type == LexerTokenType.WHITESPACE) {
- j++;
- }
- if (j < tokens.size() && tokens.get(j).type == LexerTokenType.IDENTIFIER && tokens.get(j).text.equals("line")) {
- j++;
- while (j < tokens.size() && tokens.get(j).type == LexerTokenType.WHITESPACE) {
- j++;
- }
- if (j < tokens.size() && tokens.get(j).type == LexerTokenType.NUMBER) {
- int directiveLine = -1;
- try {
- directiveLine = Integer.parseInt(tokens.get(j).text);
- } catch (NumberFormatException e) {
- directiveLine = -1;
- }
- j++;
- while (j < tokens.size() && tokens.get(j).type == LexerTokenType.WHITESPACE) {
- j++;
- }
- if (j < tokens.size() && tokens.get(j).type == LexerTokenType.OPERATOR && tokens.get(j).text.equals("\"")) {
- // Quoted filename: #line N "filename"
- j++;
- StringBuilder filenameBuilder = new StringBuilder();
- while (j < tokens.size() && !(tokens.get(j).type == LexerTokenType.OPERATOR && tokens.get(j).text.equals("\""))) {
- filenameBuilder.append(tokens.get(j).text);
- j++;
- }
- if (j < tokens.size() && tokens.get(j).type == LexerTokenType.OPERATOR && tokens.get(j).text.equals("\"")) {
- String directiveFile = filenameBuilder.toString();
- if (!directiveFile.isEmpty()) {
- currentFileName = directiveFile;
- }
- }
- } else if (j < tokens.size() && isUnquotedLineFilenameToken(tokens.get(j))) {
- // Unquoted filename: #line N filename
- // Perl allows unquoted filenames such as lib/Foo/Bar.pm.
- StringBuilder filenameBuilder = new StringBuilder();
- while (j < tokens.size() && isUnquotedLineFilenameToken(tokens.get(j))) {
- filenameBuilder.append(tokens.get(j).text);
- j++;
- }
- String directiveFile = filenameBuilder.toString();
- if (!directiveFile.isEmpty()) {
- currentFileName = directiveFile;
- }
- }
-
- if (directiveLine >= 0) {
- // The directive applies to the following line.
- // Perl allows `#line 0` (the next line becomes line 0);
- // -M/-m import injection relies on this to make caller()
- // report line 0, matching real Perl behavior.
- lineNumber = directiveLine - 1;
- }
+ if (atBeginningOfLine && token.type == LexerTokenType.OPERATOR
+ && token.text.equals("#")) {
+ ParsedLineDirective directive = parseLineDirective(i);
+ if (directive != null) {
+ if (directive.fileName != null) {
+ currentFileName = directive.fileName;
}
+ tokenIndexes.add(i);
+ physicalLines.add(physicalLine);
+ // A directive applies to the following source line. The
+ // tokens on the directive line itself retain the legacy
+ // mapping to one less than the requested logical line.
+ logicalLines.add(directive.lineNumber - 1);
+ fileNames.add(currentFileName);
}
}
-
- if (tok.type != LexerTokenType.WHITESPACE) {
+ if (token.type != LexerTokenType.WHITESPACE) {
atBeginningOfLine = false;
}
- i++;
}
+ return new SourceDirectiveIndex(
+ tokenIndexes.stream().mapToInt(Integer::intValue).toArray(),
+ physicalLines.stream().mapToInt(Integer::intValue).toArray(),
+ logicalLines.stream().mapToInt(Integer::intValue).toArray(),
+ fileNames.toArray(String[]::new));
+ }
- return new SourceLocation(currentFileName, lineNumber);
+ private ParsedLineDirective parseLineDirective(int hashIndex) {
+ int index = hashIndex + 1;
+ while (index < tokens.size()
+ && tokens.get(index).type == LexerTokenType.WHITESPACE) {
+ index++;
+ }
+ if (index >= tokens.size()
+ || tokens.get(index).type != LexerTokenType.IDENTIFIER
+ || !tokens.get(index).text.equals("line")) {
+ return null;
+ }
+ index++;
+ while (index < tokens.size()
+ && tokens.get(index).type == LexerTokenType.WHITESPACE) {
+ index++;
+ }
+ if (index >= tokens.size()
+ || tokens.get(index).type != LexerTokenType.NUMBER) {
+ return null;
+ }
+ final int lineNumber;
+ try {
+ lineNumber = Integer.parseInt(tokens.get(index).text);
+ } catch (NumberFormatException invalidLine) {
+ return null;
+ }
+ if (lineNumber < 0) return null;
+
+ index++;
+ while (index < tokens.size()
+ && tokens.get(index).type == LexerTokenType.WHITESPACE) {
+ index++;
+ }
+ String directiveFile = null;
+ if (index < tokens.size()
+ && tokens.get(index).type == LexerTokenType.OPERATOR
+ && tokens.get(index).text.equals("\"")) {
+ index++;
+ StringBuilder filename = new StringBuilder();
+ while (index < tokens.size()
+ && !(tokens.get(index).type == LexerTokenType.OPERATOR
+ && tokens.get(index).text.equals("\""))) {
+ filename.append(tokens.get(index).text);
+ index++;
+ }
+ if (index < tokens.size()
+ && tokens.get(index).type == LexerTokenType.OPERATOR
+ && tokens.get(index).text.equals("\"")
+ && !filename.isEmpty()) {
+ directiveFile = filename.toString();
+ }
+ } else if (index < tokens.size()
+ && isUnquotedLineFilenameToken(tokens.get(index))) {
+ StringBuilder filename = new StringBuilder();
+ while (index < tokens.size()
+ && isUnquotedLineFilenameToken(tokens.get(index))) {
+ filename.append(tokens.get(index).text);
+ index++;
+ }
+ if (!filename.isEmpty()) directiveFile = filename.toString();
+ }
+ return new ParsedLineDirective(lineNumber, directiveFile);
+ }
+
+ private record ParsedLineDirective(int lineNumber, String fileName) {
+ }
+
+ private record SourceDirectiveIndex(
+ int[] tokenIndexes,
+ int[] physicalLines,
+ int[] logicalLines,
+ String[] fileNames) {
+ private int floor(int tokenIndex) {
+ int low = 0;
+ int high = tokenIndexes.length - 1;
+ while (low <= high) {
+ int middle = (low + high) >>> 1;
+ if (tokenIndexes[middle] <= tokenIndex) {
+ low = middle + 1;
+ } else {
+ high = middle - 1;
+ }
+ }
+ return high;
+ }
}
public int getSourceOffset(int index) {
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java
index 1f1a1a74f5..02e2e3ed4d 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java
@@ -331,6 +331,7 @@ public static void initializeGlobals(CompilerOptions compilerOptions) {
Parent.initialize();
Lib.initialize();
Re.initialize();
+ TieHashNamedCapture.initialize();
// Cwd.initialize(); // Use Perl Cwd.pm instead (has pure Perl fallbacks)
FileSpec.initialize();
// Deferred to XSLoader::load() for faster startup - only loaded when actually used:
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java
index 16392005f1..20b2f1148b 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalVariable.java
@@ -1442,8 +1442,10 @@ private static RuntimeHash createNamedGlobalHash(java.util.List names) {
// accessed. A glob alias of %! must materialize the same magic.
hash.elements = new ErrnoHash();
} else if (names.contains("main::+")) {
+ hash.type = RuntimeHash.READONLY_HASH;
hash.elements = new HashSpecialVariable(HashSpecialVariable.Id.CAPTURE);
} else if (names.contains("main::-")) {
+ hash.type = RuntimeHash.READONLY_HASH;
hash.elements = new HashSpecialVariable(HashSpecialVariable.Id.CAPTURE_ALL);
}
return hash;
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java
index 3f214250d8..7598ae3aec 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java
@@ -301,12 +301,17 @@ private static RuntimeScalar captureAllArrayRef(List captures) {
private static RuntimeScalar firstDefinedCapture(List captures) {
for (String v : captures) {
if (v != null) {
- return new RuntimeScalar(v);
+ return new RuntimeScalarReadOnly(v);
}
}
return scalarUndef;
}
+ /** Return the named-capture view represented by this magic hash. */
+ public Id captureMode() {
+ return mode == Id.CAPTURE || mode == Id.CAPTURE_ALL ? mode : null;
+ }
+
@Override
public RuntimeScalar put(String key, RuntimeScalar value) {
if (this.mode == Id.STASH) {
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/LifecycleRuntimeState.java b/src/main/java/org/perlonjava/runtime/runtimetypes/LifecycleRuntimeState.java
index 429b8a7a06..580c46b670 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/LifecycleRuntimeState.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/LifecycleRuntimeState.java
@@ -40,6 +40,7 @@ final class LifecycleRuntimeState {
volatile boolean weakRefsExist;
final BitSet destroyClasses = new BitSet();
+ final BitSet destroyClassesChecked = new BitSet();
final ConcurrentHashMap destroyMethodCache = new ConcurrentHashMap<>();
final Set destroyableObjects =
Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>()));
@@ -80,6 +81,7 @@ void clear() {
referentToWeakRefs.clear();
weakRefsExist = false;
destroyClasses.clear();
+ destroyClassesChecked.clear();
destroyMethodCache.clear();
destroyableObjects.clear();
currentDestroyTarget = null;
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java
index cd17d16c97..6c0c4991bc 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlCompilerException.java
@@ -65,6 +65,12 @@ public PerlCompilerException(String message) {
/** Attach the compiler's exact source location without a parser "near" clause. */
public static PerlCompilerException withSourceLocation(
int tokenIndex, String message, ErrorMessageUtil errorMessageUtil) {
+ if (message != null && message.endsWith(", within pattern\n")) {
+ // Some lexical regex diagnostics already carry Perl's exact
+ // source location. Reattaching the token location would produce
+ // "within pattern at ... line ..." a second time.
+ return new PerlCompilerException(message);
+ }
ErrorMessageUtil.SourceLocation location =
errorMessageUtil.getSourceLocationAccurate(tokenIndex);
String coreMessage = message != null && message.endsWith("\n")
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlJavaUnimplementedException.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlJavaUnimplementedException.java
index cc4401570f..748457ee94 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlJavaUnimplementedException.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlJavaUnimplementedException.java
@@ -2,7 +2,7 @@
/**
* Exception thrown when PerlOnJava encounters a Perl feature that is not yet implemented
- * in the Java regex engine or other Java components. This is distinct from PerlCompilerException
+ * in the regex engine or other Java components. This is distinct from PerlCompilerException
* which represents actual Perl syntax errors.
*
* This exception is used to distinguish between:
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRuntime.java b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRuntime.java
index ef00524eb7..f66fc0790c 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRuntime.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/PerlRuntime.java
@@ -460,6 +460,7 @@ private RootSnapshot snapshotCloneInternal(
RuntimeGraphCloner cloner = new RuntimeGraphCloner(this, child, skipped);
try (Binding ignored = bind()) {
globalState.snapshotInto(child.globalState, cloner);
+ cloner.cloneCompilationHints(compilationState, child.compilationState);
runtimeCodeState.snapshotCompiledMetadataInto(child.runtimeCodeState);
regexState.snapshotInto(child.regexState);
}
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RegexCallbackMutationSnapshot.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RegexCallbackMutationSnapshot.java
new file mode 100644
index 0000000000..c674cc99fa
--- /dev/null
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RegexCallbackMutationSnapshot.java
@@ -0,0 +1,89 @@
+package org.perlonjava.runtime.runtimetypes;
+
+import java.util.ArrayDeque;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/** Backtracking savepoint for mutations made by a plain {@code (?{...})} block. */
+public final class RegexCallbackMutationSnapshot {
+ private final IdentityHashMap scalars = new IdentityHashMap<>();
+ private final IdentityHashMap arrays = new IdentityHashMap<>();
+ private final IdentityHashMap hashes = new IdentityHashMap<>();
+ private final IdentityHashMap seen = new IdentityHashMap<>();
+
+ private RegexCallbackMutationSnapshot(RuntimeCode callback) {
+ ArrayDeque work = new ArrayDeque<>();
+ if (callback.closedOverVariables != null) {
+ addAll(work, callback.closedOverVariables.values());
+ }
+ addAll(work, callback.capturedScalars);
+ addAll(work, callback.capturedAggregates);
+ if (callback.ourVariableRegistry != null) {
+ for (Map.Entry entry : callback.ourVariableRegistry.entrySet()) {
+ String name = entry.getKey();
+ String packageName = entry.getValue();
+ if (name == null || name.length() < 2 || packageName == null) continue;
+ String fullName = packageName + "::" + name.substring(1);
+ RuntimeBase cell = switch (name.charAt(0)) {
+ case '@' -> GlobalVariable.getGlobalArray(fullName);
+ case '%' -> GlobalVariable.getGlobalHash(fullName);
+ default -> GlobalVariable.getGlobalVariable(fullName);
+ };
+ work.add(cell);
+ }
+ }
+ capture(work);
+ }
+
+ public static RegexCallbackMutationSnapshot capture(RuntimeCode callback) {
+ return new RegexCallbackMutationSnapshot(callback);
+ }
+
+ private void capture(ArrayDeque work) {
+ while (!work.isEmpty()) {
+ RuntimeBase value = work.removeLast();
+ if (value == null || seen.put(value, Boolean.TRUE) != null) continue;
+ if (value instanceof RuntimeScalar scalar) {
+ Object state = scalar.snapshotRegexMutationState();
+ if (state != null) scalars.put(scalar, state);
+ if (scalar.value instanceof RuntimeArray array) work.add(array);
+ else if (scalar.value instanceof RuntimeHash hash) work.add(hash);
+ else if (scalar.value instanceof RuntimeScalar nested) work.add(nested);
+ } else if (value instanceof RuntimeArray array) {
+ Object state = array.snapshotRegexMutationState();
+ if (state == null) continue;
+ arrays.put(array, state);
+ addAll(work, array.elements);
+ } else if (value instanceof RuntimeHash hash) {
+ Object state = hash.snapshotRegexMutationState();
+ if (state == null) continue;
+ hashes.put(hash, state);
+ addAll(work, hash.elements.values());
+ }
+ }
+ }
+
+ public void restore() {
+ for (Map.Entry entry : arrays.entrySet()) {
+ entry.getKey().restoreRegexMutationState(entry.getValue());
+ }
+ for (Map.Entry entry : hashes.entrySet()) {
+ entry.getKey().restoreRegexMutationState(entry.getValue());
+ }
+ for (Map.Entry entry : scalars.entrySet()) {
+ entry.getKey().restoreRegexMutationState(entry.getValue());
+ }
+ MortalList.flush();
+ }
+
+ private static void addAll(ArrayDeque work,
+ Iterable extends RuntimeBase> values) {
+ if (values == null) return;
+ for (RuntimeBase value : values) if (value != null) work.add(value);
+ }
+
+ private static void addAll(ArrayDeque work, RuntimeBase[] values) {
+ if (values == null) return;
+ for (RuntimeBase value : values) if (value != null) work.add(value);
+ }
+}
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java
index 2fb46e5534..8a60151c7d 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java
@@ -1708,6 +1708,14 @@ public Iterator iterator() {
return new RuntimeArrayIterator();
}
+ @Override
+ public Iterator foreachAliasIterator() {
+ // Plain Perl arrays are live during foreach: structural changes alter
+ // the remaining membership. Tied arrays retain FETCH-based snapshot
+ // behavior because their backing TieArray is not directly iterable.
+ return type == TIED_ARRAY ? getArrayOfAlias().iterator() : iterator();
+ }
+
/**
* Returns a string representation of the array reference.
*
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java
index 8e2c058aa5..135e5be019 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java
@@ -491,6 +491,14 @@ public RuntimeArray getArrayOfAlias() {
return arr;
}
+ /**
+ * Returns the iterator used by an implicit-topic foreach loop.
+ * Non-array list expressions retain their evaluated membership snapshot.
+ */
+ public Iterator foreachAliasIterator() {
+ return getArrayOfAlias().iterator();
+ }
+
/**
* Retrieves the argument array for {@code goto &sub}. Most values use
* ordinary aliasing, but RuntimeArray overrides this to transfer ownership
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
index fe422a748f..2230341947 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
@@ -21,6 +21,7 @@
import org.perlonjava.runtime.ForkOpenCompleteException;
import org.perlonjava.runtime.HintHashRegistry;
import org.perlonjava.runtime.WarningBitsRegistry;
+import org.perlonjava.runtime.regex.RegexQuoteMeta;
import org.perlonjava.runtime.mro.InheritanceResolver;
import org.perlonjava.runtime.debugger.DebugHooks;
import org.perlonjava.runtime.debugger.DebugState;
@@ -279,7 +280,8 @@ public static void pushActiveCode(RuntimeCode code) {
PerlRuntime runtime = PerlRuntime.current();
ExecutionRuntimeState executionState = runtime.executionState();
activeCodeStack(executionState).push(code);
- if (runtime.runtimeCodeState().lexicalAliasSupportEnabled) {
+ if (runtime.runtimeCodeState().lexicalAliasSupportEnabled
+ || code.tracksRuntimeRegexLexicals) {
activeLexicalFrames(executionState).push(
new ActiveLexicalFrame(code, new HashMap<>()));
}
@@ -288,7 +290,8 @@ public static void pushActiveCode(RuntimeCode code) {
public static void popActiveCode(RuntimeCode code) {
PerlRuntime runtime = PerlRuntime.current();
ExecutionRuntimeState executionState = runtime.executionState();
- if (runtime.runtimeCodeState().lexicalAliasSupportEnabled) {
+ if (runtime.runtimeCodeState().lexicalAliasSupportEnabled
+ || code.tracksRuntimeRegexLexicals) {
Deque frames = activeLexicalFrames(executionState);
if (!frames.isEmpty() && frames.peek().code() == code) {
frames.pop();
@@ -351,7 +354,8 @@ private static boolean sameLogicalCode(RuntimeCode left, RuntimeCode right) {
private static void registerActiveLexical(
RuntimeCode code, String variableName, RuntimeBase cell) {
PerlRuntime runtime = PerlRuntime.current();
- if (!runtime.runtimeCodeState().lexicalAliasSupportEnabled) return;
+ if (!runtime.runtimeCodeState().lexicalAliasSupportEnabled
+ && (code == null || !code.tracksRuntimeRegexLexicals)) return;
Deque frames = activeLexicalFrames(runtime.executionState());
for (ActiveLexicalFrame frame : frames) {
if (sameLogicalCode(frame.code(), code)) {
@@ -399,7 +403,8 @@ public static String findActiveLexicalName(RuntimeBase cell) {
public static Map snapshotActiveLexicals(RuntimeCode code) {
if (code == null) return Collections.emptyMap();
PerlRuntime runtime = PerlRuntime.current();
- if (!runtime.runtimeCodeState().lexicalAliasSupportEnabled) {
+ if (!runtime.runtimeCodeState().lexicalAliasSupportEnabled
+ && !code.tracksRuntimeRegexLexicals) {
return Collections.emptyMap();
}
for (ActiveLexicalFrame frame : activeLexicalFrames(runtime.executionState())) {
@@ -985,6 +990,10 @@ public static void registerPadConstants(String className, RuntimeBase[] constant
// A callback compiled as part of qr// contributes Perl's anonymous-regex
// frame to caller(), unlike an inline m// callback pseudo-block.
public boolean isQuotedRegexCallback = false;
+ // Executable regex interpolation may stringify an overloaded value into
+ // source that references any lexical in the containing CV. Track live pad
+ // cells for these rare CVs without enabling global Devel::LexAlias support.
+ public boolean tracksRuntimeRegexLexicals = false;
// Implementation callbacks such as map/grep do not introduce a Perl
// subroutine scope, so their __SUB__ comes from the enclosing RuntimeCode.
public boolean inheritsSelfReference = false;
@@ -1196,6 +1205,14 @@ public static RuntimeScalar attachLexicalVariableNames(
return codeRef;
}
+ /** Mark a generated CODE value as requiring live lexical regex-source access. */
+ public static RuntimeScalar markRuntimeRegexLexicals(RuntimeScalar codeRef) {
+ if (codeRef != null && codeRef.value instanceof RuntimeCode code) {
+ code.tracksRuntimeRegexLexicals = true;
+ }
+ return codeRef;
+ }
+
/** Devel::LexAlias replacements applied when a lexical is instantiated. */
public Map lexicalAliases;
@@ -1432,6 +1449,7 @@ public RuntimeCode cloneForClosure() {
clone.cvStartLine = this.cvStartLine;
clone.isRegexCallbackPseudoBlock = this.isRegexCallbackPseudoBlock;
clone.isQuotedRegexCallback = this.isQuotedRegexCallback;
+ clone.tracksRuntimeRegexLexicals = this.tracksRuntimeRegexLexicals;
clone.deparseSourceText = this.deparseSourceText;
clone.deparseFlags = this.deparseFlags;
clone.deparseSourceOffset = this.deparseSourceOffset;
@@ -1990,6 +2008,11 @@ public void adoptDefinitionFrom(RuntimeCode codeFrom) {
this.isMapGrepBlock = codeFrom.isMapGrepBlock;
this.isRegexCallbackPseudoBlock = codeFrom.isRegexCallbackPseudoBlock;
this.isQuotedRegexCallback = codeFrom.isQuotedRegexCallback;
+ // A lazy named-CV placeholder is annotated from its parsed body before
+ // the backend executable exists. Materialization must not overwrite
+ // that source-policy marker with the backend object's default false.
+ this.tracksRuntimeRegexLexicals = this.tracksRuntimeRegexLexicals
+ || codeFrom.tracksRuntimeRegexLexicals;
this.inheritsSelfReference = codeFrom.inheritsSelfReference;
this.isEvalBlock = codeFrom.isEvalBlock;
this.explicitlyRenamed = codeFrom.explicitlyRenamed;
@@ -2870,6 +2893,7 @@ public static RuntimeList evalStringWithInterpreter(
// Create parser context
ScopedSymbolTable parseSymbolTable = capturedSymbolTable.snapShot();
+ String lexicalEvalWarningBits = parseSymbolTable.getWarningBitsString();
// Eval STRING inherits the caller's lexical warning bits. The
// interpreter does not have JVM call-site instructions to
// reconstruct this state later, so seed the parser scope from
@@ -2906,9 +2930,12 @@ public static RuntimeList evalStringWithInterpreter(
Parser parser = new Parser(evalCtx, tokens);
BHooksEndOfScope.beginFileLoad(evalCompilerOptions.fileName);
+ String savedRegexWarningBits = RegexQuoteMeta.getParserWarningBits();
+ RegexQuoteMeta.setParserWarningBits(lexicalEvalWarningBits);
try {
ast = parser.parse();
} finally {
+ RegexQuoteMeta.setParserWarningBits(savedRegexWarningBits);
BHooksEndOfScope.endFileLoad(evalCompilerOptions.fileName);
}
@@ -3902,6 +3929,12 @@ public static RuntimeList callerWithSub(RuntimeList args, int ctx, RuntimeScalar
}
frame = callerFrameIncludingRegexPseudoBlocks(frame);
+ // Perl treats every negative caller depth as an out-of-range frame.
+ // Return the usual empty result before indexing implementation stacks;
+ // in scalar context an empty RuntimeList naturally becomes undef.
+ if (frame < 0) {
+ return res;
+ }
// Save the original user-supplied frame before the JVM skip adjustment.
// This value maps directly to hasArgsStack depth: caller(0) → depth 0 (current frame),
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGraphCloner.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGraphCloner.java
index 185693fb1c..18eacec20a 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGraphCloner.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGraphCloner.java
@@ -1,7 +1,9 @@
package org.perlonjava.runtime.runtimetypes;
import org.perlonjava.backend.bytecode.InterpretedCode;
+import org.perlonjava.runtime.CompilationRuntimeState;
import org.perlonjava.runtime.regex.RuntimeRegex;
+import org.perlonjava.runtime.regex.RuntimeRegexCallback;
import org.perlonjava.runtime.io.BorrowedIOHandle;
import org.perlonjava.runtime.io.ClosedIOHandle;
import org.perlonjava.runtime.io.DupIOHandle;
@@ -95,6 +97,28 @@ List cloneSnapshotRoots(List extends RuntimeBase> roots) {
return result;
}
+ /**
+ * Clone the compile-time hint snapshots referenced by compiled call sites.
+ *
+ * The integer IDs are embedded in JVM/interpreter code. Their typed
+ * values must therefore cross an ithread boundary with the same IDs and
+ * through the same graph map as package globals and entry CODE. In
+ * particular, a lexical {@code %^H} CODE reference must resolve to the
+ * child-owned clone of that callback rather than the parent's pad.
+ */
+ void cloneCompilationHints(
+ CompilationRuntimeState source, CompilationRuntimeState target) {
+ source.hintSnapshots.forEach((id, snapshot) ->
+ target.hintSnapshots.put(id, Map.copyOf(snapshot)));
+ source.hintScalarSnapshots.forEach((id, snapshot) -> {
+ Map cloned = new LinkedHashMap<>();
+ snapshot.forEach((name, value) ->
+ cloned.put(name, (RuntimeScalar) cloneValue(value)));
+ target.hintScalarSnapshots.put(id, Map.copyOf(cloned));
+ });
+ target.nextHintSnapshotId.set(source.nextHintSnapshotId.get());
+ }
+
private void finishCloneBoundary() {
Map observed = sourceRuntime.snapshotReferenceAddresses();
// A stringified object can remain visible only through a weak Perl
@@ -197,6 +221,15 @@ protected RuntimeBase cloneCode(RuntimeCode source) {
return target;
}
+ private RuntimeRegexCallback cloneRegexCallback(RuntimeRegexCallback source) {
+ Object existing = clones.get(source);
+ if (existing != null) return (RuntimeRegexCallback) existing;
+ RuntimeRegexCallback target = source.cloneForThread(
+ code -> (RuntimeCode) cloneValue(code));
+ clones.put(source, target);
+ return target;
+ }
+
private InterpretedCode cloneInterpretedCode(InterpretedCode source) {
// Register a temporary RuntimeCode first so recursive captured CODE
// edges resolve. The finished interpreted object replaces it before
@@ -251,6 +284,7 @@ private void copyCodeMetadata(RuntimeCode source, RuntimeCode target) {
target.isDeclared = source.isDeclared;
target.isSymbolicReference = source.isSymbolicReference;
target.isClosurePrototype = source.isClosurePrototype;
+ target.tracksRuntimeRegexLexicals = source.tracksRuntimeRegexLexicals;
target.definitionPending = source.definitionPending;
if (source.compilerSupplier == null) {
target.compilerSupplier = null;
@@ -441,7 +475,7 @@ private RuntimeScalar cloneScalar(RuntimeScalar source) {
} else if (source.type == CODE && source.value instanceof RuntimeCode code) {
target.value = cloneCode(code);
} else if (source.value instanceof RuntimeRegex regex) {
- target.value = regex.cloneTracked();
+ target.value = regex.cloneTrackedForThread(this::cloneRegexCallback);
} else if (source.value instanceof RuntimeIO io) {
RuntimeIO inherited = cloneRuntimeIO(io);
if (inherited != null) {
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHashProxyEntry.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHashProxyEntry.java
index 5f7dedb036..d80d7d606d 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHashProxyEntry.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHashProxyEntry.java
@@ -192,4 +192,27 @@ public void dynamicRestoreState() {
}
}
}
+
+ /**
+ * Detach a localized hash element while a regex callback frame is held by
+ * the matcher. The ordinary save/restore stack remembers the outer value;
+ * this token separately carries the active localized value to the next
+ * callback on the same successful path.
+ */
+ @Override
+ public Object dynamicSuspendState() {
+ RuntimeScalar active = parent.elements.get(key);
+ RuntimeScalar token = active == null ? null : new RuntimeScalar(active);
+ dynamicRestoreState();
+ return token;
+ }
+
+ /** Reinstall a localized hash element detached by dynamicSuspendState(). */
+ @Override
+ public void dynamicResumeState(Object token) {
+ dynamicSaveState();
+ if (token instanceof RuntimeScalar active) {
+ active.addToScalar(this);
+ }
+ }
}
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java
index 0dc8511bdb..f65577fd3f 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java
@@ -717,16 +717,16 @@ Object snapshotRegexMutationState() {
void restoreRegexMutationState(Object token) {
if (!(token instanceof RegexMutationState state)) return;
- type = state.type;
- value = state.value;
- utf8UncheckedOctets = state.utf8UncheckedOctets;
- tainted = state.tainted;
- numericLiteralText = state.numericLiteralText;
- numericContextSeen = state.numericContextSeen;
- firstClassRegexScalar = state.firstClassRegexScalar;
- formatPictureTainted = state.formatPictureTainted;
- RuntimePosLvalue.invalidatePos(this);
- refreshSubstrLvalues();
+ RuntimeScalar restored = new RuntimeScalar();
+ restored.type = state.type;
+ restored.value = state.value;
+ restored.utf8UncheckedOctets = state.utf8UncheckedOctets;
+ restored.tainted = state.tainted;
+ restored.numericLiteralText = state.numericLiteralText;
+ restored.numericContextSeen = state.numericContextSeen;
+ restored.firstClassRegexScalar = state.firstClassRegexScalar;
+ restored.formatPictureTainted = state.formatPictureTainted;
+ set(restored);
}
private record RegexMutationState(int type, Object value,
@@ -2746,16 +2746,7 @@ public RuntimeScalar scalarDeref() {
yield newScalar;
}
case REFERENCE -> (RuntimeScalar) value;
- case REGEX -> {
- // Dereferencing qr// produces a non-reference scalar whose
- // string value is the compiled pattern, but whose referent type
- // remains REGEXP (reftype(\${qr/foo/}) eq "REGEXP").
- RuntimeScalar result = new RuntimeScalar();
- result.type = RuntimeScalarType.STRING;
- result.value = this.value.toString();
- result.firstClassRegexScalar = true;
- yield result.propagateTaint(this);
- }
+ case REGEX -> dereferencedRegexScalar();
case GLOB -> {
// Dereferencing a glob as scalar returns the scalar slot
// e.g., ${*Foo::VERSION} or ${$glob} where $glob is a glob
@@ -2803,6 +2794,7 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) {
return switch (type) {
case REFERENCE -> (RuntimeScalar) value;
+ case REGEX -> dereferencedRegexScalar();
case GLOB -> {
// Dereferencing a glob as scalar returns the scalar slot
if (value instanceof RuntimeGlob glob) {
@@ -2832,6 +2824,14 @@ public RuntimeScalar scalarDerefNonStrict(String packageName) {
};
}
+ private RuntimeScalar dereferencedRegexScalar() {
+ RuntimeScalar result = new RuntimeScalar();
+ result.type = RuntimeScalarType.STRING;
+ result.value = this.value.toString();
+ result.firstClassRegexScalar = true;
+ return result.propagateTaint(this);
+ }
+
// Method to implement `%$v`, when "no strict refs" is in effect
public RuntimeHash hashDerefNonStrict(String packageName) {
// Check if object is eligible for overloading
@@ -3746,15 +3746,15 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() {
// Prepare overload context and check if object is eligible for overloading
OverloadContext ctx = OverloadContext.prepare(blessId);
if (ctx != null) {
+ boolean copiedToPlainScalar = false;
// Copy-on-write: If the object has the = overload, call it to create
// a copy BEFORE any mutation. This implements Perl's COW semantics
// where shared references are copied before modification.
// Example: my $b = $a; $b++; should NOT modify $a
RuntimeScalar copyResult = ctx.tryOverload("(=", new RuntimeArray(this));
if (copyResult != null) {
- // Copy the cloned object's fields into this
- this.type = copyResult.type;
- this.value = copyResult.value;
+ set(copyResult);
+ copiedToPlainScalar = !RuntimeScalarType.isReference(this);
}
// Try direct overload method for ++
@@ -3773,6 +3773,13 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() {
this.value = result.value;
return this;
}
+
+ // An explicit copy constructor may return a plain value.
+ // Perl applies the native increment to that copied value
+ // when neither ++ nor + supplies the mutation.
+ if (copiedToPlainScalar) {
+ return preAutoIncrementWithoutWatcherNotification();
+ }
}
}
@@ -3881,14 +3888,14 @@ private RuntimeScalar postAutoIncrementLarge() {
// Prepare overload context and check if object is eligible for overloading
OverloadContext ctx = OverloadContext.prepare(blessId);
if (ctx != null) {
+ boolean copiedToPlainScalar = false;
// Copy-on-write: If the object has the = overload, call it to create
// a copy BEFORE any mutation. This implements Perl's COW semantics
// where shared references are copied before modification.
RuntimeScalar copyResult = ctx.tryOverload("(=", new RuntimeArray(this));
if (copyResult != null) {
- // Copy the cloned object's fields into this
- this.type = copyResult.type;
- this.value = copyResult.value;
+ set(copyResult);
+ copiedToPlainScalar = !RuntimeScalarType.isReference(this);
}
// Try direct overload method for ++
@@ -3907,6 +3914,11 @@ private RuntimeScalar postAutoIncrementLarge() {
this.value = result.value;
return old;
}
+
+ if (copiedToPlainScalar) {
+ preAutoIncrementWithoutWatcherNotification();
+ return old;
+ }
}
}
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly.java
index baaaa43b6f..7ee739f5b1 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalarReadOnly.java
@@ -1,5 +1,7 @@
package org.perlonjava.runtime.runtimetypes;
+import org.perlonjava.runtime.operators.PerlUtfString;
+
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.BYTE_STRING;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.DOUBLE;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.INTEGER;
@@ -154,7 +156,8 @@ public RuntimeScalar chop() {
return new RuntimeScalar("");
}
if (s == null || s.isEmpty()) return new RuntimeScalar("");
- return new RuntimeScalar(s.substring(s.length() - 1));
+ return new RuntimeScalar(s.substring(
+ PerlUtfString.lastLogicalCharacterStart(s)));
}
/**
diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/WarningFlags.java b/src/main/java/org/perlonjava/runtime/runtimetypes/WarningFlags.java
index a37c15c2e9..1163d10f04 100644
--- a/src/main/java/org/perlonjava/runtime/runtimetypes/WarningFlags.java
+++ b/src/main/java/org/perlonjava/runtime/runtimetypes/WarningFlags.java
@@ -692,6 +692,12 @@ public static boolean areWarningsForcedOn() {
public static boolean areWarningsForcedOff() {
return state().commandLineWarningOverride < 0;
}
+
+ /** Whether Perl's package-global {@code $^W} warning switch is active. */
+ public static boolean isGlobalWarningVariableEnabled() {
+ return GlobalVariable.getGlobalVariable(
+ "main::" + Character.toString('W' - 'A' + 1)).getBoolean();
+ }
/**
* Expands a warning category to include all its subcategories.
diff --git a/src/main/perl/lib/I18N/LangTags/List.pm b/src/main/perl/lib/I18N/LangTags/List.pm
index 005c2eb9d5..6bbb1980a0 100644
--- a/src/main/perl/lib/I18N/LangTags/List.pm
+++ b/src/main/perl/lib/I18N/LangTags/List.pm
@@ -28,6 +28,7 @@ our $VERSION = '0.42';
$Is_Disrec{$1} = 1;
}
}
+ close DATA;
die "No tags read??" unless $count;
}
#----------------------------------------------------------------------
diff --git a/src/main/perl/lib/IO/Dir.pm b/src/main/perl/lib/IO/Dir.pm
index a62c146ee6..fa1ec63dac 100644
--- a/src/main/perl/lib/IO/Dir.pm
+++ b/src/main/perl/lib/IO/Dir.pm
@@ -18,7 +18,7 @@ use File::stat;
use File::Spec;
our @ISA = qw(Tie::Hash Exporter);
-our $VERSION = "1.56";
+our $VERSION = "1.57";
our @EXPORT_OK = qw(DIR_UNLINK);
diff --git a/src/main/perl/lib/IO/Select.pm b/src/main/perl/lib/IO/Select.pm
index 97ffd0f499..8e8846fa75 100644
--- a/src/main/perl/lib/IO/Select.pm
+++ b/src/main/perl/lib/IO/Select.pm
@@ -10,7 +10,7 @@ use strict;
use warnings::register;
require Exporter;
-our $VERSION = "1.56";
+our $VERSION = "1.57";
our @ISA = qw(Exporter); # This is only so we can do version checking
diff --git a/src/main/perl/lib/IO/Socket.pm b/src/main/perl/lib/IO/Socket.pm
index 779b23bf8b..f8a0cd552c 100644
--- a/src/main/perl/lib/IO/Socket.pm
+++ b/src/main/perl/lib/IO/Socket.pm
@@ -23,7 +23,7 @@ require IO::Socket::UNIX if ($^O ne 'epoc' && $^O ne 'symbian');
our @ISA = qw(IO::Handle);
-our $VERSION = "1.56";
+our $VERSION = "1.57";
our @EXPORT_OK = qw(sockatmark);
diff --git a/src/main/perl/lib/IO/Socket/INET.pm b/src/main/perl/lib/IO/Socket/INET.pm
index 45fd139d92..ed981bbcdf 100644
--- a/src/main/perl/lib/IO/Socket/INET.pm
+++ b/src/main/perl/lib/IO/Socket/INET.pm
@@ -14,7 +14,7 @@ use Exporter;
use Errno;
our @ISA = qw(IO::Socket);
-our $VERSION = "1.56";
+our $VERSION = "1.57";
my $EINVAL = exists(&Errno::EINVAL) ? Errno::EINVAL() : 1;
diff --git a/src/main/perl/lib/IO/Socket/UNIX.pm b/src/main/perl/lib/IO/Socket/UNIX.pm
index 2542b82b14..fb9ee5e8ef 100644
--- a/src/main/perl/lib/IO/Socket/UNIX.pm
+++ b/src/main/perl/lib/IO/Socket/UNIX.pm
@@ -11,7 +11,7 @@ use IO::Socket;
use Carp;
our @ISA = qw(IO::Socket);
-our $VERSION = "1.56";
+our $VERSION = "1.57";
IO::Socket::UNIX->register_domain( AF_UNIX );
diff --git a/src/main/perl/lib/Net/Ping.pm b/src/main/perl/lib/Net/Ping.pm
index 2b1e51b113..e51d854498 100644
--- a/src/main/perl/lib/Net/Ping.pm
+++ b/src/main/perl/lib/Net/Ping.pm
@@ -22,7 +22,7 @@ use Time::HiRes;
@ISA = qw(Exporter);
@EXPORT = qw(pingecho);
@EXPORT_OK = qw(wakeonlan);
-$VERSION = "2.77";
+$VERSION = "2.78";
# Globals
diff --git a/src/main/perl/lib/Pod/perl.pod b/src/main/perl/lib/Pod/perl.pod
index 55f95cde84..0a4e0a5f2f 100644
--- a/src/main/perl/lib/Pod/perl.pod
+++ b/src/main/perl/lib/Pod/perl.pod
@@ -182,7 +182,8 @@ aux h2ph h2xs perlbug pl2pm pod2html pod2man splain xsubpp
perlhist Perl history records
perldelta Perl changes since previous version
- perl5450delta Perl changes in version 5.45.0
+ perl5452delta Perl changes in version 5.45.2
+ perl5451delta Perl changes in version 5.45.1
perl5440delta Perl changes in version 5.44.0
perl5423delta Perl changes in version 5.42.3
perl5422delta Perl changes in version 5.42.2
diff --git a/src/main/perl/lib/Pod/perl5450delta.pod b/src/main/perl/lib/Pod/perl5450delta.pod
index 8edcaca4cc..2df22e1a95 100644
--- a/src/main/perl/lib/Pod/perl5450delta.pod
+++ b/src/main/perl/lib/Pod/perl5450delta.pod
@@ -168,7 +168,7 @@ accuracy will hopefully follow within this development cycle.
=head1 Acknowledgements
-Perl 5.45.1 represents approximately 1 week of development since Perl 5.44.0
+Perl 5.45.0 represents approximately 1 week of development since Perl 5.44.0
and contains approximately 36,000 lines of changes across 470 files from 19
authors.
@@ -177,7 +177,7 @@ approximately 12,000 lines of changes to 310 .pm, .t, .c and .h files.
Perl continues to flourish into its fourth decade thanks to a vibrant
community of users and developers. The following people are known to have
-contributed the improvements that became Perl 5.45.1:
+contributed the improvements that became Perl 5.45.0:
Andrew Fresh, Chad Granum, Chris 'BinGOs' Williams, Craig A. Berry, Dagfinn
Ilmari Mannsåker, David Mitchell, Georgij Tsarin, Graham Knop, James E
diff --git a/src/main/perl/lib/Pod/perl5451delta.pod b/src/main/perl/lib/Pod/perl5451delta.pod
new file mode 100644
index 0000000000..aac1395f26
--- /dev/null
+++ b/src/main/perl/lib/Pod/perl5451delta.pod
@@ -0,0 +1,254 @@
+=encoding utf8
+
+=head1 NAME
+
+perl5451delta - what is new for perl v5.45.1
+
+=head1 DESCRIPTION
+
+This document describes differences between the 5.44.0 release and the 5.45.1
+release.
+
+If you are upgrading from an earlier release such as 5.43.0, first read
+L, which describes differences between 5.43.0 and 5.44.0.
+
+=head1 Performance Enhancements
+
+=over 4
+
+=item *
+
+Some pattern matches against long strings are now faster: in particular
+where the pattern: is anchored; contains a short fixed substring; and
+contains a floating substring which is as long or longer than than the
+fixed substring; but where the fixed substring isn't present in the
+target string. For example this match will be rejected much faster now:
+
+ "-" x 10_000_000 =~ /^abc.*def/
+
+=back
+
+=head1 Modules and Pragmata
+
+=head2 Updated Modules and Pragmata
+
+=over 4
+
+=item *
+
+L has been upgraded from version 1.11 to 1.12.
+
+=item *
+
+L has been upgraded from version 3.63 to 3.64.
+
+=item *
+
+L has been upgraded from version 3.63 to 3.64.
+
+=item *
+
+L has been upgraded from version 1.20 to 1.21.
+
+=item *
+
+L has been upgraded from version 2.02 to 2.03.
+
+=item *
+
+L has been upgraded from version 4.16 to 4.18.
+
+=item *
+
+L has been upgraded from version 5.20260715 to 5.20260720.
+
+=item *
+
+L has been upgraded from version v6.0.2 to v6.1.0.
+
+=item *
+
+L has been upgraded from version 1.29 to 1.30.
+
+=item *
+
+L has been upgraded from version 1.302219 to 1.302222.
+
+=item *
+
+L has been upgraded from version 2.45 to 2.46.
+
+=item *
+
+L has been upgraded from version 1.73 to 1.74.
+
+=item *
+
+L has been upgraded from version 1.50 to 1.51.
+
+=back
+
+=head1 Platform Support
+
+=head2 Platform-Specific Notes
+
+=over 4
+
+=item VMS
+
+Socket support is no longer a configure-time option on OpenVMS; sockets will
+always be available in Perl and are based on the universal socket support in the
+CRTL.
+
+=back
+
+=head1 Internal Changes
+
+=over 4
+
+=item *
+
+Added new API function C which does the same lexical export
+that the L function does.
+
+=item *
+
+Mutex locks for C and C are removed
+
+The macros C and C macros are
+removed, along with their unlocking counterparts. These macros, though
+never documented, were intended to create thread safety when using their
+corresponding functions in a threaded-perl. But they didn't actually
+furnish that, so using them would give a false sense of security.
+Another thread simultaneously accessing the database could move the
+pointers, so these would act unpredictably.
+
+=item *
+
+New macros created for thread-safety in libc calls
+
+Many C library functions are not thread safe, but can be made so by
+using mutexes around them to lock out other threads from interrupting
+their execution. Many macros have now been created for this purpose.
+Guidance for using them, and the functions they protect are listed in
+L; their actual definitions are found in
+F. If you call a libc function, look in
+L to see if we have created locking macros for it. The macros
+are based on man pages (especially Linux ones), and our experience. We
+also know from experience that man pages can be wrong or incomplete, and
+the behavior of any given function may be platform dependent. Patches
+to update our knowledge base are welcome.
+
+More detail can be found in
+L.
+
+=back
+
+=head1 Selected Bug Fixes
+
+=over 4
+
+=item *
+
+Allow field lookups within methods in nested scopes to succeed,
+previously they could complain that C.
+
+This was most confusingly broken with a use:
+
+ class A {
+ field $fieldname;
+ use overload '""' => method (@) { $fieldname };
+ }
+
+since the syntax hides the C sub.
+
+[L]
+
+=item *
+
+Many potential threads-related race conditions have been fixed in the perl
+core and the two L and L modules.
+
+=item *
+
+Line numbers reported by C and diagnostic messages are now more
+accurate when the relevant COP has been optimized out (occurring often
+when the line is the first in an C block, or when the line is
+inside a loop condition (such as within a C).
+[L]
+[L]
+
+Note that (i) these changes could break any tests that hardcode the
+less-accurate line numbers (ii) further improvements to line number
+accuracy will hopefully follow within this development cycle.
+
+=back
+
+=head1 Acknowledgements
+
+Perl 5.45.1 represents approximately 1 week of development since Perl 5.44.0
+and contains approximately 36,000 lines of changes across 470 files from 19
+authors.
+
+Excluding auto-generated files, documentation and release tools, there were
+approximately 12,000 lines of changes to 310 .pm, .t, .c and .h files.
+
+Perl continues to flourish into its fourth decade thanks to a vibrant
+community of users and developers. The following people are known to have
+contributed the improvements that became Perl 5.45.1:
+
+Andrew Fresh, Chad Granum, Chris 'BinGOs' Williams, Craig A. Berry, Dagfinn
+Ilmari Mannsåker, David Mitchell, Georgij Tsarin, Graham Knop, James E
+Keenan, Karl Williamson, Leon Timmermans, Lukas Mai, Paul Evans, Philippe
+Bruhat (BooK), Richard Leach, Russ Allbery, Steve Hay, Tony Cook, Yves
+Orton.
+
+The list above is almost certainly incomplete as it is automatically
+generated from version control history. In particular, it does not include
+the names of the (very much appreciated) contributors who reported issues to
+the Perl bug tracker.
+
+Many of the changes included in this version originated in the CPAN modules
+included in Perl's core. We're grateful to the entire CPAN community for
+helping Perl to flourish.
+
+For a more complete list of all of Perl's historical contributors, please
+see the F file in the Perl source distribution.
+
+=head1 Reporting Bugs
+
+If you find what you think is a bug, you might check the perl bug database
+at L. There may also be information at
+L, the Perl Home Page.
+
+If you believe you have an unreported bug, please open an issue at
+L. Be sure to trim your bug down to a
+tiny but sufficient test case.
+
+If the bug you are reporting has security implications which make it
+inappropriate to send to a public issue tracker, then see
+L
+for details of how to report the issue.
+
+=head1 Give Thanks
+
+If you wish to thank the Perl 5 Porters for the work we had done in Perl 5,
+you can do so by running the C program:
+
+ perlthanks
+
+This will send an email to the Perl 5 Porters list with your show of thanks.
+
+=head1 SEE ALSO
+
+The F file for an explanation of how to view exhaustive details on
+what changed.
+
+The F file for how to build Perl.
+
+The F file for general stuff.
+
+The F and F files for copyright information.
+
+=cut
diff --git a/src/main/perl/lib/Pod/perl5452delta.pod b/src/main/perl/lib/Pod/perl5452delta.pod
new file mode 100644
index 0000000000..bce2d88dc1
--- /dev/null
+++ b/src/main/perl/lib/Pod/perl5452delta.pod
@@ -0,0 +1,434 @@
+=encoding utf8
+
+=head1 NAME
+
+perl5452delta - what is new for perl v5.45.2
+
+=head1 DESCRIPTION
+
+This document describes differences between the 5.45.1 release and the 5.45.2
+release.
+
+If you are upgrading from an earlier release such as 5.44.0, first read
+L, which describes differences between 5.44.0 and 5.45.1.
+
+=head1 Core Enhancements
+
+=head2 Magic v2
+
+This version introduces a whole new set of API functions, data structures, and
+related items that provides a new kind of variable magic; providing the
+ability to attach user-supplied trigger functions to behaviours on existing
+variables.
+
+The eventual intention of this new version is to extend magic with new trigger
+functions that can apply at different times to those currently supported, and
+to allow magic to be attached to new kinds of items than are currently
+permitted. Right now as of this iteration, it does not offer any new abilities
+but simply changes the shape and structure of existing things to allow space
+for those extensions to be made at a later date.
+
+For more details, see L.
+
+=head1 Incompatible Changes
+
+=head2 Names derived from class fields skip a leading underscore
+
+If the name of a class field starts with an underscore, any derived constructor
+parameters (C<:param>) or methods (C<:reader>, C<:writer>) will skip that
+underscore:
+
+ class Foo {
+ field $_bar :param :reader :writer;
+ # Equivalent to:
+ # field $_bar :param(bar) :reader(bar) :writer(set_bar);
+ }
+
+This is how L works, too.
+
+In previous versions of Perl, the underscore was retained, making the above
+declaration equivalent to C<< field $_bar :param(_bar) :reader(_bar)
+:writer(set__bar); >>.
+
+=head1 Performance Enhancements
+
+=over 4
+
+=item *
+
+C has been added to streamline expressions such as
+C<(ref $x eq 'SCALAR')> where (i) the first operator is C[ or
+C, (ii) the comparison is C or C, and (iii) the value
+being compared against is one of the internal reftype strings mentioned
+in L, is C, or is the empty string C<''>.
+
+=item *
+
+Counting the number of matches within a string using the following common
+idiom is now much more performant.
+
+ my $count = () = $str =~ /$pat/g;
+
+Instead of pushing all matches to the stack as mortal SVs, just so that
+the list assignment can count them, C maintains an internal
+count during matching and pushes just that upon completion.
+[L]
+
+=back
+
+=head1 Modules and Pragmata
+
+=head2 Updated Modules and Pragmata
+
+=over 4
+
+=item *
+
+L has been upgraded from version 1.92 to 1.93.
+
+=item *
+
+L has been upgraded from version 1.89 to 1.90.
+
+=item *
+
+L has been upgraded from version 0.39 to 0.40.
+
+=item *
+
+L has been upgraded from version 1.06 to 1.07.
+
+=item *
+
+L has been upgraded from version 3.64 to 3.65.
+
+XS files compiled with this new version now output a slightly nicer message
+when loaded in an incompatible perl binary. For example:
+
+ XS.c in List::MoreUtils::XS: loadable library and perl binaries are
+ mismatched (got first handshake key 0xed00080, needed 0xee00080)
+
+The old message didn't include the module name, just the (potentially
+ambiguous) name of the C source file:
+
+ XS.c: loadable library and perl binaries are mismatched
+ (got first handshake key 0xed00080, needed 0xee00080)
+
+[L], [L], [L]
+
+=item *
+
+L has been upgraded from version 3.64 to 3.65.
+
+=item *
+
+L has been upgraded from version 0.41 to 0.42.
+
+It no longer sets C<$_> to C when loaded, and it now closes an internal
+filehandle that it only uses during initialization (instead of keeping it open
+for the rest of the program's lifetime).
+
+=item *
+
+L has been upgraded from version 5.20260720 to 5.20260722.
+
+=item *
+
+L has been upgraded from version 1.000039 to 1.000040.
+
+=item *
+
+L has been upgraded from version 2.77 to 2.78.
+
+=item *
+
+L has been upgraded from version 1.71 to 1.72.
+
+=item *
+
+L has been upgraded from version 0.31 to 0.32.
+
+=item *
+
+L has been upgraded from version 0.21 to 0.22.
+
+=item *
+
+L has been upgraded from version 1.36 to 1.37.
+
+=item *
+
+L has been upgraded from version 2.041 to 2.043.
+
+=item *
+
+L has been upgraded from version 1.9780 to 1.9781.
+
+=item *
+
+L has been upgraded from version 0.59_01 to 0.64.
+
+=item *
+
+L has been upgraded from version 1.51 to 1.52.
+
+=back
+
+=head1 Documentation
+
+=head2 Changes to Existing Documentation
+
+We have attempted to update the documentation to reflect the changes
+listed in this document. If you find any we have missed, open an issue
+at L.
+
+Additionally, the following selected changes have been made:
+
+=head3 L
+
+=over 4
+
+=item *
+
+Expanded commentary on symbol visibility
+
+=back
+
+=head1 Diagnostics
+
+The following additions or changes have been made to diagnostic output,
+including warnings and fatal error messages. For the complete list of
+diagnostic messages, see L.
+
+=head2 New Diagnostics
+
+=head3 New Errors
+
+=over 4
+
+=item *
+
+L
+
+=item *
+
+Lshape value %d|perldiag/"Unrecognized magicfuncs-Eshape value %d">
+
+=item *
+
+Lver value of %u|perldiag/"Unsupported MagicFunctions-Ever value of %u">
+
+=back
+
+=head1 Configuration and Compilation
+
+=over 4
+
+=item *
+
+Perl can now be built from a directory whose path contains spaces. (However, a
+few module tests still fail.)
+[L]
+
+=item *
+
+The C<-[DU]usetaint> option has been added to Configure.
+
+=back
+
+=head1 Testing
+
+Tests were added and changed to reflect the other additions and
+changes in this release. Furthermore, these significant changes were
+made:
+
+=over 4
+
+=item *
+
+F: Corrected to now check for entries in
+F that no longer need to be there.
+
+=back
+
+=head1 Platform Support
+
+=head2 Platform-Specific Notes
+
+=over 4
+
+=item MacOS (Darwin)
+
+Use of mkostemp(3) enabled on macOS 10.12 Sierra (Darwin 16) and newer,
+where it is available.
+
+=back
+
+=head1 Internal Changes
+
+=over 4
+
+=item *
+
+During compiletime of a named subroutine, the name of the new sub
+currently being compiled is now accessible via C.
+
+=item *
+
+A new internal function, C attaches an SV onto a
+PADNAME instance, which is used to store the name string of that padname.
+This SV can be used by code that wishes to attach magic onto the padname.
+
+=item *
+
+The C pragma previously lacked increment/decrement operators that
+implemented the documented pragma behaviour; these have now been added.
+Code or tests that depend upon increment/decrement behaving identically
+under both C]