aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/preg.rs
AgeCommit message (Collapse)Author
2026-08-18refactor(preg): back PregMatches with regex::Capturesnsfisis
PregMatches was an IndexMap of owned Strings copied out of the match, so every preg_match2/preg_replace_callback call allocated a String per capture group (twice over for a named group) whether or not the caller read it. It now wraps the regex::Captures itself, held alongside the pattern it came from so groups stay reachable by both their named and their numbered form, and hands out &str borrowed from the subject. The subject's lifetime becomes a parameter of the type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(pcre): return the Preg $matches instead of filling an out-paramnsfisis
`Composer\Pcre\Preg` fills `$matches` through a by-ref parameter, and the port mirrored that with a `&mut` (or `Option<&mut>`) out-param plus a bool or count return. Callers had to declare an empty map one line ahead of the call, and the type never said the map is only meaningful when the call matched. Return the matches instead: - match3/match4/is_match3/is_match4 -> Option<PregMatchedGroups> - is_match_named -> Option<PregNamedGroups> - match_all2/is_match_all -> PregMatchesAll - is_match_all_with_offsets3 -> PregMatchesAllWithOffsets Nothing is lost: the bool is `Option::is_some()`, and the occurrence count is the length of any one column of a PREG_PATTERN_ORDER map, now spelled `PregMatchesAll::occurrence_count()`. is_match() still answers the bool question directly for callers that want no groups. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): return the preg_* $matches instead of filling an out-paramnsfisis
PHP fills `$matches` through a by-ref parameter, which the port mirrored with a `&mut` out-param plus a bool or count return. Every caller then had to declare an empty binding one line ahead of the call, and nothing in the type said the binding is only meaningful when the call succeeded. Return the matches instead: preg_match() and preg_match2() hand back an Option, and the three preg_match_all* functions hand back the collection they used to fill. The occurrence count the two map-shaped preg_match_all* functions used to return is the length of any one of the map's columns, so it is not lost -- Preg::match_all() and friends derive it via occurrence_count(). preg_replace2() keeps its `count: Option<&mut usize>`: that one is not derivable from the replaced string, and callers that do not want it pay nothing for passing None. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): report unmatched groups as null in the vec-shaped preg_*nsfisis
preg_match_all() and preg_match_all_set_order() were the last preg_* functions handing back a bare Vec<String>, where a group that did not participate is indistinguishable from one that captured "". Hand back Option<String> as the map-shaped functions already do; php_match_row(), the last of the truncate-then-pad helpers, goes with them. preg_split_delim_capture() keeps its Vec<String>. preg_split() accepts no PREG_UNMATCHED_AS_NULL, so there is no null form to move it to, and its result interleaves split segments -- which can never be absent -- with the captured delimiters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): report unmatched groups as null throughoutnsfisis
The shim carried two reporting modes for the preg_* $matches maps: PHP's default (trailing unmatched groups dropped, interior ones ""), and the PREG_UNMATCHED_AS_NULL form, picked by calling a *_unmatched_as_null() variant. The regex crate hands out Option<Match>, which maps onto the null form directly, and no caller distinguished a dropped group from a null one -- preg_match() and preg_match_all2() already reported nulls unconditionally. Keep only the null form; the shim API no longer mirrors PHP's flag set, which is intended. Preg::is_match_with_indexed_captures() modelled PHP's "unset" as a truncated Vec<String>, and now returns Vec<Option<String>>. That is what Composer actually does: Preg::isMatch() always sets PREG_UNMATCHED_AS_NULL, and its callers test groups with `!== null`. preg_match_all(), preg_match_all_set_order() and preg_split_delim_capture() still hand back Vec<String> and keep the "" form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): wrap the preg_* $matches maps in newtypesnsfisis
The five IndexMap shapes that the preg_* functions and Preg fill in are now distinct types generated by preg_match_map!, so a matches map no longer interchanges with any other map of the same key and value type. Index<usize> is kept alongside Index<&Q> because call sites such as config_command and event_dispatcher reach for a group by its position in the map rather than by its capture key.
2026-08-17refactor(preg): split the PREG_UNMATCHED_AS_NULL preg_* by flagnsfisis
preg_match2() and preg_match_all_offset_capture() each become a pair over a shared private impl, and their flags arguments are gone: no caller passed anything but 0 or PREG_UNMATCHED_AS_NULL. Preg::match5()/is_match5() lose their own flags argument for the same reason -- both call sites passed 0, and the value had nowhere left to go -- so they are renumbered to match4()/is_match4(). PREG_UNMATCHED_AS_NULL itself is now unreferenced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(php-shim): split single_match_map() by unmatched_as_nullnsfisis
Each branch of the flag is now its own function. The PREG_UNMATCHED_AS_NULL variant needs neither the trailing-group truncation nor the empty-string fallback, so it loses the break condition and the last_participating scan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(pcre): drop the guards for unsupported preg flagsnsfisis
Preg::match5() rejected PREG_OFFSET_CAPTURE and check_set_order() rejected PREG_SET_ORDER, mirroring the PHP where those flags would change the type of $matches. The Rust ports of both take a typed `matches` out-param instead, so neither flag can reach them; check_set_order() already had no caller. The three constants are unreferenced once the guards are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): replace Preg::split*() with shim preg_split*()nsfisis
preg_split2()'s limit was always -1 and its flags were always either 0 or PREG_SPLIT_DELIM_CAPTURE alone, so both arguments are gone: the shim now exposes preg_split() and preg_split_delim_capture() over a shared preg_split_impl(). That leaves Preg::split()/split4() as bare pass-throughs, so callers use the shim functions directly and the wrappers are dropped along with the now-unreferenced PREG_SPLIT_* constants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): merge preg_match_all_offset_capture2() into its siblingnsfisis
The two functions ran the same search and differed only in how they reported a non-participating group: one as ("", 0) in a bespoke struct, the other as (null, -1) in a capture-key map. Neither shape covered both callers, because PHP reaches preg_match_all() with different flags from each: Preg::matchAllWithOffsets() always ORs in PREG_UNMATCHED_AS_NULL, while OutputFormatter::formatAndWrap() passes PREG_OFFSET_CAPTURE alone. Keep the capture-key map, which also exposes named groups, and take the flags that decide between null and "" for an unmatched group. The offset is -1 either way, so the ("", 0) approximation is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): drop unused args of preg_match_all_offset_capture2()nsfisis
The sole caller, Preg::match_all_with_offsets5(), always passed flags=0 and offset=0, so PREG_UNMATCHED_AS_NULL is now unconditional and the subject is scanned from the beginning. That method is only reached from Preg::match_all_with_offsets(), so it is no longer public either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): drop unused flags and offset of preg_match_all2()nsfisis
Every caller reached preg_match_all2() through Preg::match_all5(), which always passed flags = PREG_UNMATCHED_AS_NULL and offset = 0. Inline those constants and make match_all5() private, since the two public wrappers are its only callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(preg): make preg_grep() return an iteratornsfisis
Callers had to build a temporary Vec<&str> at every call site to satisfy the &[&str] parameter. Taking IntoIterator and yielding the matched items lets them pass owned or borrowed strings directly. The flags variant and PREG_GREP_INVERT go away with it: no caller passes flags, and Composer's Preg::grep() has no such parameter either.
2026-08-16refactor(preg): re-order preg_*() functions in shimnsfisis
2026-08-16refactor(preg): merge preg_replace_callback2 into preg_replace_callbacknsfisis
preg_replace_callback2 had one caller, Preg::replace_callback6, which was itself reached only from Preg::replace_callback with the default limit, count and flags. Fold the two shim functions into one and drop those parameters along with replace_callback6. The surviving callback takes callback2's IndexMap of matches: it can be keyed by group name, and it omits trailing non-participating groups the way PHP does, which is what the callbacks in Process and ProgressBar test for.
2026-08-12docs(todo): retag TODO markers by root causensfisis
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02chore(todo): consolidate TODO comments into the five fixed marker tagsnsfisis
Retag every Shirabe-authored TODO comment to one of the fixed tags: phase-c, phase-d, plugin, php-runtime, phase-e. Upstream-authored TODO comments from Composer/Symfony are left untouched to preserve the ported code shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18perf(regex): eliminate per-call clone overhead in preg_* dispatchnsfisis
regex::Regex::clone() does not share the underlying meta engine's search-cache pool, so every fresh clone pays a ~10us warmup cost on its first use. Two changes together eliminate this across nearly all preg_* call sites: - A php_regex! macro resolves PHP-style patterns to a per-call-site &'static regex::Regex (via regex-macro's LazyLock), applied at the majority of call sites throughout the codebase. - Call sites still passing dynamic pattern strings go through PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out Arc::clone()s instead of cloning the Regex itself. PregPattern::resolve() returns a ResolvedPattern enum (Arc or 'static reference) rather than an owned Regex, so neither path ever clones the Regex proper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05perf(pcre): cache compiled regex patterns across preg_* callsnsfisis
Composer's classmap generator re-issues the same PHP-derived pattern string for every scanned file (and every token within it), relying on PCRE's built-in compiled-pattern cache to make that free. shirabe had no equivalent, so every Preg::* call recompiled the pattern from scratch via regex::Regex::new(), making `composer create-project` autoload generation ~130x slower than Composer on a fresh laravel/laravel install (130s vs ~1s). Memoizing compiled patterns by their raw string in compile_php_pattern closes that gap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-27refactor: fix compiler warnings and clippy warningsnsfisis
2026-06-26test: port 59 autoload/vcs/installer/util/command tests; fix output capturensfisis
Port autoload_generator (24), bitbucket (14), suggested_packages (11), git_driver (6), archive_manager (3), and a bump command test. Fix the ApplicationTester output-capture root cause (php://memory streams must be readable regardless of fopen mode). Implement posix_getuid/geteuid, the PCRE 'A' anchored modifier, php_strip_whitespace, stream_get_wrappers, is_callable scalars; fix preg_quote angle-bracket escaping and class-map parser regexes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25fix(preg): treat escaped \$ in replacement as a literal dollar signnsfisis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20refactor(clippy): resolve idiomatic lint warningsnsfisis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20refactor: auto-fix clippy warningsnsfisis
2026-06-14feat(php-shim): implement more shim functionsnsfisis
2026-06-14refactor: auto-fix clippy warningsnsfisis
2026-06-14refactor(pcre): take &mut matches in preg_*2 shim helpersnsfisis
The optional matches output was always passed Some(&mut ...) at every call site, so the Option wrapper added no value. Take &mut directly and inline the former internal helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14refactor(pcre): return bool/usize from preg_*2 shim helpersnsfisis
preg_match2 returns bool and the preg_match_all* helpers return usize (the match count is never negative), matching how callers use them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14refactor(pcre): return bool from preg_match shimnsfisis
preg_match can only return 1 or 0 now that compile failure panics, so return bool and update all call sites accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14refactor(pcre): treat regex compile failure as fatal in shimnsfisis
The preg_* shim helpers wrapped their results in Option/Result solely to signal a regex that failed to compile. Composer never feeds a pattern that fails at runtime, so such a failure is a programming error: panic instead and drop the Option/Result wrappers, updating all callers. preg_replace_callback keeps its Result return type since the callback itself is fallible. preg_match_groups is removed in favor of preg_match at its sole call site. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14refactor(pcre): consolidate duplicate preg_* shim helpersnsfisis
The preg.rs shim had several near-duplicate functions: simple helpers re-implementing logic already covered by their full-featured `2` variants. Delegate or remove the redundant ones while preserving behavior, and migrate the affected callers: - preg_replace / preg_split now delegate to preg_replace2 / preg_split2 - preg_match_all_simple removed; its caller uses preg_match_all - preg_split_chars removed; its caller uses a char-boundary iterator - preg_match_offset removed; its callers use preg_match2 directly Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14refactor(pcre): move preg_* free functions into php-shimnsfisis
The free preg_* functions, the CaptureKey type, and the PREG_* constants move from the pcre crate into shirabe-php-shim, where the rest of the PHP standard-library shims live. Colliding names take a 2 suffix (preg_match2, preg_split2, etc.) since the shim already exposes differently-shaped variants. Preg keeps its public API and delegates to the relocated functions; CaptureKey is re-exported from composer::pcre so consumers are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>