diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-01 04:52:50 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-01 04:52:50 +0900 |
| commit | b8d46b0495d00815a699932ced0b43955c949ab9 (patch) | |
| tree | 17c340f51cbf259ea631ca7da7549714c91b916f /crates/shirabe-php-shim/src/runtime.rs | |
| parent | 8e8a3c147aa388c4b0fc021a08b30113eb590727 (diff) | |
| download | php-shirabe-b8d46b0495d00815a699932ced0b43955c949ab9.tar.gz php-shirabe-b8d46b0495d00815a699932ced0b43955c949ab9.tar.zst php-shirabe-b8d46b0495d00815a699932ced0b43955c949ab9.zip | |
feat(php-src): add a BSD-licensed crate for php-src derived code
The audit in .ken/php-shim-copying.md judged 14 functions in
shirabe-php-shim (plus php_wordwrap in shirabe-external-packages) to be
line-by-line transcriptions or structural imitations of php-src. PHP's
relicensing to 3-clause BSD makes keeping them legal, but the boundary
between BSD-derived and MIT code was invisible in the source tree.
Moving them into their own crate puts the license into the build
metadata (so NOTICE generation follows the binary), makes a reverse
dependency a compile error, and encodes the origin in the module path,
which mirrors php-src's ext tree. Each function records its origin in a
fixed-format doc comment, and a new php_src_derivation_boundary linter
fails if `php-src` appears in any Rust source outside the crate.
Public paths under shirabe_php_shim:: are unchanged: functions that are
themselves derived are re-exported with `pub use`, and the wrappers that
only validate arguments stay on the MIT side.
This also resolves the duplicate wordwrap implementation.
shirabe_php_shim::wordwrap was todo!(), so SymfonyStyle::block panicked,
while shirabe-external-packages carried its own copy. Both now go
through the single port, verified against real PHP on 13 cases covering
multi-character breaks and cut.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/runtime.rs')
| -rw-r--r-- | crates/shirabe-php-shim/src/runtime.rs | 111 |
1 files changed, 1 insertions, 110 deletions
diff --git a/crates/shirabe-php-shim/src/runtime.rs b/crates/shirabe-php-shim/src/runtime.rs index f65ce871..00104051 100644 --- a/crates/shirabe-php-shim/src/runtime.rs +++ b/crates/shirabe-php-shim/src/runtime.rs @@ -1,5 +1,6 @@ use crate::PhpMixed; use indexmap::IndexMap; +use shirabe_php_src::standard::versioning::php_version_compare; pub const PHP_VERSION_ID: i64 = 80100; pub const PHP_VERSION: &str = "8.1.0"; @@ -276,116 +277,6 @@ pub fn version_compare_2(_v1: &str, _v2: &str) -> i64 { php_version_compare(_v1, _v2) as i64 } -// Port of PHP's php_version_compare (ext/standard/versioning.c). Returns -1, 0 or 1. -fn php_version_compare(v1: &str, v2: &str) -> i32 { - if v1.is_empty() || v2.is_empty() { - return match (v1.is_empty(), v2.is_empty()) { - (true, true) => 0, - (false, _) => 1, - (_, false) => -1, - }; - } - let c1 = canonicalize_version(v1); - let c2 = canonicalize_version(v2); - let t1: Vec<&str> = c1.split('.').filter(|s| !s.is_empty()).collect(); - let t2: Vec<&str> = c2.split('.').filter(|s| !s.is_empty()).collect(); - - let mut compare = 0; - let mut i = 0; - while i < t1.len() && i < t2.len() && compare == 0 { - compare = version_token_compare(t1[i], t2[i]); - i += 1; - } - if compare == 0 { - // A leftover numeric token wins; a leftover special form is compared against the implicit - // release baseline ("#", order 4). - if i < t1.len() { - let p = t1[i]; - compare = if p.as_bytes()[0].is_ascii_digit() { - 1 - } else { - special_form_order(p).cmp(&4) as i32 - }; - } else if i < t2.len() { - let p = t2[i]; - compare = if p.as_bytes()[0].is_ascii_digit() { - -1 - } else { - 4.cmp(&special_form_order(p)) as i32 - }; - } - } - compare -} - -// PHP's php_canonicalize_version: separators (-, _, +, .) collapse to a single '.', and a '.' is -// inserted at every digit <-> non-digit boundary. -fn canonicalize_version(version: &str) -> String { - let bytes = version.as_bytes(); - if bytes.is_empty() { - return String::new(); - } - let mut q: Vec<u8> = Vec::with_capacity(bytes.len() * 2); - q.push(bytes[0]); - for &raw in &bytes[1..] { - let ch = if matches!(raw, b'-' | b'_' | b'+') { - b'.' - } else { - raw - }; - let last = *q.last().unwrap(); - if ch == b'.' { - if last != b'.' { - q.push(b'.'); - } - } else if last.is_ascii_digit() != ch.is_ascii_digit() { - q.push(b'.'); - q.push(ch); - } else { - q.push(ch); - } - } - String::from_utf8_lossy(&q).into_owned() -} - -fn version_token_compare(t1: &str, t2: &str) -> i32 { - let d1 = t1.as_bytes()[0].is_ascii_digit(); - let d2 = t2.as_bytes()[0].is_ascii_digit(); - if d1 && d2 { - let l1 = t1.parse::<i64>().unwrap_or(0); - let l2 = t2.parse::<i64>().unwrap_or(0); - l1.cmp(&l2) as i32 - } else if !d1 && !d2 { - special_form_order(t1).cmp(&special_form_order(t2)) as i32 - } else if d1 { - // A numeric token is treated as the "#" form (order 4). - 4.cmp(&special_form_order(t2)) as i32 - } else { - special_form_order(t1).cmp(&4) as i32 - } -} - -fn special_form_order(form: &str) -> i32 { - const FORMS: &[(&str, i32)] = &[ - ("dev", 0), - ("alpha", 1), - ("a", 1), - ("beta", 2), - ("b", 2), - ("RC", 3), - ("rc", 3), - ("#", 4), - ("pl", 5), - ("p", 5), - ]; - for (name, order) in FORMS { - if form.starts_with(name) { - return *order; - } - } - -1 -} - // TODO(php-runtime): the previous handler should be restored in the PHP runtime. // Paired with set_error_handler, which is a no-op in this shim. pub fn restore_error_handler() {} |
