aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-src/src/zend
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-03 01:05:57 +0900
committernsfisis <nsfisis@gmail.com>2026-08-03 01:05:57 +0900
commit2f28d8112970960dbb9b6b582a3c6cd259337d21 (patch)
treeaf8bc223b3af9098e6925cbf6c47e198459ac818 /crates/shirabe-php-src/src/zend
parentbf2c6fa58ae51f44fa0ec65c615f8e62de87812c (diff)
downloadphp-shirabe-2f28d8112970960dbb9b6b582a3c6cd259337d21.tar.gz
php-shirabe-2f28d8112970960dbb9b6b582a3c6cd259337d21.tar.zst
php-shirabe-2f28d8112970960dbb9b6b582a3c6cd259337d21.zip
feat(php-rpc): rework the RPC channel into the tagged plugin protocol
Replace the name\0arg framing with the plugin wire protocol: tagged frames with corr_id multiplexing, a MAX_FRAME_LEN bound, a thread-ID based reentrant SessionLock, and the PluginValue codec (encoder plus the first recursive decoder, iterative with a 512-level depth cap). Float formatting is ported from php-src into shirabe-php-src so the encoder is byte-compatible with serialize() under serialize_precision=-1, which the spawned worker now pins. The worker gains a standing dispatch loop, CallRustMethod reentrancy, hand-written Event proxy stubs, and explicit-error answers for everything not implemented yet. The public query API (get_php_version and friends) is unchanged and now rides the new protocol; the codec is verified against real PHP by roundtrip oracle tests covering floats, non-UTF-8 bytes and deep nesting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-src/src/zend')
-rw-r--r--crates/shirabe-php-src/src/zend/zend_smart_str.rs73
1 files changed, 73 insertions, 0 deletions
diff --git a/crates/shirabe-php-src/src/zend/zend_smart_str.rs b/crates/shirabe-php-src/src/zend/zend_smart_str.rs
new file mode 100644
index 00000000..b4a9f2ec
--- /dev/null
+++ b/crates/shirabe-php-src/src/zend/zend_smart_str.rs
@@ -0,0 +1,73 @@
+use crate::main::snprintf::php_gcvt;
+
+/// php-src: Zend/zend_smart_str.c `smart_str_append_double` (PHP 8.5.8), folding in the `%H`
+/// NAN/INF handling from main/snprintf.c `format_converter` that the original reaches through
+/// `snprintf(buf, sizeof(buf), "%.*H", precision, num)`.
+pub fn smart_str_append_double(dest: &mut String, num: f64, precision: i32, zero_fraction: bool) {
+ if num.is_nan() {
+ dest.push_str("NAN");
+ return;
+ }
+ if num.is_infinite() {
+ dest.push_str(if num > 0.0 { "INF" } else { "-INF" });
+ return;
+ }
+ let buf = php_gcvt(num, precision, '.', 'E');
+ let had_period = buf.contains('.');
+ dest.push_str(&buf);
+ if zero_fraction && !had_period {
+ dest.push_str(".0");
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn serialize_repr(num: f64) -> String {
+ let mut out = String::new();
+ smart_str_append_double(&mut out, num, -1, false);
+ out
+ }
+
+ // Expected strings are the output of `serialize()` under PHP 8.5.8 with
+ // serialize_precision=-1 (without the `d:`/`;` wrapper).
+ #[test]
+ fn matches_php_serialize_output() {
+ assert_eq!(serialize_repr(0.0), "0");
+ assert_eq!(serialize_repr(-0.0), "-0");
+ assert_eq!(serialize_repr(1.5), "1.5");
+ assert_eq!(serialize_repr(0.1), "0.1");
+ assert_eq!(serialize_repr(2.0), "2");
+ assert_eq!(serialize_repr(-2.0), "-2");
+ assert_eq!(serialize_repr(100.0), "100");
+ assert_eq!(serialize_repr(1e15), "1000000000000000");
+ assert_eq!(serialize_repr(1e16), "10000000000000000");
+ assert_eq!(serialize_repr(1e17), "1.0E+17");
+ assert_eq!(serialize_repr(1e18), "1.0E+18");
+ assert_eq!(serialize_repr(1e20), "1.0E+20");
+ assert_eq!(serialize_repr(1.5e20), "1.5E+20");
+ assert_eq!(serialize_repr(1e-4), "0.0001");
+ assert_eq!(serialize_repr(1e-5), "1.0E-5");
+ assert_eq!(serialize_repr(12345.6789e-9), "1.23456789E-5");
+ assert_eq!(serialize_repr(1e-300), "1.0E-300");
+ assert_eq!(serialize_repr(f64::MAX), "1.7976931348623157E+308");
+ assert_eq!(serialize_repr(5e-324), "5.0E-324");
+ assert_eq!(serialize_repr(1.0 / 3.0), "0.3333333333333333");
+ assert_eq!(serialize_repr(0.30000000000000004), "0.30000000000000004");
+ assert_eq!(serialize_repr(f64::NAN), "NAN");
+ assert_eq!(serialize_repr(f64::INFINITY), "INF");
+ assert_eq!(serialize_repr(f64::NEG_INFINITY), "-INF");
+ }
+
+ #[test]
+ fn zero_fraction_appends_dot_zero_to_integral_values() {
+ let mut out = String::new();
+ smart_str_append_double(&mut out, 2.0, -1, true);
+ assert_eq!(out, "2.0");
+
+ let mut out = String::new();
+ smart_str_append_double(&mut out, 1e17, -1, true);
+ assert_eq!(out, "1.0E+17");
+ }
+}