diff --git a/ROADMAP.md b/ROADMAP.md index 99f78a6d76..09b5401f10 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -968,6 +968,7 @@ and 0.x validation rather than by speculative pass work. - [x] PHP 8.3 typed class constants — declared types enforced for initializers and inherited overrides (including covariant narrowing) on classes, interfaces, traits, and enums, exposed via `ReflectionClassConstant::hasType()`/`getType()` - [x] `$object::class` on object expressions — returns the receiver's concrete runtime class name, evaluates the receiver exactly once, and rejects statically known non-object receivers - [x] `mb_strlen()` — nullable optional `$encoding` argument, UTF-8 malformed-sequence handling, byte-count aliases, iconv-backed multibyte encodings, callable dispatch, catchable `ValueError` for unknown encodings +- [x] `mb_strimwidth()` — UTF-8 East Asian display-width trim with optional marker, `8bit`/`binary`/`7bit` byte-width aliases, character `$start` (including negative), catchable `ValueError` for unknown encodings and out-of-range `$start`/`$width`, AOT + Magician/eval, callable dispatch - [x] `static $x;` function-static declarations without an initializer — desugar to `= null` in both the native and Magician `eval()` parsers, matching PHP - [x] Purity / may-throw v2 — closed-world instance dispatch now unions concrete override summaries (with exact fixed-construction/final/private targets), named and dynamic property reads distinguish declared untyped slots, typed-slot throws, missing-property warnings, hooks, and `__get`, known array offsets separate silent reads from undefined-key warnings, and registry/runtime builtin effects use shared argument-sensitive contracts instead of the previous blanket barrier. A final whole-module fixed point writes these summaries onto refinable EIR call/property instructions before validation, while unresolved/eval/external targets retain conservative defaults - [x] Guard reasoning v2 for dead-code elimination — integer interval facts from `$x int` relational branches when `$x` has a proven integer domain from an exact `int` parameter, typed local, or literal guard (intersected across nested paths and discharged for transitive relational / strict-int contradictions and impossible `switch` int cases); cross-variable relational / strict-equality atoms with safe complements, full exact coupling after strict substitution, and pure non-throwing `while` / `for` body-entry strengthening, still under the path-local AST `GuardState` protocol with write invalidation, float/string-domain refusal, NaN-safe false-branch policy, and no general CFG join diff --git a/crates/elephc-builtin-contract/src/catalog_data.rs b/crates/elephc-builtin-contract/src/catalog_data.rs index 28c733f0c2..083a4f8c76 100644 --- a/crates/elephc-builtin-contract/src/catalog_data.rs +++ b/crates/elephc-builtin-contract/src/catalog_data.rs @@ -11672,6 +11672,58 @@ pub(crate) static CONTRACTS: &[BuiltinContract] = &[ internal: false, requirements: &[], }, + BuiltinContract { + id: BuiltinId::from_canonical_name("mb_strimwidth"), + name: "mb_strimwidth", + area: Area::String, + kind: BuiltinKind::Function, + params: &[ + ParamSpec { + name: "string", + ty: TypeSpec::Str, + default: None, + by_ref: false, + }, + ParamSpec { + name: "start", + ty: TypeSpec::Int, + default: None, + by_ref: false, + }, + ParamSpec { + name: "width", + ty: TypeSpec::Int, + default: None, + by_ref: false, + }, + ParamSpec { + name: "trim_marker", + ty: TypeSpec::Str, + default: Some(DefaultSpec::Str("")), + by_ref: false, + }, + ParamSpec { + name: "encoding", + ty: TypeSpec::Str, + default: Some(DefaultSpec::Null), + by_ref: false, + }, + ], + variadic: None, + min_args: None, + max_args: None, + arity_error: None, + returns: TypeSpec::Str, + by_ref_return: false, + summary: "Truncates a string to a display width, optionally appending a trim marker.", + examples: &[ + ], + php_manual: Some("https://www.php.net/manual/en/function.mb-strimwidth.php"), + deprecation: None, + extension: false, + internal: false, + requirements: &[], + }, BuiltinContract { id: BuiltinId::from_canonical_name("mb_strlen"), name: "mb_strlen", diff --git a/crates/elephc-builtin-contract/src/registry.rs b/crates/elephc-builtin-contract/src/registry.rs index 24f541718f..f55b3391bc 100644 --- a/crates/elephc-builtin-contract/src/registry.rs +++ b/crates/elephc-builtin-contract/src/registry.rs @@ -144,7 +144,7 @@ mod tests { // The PHP-visible `curl_*` surface is published only with the `curl` // feature; see `crate::catalog_curl`'s module doc. let curl_surface = if cfg!(feature = "curl") { 34 } else { 0 }; - assert_eq!(contracts().len(), 597 + curl_surface); + assert_eq!(contracts().len(), 598 + curl_surface); assert_eq!(lookup("STRLEN").map(|contract| contract.name), Some("strlen")); assert_eq!(lookup("\\parse_url").map(|contract| contract.name), Some("parse_url")); } diff --git a/crates/elephc-builtin-contract/src/support.rs b/crates/elephc-builtin-contract/src/support.rs index a00c748288..debcac6367 100644 --- a/crates/elephc-builtin-contract/src/support.rs +++ b/crates/elephc-builtin-contract/src/support.rs @@ -280,14 +280,14 @@ mod tests { // The thirty-four prelude-provided `curl_*` contracts are published only // with the `curl` feature; see `crate::catalog_curl`'s module doc. let curl_surface = if cfg!(feature = "curl") { 34 } else { 0 }; - assert_eq!(eval_registry, 484 + curl_surface); + assert_eq!(eval_registry, 485 + curl_surface); assert_eq!(eval_internal, 82); assert_eq!(eval_pending, 31); // Main's BCMath registry adds fourteen AOT contracts; this branch also // promotes get_object_vars from an external surface into the registry and // adds the ten iconv contracts and forty-three internal `__elephc_curl_*` // entry points. - assert_eq!(aot_registry, 584); + assert_eq!(aot_registry, 585); assert_eq!(aot_external, 10 + curl_surface); assert_eq!(aot_unsupported, 3); } @@ -336,7 +336,7 @@ mod tests { let curl_surface = if cfg!(feature = "curl") { 34 } else { 0 }; assert_eq!(shared_runtime, 19); assert_eq!(hybrid_adapter, 2); - assert_eq!(interpreter_adapter, 463 + curl_surface); + assert_eq!(interpreter_adapter, 464 + curl_surface); assert_eq!(unsupported, 113); assert_eq!( eval_execution(lookup("strval").expect("strval contract")), diff --git a/crates/elephc-magician/src/interpreter/builtins/hooks/direct.rs b/crates/elephc-magician/src/interpreter/builtins/hooks/direct.rs index 2f7a14b523..889d0ef41f 100644 --- a/crates/elephc-magician/src/interpreter/builtins/hooks/direct.rs +++ b/crates/elephc-magician/src/interpreter/builtins/hooks/direct.rs @@ -205,6 +205,8 @@ pub(in crate::interpreter) enum EvalDirectHook { Range, /// Dispatches `mb_ereg_match(...)`. MbEregMatch, + /// Dispatches `mb_strimwidth(...)`. + MbStrimwidth, /// Dispatches `preg_match(...)`. PregMatch, /// Dispatches `preg_match_all(...)`. @@ -453,6 +455,7 @@ impl EvalDirectHook { Self::RandomInt => eval_builtin_random_int(args, context, scope, values), Self::Round => eval_builtin_round(args, context, scope, values), Self::MbEregMatch => eval_builtin_mb_ereg_match(args, context, scope, values), + Self::MbStrimwidth => eval_builtin_mb_strimwidth(args, context, scope, values), Self::PregMatch => eval_builtin_preg_match(args, context, scope, values), Self::PregMatchAll => eval_builtin_preg_match_all(args, context, scope, values), Self::PregReplace => eval_builtin_preg_replace(args, context, scope, values), diff --git a/crates/elephc-magician/src/interpreter/builtins/hooks/values.rs b/crates/elephc-magician/src/interpreter/builtins/hooks/values.rs index 4fcf425513..46bdd48ec9 100644 --- a/crates/elephc-magician/src/interpreter/builtins/hooks/values.rs +++ b/crates/elephc-magician/src/interpreter/builtins/hooks/values.rs @@ -208,6 +208,8 @@ pub(in crate::interpreter) enum EvalValuesHook { Range, /// Dispatches `mb_ereg_match(...)`. MbEregMatch, + /// Dispatches `mb_strimwidth(...)`. + MbStrimwidth, /// Dispatches `preg_match(...)`. PregMatch, /// Dispatches `preg_match_all(...)`. @@ -534,6 +536,30 @@ impl EvalValuesHook { _ => Err(EvalStatus::RuntimeFatal), }, Self::MbEregMatch => eval_mb_ereg_match_values_result(evaluated_args, values), + Self::MbStrimwidth => match evaluated_args { + [value, start, width] => { + eval_mb_strimwidth_result(*value, *start, *width, None, None, context, values) + } + [value, start, width, trim_marker] => eval_mb_strimwidth_result( + *value, + *start, + *width, + Some(*trim_marker), + None, + context, + values, + ), + [value, start, width, trim_marker, encoding] => eval_mb_strimwidth_result( + *value, + *start, + *width, + Some(*trim_marker), + Some(*encoding), + context, + values, + ), + _ => Err(EvalStatus::RuntimeFatal), + }, Self::PregMatch => eval_preg_match_values_result(evaluated_args, values), Self::PregMatchAll => eval_preg_match_all_values_result(evaluated_args, values), Self::PregReplace => eval_preg_replace_values_result(evaluated_args, values), diff --git a/crates/elephc-magician/src/interpreter/builtins/registry/tests/metadata_core.rs b/crates/elephc-magician/src/interpreter/builtins/registry/tests/metadata_core.rs index fe205772a4..3ed84f1b2b 100644 --- a/crates/elephc-magician/src/interpreter/builtins/registry/tests/metadata_core.rs +++ b/crates/elephc-magician/src/interpreter/builtins/registry/tests/metadata_core.rs @@ -32,6 +32,18 @@ fn declared_builtin_registry_derives_core_metadata() { eval_declared_builtin_default_value("mb_strlen", 1), Some(EvalBuiltinDefaultValue::Null) ); + assert_eq!( + eval_declared_builtin_param_names("mb_strimwidth"), + Some(["string", "start", "width", "trim_marker", "encoding"].as_slice()) + ); + assert_eq!( + eval_declared_builtin_default_value("mb_strimwidth", 3), + Some(EvalBuiltinDefaultValue::String("")) + ); + assert_eq!( + eval_declared_builtin_default_value("mb_strimwidth", 4), + Some(EvalBuiltinDefaultValue::Null) + ); assert_eq!( eval_declared_builtin_param_names("is_finite"), Some(["num"].as_slice()) diff --git a/crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs b/crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs new file mode 100644 index 0000000000..1073b8030b --- /dev/null +++ b/crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs @@ -0,0 +1,479 @@ +//! Purpose: +//! Declarative eval registry entry and implementation for PHP's `mb_strimwidth()`. +//! +//! Called from: +//! - `crate::interpreter::builtins::string` and the declarative direct/values hooks. +//! +//! Key details: +//! - The eval signature matches PHP: string, start, width, optional trim marker, optional encoding. +//! - UTF-8 (default/`null`/`UTF-8`/`UTF8`) trims by East Asian display width from PHP 8.5's +//! `eaw_table.h`. `8bit`/`binary`/`7bit` treat every byte as width 1. +//! - Unknown encodings and out-of-range `$start`/`$width` raise catchable `ValueError`. + +use super::super::super::*; + +eval_builtin! { + contract: "mb_strimwidth", + area: String, + direct: MbStrimwidth, + values: MbStrimwidth, +} + +/// Inclusive Unicode ranges that PHP 8.5 treats as display width 2. +const EAW_RANGES: &[(u32, u32)] = &[ + (0x1100, 0x115f), + (0x231a, 0x231b), + (0x2329, 0x232a), + (0x23e9, 0x23ec), + (0x23f0, 0x23f0), + (0x23f3, 0x23f3), + (0x25fd, 0x25fe), + (0x2614, 0x2615), + (0x2630, 0x2637), + (0x2648, 0x2653), + (0x267f, 0x267f), + (0x268a, 0x268f), + (0x2693, 0x2693), + (0x26a1, 0x26a1), + (0x26aa, 0x26ab), + (0x26bd, 0x26be), + (0x26c4, 0x26c5), + (0x26ce, 0x26ce), + (0x26d4, 0x26d4), + (0x26ea, 0x26ea), + (0x26f2, 0x26f3), + (0x26f5, 0x26f5), + (0x26fa, 0x26fa), + (0x26fd, 0x26fd), + (0x2705, 0x2705), + (0x270a, 0x270b), + (0x2728, 0x2728), + (0x274c, 0x274c), + (0x274e, 0x274e), + (0x2753, 0x2755), + (0x2757, 0x2757), + (0x2795, 0x2797), + (0x27b0, 0x27b0), + (0x27bf, 0x27bf), + (0x2b1b, 0x2b1c), + (0x2b50, 0x2b50), + (0x2b55, 0x2b55), + (0x2e80, 0x2e99), + (0x2e9b, 0x2ef3), + (0x2f00, 0x2fd5), + (0x2ff0, 0x303e), + (0x3041, 0x3096), + (0x3099, 0x30ff), + (0x3105, 0x312f), + (0x3131, 0x318e), + (0x3190, 0x31e5), + (0x31ef, 0x321e), + (0x3220, 0x3247), + (0x3250, 0xa48c), + (0xa490, 0xa4c6), + (0xa960, 0xa97c), + (0xac00, 0xd7a3), + (0xf900, 0xfaff), + (0xfe10, 0xfe19), + (0xfe30, 0xfe52), + (0xfe54, 0xfe66), + (0xfe68, 0xfe6b), + (0xff01, 0xff60), + (0xffe0, 0xffe6), + (0x16fe0, 0x16fe4), + (0x16ff0, 0x16ff6), + (0x17000, 0x18cd5), + (0x18cff, 0x18d1e), + (0x18d80, 0x18df2), + (0x1aff0, 0x1aff3), + (0x1aff5, 0x1affb), + (0x1affd, 0x1affe), + (0x1b000, 0x1b122), + (0x1b132, 0x1b132), + (0x1b150, 0x1b152), + (0x1b155, 0x1b155), + (0x1b164, 0x1b167), + (0x1b170, 0x1b2fb), + (0x1d300, 0x1d356), + (0x1d360, 0x1d376), + (0x1f004, 0x1f004), + (0x1f0cf, 0x1f0cf), + (0x1f18e, 0x1f18e), + (0x1f191, 0x1f19a), + (0x1f200, 0x1f202), + (0x1f210, 0x1f23b), + (0x1f240, 0x1f248), + (0x1f250, 0x1f251), + (0x1f260, 0x1f265), + (0x1f300, 0x1f320), + (0x1f32d, 0x1f335), + (0x1f337, 0x1f37c), + (0x1f37e, 0x1f393), + (0x1f3a0, 0x1f3ca), + (0x1f3cf, 0x1f3d3), + (0x1f3e0, 0x1f3f0), + (0x1f3f4, 0x1f3f4), + (0x1f3f8, 0x1f43e), + (0x1f440, 0x1f440), + (0x1f442, 0x1f4fc), + (0x1f4ff, 0x1f53d), + (0x1f54b, 0x1f54e), + (0x1f550, 0x1f567), + (0x1f57a, 0x1f57a), + (0x1f595, 0x1f596), + (0x1f5a4, 0x1f5a4), + (0x1f5fb, 0x1f64f), + (0x1f680, 0x1f6c5), + (0x1f6cc, 0x1f6cc), + (0x1f6d0, 0x1f6d2), + (0x1f6d5, 0x1f6d8), + (0x1f6dc, 0x1f6df), + (0x1f6eb, 0x1f6ec), + (0x1f6f4, 0x1f6fc), + (0x1f7e0, 0x1f7eb), + (0x1f7f0, 0x1f7f0), + (0x1f90c, 0x1f93a), + (0x1f93c, 0x1f945), + (0x1f947, 0x1f9ff), + (0x1fa70, 0x1fa7c), + (0x1fa80, 0x1fa8a), + (0x1fa8e, 0x1fac6), + (0x1fac8, 0x1fac8), + (0x1facd, 0x1fadc), + (0x1fadf, 0x1faea), + (0x1faef, 0x1faf8), + (0x20000, 0x2fffd), + (0x30000, 0x3fffd), +]; + +/// Evaluates direct `mb_strimwidth()` calls while preserving PHP source-order evaluation. +pub(in crate::interpreter) fn eval_builtin_mb_strimwidth( + args: &[EvalExpr], + context: &mut ElephcEvalContext, + scope: &mut ElephcEvalScope, + values: &mut impl RuntimeValueOps, +) -> Result { + match args { + [value, start, width] => { + let value = eval_expr(value, context, scope, values)?; + let start = eval_expr(start, context, scope, values)?; + let width = eval_expr(width, context, scope, values)?; + eval_mb_strimwidth_result(value, start, width, None, None, context, values) + } + [value, start, width, trim_marker] => { + let value = eval_expr(value, context, scope, values)?; + let start = eval_expr(start, context, scope, values)?; + let width = eval_expr(width, context, scope, values)?; + let trim_marker = eval_expr(trim_marker, context, scope, values)?; + eval_mb_strimwidth_result( + value, + start, + width, + Some(trim_marker), + None, + context, + values, + ) + } + [value, start, width, trim_marker, encoding] => { + let value = eval_expr(value, context, scope, values)?; + let start = eval_expr(start, context, scope, values)?; + let width = eval_expr(width, context, scope, values)?; + let trim_marker = eval_expr(trim_marker, context, scope, values)?; + let encoding = eval_expr(encoding, context, scope, values)?; + eval_mb_strimwidth_result( + value, + start, + width, + Some(trim_marker), + Some(encoding), + context, + values, + ) + } + _ => Err(EvalStatus::RuntimeFatal), + } +} + +/// Trims one materialized eval string to a PHP display width with an optional marker. +pub(in crate::interpreter) fn eval_mb_strimwidth_result( + value: RuntimeCellHandle, + start: RuntimeCellHandle, + width: RuntimeCellHandle, + trim_marker: Option, + encoding: Option, + context: &mut ElephcEvalContext, + values: &mut impl RuntimeValueOps, +) -> Result { + let bytes = values.string_bytes(value)?; + let start = eval_int_value(start, values)?; + let width = eval_int_value(width, values)?; + let marker = match trim_marker { + Some(trim_marker) => values.string_bytes(trim_marker)?, + None => Vec::new(), + }; + let encoding = match encoding { + Some(encoding) if !values.is_null(encoding)? => Some(values.string_bytes(encoding)?), + _ => None, + }; + let byte_width = match encoding.as_deref() { + None => false, + Some(encoding) if is_utf8_encoding(encoding) => false, + Some(encoding) if is_byte_encoding(encoding) => true, + Some(encoding) => return eval_mb_strimwidth_encoding_error(encoding, context, values), + }; + + match trim_to_width(&bytes, start, width, &marker, byte_width) { + Ok(trimmed) => values.string_bytes_value(&trimmed), + Err(MbStrimwidthError::StartOutOfRange) => eval_throw_builtin_value_error( + "mb_strimwidth(): Argument #2 ($start) is out of range", + context, + values, + ), + Err(MbStrimwidthError::WidthOutOfRange) => eval_throw_builtin_value_error( + "mb_strimwidth(): Argument #3 ($width) is out of range", + context, + values, + ), + } +} + +/// PHP-compatible encoding aliases that use the UTF-8 display-width scanner. +fn is_utf8_encoding(encoding: &[u8]) -> bool { + encoding.eq_ignore_ascii_case(b"UTF-8") || encoding.eq_ignore_ascii_case(b"UTF8") +} + +/// PHP byte-count aliases that treat every byte as one width-1 character. +fn is_byte_encoding(encoding: &[u8]) -> bool { + encoding.eq_ignore_ascii_case(b"8bit") + || encoding.eq_ignore_ascii_case(b"binary") + || encoding.eq_ignore_ascii_case(b"7bit") +} + +/// Catchable `ValueError` raised when the encoding name is not a supported alias. +fn eval_mb_strimwidth_encoding_error( + encoding: &[u8], + context: &mut ElephcEvalContext, + values: &mut impl RuntimeValueOps, +) -> Result { + let encoding = String::from_utf8_lossy(encoding); + let message = format!( + "mb_strimwidth(): Argument #5 ($encoding) must be a valid encoding, \"{}\" given", + encoding + ); + eval_throw_builtin_value_error(&message, context, values) +} + +/// Failures that PHP 8.5 reports as argument `ValueError`s for `mb_strimwidth()`. +#[derive(Debug)] +enum MbStrimwidthError { + StartOutOfRange, + WidthOutOfRange, +} + +/// Implements PHP 8.5 `mb_trim_string` plus start/negative-width resolution. +fn trim_to_width( + bytes: &[u8], + mut start: i64, + mut width: i64, + marker: &[u8], + byte_width: bool, +) -> Result, MbStrimwidthError> { + let char_count = count_chars(bytes, byte_width); + if start != 0 { + if start < 0 { + start += char_count; + } + if start < 0 || start > char_count { + return Err(MbStrimwidthError::StartOutOfRange); + } + } + let start = usize::try_from(start).unwrap_or(0); + + if width < 0 { + let total_width = i64::try_from(string_width(bytes, byte_width)).unwrap_or(i64::MAX); + width += total_width; + if start > 0 { + let prefix_end = skip_chars(bytes, start, byte_width); + width -= i64::try_from(string_width(&bytes[..prefix_end], byte_width)).unwrap_or(0); + } + if width < 0 { + return Err(MbStrimwidthError::WidthOutOfRange); + } + } + let width = usize::try_from(width).unwrap_or(0); + + let start_byte = skip_chars(bytes, start, byte_width); + let rest = &bytes[start_byte..]; + if string_width(rest, byte_width) <= width { + return Ok(rest.to_vec()); + } + + let marker_width = string_width(marker, byte_width); + if width <= marker_width { + return Ok(marker.to_vec()); + } + + let take_end = take_display_width(rest, width - marker_width, byte_width); + let mut out = rest[..take_end].to_vec(); + out.extend_from_slice(marker); + Ok(out) +} + +/// Counts characters using the same UTF-8 substitution boundaries as `mb_strlen()`. +fn count_chars(bytes: &[u8], byte_width: bool) -> i64 { + if byte_width { + return i64::try_from(bytes.len()).unwrap_or(i64::MAX); + } + let mut offset = 0usize; + let mut count = 0i64; + while let Some((next, _)) = next_char(bytes, offset) { + count += 1; + offset = next; + } + count +} + +/// Returns the East Asian display width of a whole string. +fn string_width(bytes: &[u8], byte_width: bool) -> usize { + if byte_width { + return bytes.len(); + } + let mut offset = 0usize; + let mut width = 0usize; + while let Some((next, codepoint)) = next_char(bytes, offset) { + width += character_width(codepoint); + offset = next; + } + width +} + +/// Returns the byte offset after skipping `count` characters from the start of `bytes`. +fn skip_chars(bytes: &[u8], count: usize, byte_width: bool) -> usize { + if byte_width { + return count.min(bytes.len()); + } + let mut offset = 0usize; + let mut seen = 0usize; + while seen < count { + let Some((next, _)) = next_char(bytes, offset) else { + break; + }; + offset = next; + seen += 1; + } + offset +} + +/// Returns the byte length of the longest prefix whose display width is at most `budget`. +fn take_display_width(bytes: &[u8], budget: usize, byte_width: bool) -> usize { + if byte_width { + return budget.min(bytes.len()); + } + let mut offset = 0usize; + let mut remaining = budget; + while let Some((next, codepoint)) = next_char(bytes, offset) { + let width = character_width(codepoint); + if remaining < width { + break; + } + remaining -= width; + offset = next; + } + offset +} + +/// Walks one mbstring character: a valid scalar, one malformed sequence, or a truncated suffix. +fn next_char(bytes: &[u8], offset: usize) -> Option<(usize, u32)> { + if offset >= bytes.len() { + return None; + } + match std::str::from_utf8(&bytes[offset..]) { + Ok(valid) => { + let ch = valid.chars().next()?; + Some((offset + ch.len_utf8(), ch as u32)) + } + Err(error) => { + let valid_len = error.valid_up_to(); + if valid_len > 0 { + let valid = std::str::from_utf8(&bytes[offset..offset + valid_len]) + .expect("from_utf8 valid prefix"); + let ch = valid.chars().next()?; + return Some((offset + ch.len_utf8(), ch as u32)); + } + match error.error_len() { + Some(invalid_len) => Some((offset + invalid_len, 0xffff_ffff)), + None => Some((bytes.len(), 0xffff_ffff)), + } + } + } +} + +/// Returns PHP mbstring display width: 2 inside the East Asian Width table, otherwise 1. +fn character_width(codepoint: u32) -> usize { + if codepoint < 0x1100 { + return 1; + } + let mut lo = 0usize; + let mut hi = EAW_RANGES.len(); + while lo < hi { + let probe = (lo + hi) / 2; + let (begin, end) = EAW_RANGES[probe]; + if codepoint < begin { + hi = probe; + } else if codepoint > end { + lo = probe + 1; + } else { + return 2; + } + } + 1 +} + +#[cfg(test)] +mod tests { + use super::{character_width, trim_to_width, MbStrimwidthError}; + + /// Verifies ASCII halfwidth characters and CJK fullwidth characters match PHP widths. + #[test] + fn character_width_matches_php_east_asian_table() { + assert_eq!(character_width(b'a' as u32), 1); + assert_eq!(character_width(0xff41), 2); + assert_eq!(character_width(0x65e5), 2); + assert_eq!(character_width(0x2026), 1); + } + + /// Verifies PHP's trim-marker replacement and CJK width accounting. + #[test] + fn trim_to_width_matches_php_truncation_rules() { + assert_eq!( + trim_to_width(b"hello", 0, 3, b"...", false).unwrap(), + b"...".to_vec() + ); + assert_eq!( + trim_to_width(b"hello", 0, 4, b"...", false).unwrap(), + b"h...".to_vec() + ); + assert_eq!( + trim_to_width("日本語".as_bytes(), 0, 4, "…".as_bytes(), false).unwrap(), + "日…".as_bytes() + ); + assert_eq!( + trim_to_width(b"hello", 1, 3, b"", false).unwrap(), + b"ell".to_vec() + ); + assert!(matches!( + trim_to_width(b"ab", 3, 1, b"", false), + Err(MbStrimwidthError::StartOutOfRange) + )); + assert_eq!( + trim_to_width(b"hello", 0, -2, b"", false).unwrap(), + b"hel".to_vec() + ); + assert_eq!(trim_to_width(b"ab", 2, 1, b"...", false).unwrap(), b"".to_vec()); + assert!(matches!( + trim_to_width(b"ab", 2, -1, b"", false), + Err(MbStrimwidthError::WidthOutOfRange) + )); + } +} diff --git a/crates/elephc-magician/src/interpreter/builtins/string/mod.rs b/crates/elephc-magician/src/interpreter/builtins/string/mod.rs index 63f6ef9060..61c7dd2f1c 100644 --- a/crates/elephc-magician/src/interpreter/builtins/string/mod.rs +++ b/crates/elephc-magician/src/interpreter/builtins/string/mod.rs @@ -54,6 +54,7 @@ mod iconv_substr; mod implode; mod lcfirst; mod ltrim; +mod mb_strimwidth; mod mb_strlen; mod md5; mod nl2br; @@ -141,6 +142,7 @@ pub(in crate::interpreter) use iconv::*; pub(in crate::interpreter) use implode::*; pub(in crate::interpreter) use lcfirst::*; pub(in crate::interpreter) use ltrim::*; +pub(in crate::interpreter) use mb_strimwidth::*; pub(in crate::interpreter) use mb_strlen::*; pub(in crate::interpreter) use md5::*; pub(in crate::interpreter) use nl2br::*; diff --git a/crates/elephc-magician/src/interpreter/tests/builtins_strings_encoding.rs b/crates/elephc-magician/src/interpreter/tests/builtins_strings_encoding.rs index 480b1c3515..ee642b1a59 100644 --- a/crates/elephc-magician/src/interpreter/tests/builtins_strings_encoding.rs +++ b/crates/elephc-magician/src/interpreter/tests/builtins_strings_encoding.rs @@ -40,6 +40,33 @@ fn execute_program_dispatches_mb_strlen_builtin() { assert_eq!(values.get(result), FakeValue::Bool(true)); } +/// Verifies eval `mb_strimwidth()` matches display-width trim, encoding, callable, and error behavior. +#[test] +fn execute_program_dispatches_mb_strimwidth_builtin() { + let program = parse_fragment( + r#"echo mb_strimwidth("hello", 0, 4, "..."); echo ":"; + echo mb_strimwidth(string: "日本語", start: 0, width: 4, trim_marker: "…"); echo ":"; + echo mb_strimwidth("héllo", 0, 3, "", "8bit"); echo ":"; + echo call_user_func("mb_strimwidth", "hello", 1, 3); echo ":"; + try { + mb_strimwidth("abc", 0, 1, "", "definitely-not-an-encoding"); + } catch (ValueError $error) { + echo "caught"; + } + echo ":"; + return function_exists("mb_strimwidth") && is_callable("mb_strimwidth");"# + .as_bytes(), + ) + .expect("parse eval fragment"); + let mut scope = ElephcEvalScope::new(); + let mut values = FakeOps::default(); + + let result = execute_program(&program, &mut scope, &mut values).expect("execute eval ir"); + + assert_eq!(values.output, "h...:日…:hé:ell:caught:"); + assert_eq!(values.get(result), FakeValue::Bool(true)); +} + /// Verifies eval `explode()` and `implode()` bridge byte strings and arrays. #[test] fn execute_program_dispatches_explode_implode_builtins() { diff --git a/docs/internals/builtins/_internal/__elephc_callable_ptr.md b/docs/internals/builtins/_internal/__elephc_callable_ptr.md index 67a0a6f954..4edd37e8b5 100644 --- a/docs/internals/builtins/_internal/__elephc_callable_ptr.md +++ b/docs/internals/builtins/_internal/__elephc_callable_ptr.md @@ -2,7 +2,7 @@ title: "__elephc_callable_ptr() — internals" description: "Compiler internals for __elephc_callable_ptr(): lowering path, type checks, and runtime helpers." sidebar: - order: 550 + order: 551 --- ## `__elephc_callable_ptr()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_class_has_constructor.md b/docs/internals/builtins/_internal/__elephc_class_has_constructor.md index b862b175de..e5cc828eb1 100644 --- a/docs/internals/builtins/_internal/__elephc_class_has_constructor.md +++ b/docs/internals/builtins/_internal/__elephc_class_has_constructor.md @@ -2,7 +2,7 @@ title: "__elephc_class_has_constructor() — internals" description: "Compiler internals for __elephc_class_has_constructor(): lowering path, type checks, and runtime helpers." sidebar: - order: 551 + order: 552 --- ## `__elephc_class_has_constructor()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_adapter_addr.md b/docs/internals/builtins/_internal/__elephc_curl_adapter_addr.md index 77798c8cc0..410e839383 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_adapter_addr.md +++ b/docs/internals/builtins/_internal/__elephc_curl_adapter_addr.md @@ -2,7 +2,7 @@ title: "__elephc_curl_adapter_addr() — internals" description: "Compiler internals for __elephc_curl_adapter_addr(): lowering path, type checks, and runtime helpers." sidebar: - order: 552 + order: 553 --- ## `__elephc_curl_adapter_addr()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_body.md b/docs/internals/builtins/_internal/__elephc_curl_easy_body.md index b0adb0b689..d60b24db99 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_body.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_body.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_body() — internals" description: "Compiler internals for __elephc_curl_easy_body(): lowering path, type checks, and runtime helpers." sidebar: - order: 553 + order: 554 --- ## `__elephc_curl_easy_body()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_copy.md b/docs/internals/builtins/_internal/__elephc_curl_easy_copy.md index f0adac28ef..6d91dff14b 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_copy.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_copy.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_copy() — internals" description: "Compiler internals for __elephc_curl_easy_copy(): lowering path, type checks, and runtime helpers." sidebar: - order: 554 + order: 555 --- ## `__elephc_curl_easy_copy()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_errno.md b/docs/internals/builtins/_internal/__elephc_curl_easy_errno.md index 49452e991e..0160e63043 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_errno.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_errno.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_errno() — internals" description: "Compiler internals for __elephc_curl_easy_errno(): lowering path, type checks, and runtime helpers." sidebar: - order: 555 + order: 556 --- ## `__elephc_curl_easy_errno()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_error.md b/docs/internals/builtins/_internal/__elephc_curl_easy_error.md index 9f08f6ef40..31e7185832 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_error.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_error.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_error() — internals" description: "Compiler internals for __elephc_curl_easy_error(): lowering path, type checks, and runtime helpers." sidebar: - order: 556 + order: 557 --- ## `__elephc_curl_easy_error()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_double.md b/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_double.md index 7fbe384743..e8d5a29362 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_double.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_double.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_getinfo_double() — internals" description: "Compiler internals for __elephc_curl_easy_getinfo_double(): lowering path, type checks, and runtime helpers." sidebar: - order: 557 + order: 558 --- ## `__elephc_curl_easy_getinfo_double()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_long.md b/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_long.md index 9a0af5537c..51f27d39de 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_long.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_getinfo_long.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_getinfo_long() — internals" description: "Compiler internals for __elephc_curl_easy_getinfo_long(): lowering path, type checks, and runtime helpers." sidebar: - order: 558 + order: 559 --- ## `__elephc_curl_easy_getinfo_long()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_id.md b/docs/internals/builtins/_internal/__elephc_curl_easy_id.md index 0db9360dee..3ccaa9de2e 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_id.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_id.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_id() — internals" description: "Compiler internals for __elephc_curl_easy_id(): lowering path, type checks, and runtime helpers." sidebar: - order: 559 + order: 560 --- ## `__elephc_curl_easy_id()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_init.md b/docs/internals/builtins/_internal/__elephc_curl_easy_init.md index 770ec85c31..bb29a2cf46 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_init.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_init.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_init() — internals" description: "Compiler internals for __elephc_curl_easy_init(): lowering path, type checks, and runtime helpers." sidebar: - order: 560 + order: 561 --- ## `__elephc_curl_easy_init()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_pause.md b/docs/internals/builtins/_internal/__elephc_curl_easy_pause.md index 1a1ac5a285..92f55d452c 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_pause.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_pause.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_pause() — internals" description: "Compiler internals for __elephc_curl_easy_pause(): lowering path, type checks, and runtime helpers." sidebar: - order: 561 + order: 562 --- ## `__elephc_curl_easy_pause()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_perform.md b/docs/internals/builtins/_internal/__elephc_curl_easy_perform.md index 2059656b46..480cd5155c 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_perform.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_perform.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_perform() — internals" description: "Compiler internals for __elephc_curl_easy_perform(): lowering path, type checks, and runtime helpers." sidebar: - order: 562 + order: 563 --- ## `__elephc_curl_easy_perform()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_reset.md b/docs/internals/builtins/_internal/__elephc_curl_easy_reset.md index 6056faaebb..940abb8a58 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_reset.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_reset.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_reset() — internals" description: "Compiler internals for __elephc_curl_easy_reset(): lowering path, type checks, and runtime helpers." sidebar: - order: 563 + order: 564 --- ## `__elephc_curl_easy_reset()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_set_callback.md b/docs/internals/builtins/_internal/__elephc_curl_easy_set_callback.md index 0fc0465ecd..69059449aa 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_set_callback.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_set_callback.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_set_callback() — internals" description: "Compiler internals for __elephc_curl_easy_set_callback(): lowering path, type checks, and runtime helpers." sidebar: - order: 564 + order: 565 --- ## `__elephc_curl_easy_set_callback()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_set_share.md b/docs/internals/builtins/_internal/__elephc_curl_easy_set_share.md index b20e67f043..fca5bb231d 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_set_share.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_set_share.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_set_share() — internals" description: "Compiler internals for __elephc_curl_easy_set_share(): lowering path, type checks, and runtime helpers." sidebar: - order: 565 + order: 566 --- ## `__elephc_curl_easy_set_share()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_long.md b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_long.md index 89cb666e95..b1791d5331 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_long.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_long.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_setopt_long() — internals" description: "Compiler internals for __elephc_curl_easy_setopt_long(): lowering path, type checks, and runtime helpers." sidebar: - order: 566 + order: 567 --- ## `__elephc_curl_easy_setopt_long()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_slist.md b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_slist.md index de77f746f2..bca7669e5c 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_slist.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_slist.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_setopt_slist() — internals" description: "Compiler internals for __elephc_curl_easy_setopt_slist(): lowering path, type checks, and runtime helpers." sidebar: - order: 567 + order: 568 --- ## `__elephc_curl_easy_setopt_slist()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_str.md b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_str.md index 5d7e4abeed..48c2600a43 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_str.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_setopt_str.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_setopt_str() — internals" description: "Compiler internals for __elephc_curl_easy_setopt_str(): lowering path, type checks, and runtime helpers." sidebar: - order: 568 + order: 569 --- ## `__elephc_curl_easy_setopt_str()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_str_op.md b/docs/internals/builtins/_internal/__elephc_curl_easy_str_op.md index 73956470a8..7d0ea28603 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_str_op.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_str_op.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_str_op() — internals" description: "Compiler internals for __elephc_curl_easy_str_op(): lowering path, type checks, and runtime helpers." sidebar: - order: 569 + order: 570 --- ## `__elephc_curl_easy_str_op()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_easy_upkeep.md b/docs/internals/builtins/_internal/__elephc_curl_easy_upkeep.md index ccf18c05d9..aabb00dbdd 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_easy_upkeep.md +++ b/docs/internals/builtins/_internal/__elephc_curl_easy_upkeep.md @@ -2,7 +2,7 @@ title: "__elephc_curl_easy_upkeep() — internals" description: "Compiler internals for __elephc_curl_easy_upkeep(): lowering path, type checks, and runtime helpers." sidebar: - order: 570 + order: 571 --- ## `__elephc_curl_easy_upkeep()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_mime_abort.md b/docs/internals/builtins/_internal/__elephc_curl_mime_abort.md index 5faa72c39b..74d936547d 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_mime_abort.md +++ b/docs/internals/builtins/_internal/__elephc_curl_mime_abort.md @@ -2,7 +2,7 @@ title: "__elephc_curl_mime_abort() — internals" description: "Compiler internals for __elephc_curl_mime_abort(): lowering path, type checks, and runtime helpers." sidebar: - order: 571 + order: 572 --- ## `__elephc_curl_mime_abort()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_mime_add_part.md b/docs/internals/builtins/_internal/__elephc_curl_mime_add_part.md index 19c5a994e7..9a37bcc2a1 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_mime_add_part.md +++ b/docs/internals/builtins/_internal/__elephc_curl_mime_add_part.md @@ -2,7 +2,7 @@ title: "__elephc_curl_mime_add_part() — internals" description: "Compiler internals for __elephc_curl_mime_add_part(): lowering path, type checks, and runtime helpers." sidebar: - order: 572 + order: 573 --- ## `__elephc_curl_mime_add_part()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_mime_new.md b/docs/internals/builtins/_internal/__elephc_curl_mime_new.md index 0b7e4a6b1e..81af8b1aed 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_mime_new.md +++ b/docs/internals/builtins/_internal/__elephc_curl_mime_new.md @@ -2,7 +2,7 @@ title: "__elephc_curl_mime_new() — internals" description: "Compiler internals for __elephc_curl_mime_new(): lowering path, type checks, and runtime helpers." sidebar: - order: 573 + order: 574 --- ## `__elephc_curl_mime_new()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_mime_part_field.md b/docs/internals/builtins/_internal/__elephc_curl_mime_part_field.md index e38b89b6e9..820ca19109 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_mime_part_field.md +++ b/docs/internals/builtins/_internal/__elephc_curl_mime_part_field.md @@ -2,7 +2,7 @@ title: "__elephc_curl_mime_part_field() — internals" description: "Compiler internals for __elephc_curl_mime_part_field(): lowering path, type checks, and runtime helpers." sidebar: - order: 574 + order: 575 --- ## `__elephc_curl_mime_part_field()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_mime_post.md b/docs/internals/builtins/_internal/__elephc_curl_mime_post.md index 847e9587af..a817cef290 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_mime_post.md +++ b/docs/internals/builtins/_internal/__elephc_curl_mime_post.md @@ -2,7 +2,7 @@ title: "__elephc_curl_mime_post() — internals" description: "Compiler internals for __elephc_curl_mime_post(): lowering path, type checks, and runtime helpers." sidebar: - order: 575 + order: 576 --- ## `__elephc_curl_mime_post()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_add.md b/docs/internals/builtins/_internal/__elephc_curl_multi_add.md index fa562755d3..0bf7eee19a 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_add.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_add.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_add() — internals" description: "Compiler internals for __elephc_curl_multi_add(): lowering path, type checks, and runtime helpers." sidebar: - order: 576 + order: 577 --- ## `__elephc_curl_multi_add()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_errno.md b/docs/internals/builtins/_internal/__elephc_curl_multi_errno.md index 07993c5ab3..984aa9769d 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_errno.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_errno.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_errno() — internals" description: "Compiler internals for __elephc_curl_multi_errno(): lowering path, type checks, and runtime helpers." sidebar: - order: 577 + order: 578 --- ## `__elephc_curl_multi_errno()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_exec.md b/docs/internals/builtins/_internal/__elephc_curl_multi_exec.md index 6c97defda0..3280690bcb 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_exec.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_exec.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_exec() — internals" description: "Compiler internals for __elephc_curl_multi_exec(): lowering path, type checks, and runtime helpers." sidebar: - order: 578 + order: 579 --- ## `__elephc_curl_multi_exec()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_info_read.md b/docs/internals/builtins/_internal/__elephc_curl_multi_info_read.md index bb6ac90d23..70a9fb0098 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_info_read.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_info_read.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_info_read() — internals" description: "Compiler internals for __elephc_curl_multi_info_read(): lowering path, type checks, and runtime helpers." sidebar: - order: 579 + order: 580 --- ## `__elephc_curl_multi_info_read()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_init.md b/docs/internals/builtins/_internal/__elephc_curl_multi_init.md index 4ffd4dd4dd..252a6371b6 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_init.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_init.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_init() — internals" description: "Compiler internals for __elephc_curl_multi_init(): lowering path, type checks, and runtime helpers." sidebar: - order: 580 + order: 581 --- ## `__elephc_curl_multi_init()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_remove.md b/docs/internals/builtins/_internal/__elephc_curl_multi_remove.md index e31cec8bbf..d844bed7dd 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_remove.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_remove.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_remove() — internals" description: "Compiler internals for __elephc_curl_multi_remove(): lowering path, type checks, and runtime helpers." sidebar: - order: 581 + order: 582 --- ## `__elephc_curl_multi_remove()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_select.md b/docs/internals/builtins/_internal/__elephc_curl_multi_select.md index d208dfc9cc..7c8b6bb618 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_select.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_select.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_select() — internals" description: "Compiler internals for __elephc_curl_multi_select(): lowering path, type checks, and runtime helpers." sidebar: - order: 582 + order: 583 --- ## `__elephc_curl_multi_select()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_setopt.md b/docs/internals/builtins/_internal/__elephc_curl_multi_setopt.md index 670499b4f3..e3ffaf22a3 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_setopt.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_setopt.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_setopt() — internals" description: "Compiler internals for __elephc_curl_multi_setopt(): lowering path, type checks, and runtime helpers." sidebar: - order: 583 + order: 584 --- ## `__elephc_curl_multi_setopt()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_setopt_unsupported_warning.md b/docs/internals/builtins/_internal/__elephc_curl_multi_setopt_unsupported_warning.md index d8380233bb..d7aa5c88d6 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_setopt_unsupported_warning.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_setopt_unsupported_warning.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_setopt_unsupported_warning() — internals" description: "Compiler internals for __elephc_curl_multi_setopt_unsupported_warning(): lowering path, type checks, and runtime helpers." sidebar: - order: 584 + order: 585 --- ## `__elephc_curl_multi_setopt_unsupported_warning()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_multi_strerror.md b/docs/internals/builtins/_internal/__elephc_curl_multi_strerror.md index fc8ff7b288..d8f311e0d5 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_multi_strerror.md +++ b/docs/internals/builtins/_internal/__elephc_curl_multi_strerror.md @@ -2,7 +2,7 @@ title: "__elephc_curl_multi_strerror() — internals" description: "Compiler internals for __elephc_curl_multi_strerror(): lowering path, type checks, and runtime helpers." sidebar: - order: 585 + order: 586 --- ## `__elephc_curl_multi_strerror()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_option_kind.md b/docs/internals/builtins/_internal/__elephc_curl_option_kind.md index 24ebc1457c..cbb8fe6a17 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_option_kind.md +++ b/docs/internals/builtins/_internal/__elephc_curl_option_kind.md @@ -2,7 +2,7 @@ title: "__elephc_curl_option_kind() — internals" description: "Compiler internals for __elephc_curl_option_kind(): lowering path, type checks, and runtime helpers." sidebar: - order: 586 + order: 587 --- ## `__elephc_curl_option_kind()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_setopt_unsupported_warning.md b/docs/internals/builtins/_internal/__elephc_curl_setopt_unsupported_warning.md index bde2ac3d8a..1fbebab2b4 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_setopt_unsupported_warning.md +++ b/docs/internals/builtins/_internal/__elephc_curl_setopt_unsupported_warning.md @@ -2,7 +2,7 @@ title: "__elephc_curl_setopt_unsupported_warning() — internals" description: "Compiler internals for __elephc_curl_setopt_unsupported_warning(): lowering path, type checks, and runtime helpers." sidebar: - order: 587 + order: 588 --- ## `__elephc_curl_setopt_unsupported_warning()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_share_errno.md b/docs/internals/builtins/_internal/__elephc_curl_share_errno.md index f5720f432a..1abd1336a7 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_share_errno.md +++ b/docs/internals/builtins/_internal/__elephc_curl_share_errno.md @@ -2,7 +2,7 @@ title: "__elephc_curl_share_errno() — internals" description: "Compiler internals for __elephc_curl_share_errno(): lowering path, type checks, and runtime helpers." sidebar: - order: 588 + order: 589 --- ## `__elephc_curl_share_errno()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_share_init.md b/docs/internals/builtins/_internal/__elephc_curl_share_init.md index ec74df013c..d4cb7c30e2 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_share_init.md +++ b/docs/internals/builtins/_internal/__elephc_curl_share_init.md @@ -2,7 +2,7 @@ title: "__elephc_curl_share_init() — internals" description: "Compiler internals for __elephc_curl_share_init(): lowering path, type checks, and runtime helpers." sidebar: - order: 589 + order: 590 --- ## `__elephc_curl_share_init()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_share_init_persistent.md b/docs/internals/builtins/_internal/__elephc_curl_share_init_persistent.md index a315b47f99..9964860fa9 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_share_init_persistent.md +++ b/docs/internals/builtins/_internal/__elephc_curl_share_init_persistent.md @@ -2,7 +2,7 @@ title: "__elephc_curl_share_init_persistent() — internals" description: "Compiler internals for __elephc_curl_share_init_persistent(): lowering path, type checks, and runtime helpers." sidebar: - order: 590 + order: 591 --- ## `__elephc_curl_share_init_persistent()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_share_setopt.md b/docs/internals/builtins/_internal/__elephc_curl_share_setopt.md index c46fcd0c1d..fc4e1e2bd7 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_share_setopt.md +++ b/docs/internals/builtins/_internal/__elephc_curl_share_setopt.md @@ -2,7 +2,7 @@ title: "__elephc_curl_share_setopt() — internals" description: "Compiler internals for __elephc_curl_share_setopt(): lowering path, type checks, and runtime helpers." sidebar: - order: 591 + order: 592 --- ## `__elephc_curl_share_setopt()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_share_strerror.md b/docs/internals/builtins/_internal/__elephc_curl_share_strerror.md index 186206d0a5..332b46c812 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_share_strerror.md +++ b/docs/internals/builtins/_internal/__elephc_curl_share_strerror.md @@ -2,7 +2,7 @@ title: "__elephc_curl_share_strerror() — internals" description: "Compiler internals for __elephc_curl_share_strerror(): lowering path, type checks, and runtime helpers." sidebar: - order: 592 + order: 593 --- ## `__elephc_curl_share_strerror()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_strerror.md b/docs/internals/builtins/_internal/__elephc_curl_strerror.md index d73cf4211e..b94dad87db 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_strerror.md +++ b/docs/internals/builtins/_internal/__elephc_curl_strerror.md @@ -2,7 +2,7 @@ title: "__elephc_curl_strerror() — internals" description: "Compiler internals for __elephc_curl_strerror(): lowering path, type checks, and runtime helpers." sidebar: - order: 593 + order: 594 --- ## `__elephc_curl_strerror()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_curl_version.md b/docs/internals/builtins/_internal/__elephc_curl_version.md index 446f23fa04..c7b4be257f 100644 --- a/docs/internals/builtins/_internal/__elephc_curl_version.md +++ b/docs/internals/builtins/_internal/__elephc_curl_version.md @@ -2,7 +2,7 @@ title: "__elephc_curl_version() — internals" description: "Compiler internals for __elephc_curl_version(): lowering path, type checks, and runtime helpers." sidebar: - order: 594 + order: 595 --- ## `__elephc_curl_version()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_gmmktime_raw.md b/docs/internals/builtins/_internal/__elephc_gmmktime_raw.md index 9c2a781ab4..c2ef5baf03 100644 --- a/docs/internals/builtins/_internal/__elephc_gmmktime_raw.md +++ b/docs/internals/builtins/_internal/__elephc_gmmktime_raw.md @@ -2,7 +2,7 @@ title: "__elephc_gmmktime_raw() — internals" description: "Compiler internals for __elephc_gmmktime_raw(): lowering path, type checks, and runtime helpers." sidebar: - order: 595 + order: 596 --- ## `__elephc_gmmktime_raw()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_hash_ctx_copy.md b/docs/internals/builtins/_internal/__elephc_hash_ctx_copy.md index aabc045723..f35d5343aa 100644 --- a/docs/internals/builtins/_internal/__elephc_hash_ctx_copy.md +++ b/docs/internals/builtins/_internal/__elephc_hash_ctx_copy.md @@ -2,7 +2,7 @@ title: "__elephc_hash_ctx_copy() — internals" description: "Compiler internals for __elephc_hash_ctx_copy(): lowering path, type checks, and runtime helpers." sidebar: - order: 596 + order: 597 --- ## `__elephc_hash_ctx_copy()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_hash_ctx_final.md b/docs/internals/builtins/_internal/__elephc_hash_ctx_final.md index eac0438f49..dc70e28c74 100644 --- a/docs/internals/builtins/_internal/__elephc_hash_ctx_final.md +++ b/docs/internals/builtins/_internal/__elephc_hash_ctx_final.md @@ -2,7 +2,7 @@ title: "__elephc_hash_ctx_final() — internals" description: "Compiler internals for __elephc_hash_ctx_final(): lowering path, type checks, and runtime helpers." sidebar: - order: 597 + order: 598 --- ## `__elephc_hash_ctx_final()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_hash_ctx_init.md b/docs/internals/builtins/_internal/__elephc_hash_ctx_init.md index bda34b654d..36370849a1 100644 --- a/docs/internals/builtins/_internal/__elephc_hash_ctx_init.md +++ b/docs/internals/builtins/_internal/__elephc_hash_ctx_init.md @@ -2,7 +2,7 @@ title: "__elephc_hash_ctx_init() — internals" description: "Compiler internals for __elephc_hash_ctx_init(): lowering path, type checks, and runtime helpers." sidebar: - order: 598 + order: 599 --- ## `__elephc_hash_ctx_init()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_hash_ctx_update.md b/docs/internals/builtins/_internal/__elephc_hash_ctx_update.md index 4d844da592..6c0356c574 100644 --- a/docs/internals/builtins/_internal/__elephc_hash_ctx_update.md +++ b/docs/internals/builtins/_internal/__elephc_hash_ctx_update.md @@ -2,7 +2,7 @@ title: "__elephc_hash_ctx_update() — internals" description: "Compiler internals for __elephc_hash_ctx_update(): lowering path, type checks, and runtime helpers." sidebar: - order: 599 + order: 600 --- ## `__elephc_hash_ctx_update()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_initialize_pdo_statement.md b/docs/internals/builtins/_internal/__elephc_initialize_pdo_statement.md index 98f2d1cf29..ba197aa990 100644 --- a/docs/internals/builtins/_internal/__elephc_initialize_pdo_statement.md +++ b/docs/internals/builtins/_internal/__elephc_initialize_pdo_statement.md @@ -2,7 +2,7 @@ title: "__elephc_initialize_pdo_statement() — internals" description: "Compiler internals for __elephc_initialize_pdo_statement(): lowering path, type checks, and runtime helpers." sidebar: - order: 600 + order: 601 --- ## `__elephc_initialize_pdo_statement()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_invoke_pdo_statement_constructor.md b/docs/internals/builtins/_internal/__elephc_invoke_pdo_statement_constructor.md index bdbbdcef5e..c5d9ff34f5 100644 --- a/docs/internals/builtins/_internal/__elephc_invoke_pdo_statement_constructor.md +++ b/docs/internals/builtins/_internal/__elephc_invoke_pdo_statement_constructor.md @@ -2,7 +2,7 @@ title: "__elephc_invoke_pdo_statement_constructor() — internals" description: "Compiler internals for __elephc_invoke_pdo_statement_constructor(): lowering path, type checks, and runtime helpers." sidebar: - order: 601 + order: 602 --- ## `__elephc_invoke_pdo_statement_constructor()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_mktime_raw.md b/docs/internals/builtins/_internal/__elephc_mktime_raw.md index 8f2c4af7bc..a1cff0c1cb 100644 --- a/docs/internals/builtins/_internal/__elephc_mktime_raw.md +++ b/docs/internals/builtins/_internal/__elephc_mktime_raw.md @@ -2,7 +2,7 @@ title: "__elephc_mktime_raw() — internals" description: "Compiler internals for __elephc_mktime_raw(): lowering path, type checks, and runtime helpers." sidebar: - order: 602 + order: 603 --- ## `__elephc_mktime_raw()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_new_without_constructor.md b/docs/internals/builtins/_internal/__elephc_new_without_constructor.md index b34afcf1a0..3e525aaf1d 100644 --- a/docs/internals/builtins/_internal/__elephc_new_without_constructor.md +++ b/docs/internals/builtins/_internal/__elephc_new_without_constructor.md @@ -2,7 +2,7 @@ title: "__elephc_new_without_constructor() — internals" description: "Compiler internals for __elephc_new_without_constructor(): lowering path, type checks, and runtime helpers." sidebar: - order: 603 + order: 604 --- ## `__elephc_new_without_constructor()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_normalize_callable.md b/docs/internals/builtins/_internal/__elephc_normalize_callable.md index be6001c392..df3436f664 100644 --- a/docs/internals/builtins/_internal/__elephc_normalize_callable.md +++ b/docs/internals/builtins/_internal/__elephc_normalize_callable.md @@ -2,7 +2,7 @@ title: "__elephc_normalize_callable() — internals" description: "Compiler internals for __elephc_normalize_callable(): lowering path, type checks, and runtime helpers." sidebar: - order: 604 + order: 605 --- ## `__elephc_normalize_callable()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_object_is_enum.md b/docs/internals/builtins/_internal/__elephc_object_is_enum.md index 99612c5c1e..40c8338bfc 100644 --- a/docs/internals/builtins/_internal/__elephc_object_is_enum.md +++ b/docs/internals/builtins/_internal/__elephc_object_is_enum.md @@ -2,7 +2,7 @@ title: "__elephc_object_is_enum() — internals" description: "Compiler internals for __elephc_object_is_enum(): lowering path, type checks, and runtime helpers." sidebar: - order: 605 + order: 606 --- ## `__elephc_object_is_enum()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_object_prop_count.md b/docs/internals/builtins/_internal/__elephc_object_prop_count.md index 4549d63e60..df5c6a4c39 100644 --- a/docs/internals/builtins/_internal/__elephc_object_prop_count.md +++ b/docs/internals/builtins/_internal/__elephc_object_prop_count.md @@ -2,7 +2,7 @@ title: "__elephc_object_prop_count() — internals" description: "Compiler internals for __elephc_object_prop_count(): lowering path, type checks, and runtime helpers." sidebar: - order: 606 + order: 607 --- ## `__elephc_object_prop_count()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_object_prop_name.md b/docs/internals/builtins/_internal/__elephc_object_prop_name.md index f2cd97dda5..1d6a61b22c 100644 --- a/docs/internals/builtins/_internal/__elephc_object_prop_name.md +++ b/docs/internals/builtins/_internal/__elephc_object_prop_name.md @@ -2,7 +2,7 @@ title: "__elephc_object_prop_name() — internals" description: "Compiler internals for __elephc_object_prop_name(): lowering path, type checks, and runtime helpers." sidebar: - order: 607 + order: 608 --- ## `__elephc_object_prop_name()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_object_prop_value.md b/docs/internals/builtins/_internal/__elephc_object_prop_value.md index 3d2c1ff7e9..2ecda3015e 100644 --- a/docs/internals/builtins/_internal/__elephc_object_prop_value.md +++ b/docs/internals/builtins/_internal/__elephc_object_prop_value.md @@ -2,7 +2,7 @@ title: "__elephc_object_prop_value() — internals" description: "Compiler internals for __elephc_object_prop_value(): lowering path, type checks, and runtime helpers." sidebar: - order: 608 + order: 609 --- ## `__elephc_object_prop_value()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_pdo_adapter_addr.md b/docs/internals/builtins/_internal/__elephc_pdo_adapter_addr.md index 6a73e06cb8..83b379c900 100644 --- a/docs/internals/builtins/_internal/__elephc_pdo_adapter_addr.md +++ b/docs/internals/builtins/_internal/__elephc_pdo_adapter_addr.md @@ -2,7 +2,7 @@ title: "__elephc_pdo_adapter_addr() — internals" description: "Compiler internals for __elephc_pdo_adapter_addr(): lowering path, type checks, and runtime helpers." sidebar: - order: 609 + order: 610 --- ## `__elephc_pdo_adapter_addr()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_pdo_called_class_status.md b/docs/internals/builtins/_internal/__elephc_pdo_called_class_status.md index 08a15d2d64..5667e2a111 100644 --- a/docs/internals/builtins/_internal/__elephc_pdo_called_class_status.md +++ b/docs/internals/builtins/_internal/__elephc_pdo_called_class_status.md @@ -2,7 +2,7 @@ title: "__elephc_pdo_called_class_status() — internals" description: "Compiler internals for __elephc_pdo_called_class_status(): lowering path, type checks, and runtime helpers." sidebar: - order: 610 + order: 611 --- ## `__elephc_pdo_called_class_status()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_pdo_statement_class_status.md b/docs/internals/builtins/_internal/__elephc_pdo_statement_class_status.md index cd214acc70..ae2d1b0b32 100644 --- a/docs/internals/builtins/_internal/__elephc_pdo_statement_class_status.md +++ b/docs/internals/builtins/_internal/__elephc_pdo_statement_class_status.md @@ -2,7 +2,7 @@ title: "__elephc_pdo_statement_class_status() — internals" description: "Compiler internals for __elephc_pdo_statement_class_status(): lowering path, type checks, and runtime helpers." sidebar: - order: 611 + order: 612 --- ## `__elephc_pdo_statement_class_status()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_bzip2_archive.md b/docs/internals/builtins/_internal/__elephc_phar_bzip2_archive.md index 5623ab338f..4cfd62eb83 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_bzip2_archive.md +++ b/docs/internals/builtins/_internal/__elephc_phar_bzip2_archive.md @@ -2,7 +2,7 @@ title: "__elephc_phar_bzip2_archive() — internals" description: "Compiler internals for __elephc_phar_bzip2_archive(): lowering path, type checks, and runtime helpers." sidebar: - order: 612 + order: 613 --- ## `__elephc_phar_bzip2_archive()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_decompress_archive.md b/docs/internals/builtins/_internal/__elephc_phar_decompress_archive.md index 195679ee5f..d0c65cc65c 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_decompress_archive.md +++ b/docs/internals/builtins/_internal/__elephc_phar_decompress_archive.md @@ -2,7 +2,7 @@ title: "__elephc_phar_decompress_archive() — internals" description: "Compiler internals for __elephc_phar_decompress_archive(): lowering path, type checks, and runtime helpers." sidebar: - order: 613 + order: 614 --- ## `__elephc_phar_decompress_archive()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_get_file_metadata.md b/docs/internals/builtins/_internal/__elephc_phar_get_file_metadata.md index eb0dc63d59..9bbe8f1ff1 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_get_file_metadata.md +++ b/docs/internals/builtins/_internal/__elephc_phar_get_file_metadata.md @@ -2,7 +2,7 @@ title: "__elephc_phar_get_file_metadata() — internals" description: "Compiler internals for __elephc_phar_get_file_metadata(): lowering path, type checks, and runtime helpers." sidebar: - order: 614 + order: 615 --- ## `__elephc_phar_get_file_metadata()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_get_metadata.md b/docs/internals/builtins/_internal/__elephc_phar_get_metadata.md index f043a05909..324abd3024 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_get_metadata.md +++ b/docs/internals/builtins/_internal/__elephc_phar_get_metadata.md @@ -2,7 +2,7 @@ title: "__elephc_phar_get_metadata() — internals" description: "Compiler internals for __elephc_phar_get_metadata(): lowering path, type checks, and runtime helpers." sidebar: - order: 615 + order: 616 --- ## `__elephc_phar_get_metadata()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_get_signature_hash.md b/docs/internals/builtins/_internal/__elephc_phar_get_signature_hash.md index e077202b35..68fcdd0a2d 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_get_signature_hash.md +++ b/docs/internals/builtins/_internal/__elephc_phar_get_signature_hash.md @@ -2,7 +2,7 @@ title: "__elephc_phar_get_signature_hash() — internals" description: "Compiler internals for __elephc_phar_get_signature_hash(): lowering path, type checks, and runtime helpers." sidebar: - order: 616 + order: 617 --- ## `__elephc_phar_get_signature_hash()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_get_signature_type.md b/docs/internals/builtins/_internal/__elephc_phar_get_signature_type.md index 12f7c349ef..b30033273f 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_get_signature_type.md +++ b/docs/internals/builtins/_internal/__elephc_phar_get_signature_type.md @@ -2,7 +2,7 @@ title: "__elephc_phar_get_signature_type() — internals" description: "Compiler internals for __elephc_phar_get_signature_type(): lowering path, type checks, and runtime helpers." sidebar: - order: 617 + order: 618 --- ## `__elephc_phar_get_signature_type()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_get_stub.md b/docs/internals/builtins/_internal/__elephc_phar_get_stub.md index 2e4391180f..4f026cc22b 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_get_stub.md +++ b/docs/internals/builtins/_internal/__elephc_phar_get_stub.md @@ -2,7 +2,7 @@ title: "__elephc_phar_get_stub() — internals" description: "Compiler internals for __elephc_phar_get_stub(): lowering path, type checks, and runtime helpers." sidebar: - order: 618 + order: 619 --- ## `__elephc_phar_get_stub()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_gzip_archive.md b/docs/internals/builtins/_internal/__elephc_phar_gzip_archive.md index 201a868de4..f29ba14b4b 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_gzip_archive.md +++ b/docs/internals/builtins/_internal/__elephc_phar_gzip_archive.md @@ -2,7 +2,7 @@ title: "__elephc_phar_gzip_archive() — internals" description: "Compiler internals for __elephc_phar_gzip_archive(): lowering path, type checks, and runtime helpers." sidebar: - order: 619 + order: 620 --- ## `__elephc_phar_gzip_archive()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_list_entries.md b/docs/internals/builtins/_internal/__elephc_phar_list_entries.md index e57d76cf3c..bb6b6c76d0 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_list_entries.md +++ b/docs/internals/builtins/_internal/__elephc_phar_list_entries.md @@ -2,7 +2,7 @@ title: "__elephc_phar_list_entries() — internals" description: "Compiler internals for __elephc_phar_list_entries(): lowering path, type checks, and runtime helpers." sidebar: - order: 620 + order: 621 --- ## `__elephc_phar_list_entries()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_set_compression.md b/docs/internals/builtins/_internal/__elephc_phar_set_compression.md index 9714101c1b..cf7a88ac53 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_set_compression.md +++ b/docs/internals/builtins/_internal/__elephc_phar_set_compression.md @@ -2,7 +2,7 @@ title: "__elephc_phar_set_compression() — internals" description: "Compiler internals for __elephc_phar_set_compression(): lowering path, type checks, and runtime helpers." sidebar: - order: 621 + order: 622 --- ## `__elephc_phar_set_compression()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_set_file_metadata.md b/docs/internals/builtins/_internal/__elephc_phar_set_file_metadata.md index dd8e3e1183..c369aa46b5 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_set_file_metadata.md +++ b/docs/internals/builtins/_internal/__elephc_phar_set_file_metadata.md @@ -2,7 +2,7 @@ title: "__elephc_phar_set_file_metadata() — internals" description: "Compiler internals for __elephc_phar_set_file_metadata(): lowering path, type checks, and runtime helpers." sidebar: - order: 622 + order: 623 --- ## `__elephc_phar_set_file_metadata()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_set_metadata.md b/docs/internals/builtins/_internal/__elephc_phar_set_metadata.md index 48ea1040f2..0ccd7720ed 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_set_metadata.md +++ b/docs/internals/builtins/_internal/__elephc_phar_set_metadata.md @@ -2,7 +2,7 @@ title: "__elephc_phar_set_metadata() — internals" description: "Compiler internals for __elephc_phar_set_metadata(): lowering path, type checks, and runtime helpers." sidebar: - order: 623 + order: 624 --- ## `__elephc_phar_set_metadata()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_set_stub.md b/docs/internals/builtins/_internal/__elephc_phar_set_stub.md index f8c0e12418..e04444bf89 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_set_stub.md +++ b/docs/internals/builtins/_internal/__elephc_phar_set_stub.md @@ -2,7 +2,7 @@ title: "__elephc_phar_set_stub() — internals" description: "Compiler internals for __elephc_phar_set_stub(): lowering path, type checks, and runtime helpers." sidebar: - order: 624 + order: 625 --- ## `__elephc_phar_set_stub()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_set_zip_password.md b/docs/internals/builtins/_internal/__elephc_phar_set_zip_password.md index 23ad9e377a..9a790711e9 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_set_zip_password.md +++ b/docs/internals/builtins/_internal/__elephc_phar_set_zip_password.md @@ -2,7 +2,7 @@ title: "__elephc_phar_set_zip_password() — internals" description: "Compiler internals for __elephc_phar_set_zip_password(): lowering path, type checks, and runtime helpers." sidebar: - order: 625 + order: 626 --- ## `__elephc_phar_set_zip_password()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_sign_hash.md b/docs/internals/builtins/_internal/__elephc_phar_sign_hash.md index 860e1791a3..823b0034c1 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_sign_hash.md +++ b/docs/internals/builtins/_internal/__elephc_phar_sign_hash.md @@ -2,7 +2,7 @@ title: "__elephc_phar_sign_hash() — internals" description: "Compiler internals for __elephc_phar_sign_hash(): lowering path, type checks, and runtime helpers." sidebar: - order: 626 + order: 627 --- ## `__elephc_phar_sign_hash()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_phar_sign_openssl.md b/docs/internals/builtins/_internal/__elephc_phar_sign_openssl.md index 86cedf8384..65f96b2e21 100644 --- a/docs/internals/builtins/_internal/__elephc_phar_sign_openssl.md +++ b/docs/internals/builtins/_internal/__elephc_phar_sign_openssl.md @@ -2,7 +2,7 @@ title: "__elephc_phar_sign_openssl() — internals" description: "Compiler internals for __elephc_phar_sign_openssl(): lowering path, type checks, and runtime helpers." sidebar: - order: 627 + order: 628 --- ## `__elephc_phar_sign_openssl()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_ptr_is_null.md b/docs/internals/builtins/_internal/__elephc_ptr_is_null.md index 06e65ff36a..22bc3820fd 100644 --- a/docs/internals/builtins/_internal/__elephc_ptr_is_null.md +++ b/docs/internals/builtins/_internal/__elephc_ptr_is_null.md @@ -2,7 +2,7 @@ title: "__elephc_ptr_is_null() — internals" description: "Compiler internals for __elephc_ptr_is_null(): lowering path, type checks, and runtime helpers." sidebar: - order: 628 + order: 629 --- ## `__elephc_ptr_is_null()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_ptr_read_string.md b/docs/internals/builtins/_internal/__elephc_ptr_read_string.md index 3548a4b88a..dc83c33073 100644 --- a/docs/internals/builtins/_internal/__elephc_ptr_read_string.md +++ b/docs/internals/builtins/_internal/__elephc_ptr_read_string.md @@ -2,7 +2,7 @@ title: "__elephc_ptr_read_string() — internals" description: "Compiler internals for __elephc_ptr_read_string(): lowering path, type checks, and runtime helpers." sidebar: - order: 629 + order: 630 --- ## `__elephc_ptr_read_string()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_ptr_write_string.md b/docs/internals/builtins/_internal/__elephc_ptr_write_string.md index 480cd5a71e..12d2c670cf 100644 --- a/docs/internals/builtins/_internal/__elephc_ptr_write_string.md +++ b/docs/internals/builtins/_internal/__elephc_ptr_write_string.md @@ -2,7 +2,7 @@ title: "__elephc_ptr_write_string() — internals" description: "Compiler internals for __elephc_ptr_write_string(): lowering path, type checks, and runtime helpers." sidebar: - order: 630 + order: 631 --- ## `__elephc_ptr_write_string()` — internals diff --git a/docs/internals/builtins/_internal/__elephc_strtotime_raw.md b/docs/internals/builtins/_internal/__elephc_strtotime_raw.md index 923fcde9b3..3abbbde9f4 100644 --- a/docs/internals/builtins/_internal/__elephc_strtotime_raw.md +++ b/docs/internals/builtins/_internal/__elephc_strtotime_raw.md @@ -2,7 +2,7 @@ title: "__elephc_strtotime_raw() — internals" description: "Compiler internals for __elephc_strtotime_raw(): lowering path, type checks, and runtime helpers." sidebar: - order: 631 + order: 632 --- ## `__elephc_strtotime_raw()` — internals diff --git a/docs/internals/builtins/string/mb_strimwidth.md b/docs/internals/builtins/string/mb_strimwidth.md new file mode 100644 index 0000000000..313b24b25f --- /dev/null +++ b/docs/internals/builtins/string/mb_strimwidth.md @@ -0,0 +1,58 @@ +--- +title: "mb_strimwidth() — internals" +description: "Compiler internals for mb_strimwidth(): lowering path, type checks, and runtime helpers." +sidebar: + order: 468 +--- + +## `mb_strimwidth()` — internals + +## Where it lives + +- **Signature**: [`src/builtins/string/mb_strimwidth.rs`](https://github.com/illegalstudio/elephc/blob/main/src/builtins/string/mb_strimwidth.rs) +- **Lowering**: [`src/builtins/semantics.rs`:553](https://github.com/illegalstudio/elephc/blob/main/src/builtins/semantics.rs#L553) (`lower_registry_call`) +- **Function symbol**: `lower_registry_call()` + + +### Lowering notes + +- Uses the `runtime_call` strategy from the single-source builtin descriptor. +- Emits the typed EIR target `runtime.mb_strimwidth` through `BuiltinLoweringContext`. +- The backend resolves that typed target through `src/codegen/lower_inst/runtime_calls.rs`; PHP builtin names do not participate in dispatch. + +## Semantic descriptor + +- **Target strategy**: `runtime_call` +- **Validation**: `checker_hook` +- **Result type source**: `checked` +- **Result ownership**: `fresh` +- **Effects**: `static (3 declared effects)` +- **Requirements**: `static (0 requirements)` +- **Callable policy**: `static_only` +- **Target support**: `macos-aarch64`, `ios-arm64`, `ios-sim-arm64`, `linux-aarch64`, `linux-x86_64` + +## EIR and runtime boundary + +- **Typed EIR target**: `runtime.mb_strimwidth` +- **Backend boundary**: `src/codegen/lower_inst/runtime_calls.rs` resolves the typed target without PHP-name dispatch. + +## Signature summary + +```php +function mb_strimwidth(string $string, int $start, int $width, string $trim_marker = '', string $encoding = null): string +``` + +## What the type checker enforces + +- **Arity**: takes 3–5 arguments (2 optional). + +## Eval interpreter (magician) + +- **Declaration**: [`crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs`](https://github.com/illegalstudio/elephc/blob/main/crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs) (`eval_builtin!`) +- **Execution**: Magician interpreter adapter. +- **Adapter reason**: `interpreter-specific-value-semantics`. +- **Dispatch hooks**: `direct`, `values` + +## Cross-references + +- [User reference for `mb_strimwidth()`](../../../php/builtins/string/mb_strimwidth.md) diff --git a/docs/internals/builtins/string/mb_strlen.md b/docs/internals/builtins/string/mb_strlen.md index a6b629d494..6280c0cab0 100644 --- a/docs/internals/builtins/string/mb_strlen.md +++ b/docs/internals/builtins/string/mb_strlen.md @@ -2,7 +2,7 @@ title: "mb_strlen() — internals" description: "Compiler internals for mb_strlen(): lowering path, type checks, and runtime helpers." sidebar: - order: 468 + order: 469 --- ## `mb_strlen()` — internals diff --git a/docs/internals/builtins/string/md5.md b/docs/internals/builtins/string/md5.md index b0738125de..b80bd4b6d2 100644 --- a/docs/internals/builtins/string/md5.md +++ b/docs/internals/builtins/string/md5.md @@ -2,7 +2,7 @@ title: "md5() — internals" description: "Compiler internals for md5(): lowering path, type checks, and runtime helpers." sidebar: - order: 469 + order: 470 --- ## `md5()` — internals diff --git a/docs/internals/builtins/string/nl2br.md b/docs/internals/builtins/string/nl2br.md index e78e3c2848..18908da8dd 100644 --- a/docs/internals/builtins/string/nl2br.md +++ b/docs/internals/builtins/string/nl2br.md @@ -2,7 +2,7 @@ title: "nl2br() — internals" description: "Compiler internals for nl2br(): lowering path, type checks, and runtime helpers." sidebar: - order: 470 + order: 471 --- ## `nl2br()` — internals diff --git a/docs/internals/builtins/string/number_format.md b/docs/internals/builtins/string/number_format.md index ff5903eed9..5efc105bf8 100644 --- a/docs/internals/builtins/string/number_format.md +++ b/docs/internals/builtins/string/number_format.md @@ -2,7 +2,7 @@ title: "number_format() — internals" description: "Compiler internals for number_format(): lowering path, type checks, and runtime helpers." sidebar: - order: 471 + order: 472 --- ## `number_format()` — internals diff --git a/docs/internals/builtins/string/openssl_cipher_iv_length.md b/docs/internals/builtins/string/openssl_cipher_iv_length.md index d9018a5879..e5ec177ad5 100644 --- a/docs/internals/builtins/string/openssl_cipher_iv_length.md +++ b/docs/internals/builtins/string/openssl_cipher_iv_length.md @@ -2,7 +2,7 @@ title: "openssl_cipher_iv_length() — internals" description: "Compiler internals for openssl_cipher_iv_length(): lowering path, type checks, and runtime helpers." sidebar: - order: 472 + order: 473 --- ## `openssl_cipher_iv_length()` — internals diff --git a/docs/internals/builtins/string/openssl_decrypt.md b/docs/internals/builtins/string/openssl_decrypt.md index 72956b1165..206ee52410 100644 --- a/docs/internals/builtins/string/openssl_decrypt.md +++ b/docs/internals/builtins/string/openssl_decrypt.md @@ -2,7 +2,7 @@ title: "openssl_decrypt() — internals" description: "Compiler internals for openssl_decrypt(): lowering path, type checks, and runtime helpers." sidebar: - order: 473 + order: 474 --- ## `openssl_decrypt()` — internals diff --git a/docs/internals/builtins/string/openssl_encrypt.md b/docs/internals/builtins/string/openssl_encrypt.md index 1c35add84e..7ca8f0c5e8 100644 --- a/docs/internals/builtins/string/openssl_encrypt.md +++ b/docs/internals/builtins/string/openssl_encrypt.md @@ -2,7 +2,7 @@ title: "openssl_encrypt() — internals" description: "Compiler internals for openssl_encrypt(): lowering path, type checks, and runtime helpers." sidebar: - order: 474 + order: 475 --- ## `openssl_encrypt()` — internals diff --git a/docs/internals/builtins/string/openssl_get_cipher_methods.md b/docs/internals/builtins/string/openssl_get_cipher_methods.md index 63e6570b2b..7243c456a3 100644 --- a/docs/internals/builtins/string/openssl_get_cipher_methods.md +++ b/docs/internals/builtins/string/openssl_get_cipher_methods.md @@ -2,7 +2,7 @@ title: "openssl_get_cipher_methods() — internals" description: "Compiler internals for openssl_get_cipher_methods(): lowering path, type checks, and runtime helpers." sidebar: - order: 475 + order: 476 --- ## `openssl_get_cipher_methods()` — internals diff --git a/docs/internals/builtins/string/ord.md b/docs/internals/builtins/string/ord.md index 25d0b3524a..7831358973 100644 --- a/docs/internals/builtins/string/ord.md +++ b/docs/internals/builtins/string/ord.md @@ -2,7 +2,7 @@ title: "ord() — internals" description: "Compiler internals for ord(): lowering path, type checks, and runtime helpers." sidebar: - order: 476 + order: 477 --- ## `ord()` — internals diff --git a/docs/internals/builtins/string/parse_url.md b/docs/internals/builtins/string/parse_url.md index 689c0a6d75..aef19dcb29 100644 --- a/docs/internals/builtins/string/parse_url.md +++ b/docs/internals/builtins/string/parse_url.md @@ -2,7 +2,7 @@ title: "parse_url() — internals" description: "Compiler internals for parse_url(): lowering path, type checks, and runtime helpers." sidebar: - order: 477 + order: 478 --- ## `parse_url()` — internals diff --git a/docs/internals/builtins/string/printf.md b/docs/internals/builtins/string/printf.md index 300809b098..a3a335037d 100644 --- a/docs/internals/builtins/string/printf.md +++ b/docs/internals/builtins/string/printf.md @@ -2,7 +2,7 @@ title: "printf() — internals" description: "Compiler internals for printf(): lowering path, type checks, and runtime helpers." sidebar: - order: 478 + order: 479 --- ## `printf()` — internals diff --git a/docs/internals/builtins/string/quoted_printable_encode.md b/docs/internals/builtins/string/quoted_printable_encode.md index 4ae4132b31..12087d880c 100644 --- a/docs/internals/builtins/string/quoted_printable_encode.md +++ b/docs/internals/builtins/string/quoted_printable_encode.md @@ -2,7 +2,7 @@ title: "quoted_printable_encode() — internals" description: "Compiler internals for quoted_printable_encode(): lowering path, type checks, and runtime helpers." sidebar: - order: 479 + order: 480 --- ## `quoted_printable_encode()` — internals diff --git a/docs/internals/builtins/string/quotemeta.md b/docs/internals/builtins/string/quotemeta.md index c7ec57f182..13988c2787 100644 --- a/docs/internals/builtins/string/quotemeta.md +++ b/docs/internals/builtins/string/quotemeta.md @@ -2,7 +2,7 @@ title: "quotemeta() — internals" description: "Compiler internals for quotemeta(): lowering path, type checks, and runtime helpers." sidebar: - order: 480 + order: 481 --- ## `quotemeta()` — internals diff --git a/docs/internals/builtins/string/rawurldecode.md b/docs/internals/builtins/string/rawurldecode.md index 6b1c0185a2..293deae141 100644 --- a/docs/internals/builtins/string/rawurldecode.md +++ b/docs/internals/builtins/string/rawurldecode.md @@ -2,7 +2,7 @@ title: "rawurldecode() — internals" description: "Compiler internals for rawurldecode(): lowering path, type checks, and runtime helpers." sidebar: - order: 481 + order: 482 --- ## `rawurldecode()` — internals diff --git a/docs/internals/builtins/string/rawurlencode.md b/docs/internals/builtins/string/rawurlencode.md index 24431b9111..782c465171 100644 --- a/docs/internals/builtins/string/rawurlencode.md +++ b/docs/internals/builtins/string/rawurlencode.md @@ -2,7 +2,7 @@ title: "rawurlencode() — internals" description: "Compiler internals for rawurlencode(): lowering path, type checks, and runtime helpers." sidebar: - order: 482 + order: 483 --- ## `rawurlencode()` — internals diff --git a/docs/internals/builtins/string/rtrim.md b/docs/internals/builtins/string/rtrim.md index 1ff1a7f9e9..bc5df3c5ea 100644 --- a/docs/internals/builtins/string/rtrim.md +++ b/docs/internals/builtins/string/rtrim.md @@ -2,7 +2,7 @@ title: "rtrim() — internals" description: "Compiler internals for rtrim(): lowering path, type checks, and runtime helpers." sidebar: - order: 483 + order: 484 --- ## `rtrim()` — internals diff --git a/docs/internals/builtins/string/sha1.md b/docs/internals/builtins/string/sha1.md index 09bec16612..d8d38829d2 100644 --- a/docs/internals/builtins/string/sha1.md +++ b/docs/internals/builtins/string/sha1.md @@ -2,7 +2,7 @@ title: "sha1() — internals" description: "Compiler internals for sha1(): lowering path, type checks, and runtime helpers." sidebar: - order: 484 + order: 485 --- ## `sha1()` — internals diff --git a/docs/internals/builtins/string/sprintf.md b/docs/internals/builtins/string/sprintf.md index 04238eeb26..4d3cc93562 100644 --- a/docs/internals/builtins/string/sprintf.md +++ b/docs/internals/builtins/string/sprintf.md @@ -2,7 +2,7 @@ title: "sprintf() — internals" description: "Compiler internals for sprintf(): lowering path, type checks, and runtime helpers." sidebar: - order: 485 + order: 486 --- ## `sprintf()` — internals diff --git a/docs/internals/builtins/string/sscanf.md b/docs/internals/builtins/string/sscanf.md index 64c4015ca4..914bc91e5a 100644 --- a/docs/internals/builtins/string/sscanf.md +++ b/docs/internals/builtins/string/sscanf.md @@ -2,7 +2,7 @@ title: "sscanf() — internals" description: "Compiler internals for sscanf(): lowering path, type checks, and runtime helpers." sidebar: - order: 486 + order: 487 --- ## `sscanf()` — internals diff --git a/docs/internals/builtins/string/str_contains.md b/docs/internals/builtins/string/str_contains.md index 7896d935f2..b4f887d825 100644 --- a/docs/internals/builtins/string/str_contains.md +++ b/docs/internals/builtins/string/str_contains.md @@ -2,7 +2,7 @@ title: "str_contains() — internals" description: "Compiler internals for str_contains(): lowering path, type checks, and runtime helpers." sidebar: - order: 487 + order: 488 --- ## `str_contains()` — internals diff --git a/docs/internals/builtins/string/str_ends_with.md b/docs/internals/builtins/string/str_ends_with.md index f5fcae4313..b6685a1982 100644 --- a/docs/internals/builtins/string/str_ends_with.md +++ b/docs/internals/builtins/string/str_ends_with.md @@ -2,7 +2,7 @@ title: "str_ends_with() — internals" description: "Compiler internals for str_ends_with(): lowering path, type checks, and runtime helpers." sidebar: - order: 488 + order: 489 --- ## `str_ends_with()` — internals diff --git a/docs/internals/builtins/string/str_ireplace.md b/docs/internals/builtins/string/str_ireplace.md index 3a608add8a..a1f8bc92ea 100644 --- a/docs/internals/builtins/string/str_ireplace.md +++ b/docs/internals/builtins/string/str_ireplace.md @@ -2,7 +2,7 @@ title: "str_ireplace() — internals" description: "Compiler internals for str_ireplace(): lowering path, type checks, and runtime helpers." sidebar: - order: 489 + order: 490 --- ## `str_ireplace()` — internals diff --git a/docs/internals/builtins/string/str_pad.md b/docs/internals/builtins/string/str_pad.md index e61fca907e..db7b942311 100644 --- a/docs/internals/builtins/string/str_pad.md +++ b/docs/internals/builtins/string/str_pad.md @@ -2,7 +2,7 @@ title: "str_pad() — internals" description: "Compiler internals for str_pad(): lowering path, type checks, and runtime helpers." sidebar: - order: 490 + order: 491 --- ## `str_pad()` — internals diff --git a/docs/internals/builtins/string/str_repeat.md b/docs/internals/builtins/string/str_repeat.md index 676015b53d..d6b683e44a 100644 --- a/docs/internals/builtins/string/str_repeat.md +++ b/docs/internals/builtins/string/str_repeat.md @@ -2,7 +2,7 @@ title: "str_repeat() — internals" description: "Compiler internals for str_repeat(): lowering path, type checks, and runtime helpers." sidebar: - order: 491 + order: 492 --- ## `str_repeat()` — internals diff --git a/docs/internals/builtins/string/str_replace.md b/docs/internals/builtins/string/str_replace.md index e46bfa8238..1834dbd1db 100644 --- a/docs/internals/builtins/string/str_replace.md +++ b/docs/internals/builtins/string/str_replace.md @@ -2,7 +2,7 @@ title: "str_replace() — internals" description: "Compiler internals for str_replace(): lowering path, type checks, and runtime helpers." sidebar: - order: 492 + order: 493 --- ## `str_replace()` — internals diff --git a/docs/internals/builtins/string/str_split.md b/docs/internals/builtins/string/str_split.md index 4da8bfd4ed..ea9d347c4e 100644 --- a/docs/internals/builtins/string/str_split.md +++ b/docs/internals/builtins/string/str_split.md @@ -2,7 +2,7 @@ title: "str_split() — internals" description: "Compiler internals for str_split(): lowering path, type checks, and runtime helpers." sidebar: - order: 493 + order: 494 --- ## `str_split()` — internals diff --git a/docs/internals/builtins/string/str_starts_with.md b/docs/internals/builtins/string/str_starts_with.md index 1c046372be..1a16bffa8d 100644 --- a/docs/internals/builtins/string/str_starts_with.md +++ b/docs/internals/builtins/string/str_starts_with.md @@ -2,7 +2,7 @@ title: "str_starts_with() — internals" description: "Compiler internals for str_starts_with(): lowering path, type checks, and runtime helpers." sidebar: - order: 494 + order: 495 --- ## `str_starts_with()` — internals diff --git a/docs/internals/builtins/string/str_word_count.md b/docs/internals/builtins/string/str_word_count.md index 06334553c5..c560873961 100644 --- a/docs/internals/builtins/string/str_word_count.md +++ b/docs/internals/builtins/string/str_word_count.md @@ -2,7 +2,7 @@ title: "str_word_count() — internals" description: "Compiler internals for str_word_count(): lowering path, type checks, and runtime helpers." sidebar: - order: 495 + order: 496 --- ## `str_word_count()` — internals diff --git a/docs/internals/builtins/string/strcasecmp.md b/docs/internals/builtins/string/strcasecmp.md index fafae26bfc..d4061a84ba 100644 --- a/docs/internals/builtins/string/strcasecmp.md +++ b/docs/internals/builtins/string/strcasecmp.md @@ -2,7 +2,7 @@ title: "strcasecmp() — internals" description: "Compiler internals for strcasecmp(): lowering path, type checks, and runtime helpers." sidebar: - order: 496 + order: 497 --- ## `strcasecmp()` — internals diff --git a/docs/internals/builtins/string/strcmp.md b/docs/internals/builtins/string/strcmp.md index 9f94c3a0cd..221b9c21ce 100644 --- a/docs/internals/builtins/string/strcmp.md +++ b/docs/internals/builtins/string/strcmp.md @@ -2,7 +2,7 @@ title: "strcmp() — internals" description: "Compiler internals for strcmp(): lowering path, type checks, and runtime helpers." sidebar: - order: 497 + order: 498 --- ## `strcmp()` — internals diff --git a/docs/internals/builtins/string/stripos.md b/docs/internals/builtins/string/stripos.md index feaf71db06..a8d19131db 100644 --- a/docs/internals/builtins/string/stripos.md +++ b/docs/internals/builtins/string/stripos.md @@ -2,7 +2,7 @@ title: "stripos() — internals" description: "Compiler internals for stripos(): lowering path, type checks, and runtime helpers." sidebar: - order: 498 + order: 499 --- ## `stripos()` — internals diff --git a/docs/internals/builtins/string/stripslashes.md b/docs/internals/builtins/string/stripslashes.md index 4002e14fd3..b7426932b2 100644 --- a/docs/internals/builtins/string/stripslashes.md +++ b/docs/internals/builtins/string/stripslashes.md @@ -2,7 +2,7 @@ title: "stripslashes() — internals" description: "Compiler internals for stripslashes(): lowering path, type checks, and runtime helpers." sidebar: - order: 499 + order: 500 --- ## `stripslashes()` — internals diff --git a/docs/internals/builtins/string/strlen.md b/docs/internals/builtins/string/strlen.md index 1064cc3222..8582ab5dfe 100644 --- a/docs/internals/builtins/string/strlen.md +++ b/docs/internals/builtins/string/strlen.md @@ -2,7 +2,7 @@ title: "strlen() — internals" description: "Compiler internals for strlen(): lowering path, type checks, and runtime helpers." sidebar: - order: 500 + order: 501 --- ## `strlen()` — internals diff --git a/docs/internals/builtins/string/strncasecmp.md b/docs/internals/builtins/string/strncasecmp.md index 15310cb7e3..9a0cfdc744 100644 --- a/docs/internals/builtins/string/strncasecmp.md +++ b/docs/internals/builtins/string/strncasecmp.md @@ -2,7 +2,7 @@ title: "strncasecmp() — internals" description: "Compiler internals for strncasecmp(): lowering path, type checks, and runtime helpers." sidebar: - order: 501 + order: 502 --- ## `strncasecmp()` — internals diff --git a/docs/internals/builtins/string/strncmp.md b/docs/internals/builtins/string/strncmp.md index 5125c45f4a..44777ba946 100644 --- a/docs/internals/builtins/string/strncmp.md +++ b/docs/internals/builtins/string/strncmp.md @@ -2,7 +2,7 @@ title: "strncmp() — internals" description: "Compiler internals for strncmp(): lowering path, type checks, and runtime helpers." sidebar: - order: 502 + order: 503 --- ## `strncmp()` — internals diff --git a/docs/internals/builtins/string/strpos.md b/docs/internals/builtins/string/strpos.md index 9f29dc8e64..4e6a49d5ae 100644 --- a/docs/internals/builtins/string/strpos.md +++ b/docs/internals/builtins/string/strpos.md @@ -2,7 +2,7 @@ title: "strpos() — internals" description: "Compiler internals for strpos(): lowering path, type checks, and runtime helpers." sidebar: - order: 503 + order: 504 --- ## `strpos()` — internals diff --git a/docs/internals/builtins/string/strrev.md b/docs/internals/builtins/string/strrev.md index 5fa1812ba9..26165b32ea 100644 --- a/docs/internals/builtins/string/strrev.md +++ b/docs/internals/builtins/string/strrev.md @@ -2,7 +2,7 @@ title: "strrev() — internals" description: "Compiler internals for strrev(): lowering path, type checks, and runtime helpers." sidebar: - order: 504 + order: 505 --- ## `strrev()` — internals diff --git a/docs/internals/builtins/string/strripos.md b/docs/internals/builtins/string/strripos.md index 0eb261c56b..9a34f4aa1d 100644 --- a/docs/internals/builtins/string/strripos.md +++ b/docs/internals/builtins/string/strripos.md @@ -2,7 +2,7 @@ title: "strripos() — internals" description: "Compiler internals for strripos(): lowering path, type checks, and runtime helpers." sidebar: - order: 505 + order: 506 --- ## `strripos()` — internals diff --git a/docs/internals/builtins/string/strrpos.md b/docs/internals/builtins/string/strrpos.md index 8a9b578c79..a38f54cfad 100644 --- a/docs/internals/builtins/string/strrpos.md +++ b/docs/internals/builtins/string/strrpos.md @@ -2,7 +2,7 @@ title: "strrpos() — internals" description: "Compiler internals for strrpos(): lowering path, type checks, and runtime helpers." sidebar: - order: 506 + order: 507 --- ## `strrpos()` — internals diff --git a/docs/internals/builtins/string/strstr.md b/docs/internals/builtins/string/strstr.md index ef4dc8074e..9b399b0e80 100644 --- a/docs/internals/builtins/string/strstr.md +++ b/docs/internals/builtins/string/strstr.md @@ -2,7 +2,7 @@ title: "strstr() — internals" description: "Compiler internals for strstr(): lowering path, type checks, and runtime helpers." sidebar: - order: 507 + order: 508 --- ## `strstr()` — internals diff --git a/docs/internals/builtins/string/strtolower.md b/docs/internals/builtins/string/strtolower.md index cb4e64cdb4..1b5dd3155e 100644 --- a/docs/internals/builtins/string/strtolower.md +++ b/docs/internals/builtins/string/strtolower.md @@ -2,7 +2,7 @@ title: "strtolower() — internals" description: "Compiler internals for strtolower(): lowering path, type checks, and runtime helpers." sidebar: - order: 508 + order: 509 --- ## `strtolower()` — internals diff --git a/docs/internals/builtins/string/strtoupper.md b/docs/internals/builtins/string/strtoupper.md index 25856b5832..198d581e4f 100644 --- a/docs/internals/builtins/string/strtoupper.md +++ b/docs/internals/builtins/string/strtoupper.md @@ -2,7 +2,7 @@ title: "strtoupper() — internals" description: "Compiler internals for strtoupper(): lowering path, type checks, and runtime helpers." sidebar: - order: 509 + order: 510 --- ## `strtoupper()` — internals diff --git a/docs/internals/builtins/string/strtr.md b/docs/internals/builtins/string/strtr.md index 52970657f4..8860924a65 100644 --- a/docs/internals/builtins/string/strtr.md +++ b/docs/internals/builtins/string/strtr.md @@ -2,7 +2,7 @@ title: "strtr() — internals" description: "Compiler internals for strtr(): lowering path, type checks, and runtime helpers." sidebar: - order: 510 + order: 511 --- ## `strtr()` — internals diff --git a/docs/internals/builtins/string/substr.md b/docs/internals/builtins/string/substr.md index f054b7b55f..6856676e5c 100644 --- a/docs/internals/builtins/string/substr.md +++ b/docs/internals/builtins/string/substr.md @@ -2,7 +2,7 @@ title: "substr() — internals" description: "Compiler internals for substr(): lowering path, type checks, and runtime helpers." sidebar: - order: 511 + order: 512 --- ## `substr()` — internals diff --git a/docs/internals/builtins/string/substr_count.md b/docs/internals/builtins/string/substr_count.md index 0fee16244d..1edaf2d7e8 100644 --- a/docs/internals/builtins/string/substr_count.md +++ b/docs/internals/builtins/string/substr_count.md @@ -2,7 +2,7 @@ title: "substr_count() — internals" description: "Compiler internals for substr_count(): lowering path, type checks, and runtime helpers." sidebar: - order: 512 + order: 513 --- ## `substr_count()` — internals diff --git a/docs/internals/builtins/string/substr_replace.md b/docs/internals/builtins/string/substr_replace.md index a85f265256..3ffd12ec99 100644 --- a/docs/internals/builtins/string/substr_replace.md +++ b/docs/internals/builtins/string/substr_replace.md @@ -2,7 +2,7 @@ title: "substr_replace() — internals" description: "Compiler internals for substr_replace(): lowering path, type checks, and runtime helpers." sidebar: - order: 513 + order: 514 --- ## `substr_replace()` — internals diff --git a/docs/internals/builtins/string/trim.md b/docs/internals/builtins/string/trim.md index 2f636e6280..868e6ad205 100644 --- a/docs/internals/builtins/string/trim.md +++ b/docs/internals/builtins/string/trim.md @@ -2,7 +2,7 @@ title: "trim() — internals" description: "Compiler internals for trim(): lowering path, type checks, and runtime helpers." sidebar: - order: 514 + order: 515 --- ## `trim()` — internals diff --git a/docs/internals/builtins/string/ucfirst.md b/docs/internals/builtins/string/ucfirst.md index e15d4f48d6..79f007a5f6 100644 --- a/docs/internals/builtins/string/ucfirst.md +++ b/docs/internals/builtins/string/ucfirst.md @@ -2,7 +2,7 @@ title: "ucfirst() — internals" description: "Compiler internals for ucfirst(): lowering path, type checks, and runtime helpers." sidebar: - order: 515 + order: 516 --- ## `ucfirst()` — internals diff --git a/docs/internals/builtins/string/ucwords.md b/docs/internals/builtins/string/ucwords.md index 530f3a6d80..975b874d6d 100644 --- a/docs/internals/builtins/string/ucwords.md +++ b/docs/internals/builtins/string/ucwords.md @@ -2,7 +2,7 @@ title: "ucwords() — internals" description: "Compiler internals for ucwords(): lowering path, type checks, and runtime helpers." sidebar: - order: 516 + order: 517 --- ## `ucwords()` — internals diff --git a/docs/internals/builtins/string/urldecode.md b/docs/internals/builtins/string/urldecode.md index 3b7d33c5e7..7515400b63 100644 --- a/docs/internals/builtins/string/urldecode.md +++ b/docs/internals/builtins/string/urldecode.md @@ -2,7 +2,7 @@ title: "urldecode() — internals" description: "Compiler internals for urldecode(): lowering path, type checks, and runtime helpers." sidebar: - order: 517 + order: 518 --- ## `urldecode()` — internals diff --git a/docs/internals/builtins/string/urlencode.md b/docs/internals/builtins/string/urlencode.md index 9c998f6736..e6ff61e58e 100644 --- a/docs/internals/builtins/string/urlencode.md +++ b/docs/internals/builtins/string/urlencode.md @@ -2,7 +2,7 @@ title: "urlencode() — internals" description: "Compiler internals for urlencode(): lowering path, type checks, and runtime helpers." sidebar: - order: 518 + order: 519 --- ## `urlencode()` — internals diff --git a/docs/internals/builtins/string/vprintf.md b/docs/internals/builtins/string/vprintf.md index 501c78c457..61758f4eee 100644 --- a/docs/internals/builtins/string/vprintf.md +++ b/docs/internals/builtins/string/vprintf.md @@ -2,7 +2,7 @@ title: "vprintf() — internals" description: "Compiler internals for vprintf(): lowering path, type checks, and runtime helpers." sidebar: - order: 519 + order: 520 --- ## `vprintf()` — internals diff --git a/docs/internals/builtins/string/vsprintf.md b/docs/internals/builtins/string/vsprintf.md index 5217164e37..001e4728dd 100644 --- a/docs/internals/builtins/string/vsprintf.md +++ b/docs/internals/builtins/string/vsprintf.md @@ -2,7 +2,7 @@ title: "vsprintf() — internals" description: "Compiler internals for vsprintf(): lowering path, type checks, and runtime helpers." sidebar: - order: 520 + order: 521 --- ## `vsprintf()` — internals diff --git a/docs/internals/builtins/string/wordwrap.md b/docs/internals/builtins/string/wordwrap.md index 9c1673f511..7d54b455a7 100644 --- a/docs/internals/builtins/string/wordwrap.md +++ b/docs/internals/builtins/string/wordwrap.md @@ -2,7 +2,7 @@ title: "wordwrap() — internals" description: "Compiler internals for wordwrap(): lowering path, type checks, and runtime helpers." sidebar: - order: 521 + order: 522 --- ## `wordwrap()` — internals diff --git a/docs/internals/builtins/type/boolval.md b/docs/internals/builtins/type/boolval.md index 441a57bf4d..579b3e8a16 100644 --- a/docs/internals/builtins/type/boolval.md +++ b/docs/internals/builtins/type/boolval.md @@ -2,7 +2,7 @@ title: "boolval() — internals" description: "Compiler internals for boolval(): lowering path, type checks, and runtime helpers." sidebar: - order: 522 + order: 523 --- ## `boolval()` — internals diff --git a/docs/internals/builtins/type/ctype_alnum.md b/docs/internals/builtins/type/ctype_alnum.md index 5ea655df77..568880808a 100644 --- a/docs/internals/builtins/type/ctype_alnum.md +++ b/docs/internals/builtins/type/ctype_alnum.md @@ -2,7 +2,7 @@ title: "ctype_alnum() — internals" description: "Compiler internals for ctype_alnum(): lowering path, type checks, and runtime helpers." sidebar: - order: 523 + order: 524 --- ## `ctype_alnum()` — internals diff --git a/docs/internals/builtins/type/ctype_alpha.md b/docs/internals/builtins/type/ctype_alpha.md index ce4fd9476c..2c90832934 100644 --- a/docs/internals/builtins/type/ctype_alpha.md +++ b/docs/internals/builtins/type/ctype_alpha.md @@ -2,7 +2,7 @@ title: "ctype_alpha() — internals" description: "Compiler internals for ctype_alpha(): lowering path, type checks, and runtime helpers." sidebar: - order: 524 + order: 525 --- ## `ctype_alpha()` — internals diff --git a/docs/internals/builtins/type/ctype_digit.md b/docs/internals/builtins/type/ctype_digit.md index 8f94ec1f81..6ccd1ca672 100644 --- a/docs/internals/builtins/type/ctype_digit.md +++ b/docs/internals/builtins/type/ctype_digit.md @@ -2,7 +2,7 @@ title: "ctype_digit() — internals" description: "Compiler internals for ctype_digit(): lowering path, type checks, and runtime helpers." sidebar: - order: 525 + order: 526 --- ## `ctype_digit()` — internals diff --git a/docs/internals/builtins/type/ctype_space.md b/docs/internals/builtins/type/ctype_space.md index e168752df7..14c5b9941f 100644 --- a/docs/internals/builtins/type/ctype_space.md +++ b/docs/internals/builtins/type/ctype_space.md @@ -2,7 +2,7 @@ title: "ctype_space() — internals" description: "Compiler internals for ctype_space(): lowering path, type checks, and runtime helpers." sidebar: - order: 526 + order: 527 --- ## `ctype_space()` — internals diff --git a/docs/internals/builtins/type/floatval.md b/docs/internals/builtins/type/floatval.md index 3a4ebbb83c..0641bf48f7 100644 --- a/docs/internals/builtins/type/floatval.md +++ b/docs/internals/builtins/type/floatval.md @@ -2,7 +2,7 @@ title: "floatval() — internals" description: "Compiler internals for floatval(): lowering path, type checks, and runtime helpers." sidebar: - order: 527 + order: 528 --- ## `floatval()` — internals diff --git a/docs/internals/builtins/type/get_resource_id.md b/docs/internals/builtins/type/get_resource_id.md index 7f720333d6..630499337b 100644 --- a/docs/internals/builtins/type/get_resource_id.md +++ b/docs/internals/builtins/type/get_resource_id.md @@ -2,7 +2,7 @@ title: "get_resource_id() — internals" description: "Compiler internals for get_resource_id(): lowering path, type checks, and runtime helpers." sidebar: - order: 528 + order: 529 --- ## `get_resource_id()` — internals diff --git a/docs/internals/builtins/type/get_resource_type.md b/docs/internals/builtins/type/get_resource_type.md index 12280fa238..355ce2b857 100644 --- a/docs/internals/builtins/type/get_resource_type.md +++ b/docs/internals/builtins/type/get_resource_type.md @@ -2,7 +2,7 @@ title: "get_resource_type() — internals" description: "Compiler internals for get_resource_type(): lowering path, type checks, and runtime helpers." sidebar: - order: 529 + order: 530 --- ## `get_resource_type()` — internals diff --git a/docs/internals/builtins/type/gettype.md b/docs/internals/builtins/type/gettype.md index eaf0c98215..7759b946e6 100644 --- a/docs/internals/builtins/type/gettype.md +++ b/docs/internals/builtins/type/gettype.md @@ -2,7 +2,7 @@ title: "gettype() — internals" description: "Compiler internals for gettype(): lowering path, type checks, and runtime helpers." sidebar: - order: 530 + order: 531 --- ## `gettype()` — internals diff --git a/docs/internals/builtins/type/intval.md b/docs/internals/builtins/type/intval.md index cee12327e9..d0053f71e1 100644 --- a/docs/internals/builtins/type/intval.md +++ b/docs/internals/builtins/type/intval.md @@ -2,7 +2,7 @@ title: "intval() — internals" description: "Compiler internals for intval(): lowering path, type checks, and runtime helpers." sidebar: - order: 531 + order: 532 --- ## `intval()` — internals diff --git a/docs/internals/builtins/type/is_array.md b/docs/internals/builtins/type/is_array.md index ec89a8f965..f07dbffd1e 100644 --- a/docs/internals/builtins/type/is_array.md +++ b/docs/internals/builtins/type/is_array.md @@ -2,7 +2,7 @@ title: "is_array() — internals" description: "Compiler internals for is_array(): lowering path, type checks, and runtime helpers." sidebar: - order: 532 + order: 533 --- ## `is_array()` — internals diff --git a/docs/internals/builtins/type/is_bool.md b/docs/internals/builtins/type/is_bool.md index 1efe240d51..c683a8ca40 100644 --- a/docs/internals/builtins/type/is_bool.md +++ b/docs/internals/builtins/type/is_bool.md @@ -2,7 +2,7 @@ title: "is_bool() — internals" description: "Compiler internals for is_bool(): lowering path, type checks, and runtime helpers." sidebar: - order: 533 + order: 534 --- ## `is_bool()` — internals diff --git a/docs/internals/builtins/type/is_callable.md b/docs/internals/builtins/type/is_callable.md index a7cb362651..5ee8831566 100644 --- a/docs/internals/builtins/type/is_callable.md +++ b/docs/internals/builtins/type/is_callable.md @@ -2,7 +2,7 @@ title: "is_callable() — internals" description: "Compiler internals for is_callable(): lowering path, type checks, and runtime helpers." sidebar: - order: 534 + order: 535 --- ## `is_callable()` — internals diff --git a/docs/internals/builtins/type/is_double.md b/docs/internals/builtins/type/is_double.md index 444908a90e..10f300713e 100644 --- a/docs/internals/builtins/type/is_double.md +++ b/docs/internals/builtins/type/is_double.md @@ -2,7 +2,7 @@ title: "is_double() — internals" description: "Compiler internals for is_double(): lowering path, type checks, and runtime helpers." sidebar: - order: 535 + order: 536 --- ## `is_double()` — internals diff --git a/docs/internals/builtins/type/is_float.md b/docs/internals/builtins/type/is_float.md index 86d38717b2..cbc258ba3d 100644 --- a/docs/internals/builtins/type/is_float.md +++ b/docs/internals/builtins/type/is_float.md @@ -2,7 +2,7 @@ title: "is_float() — internals" description: "Compiler internals for is_float(): lowering path, type checks, and runtime helpers." sidebar: - order: 536 + order: 537 --- ## `is_float()` — internals diff --git a/docs/internals/builtins/type/is_int.md b/docs/internals/builtins/type/is_int.md index f0b0b591e9..487b83c266 100644 --- a/docs/internals/builtins/type/is_int.md +++ b/docs/internals/builtins/type/is_int.md @@ -2,7 +2,7 @@ title: "is_int() — internals" description: "Compiler internals for is_int(): lowering path, type checks, and runtime helpers." sidebar: - order: 537 + order: 538 --- ## `is_int()` — internals diff --git a/docs/internals/builtins/type/is_integer.md b/docs/internals/builtins/type/is_integer.md index 9dc471b845..33b30017ea 100644 --- a/docs/internals/builtins/type/is_integer.md +++ b/docs/internals/builtins/type/is_integer.md @@ -2,7 +2,7 @@ title: "is_integer() — internals" description: "Compiler internals for is_integer(): lowering path, type checks, and runtime helpers." sidebar: - order: 538 + order: 539 --- ## `is_integer()` — internals diff --git a/docs/internals/builtins/type/is_iterable.md b/docs/internals/builtins/type/is_iterable.md index 80fbff542e..06b47c276a 100644 --- a/docs/internals/builtins/type/is_iterable.md +++ b/docs/internals/builtins/type/is_iterable.md @@ -2,7 +2,7 @@ title: "is_iterable() — internals" description: "Compiler internals for is_iterable(): lowering path, type checks, and runtime helpers." sidebar: - order: 539 + order: 540 --- ## `is_iterable()` — internals diff --git a/docs/internals/builtins/type/is_long.md b/docs/internals/builtins/type/is_long.md index eace4221ed..3c7a6b254f 100644 --- a/docs/internals/builtins/type/is_long.md +++ b/docs/internals/builtins/type/is_long.md @@ -2,7 +2,7 @@ title: "is_long() — internals" description: "Compiler internals for is_long(): lowering path, type checks, and runtime helpers." sidebar: - order: 540 + order: 541 --- ## `is_long()` — internals diff --git a/docs/internals/builtins/type/is_null.md b/docs/internals/builtins/type/is_null.md index 4e25b27d81..0f6af81b30 100644 --- a/docs/internals/builtins/type/is_null.md +++ b/docs/internals/builtins/type/is_null.md @@ -2,7 +2,7 @@ title: "is_null() — internals" description: "Compiler internals for is_null(): lowering path, type checks, and runtime helpers." sidebar: - order: 541 + order: 542 --- ## `is_null()` — internals diff --git a/docs/internals/builtins/type/is_numeric.md b/docs/internals/builtins/type/is_numeric.md index 99165d040a..45c231b64d 100644 --- a/docs/internals/builtins/type/is_numeric.md +++ b/docs/internals/builtins/type/is_numeric.md @@ -2,7 +2,7 @@ title: "is_numeric() — internals" description: "Compiler internals for is_numeric(): lowering path, type checks, and runtime helpers." sidebar: - order: 542 + order: 543 --- ## `is_numeric()` — internals diff --git a/docs/internals/builtins/type/is_object.md b/docs/internals/builtins/type/is_object.md index 3d8623b5d8..290a1315dd 100644 --- a/docs/internals/builtins/type/is_object.md +++ b/docs/internals/builtins/type/is_object.md @@ -2,7 +2,7 @@ title: "is_object() — internals" description: "Compiler internals for is_object(): lowering path, type checks, and runtime helpers." sidebar: - order: 543 + order: 544 --- ## `is_object()` — internals diff --git a/docs/internals/builtins/type/is_real.md b/docs/internals/builtins/type/is_real.md index 0ada12a7a1..d7d4ab8dd5 100644 --- a/docs/internals/builtins/type/is_real.md +++ b/docs/internals/builtins/type/is_real.md @@ -2,7 +2,7 @@ title: "is_real() — internals" description: "Compiler internals for is_real(): lowering path, type checks, and runtime helpers." sidebar: - order: 544 + order: 545 --- ## `is_real()` — internals diff --git a/docs/internals/builtins/type/is_resource.md b/docs/internals/builtins/type/is_resource.md index 6067584cb8..50d3d6beb9 100644 --- a/docs/internals/builtins/type/is_resource.md +++ b/docs/internals/builtins/type/is_resource.md @@ -2,7 +2,7 @@ title: "is_resource() — internals" description: "Compiler internals for is_resource(): lowering path, type checks, and runtime helpers." sidebar: - order: 545 + order: 546 --- ## `is_resource()` — internals diff --git a/docs/internals/builtins/type/is_scalar.md b/docs/internals/builtins/type/is_scalar.md index f9890719a9..de18699e95 100644 --- a/docs/internals/builtins/type/is_scalar.md +++ b/docs/internals/builtins/type/is_scalar.md @@ -2,7 +2,7 @@ title: "is_scalar() — internals" description: "Compiler internals for is_scalar(): lowering path, type checks, and runtime helpers." sidebar: - order: 546 + order: 547 --- ## `is_scalar()` — internals diff --git a/docs/internals/builtins/type/is_string.md b/docs/internals/builtins/type/is_string.md index bf6a7754c4..782217eb29 100644 --- a/docs/internals/builtins/type/is_string.md +++ b/docs/internals/builtins/type/is_string.md @@ -2,7 +2,7 @@ title: "is_string() — internals" description: "Compiler internals for is_string(): lowering path, type checks, and runtime helpers." sidebar: - order: 547 + order: 548 --- ## `is_string()` — internals diff --git a/docs/internals/builtins/type/settype.md b/docs/internals/builtins/type/settype.md index 9d65f6ec6a..6148c7487a 100644 --- a/docs/internals/builtins/type/settype.md +++ b/docs/internals/builtins/type/settype.md @@ -2,7 +2,7 @@ title: "settype() — internals" description: "Compiler internals for settype(): lowering path, type checks, and runtime helpers." sidebar: - order: 548 + order: 549 --- ## `settype()` — internals diff --git a/docs/internals/builtins/type/strval.md b/docs/internals/builtins/type/strval.md index d3d5cebbcd..8bd50ea1ef 100644 --- a/docs/internals/builtins/type/strval.md +++ b/docs/internals/builtins/type/strval.md @@ -2,7 +2,7 @@ title: "strval() — internals" description: "Compiler internals for strval(): lowering path, type checks, and runtime helpers." sidebar: - order: 549 + order: 550 --- ## `strval()` — internals diff --git a/docs/php/builtins.md b/docs/php/builtins.md index b27e761098..b2e246800f 100644 --- a/docs/php/builtins.md +++ b/docs/php/builtins.md @@ -478,6 +478,7 @@ Browse by category: [Array](./builtins/array.md) · [Buffer](./builtins/buffer.m | [`lcfirst()`](./builtins/string/lcfirst.md) | `(string $string): string` | `string` | ✓ | ✓ | | [`long2ip()`](./builtins/string/long2ip.md) | `(int $ip): string` | `string` | ✓ | ✓ | | [`ltrim()`](./builtins/string/ltrim.md) | `(string $string, string $characters = ' \n\r\t\x0b\x0c\x00'): string` | `string` | ✓ | ✓ | +| [`mb_strimwidth()`](./builtins/string/mb_strimwidth.md) | `(string $string, int $start, int $width, string $trim_marker = '', string $encoding = null): string` | `string` | ✓ | ✓ | | [`mb_strlen()`](./builtins/string/mb_strlen.md) | `(string $string, string $encoding = null): int` | `int` | ✓ | ✓ | | [`md5()`](./builtins/string/md5.md) | `(string $string, bool $binary = false): string` | `string` | ✓ | ✓ | | [`nl2br()`](./builtins/string/nl2br.md) | `(string $string): string` | `string` | ✓ | ✓ | diff --git a/docs/php/builtins/string.md b/docs/php/builtins/string.md index 319e9291c0..755ea2cb62 100644 --- a/docs/php/builtins/string.md +++ b/docs/php/builtins/string.md @@ -54,6 +54,7 @@ sidebar: | [`lcfirst()`](./string/lcfirst.md) | `(string $string): string` | `string` | ✓ | ✓ | | [`long2ip()`](./string/long2ip.md) | `(int $ip): string` | `string` | ✓ | ✓ | | [`ltrim()`](./string/ltrim.md) | `(string $string, string $characters = ' \n\r\t\x0b\x0c\x00'): string` | `string` | ✓ | ✓ | +| [`mb_strimwidth()`](./string/mb_strimwidth.md) | `(string $string, int $start, int $width, string $trim_marker = '', string $encoding = null): string` | `string` | ✓ | ✓ | | [`mb_strlen()`](./string/mb_strlen.md) | `(string $string, string $encoding = null): int` | `int` | ✓ | ✓ | | [`md5()`](./string/md5.md) | `(string $string, bool $binary = false): string` | `string` | ✓ | ✓ | | [`nl2br()`](./string/nl2br.md) | `(string $string): string` | `string` | ✓ | ✓ | diff --git a/docs/php/builtins/string/mb_strimwidth.md b/docs/php/builtins/string/mb_strimwidth.md new file mode 100644 index 0000000000..c907c20a35 --- /dev/null +++ b/docs/php/builtins/string/mb_strimwidth.md @@ -0,0 +1,34 @@ +--- +title: "mb_strimwidth()" +description: "Truncates a string to a display width, optionally appending a trim marker." +sidebar: + order: 468 +--- + +## mb_strimwidth() + +```php +function mb_strimwidth(string $string, int $start, int $width, string $trim_marker = '', string $encoding = null): string +``` + +Truncates a string to a display width, optionally appending a trim marker. + +**Parameters**: +- `$string` (`string`) +- `$start` (`int`) +- `$width` (`int`) +- `$trim_marker` (`string`), default `''`, optional +- `$encoding` (`string`), default `null`, optional + +**Returns**: `string` + +## Availability + +- **Compiled (AOT)**: supported by the Elephc code generator. +- **`eval()` (magician interpreter)**: supported — declarative interpreter builtin ([`crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs`](https://github.com/illegalstudio/elephc/blob/main/crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs)). + +_No examples yet — check `examples/` and `showcases/` for usage patterns._ + +## Internals + +For how `mb_strimwidth` is implemented in the compiler, see [the internals page](../../../internals/builtins/string/mb_strimwidth.md). diff --git a/docs/php/builtins/string/mb_strlen.md b/docs/php/builtins/string/mb_strlen.md index b5d9bdb1e9..83670ed539 100644 --- a/docs/php/builtins/string/mb_strlen.md +++ b/docs/php/builtins/string/mb_strlen.md @@ -2,7 +2,7 @@ title: "mb_strlen()" description: "Returns the character count of a string in the requested encoding." sidebar: - order: 468 + order: 469 --- ## mb_strlen() diff --git a/docs/php/builtins/string/md5.md b/docs/php/builtins/string/md5.md index 68aa6d7e94..cb148ef77f 100644 --- a/docs/php/builtins/string/md5.md +++ b/docs/php/builtins/string/md5.md @@ -2,7 +2,7 @@ title: "md5()" description: "Calculates the MD5 hash of a string." sidebar: - order: 469 + order: 470 --- ## md5() diff --git a/docs/php/builtins/string/nl2br.md b/docs/php/builtins/string/nl2br.md index a547d88dfa..ee2478c0fb 100644 --- a/docs/php/builtins/string/nl2br.md +++ b/docs/php/builtins/string/nl2br.md @@ -2,7 +2,7 @@ title: "nl2br()" description: "Inserts HTML line breaks before newlines in a string." sidebar: - order: 470 + order: 471 --- ## nl2br() diff --git a/docs/php/builtins/string/number_format.md b/docs/php/builtins/string/number_format.md index 5c15f26764..d2da03632a 100644 --- a/docs/php/builtins/string/number_format.md +++ b/docs/php/builtins/string/number_format.md @@ -2,7 +2,7 @@ title: "number_format()" description: "Formats a number with grouped thousands." sidebar: - order: 471 + order: 472 --- ## number_format() diff --git a/docs/php/builtins/string/openssl_cipher_iv_length.md b/docs/php/builtins/string/openssl_cipher_iv_length.md index 1568e66abb..830a953ca6 100644 --- a/docs/php/builtins/string/openssl_cipher_iv_length.md +++ b/docs/php/builtins/string/openssl_cipher_iv_length.md @@ -2,7 +2,7 @@ title: "openssl_cipher_iv_length()" description: "Returns the IV length for a supported cipher." sidebar: - order: 472 + order: 473 --- ## openssl_cipher_iv_length() diff --git a/docs/php/builtins/string/openssl_decrypt.md b/docs/php/builtins/string/openssl_decrypt.md index 89d7a95009..b6ab855dcc 100644 --- a/docs/php/builtins/string/openssl_decrypt.md +++ b/docs/php/builtins/string/openssl_decrypt.md @@ -2,7 +2,7 @@ title: "openssl_decrypt()" description: "Decrypts data with a supported AES cipher." sidebar: - order: 473 + order: 474 --- ## openssl_decrypt() diff --git a/docs/php/builtins/string/openssl_encrypt.md b/docs/php/builtins/string/openssl_encrypt.md index 54275ada01..9699e93d0d 100644 --- a/docs/php/builtins/string/openssl_encrypt.md +++ b/docs/php/builtins/string/openssl_encrypt.md @@ -2,7 +2,7 @@ title: "openssl_encrypt()" description: "Encrypts data with a supported AES cipher." sidebar: - order: 474 + order: 475 --- ## openssl_encrypt() diff --git a/docs/php/builtins/string/openssl_get_cipher_methods.md b/docs/php/builtins/string/openssl_get_cipher_methods.md index f974f237da..109bc78f42 100644 --- a/docs/php/builtins/string/openssl_get_cipher_methods.md +++ b/docs/php/builtins/string/openssl_get_cipher_methods.md @@ -2,7 +2,7 @@ title: "openssl_get_cipher_methods()" description: "Returns the supported OpenSSL cipher method names." sidebar: - order: 475 + order: 476 --- ## openssl_get_cipher_methods() diff --git a/docs/php/builtins/string/ord.md b/docs/php/builtins/string/ord.md index 1be4a464f6..1707330a1c 100644 --- a/docs/php/builtins/string/ord.md +++ b/docs/php/builtins/string/ord.md @@ -2,7 +2,7 @@ title: "ord()" description: "Returns the ASCII value of the first character of a string." sidebar: - order: 476 + order: 477 --- ## ord() diff --git a/docs/php/builtins/string/parse_url.md b/docs/php/builtins/string/parse_url.md index 9becd61961..7909c4af48 100644 --- a/docs/php/builtins/string/parse_url.md +++ b/docs/php/builtins/string/parse_url.md @@ -2,7 +2,7 @@ title: "parse_url()" description: "Parses a URL and returns its components." sidebar: - order: 477 + order: 478 --- ## parse_url() diff --git a/docs/php/builtins/string/printf.md b/docs/php/builtins/string/printf.md index 8e98de64fd..e596b45687 100644 --- a/docs/php/builtins/string/printf.md +++ b/docs/php/builtins/string/printf.md @@ -2,7 +2,7 @@ title: "printf()" description: "Outputs a formatted string." sidebar: - order: 478 + order: 479 --- ## printf() diff --git a/docs/php/builtins/string/quoted_printable_encode.md b/docs/php/builtins/string/quoted_printable_encode.md index dc4b933134..0763adc4f2 100644 --- a/docs/php/builtins/string/quoted_printable_encode.md +++ b/docs/php/builtins/string/quoted_printable_encode.md @@ -2,7 +2,7 @@ title: "quoted_printable_encode()" description: "Encodes a string with the MIME quoted-printable transfer encoding." sidebar: - order: 479 + order: 480 --- ## quoted_printable_encode() diff --git a/docs/php/builtins/string/quotemeta.md b/docs/php/builtins/string/quotemeta.md index 9167197417..3d609a161b 100644 --- a/docs/php/builtins/string/quotemeta.md +++ b/docs/php/builtins/string/quotemeta.md @@ -2,7 +2,7 @@ title: "quotemeta()" description: "Prefixes each regular-expression metacharacter in a string with a backslash." sidebar: - order: 480 + order: 481 --- ## quotemeta() diff --git a/docs/php/builtins/string/rawurldecode.md b/docs/php/builtins/string/rawurldecode.md index 07b2ccbf52..efe6e52176 100644 --- a/docs/php/builtins/string/rawurldecode.md +++ b/docs/php/builtins/string/rawurldecode.md @@ -2,7 +2,7 @@ title: "rawurldecode()" description: "Decodes an RFC 3986 percent-encoded string without treating '+' as a space." sidebar: - order: 481 + order: 482 --- ## rawurldecode() diff --git a/docs/php/builtins/string/rawurlencode.md b/docs/php/builtins/string/rawurlencode.md index bab2bdaef4..61481fa0c0 100644 --- a/docs/php/builtins/string/rawurlencode.md +++ b/docs/php/builtins/string/rawurlencode.md @@ -2,7 +2,7 @@ title: "rawurlencode()" description: "URL-encodes a string using RFC 3986 percent-encoding (no '+' for spaces)." sidebar: - order: 482 + order: 483 --- ## rawurlencode() diff --git a/docs/php/builtins/string/rtrim.md b/docs/php/builtins/string/rtrim.md index eef2f20284..71e7b16a33 100644 --- a/docs/php/builtins/string/rtrim.md +++ b/docs/php/builtins/string/rtrim.md @@ -2,7 +2,7 @@ title: "rtrim()" description: "Strips whitespace (or other characters) from the end of a string." sidebar: - order: 483 + order: 484 --- ## rtrim() diff --git a/docs/php/builtins/string/sha1.md b/docs/php/builtins/string/sha1.md index 1f2179e46c..cc88cc88bf 100644 --- a/docs/php/builtins/string/sha1.md +++ b/docs/php/builtins/string/sha1.md @@ -2,7 +2,7 @@ title: "sha1()" description: "Calculates the SHA-1 hash of a string." sidebar: - order: 484 + order: 485 --- ## sha1() diff --git a/docs/php/builtins/string/sprintf.md b/docs/php/builtins/string/sprintf.md index 54911bdd7c..e33ef63b02 100644 --- a/docs/php/builtins/string/sprintf.md +++ b/docs/php/builtins/string/sprintf.md @@ -2,7 +2,7 @@ title: "sprintf()" description: "Returns a formatted string." sidebar: - order: 485 + order: 486 --- ## sprintf() diff --git a/docs/php/builtins/string/sscanf.md b/docs/php/builtins/string/sscanf.md index 7d196ba795..40c77650c9 100644 --- a/docs/php/builtins/string/sscanf.md +++ b/docs/php/builtins/string/sscanf.md @@ -2,7 +2,7 @@ title: "sscanf()" description: "Parses a string according to a format." sidebar: - order: 486 + order: 487 --- ## sscanf() diff --git a/docs/php/builtins/string/str_contains.md b/docs/php/builtins/string/str_contains.md index 107ec6e6b6..47c5502825 100644 --- a/docs/php/builtins/string/str_contains.md +++ b/docs/php/builtins/string/str_contains.md @@ -2,7 +2,7 @@ title: "str_contains()" description: "Determines if a string contains a given substring." sidebar: - order: 487 + order: 488 --- ## str_contains() diff --git a/docs/php/builtins/string/str_ends_with.md b/docs/php/builtins/string/str_ends_with.md index f2c7b802ab..473b36d097 100644 --- a/docs/php/builtins/string/str_ends_with.md +++ b/docs/php/builtins/string/str_ends_with.md @@ -2,7 +2,7 @@ title: "str_ends_with()" description: "Checks if a string ends with a given substring." sidebar: - order: 488 + order: 489 --- ## str_ends_with() diff --git a/docs/php/builtins/string/str_ireplace.md b/docs/php/builtins/string/str_ireplace.md index e168c58dc8..dca342b6dd 100644 --- a/docs/php/builtins/string/str_ireplace.md +++ b/docs/php/builtins/string/str_ireplace.md @@ -2,7 +2,7 @@ title: "str_ireplace()" description: "Case-insensitive version of str_replace()." sidebar: - order: 489 + order: 490 --- ## str_ireplace() diff --git a/docs/php/builtins/string/str_pad.md b/docs/php/builtins/string/str_pad.md index 1185458afc..6b5a9bdca1 100644 --- a/docs/php/builtins/string/str_pad.md +++ b/docs/php/builtins/string/str_pad.md @@ -2,7 +2,7 @@ title: "str_pad()" description: "Pads a string to a certain length with another string." sidebar: - order: 490 + order: 491 --- ## str_pad() diff --git a/docs/php/builtins/string/str_repeat.md b/docs/php/builtins/string/str_repeat.md index 8c0ca30b8e..6774f31d65 100644 --- a/docs/php/builtins/string/str_repeat.md +++ b/docs/php/builtins/string/str_repeat.md @@ -2,7 +2,7 @@ title: "str_repeat()" description: "Repeats a string a given number of times." sidebar: - order: 491 + order: 492 --- ## str_repeat() diff --git a/docs/php/builtins/string/str_replace.md b/docs/php/builtins/string/str_replace.md index 0c921738ad..2e9b0821a8 100644 --- a/docs/php/builtins/string/str_replace.md +++ b/docs/php/builtins/string/str_replace.md @@ -2,7 +2,7 @@ title: "str_replace()" description: "Replaces all occurrences of a search string with a replacement string." sidebar: - order: 492 + order: 493 --- ## str_replace() diff --git a/docs/php/builtins/string/str_split.md b/docs/php/builtins/string/str_split.md index ca03ea2c37..c415a5bf4a 100644 --- a/docs/php/builtins/string/str_split.md +++ b/docs/php/builtins/string/str_split.md @@ -2,7 +2,7 @@ title: "str_split()" description: "Converts a string into an array of chunks of the given length." sidebar: - order: 493 + order: 494 --- ## str_split() diff --git a/docs/php/builtins/string/str_starts_with.md b/docs/php/builtins/string/str_starts_with.md index ff803d3985..da044ebfca 100644 --- a/docs/php/builtins/string/str_starts_with.md +++ b/docs/php/builtins/string/str_starts_with.md @@ -2,7 +2,7 @@ title: "str_starts_with()" description: "Checks if a string starts with a given substring." sidebar: - order: 494 + order: 495 --- ## str_starts_with() diff --git a/docs/php/builtins/string/str_word_count.md b/docs/php/builtins/string/str_word_count.md index 4268f5cdf5..c1730dca88 100644 --- a/docs/php/builtins/string/str_word_count.md +++ b/docs/php/builtins/string/str_word_count.md @@ -2,7 +2,7 @@ title: "str_word_count()" description: "Counts the words in a string, or returns them as a list or byte-offset map." sidebar: - order: 495 + order: 496 --- ## str_word_count() diff --git a/docs/php/builtins/string/strcasecmp.md b/docs/php/builtins/string/strcasecmp.md index 3268aa7704..7ccd9b2d90 100644 --- a/docs/php/builtins/string/strcasecmp.md +++ b/docs/php/builtins/string/strcasecmp.md @@ -2,7 +2,7 @@ title: "strcasecmp()" description: "Binary safe case-insensitive string comparison. Returns negative, zero, or positive." sidebar: - order: 496 + order: 497 --- ## strcasecmp() diff --git a/docs/php/builtins/string/strcmp.md b/docs/php/builtins/string/strcmp.md index 7aa5d77c2f..2bd62f3140 100644 --- a/docs/php/builtins/string/strcmp.md +++ b/docs/php/builtins/string/strcmp.md @@ -2,7 +2,7 @@ title: "strcmp()" description: "Binary safe string comparison. Returns negative, zero, or positive." sidebar: - order: 497 + order: 498 --- ## strcmp() diff --git a/docs/php/builtins/string/stripos.md b/docs/php/builtins/string/stripos.md index 014e9c68fd..5505f985e0 100644 --- a/docs/php/builtins/string/stripos.md +++ b/docs/php/builtins/string/stripos.md @@ -2,7 +2,7 @@ title: "stripos()" description: "Finds the numeric position of the first case-insensitive occurrence of a substring." sidebar: - order: 498 + order: 499 --- ## stripos() diff --git a/docs/php/builtins/string/stripslashes.md b/docs/php/builtins/string/stripslashes.md index d163cfc45f..6c8fe0c22b 100644 --- a/docs/php/builtins/string/stripslashes.md +++ b/docs/php/builtins/string/stripslashes.md @@ -2,7 +2,7 @@ title: "stripslashes()" description: "Removes backslashes from a string previously escaped by addslashes." sidebar: - order: 499 + order: 500 --- ## stripslashes() diff --git a/docs/php/builtins/string/strlen.md b/docs/php/builtins/string/strlen.md index a8d3425673..2c3a1a8419 100644 --- a/docs/php/builtins/string/strlen.md +++ b/docs/php/builtins/string/strlen.md @@ -2,7 +2,7 @@ title: "strlen()" description: "Returns the length of a string." sidebar: - order: 500 + order: 501 --- ## strlen() diff --git a/docs/php/builtins/string/strncasecmp.md b/docs/php/builtins/string/strncasecmp.md index 40eb1ecab6..79fd2a00dc 100644 --- a/docs/php/builtins/string/strncasecmp.md +++ b/docs/php/builtins/string/strncasecmp.md @@ -2,7 +2,7 @@ title: "strncasecmp()" description: "Compares the first n bytes of two strings, ignoring ASCII case." sidebar: - order: 501 + order: 502 --- ## strncasecmp() diff --git a/docs/php/builtins/string/strncmp.md b/docs/php/builtins/string/strncmp.md index 2f14d2e5fc..8cf7287ff4 100644 --- a/docs/php/builtins/string/strncmp.md +++ b/docs/php/builtins/string/strncmp.md @@ -2,7 +2,7 @@ title: "strncmp()" description: "Compares the first n bytes of two strings." sidebar: - order: 502 + order: 503 --- ## strncmp() diff --git a/docs/php/builtins/string/strpos.md b/docs/php/builtins/string/strpos.md index a2e3756014..53781d1ffc 100644 --- a/docs/php/builtins/string/strpos.md +++ b/docs/php/builtins/string/strpos.md @@ -2,7 +2,7 @@ title: "strpos()" description: "Finds the numeric position of the first occurrence of a substring." sidebar: - order: 503 + order: 504 --- ## strpos() diff --git a/docs/php/builtins/string/strrev.md b/docs/php/builtins/string/strrev.md index 531849e3df..055c10652a 100644 --- a/docs/php/builtins/string/strrev.md +++ b/docs/php/builtins/string/strrev.md @@ -2,7 +2,7 @@ title: "strrev()" description: "Reverses a string." sidebar: - order: 504 + order: 505 --- ## strrev() diff --git a/docs/php/builtins/string/strripos.md b/docs/php/builtins/string/strripos.md index e805b3cdfa..99f96f1bc7 100644 --- a/docs/php/builtins/string/strripos.md +++ b/docs/php/builtins/string/strripos.md @@ -2,7 +2,7 @@ title: "strripos()" description: "Finds the numeric position of the last case-insensitive occurrence of a substring." sidebar: - order: 505 + order: 506 --- ## strripos() diff --git a/docs/php/builtins/string/strrpos.md b/docs/php/builtins/string/strrpos.md index d9ab4b6aef..28e0626521 100644 --- a/docs/php/builtins/string/strrpos.md +++ b/docs/php/builtins/string/strrpos.md @@ -2,7 +2,7 @@ title: "strrpos()" description: "Finds the numeric position of the last occurrence of a substring." sidebar: - order: 506 + order: 507 --- ## strrpos() diff --git a/docs/php/builtins/string/strstr.md b/docs/php/builtins/string/strstr.md index ec55b61125..79ad45e9db 100644 --- a/docs/php/builtins/string/strstr.md +++ b/docs/php/builtins/string/strstr.md @@ -2,7 +2,7 @@ title: "strstr()" description: "Returns the portion of a string starting at the first occurrence of a substring, or false." sidebar: - order: 507 + order: 508 --- ## strstr() diff --git a/docs/php/builtins/string/strtolower.md b/docs/php/builtins/string/strtolower.md index 0c499469bd..33e32e47d1 100644 --- a/docs/php/builtins/string/strtolower.md +++ b/docs/php/builtins/string/strtolower.md @@ -2,7 +2,7 @@ title: "strtolower()" description: "Converts a string to lowercase." sidebar: - order: 508 + order: 509 --- ## strtolower() diff --git a/docs/php/builtins/string/strtoupper.md b/docs/php/builtins/string/strtoupper.md index 238709261b..e9de1c1414 100644 --- a/docs/php/builtins/string/strtoupper.md +++ b/docs/php/builtins/string/strtoupper.md @@ -2,7 +2,7 @@ title: "strtoupper()" description: "Converts a string to uppercase." sidebar: - order: 509 + order: 510 --- ## strtoupper() diff --git a/docs/php/builtins/string/strtr.md b/docs/php/builtins/string/strtr.md index 20862cabcd..b3e88cc8c8 100644 --- a/docs/php/builtins/string/strtr.md +++ b/docs/php/builtins/string/strtr.md @@ -2,7 +2,7 @@ title: "strtr()" description: "Translates bytes pairwise, or applies longest-match-first replacement pairs." sidebar: - order: 510 + order: 511 --- ## strtr() diff --git a/docs/php/builtins/string/substr.md b/docs/php/builtins/string/substr.md index 14176f9ad4..f0e8f2f1f2 100644 --- a/docs/php/builtins/string/substr.md +++ b/docs/php/builtins/string/substr.md @@ -2,7 +2,7 @@ title: "substr()" description: "Returns a portion of a string specified by the offset and length." sidebar: - order: 511 + order: 512 --- ## substr() diff --git a/docs/php/builtins/string/substr_count.md b/docs/php/builtins/string/substr_count.md index 5cd217751b..005509ce7f 100644 --- a/docs/php/builtins/string/substr_count.md +++ b/docs/php/builtins/string/substr_count.md @@ -2,7 +2,7 @@ title: "substr_count()" description: "Counts the number of non-overlapping substring occurrences." sidebar: - order: 512 + order: 513 --- ## substr_count() diff --git a/docs/php/builtins/string/substr_replace.md b/docs/php/builtins/string/substr_replace.md index 460fb2a5a5..a4eb500206 100644 --- a/docs/php/builtins/string/substr_replace.md +++ b/docs/php/builtins/string/substr_replace.md @@ -2,7 +2,7 @@ title: "substr_replace()" description: "Replaces text within a portion of a string." sidebar: - order: 513 + order: 514 --- ## substr_replace() diff --git a/docs/php/builtins/string/trim.md b/docs/php/builtins/string/trim.md index 3fc63329c7..a32d33ff93 100644 --- a/docs/php/builtins/string/trim.md +++ b/docs/php/builtins/string/trim.md @@ -2,7 +2,7 @@ title: "trim()" description: "Strips whitespace (or other characters) from the beginning and end of a string." sidebar: - order: 514 + order: 515 --- ## trim() diff --git a/docs/php/builtins/string/ucfirst.md b/docs/php/builtins/string/ucfirst.md index 29761577a4..237fcff2f5 100644 --- a/docs/php/builtins/string/ucfirst.md +++ b/docs/php/builtins/string/ucfirst.md @@ -2,7 +2,7 @@ title: "ucfirst()" description: "Uppercases the first character of a string." sidebar: - order: 515 + order: 516 --- ## ucfirst() diff --git a/docs/php/builtins/string/ucwords.md b/docs/php/builtins/string/ucwords.md index dbe0df3330..d85c1bcd86 100644 --- a/docs/php/builtins/string/ucwords.md +++ b/docs/php/builtins/string/ucwords.md @@ -2,7 +2,7 @@ title: "ucwords()" description: "Uppercases the first character of each word in a string." sidebar: - order: 516 + order: 517 --- ## ucwords() diff --git a/docs/php/builtins/string/urldecode.md b/docs/php/builtins/string/urldecode.md index eb15c2bbe9..91d08fa9ac 100644 --- a/docs/php/builtins/string/urldecode.md +++ b/docs/php/builtins/string/urldecode.md @@ -2,7 +2,7 @@ title: "urldecode()" description: "Decodes a URL-encoded string, including '+' as a space." sidebar: - order: 517 + order: 518 --- ## urldecode() diff --git a/docs/php/builtins/string/urlencode.md b/docs/php/builtins/string/urlencode.md index 51657e357f..4ac95b3572 100644 --- a/docs/php/builtins/string/urlencode.md +++ b/docs/php/builtins/string/urlencode.md @@ -2,7 +2,7 @@ title: "urlencode()" description: "URL-encodes a string using application/x-www-form-urlencoded rules." sidebar: - order: 518 + order: 519 --- ## urlencode() diff --git a/docs/php/builtins/string/vprintf.md b/docs/php/builtins/string/vprintf.md index 3948f2045f..86cf37b77a 100644 --- a/docs/php/builtins/string/vprintf.md +++ b/docs/php/builtins/string/vprintf.md @@ -2,7 +2,7 @@ title: "vprintf()" description: "Outputs a formatted string using an array of values." sidebar: - order: 519 + order: 520 --- ## vprintf() diff --git a/docs/php/builtins/string/vsprintf.md b/docs/php/builtins/string/vsprintf.md index 5508b40434..8f865e89e1 100644 --- a/docs/php/builtins/string/vsprintf.md +++ b/docs/php/builtins/string/vsprintf.md @@ -2,7 +2,7 @@ title: "vsprintf()" description: "Returns a formatted string using an array of values." sidebar: - order: 520 + order: 521 --- ## vsprintf() diff --git a/docs/php/builtins/string/wordwrap.md b/docs/php/builtins/string/wordwrap.md index 88ef2334e4..f80db48f2e 100644 --- a/docs/php/builtins/string/wordwrap.md +++ b/docs/php/builtins/string/wordwrap.md @@ -2,7 +2,7 @@ title: "wordwrap()" description: "Wraps a string to a given number of characters." sidebar: - order: 521 + order: 522 --- ## wordwrap() diff --git a/docs/php/builtins/type/boolval.md b/docs/php/builtins/type/boolval.md index d5195ad3d2..599f6c8caa 100644 --- a/docs/php/builtins/type/boolval.md +++ b/docs/php/builtins/type/boolval.md @@ -2,7 +2,7 @@ title: "boolval()" description: "Returns the boolean value of a variable." sidebar: - order: 522 + order: 523 --- ## boolval() diff --git a/docs/php/builtins/type/ctype_alnum.md b/docs/php/builtins/type/ctype_alnum.md index 752350667d..d036a2e952 100644 --- a/docs/php/builtins/type/ctype_alnum.md +++ b/docs/php/builtins/type/ctype_alnum.md @@ -2,7 +2,7 @@ title: "ctype_alnum()" description: "Checks if all characters in the string are alphanumeric." sidebar: - order: 523 + order: 524 --- ## ctype_alnum() diff --git a/docs/php/builtins/type/ctype_alpha.md b/docs/php/builtins/type/ctype_alpha.md index 7d010a6f21..3241b2798a 100644 --- a/docs/php/builtins/type/ctype_alpha.md +++ b/docs/php/builtins/type/ctype_alpha.md @@ -2,7 +2,7 @@ title: "ctype_alpha()" description: "Checks if all characters in the string are alphabetic." sidebar: - order: 524 + order: 525 --- ## ctype_alpha() diff --git a/docs/php/builtins/type/ctype_digit.md b/docs/php/builtins/type/ctype_digit.md index 617cd8a832..7a181f36da 100644 --- a/docs/php/builtins/type/ctype_digit.md +++ b/docs/php/builtins/type/ctype_digit.md @@ -2,7 +2,7 @@ title: "ctype_digit()" description: "Checks if all characters in the string are digits." sidebar: - order: 525 + order: 526 --- ## ctype_digit() diff --git a/docs/php/builtins/type/ctype_space.md b/docs/php/builtins/type/ctype_space.md index 28abe1d7a0..16a4c0a802 100644 --- a/docs/php/builtins/type/ctype_space.md +++ b/docs/php/builtins/type/ctype_space.md @@ -2,7 +2,7 @@ title: "ctype_space()" description: "Checks if all characters in the string are whitespace characters." sidebar: - order: 526 + order: 527 --- ## ctype_space() diff --git a/docs/php/builtins/type/floatval.md b/docs/php/builtins/type/floatval.md index 4f25a4b942..5ddab47952 100644 --- a/docs/php/builtins/type/floatval.md +++ b/docs/php/builtins/type/floatval.md @@ -2,7 +2,7 @@ title: "floatval()" description: "Returns the float value of a variable." sidebar: - order: 527 + order: 528 --- ## floatval() diff --git a/docs/php/builtins/type/get_resource_id.md b/docs/php/builtins/type/get_resource_id.md index 7f819c7701..df9b79774a 100644 --- a/docs/php/builtins/type/get_resource_id.md +++ b/docs/php/builtins/type/get_resource_id.md @@ -2,7 +2,7 @@ title: "get_resource_id()" description: "Returns an integer identifier for the given resource." sidebar: - order: 528 + order: 529 --- ## get_resource_id() diff --git a/docs/php/builtins/type/get_resource_type.md b/docs/php/builtins/type/get_resource_type.md index ba66536af8..5499805c72 100644 --- a/docs/php/builtins/type/get_resource_type.md +++ b/docs/php/builtins/type/get_resource_type.md @@ -2,7 +2,7 @@ title: "get_resource_type()" description: "Returns the type of a resource." sidebar: - order: 529 + order: 530 --- ## get_resource_type() diff --git a/docs/php/builtins/type/gettype.md b/docs/php/builtins/type/gettype.md index 9b7a86cf8f..ad07835ea9 100644 --- a/docs/php/builtins/type/gettype.md +++ b/docs/php/builtins/type/gettype.md @@ -2,7 +2,7 @@ title: "gettype()" description: "Returns the type of a variable as a string." sidebar: - order: 530 + order: 531 --- ## gettype() diff --git a/docs/php/builtins/type/intval.md b/docs/php/builtins/type/intval.md index 844b458886..bfcce38bcd 100644 --- a/docs/php/builtins/type/intval.md +++ b/docs/php/builtins/type/intval.md @@ -2,7 +2,7 @@ title: "intval()" description: "Returns the integer value of a variable, optionally using a given base." sidebar: - order: 531 + order: 532 --- ## intval() diff --git a/docs/php/builtins/type/is_array.md b/docs/php/builtins/type/is_array.md index 9549a8c138..8c1b72fc99 100644 --- a/docs/php/builtins/type/is_array.md +++ b/docs/php/builtins/type/is_array.md @@ -2,7 +2,7 @@ title: "is_array()" description: "Checks whether a variable is an array." sidebar: - order: 532 + order: 533 --- ## is_array() diff --git a/docs/php/builtins/type/is_bool.md b/docs/php/builtins/type/is_bool.md index 7d02b58c11..d085507793 100644 --- a/docs/php/builtins/type/is_bool.md +++ b/docs/php/builtins/type/is_bool.md @@ -2,7 +2,7 @@ title: "is_bool()" description: "Checks whether a variable is a boolean." sidebar: - order: 533 + order: 534 --- ## is_bool() diff --git a/docs/php/builtins/type/is_callable.md b/docs/php/builtins/type/is_callable.md index 4f2771b0cd..f96ab50a6d 100644 --- a/docs/php/builtins/type/is_callable.md +++ b/docs/php/builtins/type/is_callable.md @@ -2,7 +2,7 @@ title: "is_callable()" description: "Checks whether a variable can be called as a function." sidebar: - order: 534 + order: 535 --- ## is_callable() diff --git a/docs/php/builtins/type/is_double.md b/docs/php/builtins/type/is_double.md index a66c6270da..5989523c50 100644 --- a/docs/php/builtins/type/is_double.md +++ b/docs/php/builtins/type/is_double.md @@ -2,7 +2,7 @@ title: "is_double()" description: "Alias of is_float()." sidebar: - order: 535 + order: 536 --- ## is_double() diff --git a/docs/php/builtins/type/is_float.md b/docs/php/builtins/type/is_float.md index b63acb303b..c95b26fbff 100644 --- a/docs/php/builtins/type/is_float.md +++ b/docs/php/builtins/type/is_float.md @@ -2,7 +2,7 @@ title: "is_float()" description: "Checks whether a variable is a floating-point number." sidebar: - order: 536 + order: 537 --- ## is_float() diff --git a/docs/php/builtins/type/is_int.md b/docs/php/builtins/type/is_int.md index 2870e3da47..ad43d80b96 100644 --- a/docs/php/builtins/type/is_int.md +++ b/docs/php/builtins/type/is_int.md @@ -2,7 +2,7 @@ title: "is_int()" description: "Checks whether a variable is an integer." sidebar: - order: 537 + order: 538 --- ## is_int() diff --git a/docs/php/builtins/type/is_integer.md b/docs/php/builtins/type/is_integer.md index 18a8679eb5..5135f5af08 100644 --- a/docs/php/builtins/type/is_integer.md +++ b/docs/php/builtins/type/is_integer.md @@ -2,7 +2,7 @@ title: "is_integer()" description: "Alias of is_int()." sidebar: - order: 538 + order: 539 --- ## is_integer() diff --git a/docs/php/builtins/type/is_iterable.md b/docs/php/builtins/type/is_iterable.md index 1e6ccb59ce..70910689ae 100644 --- a/docs/php/builtins/type/is_iterable.md +++ b/docs/php/builtins/type/is_iterable.md @@ -2,7 +2,7 @@ title: "is_iterable()" description: "Checks whether a variable is iterable." sidebar: - order: 539 + order: 540 --- ## is_iterable() diff --git a/docs/php/builtins/type/is_long.md b/docs/php/builtins/type/is_long.md index 77293216f6..880044df05 100644 --- a/docs/php/builtins/type/is_long.md +++ b/docs/php/builtins/type/is_long.md @@ -2,7 +2,7 @@ title: "is_long()" description: "Alias of is_int()." sidebar: - order: 540 + order: 541 --- ## is_long() diff --git a/docs/php/builtins/type/is_null.md b/docs/php/builtins/type/is_null.md index 73116aea93..229385f090 100644 --- a/docs/php/builtins/type/is_null.md +++ b/docs/php/builtins/type/is_null.md @@ -2,7 +2,7 @@ title: "is_null()" description: "Checks whether a variable is null." sidebar: - order: 541 + order: 542 --- ## is_null() diff --git a/docs/php/builtins/type/is_numeric.md b/docs/php/builtins/type/is_numeric.md index 202b5b483a..a5e9ed0901 100644 --- a/docs/php/builtins/type/is_numeric.md +++ b/docs/php/builtins/type/is_numeric.md @@ -2,7 +2,7 @@ title: "is_numeric()" description: "Checks whether a variable is a number or a numeric string." sidebar: - order: 542 + order: 543 --- ## is_numeric() diff --git a/docs/php/builtins/type/is_object.md b/docs/php/builtins/type/is_object.md index 4a7bb14613..bc9eafefbf 100644 --- a/docs/php/builtins/type/is_object.md +++ b/docs/php/builtins/type/is_object.md @@ -2,7 +2,7 @@ title: "is_object()" description: "Checks whether a variable is an object." sidebar: - order: 543 + order: 544 --- ## is_object() diff --git a/docs/php/builtins/type/is_real.md b/docs/php/builtins/type/is_real.md index 679d0b894f..f46a685ec2 100644 --- a/docs/php/builtins/type/is_real.md +++ b/docs/php/builtins/type/is_real.md @@ -2,7 +2,7 @@ title: "is_real()" description: "Alias of is_float()." sidebar: - order: 544 + order: 545 --- ## is_real() diff --git a/docs/php/builtins/type/is_resource.md b/docs/php/builtins/type/is_resource.md index 6bfea2e0f0..69383cdf90 100644 --- a/docs/php/builtins/type/is_resource.md +++ b/docs/php/builtins/type/is_resource.md @@ -2,7 +2,7 @@ title: "is_resource()" description: "Checks whether a variable is a resource." sidebar: - order: 545 + order: 546 --- ## is_resource() diff --git a/docs/php/builtins/type/is_scalar.md b/docs/php/builtins/type/is_scalar.md index 9b59fc3cb3..9bafd2b04e 100644 --- a/docs/php/builtins/type/is_scalar.md +++ b/docs/php/builtins/type/is_scalar.md @@ -2,7 +2,7 @@ title: "is_scalar()" description: "Checks whether a variable is a scalar." sidebar: - order: 546 + order: 547 --- ## is_scalar() diff --git a/docs/php/builtins/type/is_string.md b/docs/php/builtins/type/is_string.md index 050d680db2..9aaeb02f64 100644 --- a/docs/php/builtins/type/is_string.md +++ b/docs/php/builtins/type/is_string.md @@ -2,7 +2,7 @@ title: "is_string()" description: "Checks whether a variable is a string." sidebar: - order: 547 + order: 548 --- ## is_string() diff --git a/docs/php/builtins/type/settype.md b/docs/php/builtins/type/settype.md index 92e62a4ac1..c23c163ad8 100644 --- a/docs/php/builtins/type/settype.md +++ b/docs/php/builtins/type/settype.md @@ -2,7 +2,7 @@ title: "settype()" description: "Sets the type of a variable." sidebar: - order: 548 + order: 549 --- ## settype() diff --git a/docs/php/builtins/type/strval.md b/docs/php/builtins/type/strval.md index 4d8b6d83cd..1793fdd533 100644 --- a/docs/php/builtins/type/strval.md +++ b/docs/php/builtins/type/strval.md @@ -2,7 +2,7 @@ title: "strval()" description: "Gets the string value of a variable." sidebar: - order: 549 + order: 550 --- ## strval() diff --git a/docs/php/compatibility.md b/docs/php/compatibility.md index 0948dfe80d..9ef8f481fb 100644 --- a/docs/php/compatibility.md +++ b/docs/php/compatibility.md @@ -10,7 +10,7 @@ sidebar: Baseline: **PHP 8.4.20** (CLI snapshot of 2026-08-11, 59 extensions, 2030 internal functions). -Overall builtin coverage: **515 / 2030** (25%). +Overall builtin coverage: **516 / 2030** (25%). ## Builtin coverage by PHP module @@ -38,7 +38,7 @@ Overall builtin coverage: **515 / 2030** (25%). | `json` | 5 / 5 | 100% | 5 | 5 | | `ldap` | 0 / 55 | 0% | 0 | 0 | | `libxml` | 0 / 8 | 0% | 0 | 0 | -| `mbstring` | 2 / 65 | 3% | 2 | 2 | +| `mbstring` | 3 / 65 | 5% | 3 | 3 | | `mysqli`† | 0 / 106 | 0% | 0 | 0 | | `openssl` | 4 / 66 | 6% | 4 | 4 | | `pcntl` | 0 / 25 | 0% | 0 | 0 | diff --git a/docs/php/strings.md b/docs/php/strings.md index 5fb1d97666..dee8c31c79 100644 --- a/docs/php/strings.md +++ b/docs/php/strings.md @@ -160,6 +160,7 @@ documented divergence (PHP's `E_DEPRECATED` notices are not emitted). |---|---|---| | `strlen()` | `strlen($str): int` | Returns string length | | `mb_strlen()` | `mb_strlen($str, $encoding = null): int` | Character count in the given encoding. An omitted or `null` encoding counts UTF-8, grouping malformed sequences like mbstring; `8bit`/`binary`/`7bit` return the byte length; other encodings are decoded through the system `iconv`. An unknown encoding name throws `\ValueError` | +| `mb_strimwidth()` | `mb_strimwidth($string, $start, $width, $trim_marker = "", $encoding = null): string` | Truncates a string to a display width, optionally appending `$trim_marker`. An omitted or `null` encoding uses UTF-8 East Asian Width (PHP 8.5); `8bit`/`binary`/`7bit` treat every byte as width 1. `$start` is a character offset (negative counts from the end). An unknown encoding or out-of-range `$start`/`$width` throws `\ValueError` | | `iconv_strlen()` | `iconv_strlen($str, $encoding = null): int\|false` | Character count through the platform `iconv`. See [iconv](./iconv.md) for the whole extension | | `substr()` | `substr($str, $start [, $len]): string` | Extract a substring. Negative `$start` counts from the end; a negative `$len` omits that many trailing bytes from the selected suffix, matching PHP | | `strpos()` | `strpos($haystack, $needle, $offset = 0): int\|false` | Find first occurrence at or after `$offset`. A negative `$offset` counts from the end; one outside the haystack raises `ValueError`. Returns `false` if not found | diff --git a/examples/string-ops/main.php b/examples/string-ops/main.php index bee6b753b7..2fe8c6c284 100644 --- a/examples/string-ops/main.php +++ b/examples/string-ops/main.php @@ -156,6 +156,8 @@ echo "\n--- Encoding ---\n"; echo "mb_strlen UTF-8: " . mb_strlen("héllo", "UTF-8") . "\n"; echo "mb_strlen bytes: " . mb_strlen("héllo", "8bit") . "\n"; +echo "mb_strimwidth ASCII: " . mb_strimwidth("hello", 0, 4, "...") . "\n"; +echo "mb_strimwidth CJK: " . mb_strimwidth("日本語", 0, 4, "…", "UTF-8") . "\n"; echo "htmlspecialchars: " . htmlspecialchars("bold") . "\n"; echo "urlencode: " . urlencode("hello world") . "\n"; echo "base64: " . base64_encode("Hello") . "\n"; diff --git a/scripts/docs/builtin_registry.json b/scripts/docs/builtin_registry.json index 406ead29ae..174b876eae 100644 --- a/scripts/docs/builtin_registry.json +++ b/scripts/docs/builtin_registry.json @@ -55388,6 +55388,217 @@ "slug": "mb_ereg_match", "sub_area": "Regex" }, + { + "aot": { + "kind": "registry", + "params": [ + { + "by_ref": false, + "default": null, + "name": "string", + "optional": false, + "type": "string" + }, + { + "by_ref": false, + "default": null, + "name": "start", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": null, + "name": "width", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": "", + "name": "trim_marker", + "optional": true, + "type": "string" + }, + { + "by_ref": false, + "default": null, + "name": "encoding", + "optional": true, + "type": "string" + } + ], + "required_param_count": 3, + "signature_override_reason": null, + "supported": true, + "variadic": null + }, + "area": "String", + "canonical_name": "mb_strimwidth", + "description": "Truncates a string to a display width, optionally appending a trim marker.", + "eval": { + "adapter_reason": "interpreter-specific-value-semantics", + "area": "string", + "execution": "interpreter-adapter", + "home_file": "crates/elephc-magician/src/interpreter/builtins/string/mb_strimwidth.rs", + "hooks": [ + "direct", + "values" + ], + "kind": "registry", + "params": [ + { + "by_ref": false, + "default": null, + "name": "string", + "optional": false, + "type": "string" + }, + { + "by_ref": false, + "default": null, + "name": "start", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": null, + "name": "width", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": "\"\"", + "name": "trim_marker", + "optional": true, + "type": "string" + }, + { + "by_ref": false, + "default": "null", + "name": "encoding", + "optional": true, + "type": "string" + } + ], + "required_param_count": 3, + "runtime_builtin_id": null, + "signature_override_reason": null, + "supported": true, + "variadic": null + }, + "eval_only": false, + "examples": [], + "in_catalog": true, + "is_extension": false, + "is_internal": false, + "lowering": { + "checker_file": null, + "checker_line": null, + "codegen_file": "src/builtins/semantics.rs", + "codegen_function": "lower_registry_call", + "codegen_line": 553, + "notes": [ + "Uses the `runtime_call` strategy from the single-source builtin descriptor.", + "Emits the typed EIR target `runtime.mb_strimwidth` through `BuiltinLoweringContext`.", + "The backend resolves that typed target through `src/codegen/lower_inst/runtime_calls.rs`; PHP builtin names do not participate in dispatch." + ], + "runtime_helpers": [], + "sig_arm": null, + "sig_file": "src/builtins/string/mb_strimwidth.rs", + "sig_line": null + }, + "name": "mb_strimwidth", + "semantics": { + "argument_lowering": "standard", + "callable": { + "kind": "static_only", + "reason": "typed backend operation has no runtime-selected wrapper contract" + }, + "effects": { + "kind": "static", + "names": [ + "alloc_heap", + "alloc_concat", + "may_throw" + ] + }, + "lowering": { + "kind": "runtime_call", + "target": "mb_strimwidth" + }, + "ownership": { + "argument_indexes": [], + "kind": "fresh" + }, + "requirements": { + "kind": "static", + "values": [] + }, + "result_type": "checked", + "runtime_functions": [ + "mb_strimwidth" + ], + "target_strategy": "runtime_call", + "target_support": [ + "macos-aarch64", + "ios-arm64", + "ios-sim-arm64", + "linux-aarch64", + "linux-x86_64" + ], + "target_support_kind": "all", + "validation": { + "kind": "checker_hook", + "lazy": true + } + }, + "sig": { + "params": [ + { + "by_ref": false, + "default": null, + "name": "string", + "optional": false, + "type": "string" + }, + { + "by_ref": false, + "default": null, + "name": "start", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": null, + "name": "width", + "optional": false, + "type": "int" + }, + { + "by_ref": false, + "default": "''", + "name": "trim_marker", + "optional": true, + "type": "string" + }, + { + "by_ref": false, + "default": "null", + "name": "encoding", + "optional": true, + "type": "string" + } + ], + "return_type": "string", + "variadic": null + }, + "slug": "mb_strimwidth", + "sub_area": "String" + }, { "aot": { "kind": "registry", diff --git a/src/builtins/string/mb_strimwidth.rs b/src/builtins/string/mb_strimwidth.rs new file mode 100644 index 0000000000..6b9536f62f --- /dev/null +++ b/src/builtins/string/mb_strimwidth.rs @@ -0,0 +1,67 @@ +//! Purpose: +//! Home of the PHP `mb_strimwidth` builtin: declaration and semantic metadata. +//! +//! Called from: +//! - Checker, EIR, optimizer, ownership, and callable consumers through +//! `crate::builtins::registry`. +//! +//! Key details: +//! - The public signature matches PHP: `mb_strimwidth(string $string, int $start, +//! int $width, string $trim_marker = "", ?string $encoding = null)`. +//! - Omitted/null encoding uses UTF-8 display-width trimming; unknown encodings are +//! rejected at runtime with a catchable `ValueError`. + +use crate::{ + builtins::spec::BuiltinCheckCtx, + errors::CompileError, + types::PhpType, +}; + +builtin! { + contract: "mb_strimwidth", + check: check, + lazy_check: true, + semantics: crate::builtins::semantics::runtime_fn_semantics( + crate::ir::RuntimeFnId::MbStrimwidth, + ), +} + +/// Validates PHP's string/start/width plus optional marker and nullable encoding surface. +fn check(cx: &mut BuiltinCheckCtx) -> Result { + let string_ty = cx.checker.infer_type(&cx.args[0], cx.env)?; + if string_ty != PhpType::Str { + return Err(CompileError::new( + cx.args[0].span, + "mb_strimwidth() string argument must be string", + )); + } + + if let Some(start) = cx.args.get(1) { + cx.checker.infer_type(start, cx.env)?; + } + if let Some(width) = cx.args.get(2) { + cx.checker.infer_type(width, cx.env)?; + } + + if let Some(trim_marker) = cx.args.get(3) { + let marker_ty = cx.checker.infer_type(trim_marker, cx.env)?; + if marker_ty != PhpType::Str { + return Err(CompileError::new( + trim_marker.span, + "mb_strimwidth() trim_marker argument must be string", + )); + } + } + + if let Some(encoding) = cx.args.get(4) { + let encoding_ty = cx.checker.infer_type(encoding, cx.env)?; + if !matches!(encoding_ty, PhpType::Str | PhpType::Void) { + return Err(CompileError::new( + encoding.span, + "mb_strimwidth() encoding argument must be string or null", + )); + } + } + + Ok(PhpType::Str) +} diff --git a/src/builtins/string/mod.rs b/src/builtins/string/mod.rs index d15b766f72..8132d4f393 100644 --- a/src/builtins/string/mod.rs +++ b/src/builtins/string/mod.rs @@ -66,6 +66,7 @@ pub mod lcfirst; pub mod long2ip; pub mod ltrim; pub mod mb_ereg_match; +pub mod mb_strimwidth; pub mod mb_strlen; pub mod md5; pub mod nl2br; diff --git a/src/codegen/lower_inst/builtins/strings.rs b/src/codegen/lower_inst/builtins/strings.rs index 3f672dbc45..e55b0cb7a4 100644 --- a/src/codegen/lower_inst/builtins/strings.rs +++ b/src/codegen/lower_inst/builtins/strings.rs @@ -26,6 +26,7 @@ use super::{ mod common; mod compression; mod hash; +mod mb; mod network; mod parse_url; mod printf; @@ -64,6 +65,7 @@ pub(crate) use hash::{ lower_hash_final, lower_hash_hmac, lower_hash_init, lower_hash_update, lower_mb_strlen, lower_md5, lower_sha1, }; +pub(crate) use mb::lower_mb_strimwidth; pub(crate) use network::{lower_inet, lower_ip2long, lower_long2ip}; pub(crate) use parse_url::lower_parse_url; pub(crate) use printf::{lower_printf, lower_sprintf, lower_vprintf, lower_vsprintf}; diff --git a/src/codegen/lower_inst/builtins/strings/mb.rs b/src/codegen/lower_inst/builtins/strings/mb.rs new file mode 100644 index 0000000000..60b9f02306 --- /dev/null +++ b/src/codegen/lower_inst/builtins/strings/mb.rs @@ -0,0 +1,104 @@ +//! Purpose: +//! Lowers PHP `mb_strimwidth()` through the shared `__rt_mb_strimwidth` helper. +//! +//! Called from: +//! - `crate::codegen::lower_inst::runtime_functions` dispatch group 10. +//! +//! Key details: +//! - Materializes string/start/width plus optional marker and nullable encoding. +//! - Omitted/null encoding becomes a null pointer plus zero length; the runtime +//! treats that as UTF-8 and rejects unknown names with a catchable `ValueError`. + +use super::*; + +/// Lowers `mb_strimwidth(string, start, width, trim_marker = "", encoding = null)`. +pub(crate) fn lower_mb_strimwidth(ctx: &mut FunctionContext<'_>, inst: &Instruction) -> Result<()> { + super::super::ensure_arg_count_between(inst, "mb_strimwidth", 3, 5)?; + match ctx.emitter.target.arch { + Arch::AArch64 => lower_mb_strimwidth_aarch64(ctx, inst)?, + Arch::X86_64 => lower_mb_strimwidth_x86_64(ctx, inst)?, + } + abi::emit_call_label(ctx.emitter, "__rt_mb_strimwidth"); + store_if_result(ctx, inst) +} + +/// Materializes AArch64 `__rt_mb_strimwidth` arguments. +/// +/// Encoding length lives in `x0` so the optional encoding pointer can occupy `x7`. +/// Earlier operands are parked on the stack because later loads clobber them. +fn lower_mb_strimwidth_aarch64(ctx: &mut FunctionContext<'_>, inst: &Instruction) -> Result<()> { + load_string_arg_to_regs(ctx, inst, 0, "mb_strimwidth", "x1", "x2")?; + abi::emit_push_reg_pair(ctx.emitter, "x1", "x2"); + let start = expect_operand(inst, 1)?; + load_as_int(ctx, start, "mb_strimwidth start")?; + abi::emit_push_reg_pair(ctx.emitter, "x0", "xzr"); + let width = expect_operand(inst, 2)?; + load_as_int(ctx, width, "mb_strimwidth width")?; + abi::emit_push_reg_pair(ctx.emitter, "x0", "xzr"); + load_optional_mb_strimwidth_marker(ctx, inst, "x1", "x2")?; + abi::emit_push_reg_pair(ctx.emitter, "x1", "x2"); + load_optional_mb_strimwidth_encoding(ctx, inst, "x7", "x0")?; + abi::emit_pop_reg_pair(ctx.emitter, "x5", "x6"); + abi::emit_pop_reg_pair(ctx.emitter, "x4", "x9"); + abi::emit_pop_reg_pair(ctx.emitter, "x3", "x9"); + abi::emit_pop_reg_pair(ctx.emitter, "x1", "x2"); + Ok(()) +} + +/// Materializes x86_64 `__rt_mb_strimwidth` arguments. +/// +/// The helper reads `rax`/`rdx` (string), `rcx` (start), `r8` (width), `r9`/`r10` +/// (marker), and `r11`/`rdi` (optional encoding pointer/length). +fn lower_mb_strimwidth_x86_64(ctx: &mut FunctionContext<'_>, inst: &Instruction) -> Result<()> { + load_string_arg_to_regs(ctx, inst, 0, "mb_strimwidth", "rax", "rdx")?; + abi::emit_push_reg_pair(ctx.emitter, "rax", "rdx"); + let start = expect_operand(inst, 1)?; + load_as_int(ctx, start, "mb_strimwidth start")?; + abi::emit_push_reg_pair(ctx.emitter, "rax", "rax"); + let width = expect_operand(inst, 2)?; + load_as_int(ctx, width, "mb_strimwidth width")?; + abi::emit_push_reg_pair(ctx.emitter, "rax", "rax"); + load_optional_mb_strimwidth_marker(ctx, inst, "rax", "rdx")?; + abi::emit_push_reg_pair(ctx.emitter, "rax", "rdx"); + load_optional_mb_strimwidth_encoding(ctx, inst, "r11", "rdi")?; + abi::emit_pop_reg_pair(ctx.emitter, "r9", "r10"); + abi::emit_pop_reg_pair(ctx.emitter, "r8", "rsi"); + abi::emit_pop_reg_pair(ctx.emitter, "rcx", "rsi"); + abi::emit_pop_reg_pair(ctx.emitter, "rax", "rdx"); + Ok(()) +} + +/// Loads the optional trim marker, or a zero-length string when it is omitted. +fn load_optional_mb_strimwidth_marker( + ctx: &mut FunctionContext<'_>, + inst: &Instruction, + ptr_reg: &str, + len_reg: &str, +) -> Result<()> { + let Some(marker) = inst.operands.get(3).copied() else { + abi::emit_load_int_immediate(ctx.emitter, ptr_reg, 0); + abi::emit_load_int_immediate(ctx.emitter, len_reg, 0); + return Ok(()); + }; + load_value_as_string_to_regs(ctx, marker, "mb_strimwidth trim_marker", ptr_reg, len_reg) +} + +/// Loads the nullable optional encoding into a pointer/length pair. +fn load_optional_mb_strimwidth_encoding( + ctx: &mut FunctionContext<'_>, + inst: &Instruction, + ptr_reg: &str, + len_reg: &str, +) -> Result<()> { + let Some(encoding) = inst.operands.get(4).copied() else { + abi::emit_load_int_immediate(ctx.emitter, ptr_reg, 0); + abi::emit_load_int_immediate(ctx.emitter, len_reg, 0); + return Ok(()); + }; + if matches!(ctx.value_php_type(encoding)?, PhpType::Void | PhpType::Never) { + abi::emit_load_int_immediate(ctx.emitter, ptr_reg, 0); + abi::emit_load_int_immediate(ctx.emitter, len_reg, 0); + return Ok(()); + } + load_value_as_string_to_regs(ctx, encoding, "mb_strimwidth encoding", ptr_reg, len_reg) +} diff --git a/src/codegen/lower_inst/runtime_functions/group_10.rs b/src/codegen/lower_inst/runtime_functions/group_10.rs index 5ba7352b12..ebb6cec741 100644 --- a/src/codegen/lower_inst/runtime_functions/group_10.rs +++ b/src/codegen/lower_inst/runtime_functions/group_10.rs @@ -19,6 +19,9 @@ pub(super) fn lower( target: RuntimeFnId, ) -> Option> { match target { + RuntimeFnId::MbStrimwidth => Some({ + crate::codegen::lower_inst::builtins::strings::lower_mb_strimwidth(ctx, inst) + }), RuntimeFnId::MbStrlen => Some({ crate::codegen::lower_inst::builtins::strings::lower_mb_strlen(ctx, inst) }), diff --git a/src/codegen_support/runtime/data/fixed.rs b/src/codegen_support/runtime/data/fixed.rs index 37fbc19422..6f2334894d 100644 --- a/src/codegen_support/runtime/data/fixed.rs +++ b/src/codegen_support/runtime/data/fixed.rs @@ -13,7 +13,8 @@ use super::{ DIRNAME_LEVELS_MSG, HASH_COPY_FINALIZED_CTX_MSG, HASH_FINAL_FINALIZED_CTX_MSG, HASH_HMAC_UNKNOWN_ALGO_MSG, HASH_INIT_UNKNOWN_ALGO_MSG, HASH_UNKNOWN_ALGO_MSG, HASH_UPDATE_FINALIZED_CTX_MSG, ICONV_STRPOS_OFFSET_MSG, - MB_STRLEN_UNKNOWN_ENCODING_MSG, + MB_STRLEN_UNKNOWN_ENCODING_MSG, MB_STRIMWIDTH_START_RANGE_MSG, + MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG, MB_STRIMWIDTH_WIDTH_RANGE_MSG, OB_CLOSURE_INVOKE_NAME, OB_DEFAULT_HANDLER_NAME, OB_FATAL_IN_HANDLER, OB_NTC_CREATE_FAIL, OB_NTC_G_CLEAN, OB_NTC_G_END_CLEAN, OB_NTC_G_END_FLUSH, OB_NTC_G_FLUSH, OB_NTC_G_GET_CLEAN, OB_NTC_G_GET_FLUSH, OB_NTC_NO_CLEAN, OB_NTC_NO_END_CLEAN, OB_NTC_NO_END_FLUSH, @@ -551,6 +552,26 @@ pub(crate) fn emit_runtime_data_fixed(heap_size: usize, target: Target) -> Strin ".globl _mb_strlen_unknown_encoding_msg\n_mb_strlen_unknown_encoding_msg:\n .ascii {:?}\n", MB_STRLEN_UNKNOWN_ENCODING_MSG )); + out.push_str(&format!( + ".globl _mb_strimwidth_unknown_encoding_msg\n_mb_strimwidth_unknown_encoding_msg:\n .ascii {:?}\n", + MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG + )); + out.push_str(&format!( + ".globl _mb_strimwidth_start_range_msg\n_mb_strimwidth_start_range_msg:\n .ascii {:?}\n", + MB_STRIMWIDTH_START_RANGE_MSG + )); + out.push_str(&format!( + ".globl _mb_strimwidth_width_range_msg\n_mb_strimwidth_width_range_msg:\n .ascii {:?}\n", + MB_STRIMWIDTH_WIDTH_RANGE_MSG + )); + out.push_str(".p2align 2\n.globl _mb_eaw_table\n_mb_eaw_table:\n"); + for (begin, end) in crate::codegen_support::runtime::strings::mb_eaw::MB_EAW_RANGES { + out.push_str(&format!(" .long 0x{begin:x}, 0x{end:x}\n")); + } + out.push_str(&format!( + ".globl _mb_eaw_table_count\n_mb_eaw_table_count:\n .quad {}\n", + crate::codegen_support::runtime::strings::mb_eaw::MB_EAW_RANGES.len() + )); out.push_str(".globl _mb_strlen_utf8_name\n_mb_strlen_utf8_name:\n .asciz \"UTF-8\"\n"); out.push_str(".globl _mb_strlen_utf8_alias\n_mb_strlen_utf8_alias:\n .asciz \"UTF8\"\n"); out.push_str(".globl _mb_strlen_utf32le_name\n_mb_strlen_utf32le_name:\n .asciz \"UTF-32LE\"\n"); diff --git a/src/codegen_support/runtime/data/mod.rs b/src/codegen_support/runtime/data/mod.rs index 88e8a057d2..cae57f6a8b 100644 --- a/src/codegen_support/runtime/data/mod.rs +++ b/src/codegen_support/runtime/data/mod.rs @@ -219,3 +219,12 @@ pub(crate) const HASH_COPY_FINALIZED_CTX_MSG: &str = /// Catchable `\ValueError` message when `mb_strlen()` receives an unknown encoding name. pub(crate) const MB_STRLEN_UNKNOWN_ENCODING_MSG: &str = "mb_strlen(): Argument #2 ($encoding) must be a valid encoding"; +/// Catchable `\ValueError` message when `mb_strimwidth()` receives an unknown encoding name. +pub(crate) const MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG: &str = + "mb_strimwidth(): Argument #5 ($encoding) must be a valid encoding"; +/// Catchable `\ValueError` message when `mb_strimwidth()` `$start` is outside the string. +pub(crate) const MB_STRIMWIDTH_START_RANGE_MSG: &str = + "mb_strimwidth(): Argument #2 ($start) is out of range"; +/// Catchable `\ValueError` message when a negative `mb_strimwidth()` `$width` underflows. +pub(crate) const MB_STRIMWIDTH_WIDTH_RANGE_MSG: &str = + "mb_strimwidth(): Argument #3 ($width) is out of range"; diff --git a/src/codegen_support/runtime/emitters.rs b/src/codegen_support/runtime/emitters.rs index f86fc240ee..538724ede3 100644 --- a/src/codegen_support/runtime/emitters.rs +++ b/src/codegen_support/runtime/emitters.rs @@ -114,6 +114,7 @@ pub(crate) fn emit_runtime(emitter: &mut Emitter, features: RuntimeFeatures) { if features.mb_strlen { strings::emit_mb_strlen(emitter); } + strings::emit_mb_strimwidth(emitter); strings::emit_iconv(emitter); strings::emit_hash(emitter); strings::emit_hash_hmac(emitter); diff --git a/src/codegen_support/runtime/strings/mb_eaw.rs b/src/codegen_support/runtime/strings/mb_eaw.rs new file mode 100644 index 0000000000..e92ce53e67 --- /dev/null +++ b/src/codegen_support/runtime/strings/mb_eaw.rs @@ -0,0 +1,137 @@ +//! Purpose: +//! PHP 8.5 East Asian Width table used by `mb_strimwidth()` display-width trimming. +//! +//! Called from: +//! - `crate::codegen_support::runtime::data::fixed` when emitting `_mb_eaw_table`. +//! - `crate::codegen_support::runtime::strings::mb_strimwidth` for the range count. +//! +//! Key details: +//! - Copied from php-src `ext/mbstring/libmbfl/mbfl/eaw_table.h` on the PHP 8.5 branch. +//! - Each pair is an inclusive Unicode range that PHP treats as display width 2. + +/// Inclusive Unicode ranges that PHP 8.5 `character_width()` scores as width 2. +pub const MB_EAW_RANGES: &[(u32, u32)] = &[ + (0x1100, 0x115f), + (0x231a, 0x231b), + (0x2329, 0x232a), + (0x23e9, 0x23ec), + (0x23f0, 0x23f0), + (0x23f3, 0x23f3), + (0x25fd, 0x25fe), + (0x2614, 0x2615), + (0x2630, 0x2637), + (0x2648, 0x2653), + (0x267f, 0x267f), + (0x268a, 0x268f), + (0x2693, 0x2693), + (0x26a1, 0x26a1), + (0x26aa, 0x26ab), + (0x26bd, 0x26be), + (0x26c4, 0x26c5), + (0x26ce, 0x26ce), + (0x26d4, 0x26d4), + (0x26ea, 0x26ea), + (0x26f2, 0x26f3), + (0x26f5, 0x26f5), + (0x26fa, 0x26fa), + (0x26fd, 0x26fd), + (0x2705, 0x2705), + (0x270a, 0x270b), + (0x2728, 0x2728), + (0x274c, 0x274c), + (0x274e, 0x274e), + (0x2753, 0x2755), + (0x2757, 0x2757), + (0x2795, 0x2797), + (0x27b0, 0x27b0), + (0x27bf, 0x27bf), + (0x2b1b, 0x2b1c), + (0x2b50, 0x2b50), + (0x2b55, 0x2b55), + (0x2e80, 0x2e99), + (0x2e9b, 0x2ef3), + (0x2f00, 0x2fd5), + (0x2ff0, 0x303e), + (0x3041, 0x3096), + (0x3099, 0x30ff), + (0x3105, 0x312f), + (0x3131, 0x318e), + (0x3190, 0x31e5), + (0x31ef, 0x321e), + (0x3220, 0x3247), + (0x3250, 0xa48c), + (0xa490, 0xa4c6), + (0xa960, 0xa97c), + (0xac00, 0xd7a3), + (0xf900, 0xfaff), + (0xfe10, 0xfe19), + (0xfe30, 0xfe52), + (0xfe54, 0xfe66), + (0xfe68, 0xfe6b), + (0xff01, 0xff60), + (0xffe0, 0xffe6), + (0x16fe0, 0x16fe4), + (0x16ff0, 0x16ff6), + (0x17000, 0x18cd5), + (0x18cff, 0x18d1e), + (0x18d80, 0x18df2), + (0x1aff0, 0x1aff3), + (0x1aff5, 0x1affb), + (0x1affd, 0x1affe), + (0x1b000, 0x1b122), + (0x1b132, 0x1b132), + (0x1b150, 0x1b152), + (0x1b155, 0x1b155), + (0x1b164, 0x1b167), + (0x1b170, 0x1b2fb), + (0x1d300, 0x1d356), + (0x1d360, 0x1d376), + (0x1f004, 0x1f004), + (0x1f0cf, 0x1f0cf), + (0x1f18e, 0x1f18e), + (0x1f191, 0x1f19a), + (0x1f200, 0x1f202), + (0x1f210, 0x1f23b), + (0x1f240, 0x1f248), + (0x1f250, 0x1f251), + (0x1f260, 0x1f265), + (0x1f300, 0x1f320), + (0x1f32d, 0x1f335), + (0x1f337, 0x1f37c), + (0x1f37e, 0x1f393), + (0x1f3a0, 0x1f3ca), + (0x1f3cf, 0x1f3d3), + (0x1f3e0, 0x1f3f0), + (0x1f3f4, 0x1f3f4), + (0x1f3f8, 0x1f43e), + (0x1f440, 0x1f440), + (0x1f442, 0x1f4fc), + (0x1f4ff, 0x1f53d), + (0x1f54b, 0x1f54e), + (0x1f550, 0x1f567), + (0x1f57a, 0x1f57a), + (0x1f595, 0x1f596), + (0x1f5a4, 0x1f5a4), + (0x1f5fb, 0x1f64f), + (0x1f680, 0x1f6c5), + (0x1f6cc, 0x1f6cc), + (0x1f6d0, 0x1f6d2), + (0x1f6d5, 0x1f6d8), + (0x1f6dc, 0x1f6df), + (0x1f6eb, 0x1f6ec), + (0x1f6f4, 0x1f6fc), + (0x1f7e0, 0x1f7eb), + (0x1f7f0, 0x1f7f0), + (0x1f90c, 0x1f93a), + (0x1f93c, 0x1f945), + (0x1f947, 0x1f9ff), + (0x1fa70, 0x1fa7c), + (0x1fa80, 0x1fa8a), + (0x1fa8e, 0x1fac6), + (0x1fac8, 0x1fac8), + (0x1facd, 0x1fadc), + (0x1fadf, 0x1faea), + (0x1faef, 0x1faf8), + (0x20000, 0x2fffd), + (0x30000, 0x3fffd), +]; diff --git a/src/codegen_support/runtime/strings/mb_strimwidth.rs b/src/codegen_support/runtime/strings/mb_strimwidth.rs new file mode 100644 index 0000000000..c8443c848b --- /dev/null +++ b/src/codegen_support/runtime/strings/mb_strimwidth.rs @@ -0,0 +1,1008 @@ +//! Purpose: +//! Emits `__rt_mb_strimwidth`, the runtime helper for PHP's `mb_strimwidth()`. +//! +//! Called from: +//! - `crate::codegen_support::runtime::emitters::emit_runtime()`. +//! - `crate::codegen::lower_inst::builtins::strings::lower_mb_strimwidth()`. +//! +//! Key details: +//! - UTF-8 (omitted/`null`/`UTF-8`/`UTF8`) trims by PHP 8.5 East Asian display width. +//! - `8bit`/`binary`/`7bit` treat every byte as width 1; unknown encodings throw `ValueError`. +//! - `$start` is a character offset; a too-wide input is cut and the trim marker is appended +//! so the result's width equals the requested budget (or the marker alone when it is wider). + +use crate::codegen_support::{ + abi, + emit::Emitter, + platform::Arch, + runtime::{ + arrays::value_error, + data::{ + MB_STRIMWIDTH_START_RANGE_MSG, MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG, + MB_STRIMWIDTH_WIDTH_RANGE_MSG, + }, + }, +}; + +/// Maximum explicit encoding-name length copied into the runtime's stack buffer. +const MAX_ENCODING_NAME_LEN: usize = 63; + +/// Emits `__rt_mb_strimwidth` for the active target. +pub fn emit_mb_strimwidth(emitter: &mut Emitter) { + if emitter.target.arch == Arch::X86_64 { + emit_mb_strimwidth_x86_64(emitter); + } else { + emit_mb_strimwidth_aarch64(emitter); + } +} + +/// Emits the AArch64 implementation for macOS and Linux. +fn emit_mb_strimwidth_aarch64(emitter: &mut Emitter) { + emitter.blank(); + emitter.comment("--- runtime: mb_strimwidth (display-width trim) ---"); + emitter.label_global("__rt_mb_strimwidth"); + + // Frame: [sp+0]=x19/x20 ... [sp+80]=x29/x30. Slots sit above the saved pair. + // [sp+0] str ptr, [sp+8] str len, [sp+16] start, [sp+24] width, + // [sp+32] marker ptr, [sp+40] marker len, [sp+48] enc ptr, [sp+56] enc len, + // [sp+64] byte_mode, [sp+72] start_byte, [sp+80] result start, [sp+88] scratch, + // [sp+96] encoding name, [sp+160] x19-x28 / x29 / x30. + emitter.instruction("sub sp, sp, #256"); // reserve argument spills, encoding name, and callee-saved regs + emitter.instruction("stp x29, x30, [sp, #240]"); // save the caller frame and return address + emitter.instruction("add x29, sp, #240"); // establish the helper frame pointer + emitter.instruction("stp x19, x20, [sp, #160]"); // preserve callee-saved x19/x20 + emitter.instruction("stp x21, x22, [sp, #176]"); // preserve callee-saved x21/x22 + emitter.instruction("stp x23, x24, [sp, #192]"); // preserve callee-saved x23/x24 + emitter.instruction("stp x25, x26, [sp, #208]"); // preserve callee-saved x25/x26 + emitter.instruction("stp x27, x28, [sp, #224]"); // preserve callee-saved x27/x28 + emitter.instruction("str x1, [sp, #0]"); // spill the source string pointer + emitter.instruction("str x2, [sp, #8]"); // spill the source string length + emitter.instruction("str x3, [sp, #16]"); // spill the signed start offset + emitter.instruction("str x4, [sp, #24]"); // spill the signed display-width budget + emitter.instruction("str x5, [sp, #32]"); // spill the trim-marker pointer + emitter.instruction("str x6, [sp, #40]"); // spill the trim-marker length + emitter.instruction("str x7, [sp, #48]"); // spill the optional encoding pointer + emitter.instruction("str x0, [sp, #56]"); // spill the optional encoding length + emitter.instruction("str xzr, [sp, #64]"); // default to the UTF-8 display-width scanner + + emitter.instruction("cbz x7, __rt_mb_strimwidth_count"); // omitted/null encoding uses UTF-8 + emitter.instruction(&format!("cmp x0, #{}", MAX_ENCODING_NAME_LEN)); // does the encoding name fit the stack C-string buffer? + emitter.instruction("b.hi __rt_mb_strimwidth_unknown_encoding"); // reject names longer than every supported alias + emitter.instruction("add x9, sp, #96"); // destination is the 64-byte encoding-name buffer + emitter.instruction("mov x10, #0"); // copied-byte index starts at zero + emitter.label("__rt_mb_strimwidth_encoding_copy"); + emitter.instruction("cmp x10, x0"); // copied the whole explicit encoding name? + emitter.instruction("b.hs __rt_mb_strimwidth_encoding_copied"); // terminate the C string once every byte is copied + emitter.instruction("ldrb w11, [x7, x10]"); // load one encoding-name byte from the PHP string + emitter.instruction("strb w11, [x9, x10]"); // append the byte to the stack C string + emitter.instruction("add x10, x10, #1"); // advance the encoding-name byte index + emitter.instruction("b __rt_mb_strimwidth_encoding_copy"); // continue copying the remaining encoding-name bytes + emitter.label("__rt_mb_strimwidth_encoding_copied"); + emitter.instruction("strb wzr, [x9, x0]"); // NUL-terminate the explicit encoding name + emitter.instruction("add x0, sp, #96"); // first strcasecmp argument is the copied encoding name + abi::emit_symbol_address(emitter, "x1", "_mb_strlen_utf8_name"); + emitter.bl_c("strcasecmp"); // compare the explicit encoding with UTF-8 + emitter.instruction("cbz x0, __rt_mb_strimwidth_count"); // UTF-8 uses the display-width scanner + emitter.instruction("add x0, sp, #96"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "x1", "_mb_strlen_utf8_alias"); + emitter.bl_c("strcasecmp"); // compare the explicit encoding with UTF8 + emitter.instruction("cbz x0, __rt_mb_strimwidth_count"); // the UTF8 alias uses the same scanner + emitter.instruction("add x0, sp, #96"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "x1", "_mb_strlen_8bit_name"); + emitter.bl_c("strcasecmp"); // compare the explicit encoding with 8bit + emitter.instruction("cbz x0, __rt_mb_strimwidth_byte_mode"); // 8bit treats every byte as width 1 + emitter.instruction("add x0, sp, #96"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "x1", "_mb_strlen_binary_name"); + emitter.bl_c("strcasecmp"); // compare the explicit encoding with binary + emitter.instruction("cbz x0, __rt_mb_strimwidth_byte_mode"); // binary is PHP's alias for 8bit + emitter.instruction("add x0, sp, #96"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "x1", "_mb_strlen_7bit_name"); + emitter.bl_c("strcasecmp"); // compare the explicit encoding with 7bit + emitter.instruction("cbz x0, __rt_mb_strimwidth_byte_mode"); // 7bit preserves one-character-per-byte width + emitter.instruction("b __rt_mb_strimwidth_unknown_encoding"); // any other name is an unknown encoding + + emitter.label("__rt_mb_strimwidth_byte_mode"); + emitter.instruction("mov x9, #1"); // mark the scanner as byte-width mode + emitter.instruction("str x9, [sp, #64]"); // persist the byte-width flag + + emitter.label("__rt_mb_strimwidth_count"); + emitter.instruction("ldr x19, [sp, #0]"); // x19 = source pointer + emitter.instruction("ldr x20, [sp, #8]"); // x20 = source length + emitter.instruction("ldr x21, [sp, #64]"); // x21 = byte-mode flag + emitter.instruction("mov x0, x19"); // count characters in the whole source + emitter.instruction("mov x1, x20"); // pass the source length + emitter.instruction("mov x2, x21"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_count_chars"); // x0 = character count + emitter.instruction("mov x22, x0"); // x22 = character count + emitter.instruction("ldr x9, [sp, #16]"); // load the signed start offset + emitter.instruction("cbz x9, __rt_mb_strimwidth_start_ok"); // start 0 never needs a range check + emitter.instruction("cmp x9, #0"); // is start negative? + emitter.instruction("b.ge __rt_mb_strimwidth_start_nonneg"); // a non-negative start is already an origin offset + emitter.instruction("add x9, x9, x22"); // negative start counts from the end + emitter.label("__rt_mb_strimwidth_start_nonneg"); + emitter.instruction("cmp x9, #0"); // did a negative start underflow the string? + emitter.instruction("b.lt __rt_mb_strimwidth_start_error"); // start below zero is out of range + emitter.instruction("cmp x9, x22"); // is start past the last character? + emitter.instruction("b.gt __rt_mb_strimwidth_start_error"); // start > char_count is out of range + emitter.instruction("str x9, [sp, #16]"); // persist the resolved non-negative start + emitter.label("__rt_mb_strimwidth_start_ok"); + emitter.instruction("ldr x9, [sp, #24]"); // load the signed width budget + emitter.instruction("cmp x9, #0"); // is width negative? + emitter.instruction("b.ge __rt_mb_strimwidth_width_ok"); // a non-negative width is already a remaining budget + emitter.instruction("mov x0, x19"); // measure the whole string's display width + emitter.instruction("mov x1, x20"); // pass the source length + emitter.instruction("mov x2, x21"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_strwidth"); // x0 = total display width + emitter.instruction("ldr x9, [sp, #24]"); // reload the negative width + emitter.instruction("add x9, x9, x0"); // PHP adds the whole-string width first + emitter.instruction("str x9, [sp, #24]"); // persist before skip/strwidth clobber x9 + emitter.instruction("ldr x10, [sp, #16]"); // load the resolved start + emitter.instruction("cbz x10, __rt_mb_strimwidth_neg_width_checked"); // start 0 has no prefix width to subtract + emitter.instruction("mov x0, x19"); // skip the prefix so we can measure it + emitter.instruction("mov x1, x20"); // pass the source length + emitter.instruction("mov x2, x10"); // skip this many characters + emitter.instruction("mov x3, x21"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_skip"); // x0 = prefix byte length + emitter.instruction("mov x1, x0"); // measure only the skipped prefix + emitter.instruction("mov x0, x19"); // prefix starts at the source pointer + emitter.instruction("mov x2, x21"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_strwidth"); // x0 = prefix display width + emitter.instruction("ldr x9, [sp, #24]"); // reload the width the walkers clobbered + emitter.instruction("sub x9, x9, x0"); // subtract the skipped prefix width + emitter.label("__rt_mb_strimwidth_neg_width_checked"); + emitter.instruction("cmp x9, #0"); // did the adjusted width underflow? + emitter.instruction("b.lt __rt_mb_strimwidth_width_error"); // a still-negative width is out of range + emitter.instruction("str x9, [sp, #24]"); // persist the resolved non-negative width + emitter.label("__rt_mb_strimwidth_width_ok"); + + emitter.instruction("ldr x0, [sp, #0]"); // skip `start` characters to find the kept suffix + emitter.instruction("ldr x1, [sp, #8]"); // pass the source length + emitter.instruction("ldr x2, [sp, #16]"); // pass the resolved start + emitter.instruction("ldr x3, [sp, #64]"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_skip"); // x0 = start byte offset + emitter.instruction("str x0, [sp, #72]"); // persist the suffix origin + emitter.instruction("ldr x19, [sp, #0]"); // reload the source pointer + emitter.instruction("add x19, x19, x0"); // x19 = suffix pointer + emitter.instruction("ldr x20, [sp, #8]"); // reload the source length + emitter.instruction("sub x20, x20, x0"); // x20 = suffix byte length + emitter.instruction("mov x0, x19"); // measure the suffix display width + emitter.instruction("mov x1, x20"); // pass the suffix length + emitter.instruction("ldr x2, [sp, #64]"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_strwidth"); // x0 = suffix display width + emitter.instruction("ldr x9, [sp, #24]"); // load the resolved width budget + emitter.instruction("cmp x0, x9"); // does the suffix already fit? + emitter.instruction("b.ls __rt_mb_strimwidth_copy_suffix"); // yes → return the suffix unchanged + emitter.instruction("ldr x0, [sp, #32]"); // measure the trim marker's display width + emitter.instruction("ldr x1, [sp, #40]"); // pass the marker length + emitter.instruction("ldr x2, [sp, #64]"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_strwidth"); // x0 = marker display width + emitter.instruction("ldr x9, [sp, #24]"); // reload the requested width + emitter.instruction("cmp x9, x0"); // is the budget no larger than the marker? + emitter.instruction("b.ls __rt_mb_strimwidth_copy_marker"); // yes → PHP returns the marker alone + emitter.instruction("sub x2, x9, x0"); // remaining budget for source characters + emitter.instruction("mov x0, x19"); // take a prefix of the suffix + emitter.instruction("mov x1, x20"); // pass the suffix length + emitter.instruction("ldr x3, [sp, #64]"); // pass the width mode + emitter.instruction("bl __rt_mb_strimwidth_take"); // x0 = kept source byte count + emitter.instruction("mov x21, x0"); // x21 = kept source bytes + emitter.instruction("ldr x9, [sp, #40]"); // load the marker length + emitter.instruction("add x0, x21, x9"); // reserve source prefix plus marker + emitter.instruction("bl __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("str x0, [sp, #80]"); // persist the result start pointer + emitter.instruction("mov x1, x19"); // memcpy source is the suffix + emitter.instruction("mov x2, x21"); // memcpy length is the kept prefix + emitter.bl_c("memmove"); // copy the kept source prefix into the reservation + emitter.instruction("ldr x0, [sp, #80]"); // reload the result start + emitter.instruction("add x0, x0, x21"); // destination for the trim marker + emitter.instruction("ldr x1, [sp, #32]"); // memcpy source is the marker + emitter.instruction("ldr x2, [sp, #40]"); // memcpy length is the marker length + emitter.bl_c("memmove"); // append the trim marker + emitter.instruction("ldr x1, [sp, #80]"); // result pointer is the reservation start + emitter.instruction("ldr x2, [sp, #40]"); // start with the marker length + emitter.instruction("add x2, x2, x21"); // add the kept source bytes + emitter.instruction("bl __rt_concat_publish"); // publish the concat-scratch write offset + emitter.instruction("b __rt_mb_strimwidth_return"); // restore callee-saved regs and return + + emitter.label("__rt_mb_strimwidth_copy_suffix"); + emitter.instruction("mov x0, x20"); // reserve exactly the suffix bytes + emitter.instruction("bl __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("str x0, [sp, #80]"); // persist the result start pointer + emitter.instruction("mov x1, x19"); // memcpy source is the suffix + emitter.instruction("mov x2, x20"); // memcpy length is the suffix length + emitter.bl_c("memmove"); // copy the untrimmed suffix + emitter.instruction("ldr x1, [sp, #80]"); // result pointer is the reservation start + emitter.instruction("mov x2, x20"); // result length is the suffix length + emitter.instruction("bl __rt_concat_publish"); // publish the concat-scratch write offset + emitter.instruction("b __rt_mb_strimwidth_return"); // restore callee-saved regs and return + + emitter.label("__rt_mb_strimwidth_copy_marker"); + emitter.instruction("ldr x0, [sp, #40]"); // reserve exactly the marker bytes + emitter.instruction("bl __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("str x0, [sp, #80]"); // persist the result start pointer + emitter.instruction("ldr x1, [sp, #32]"); // memcpy source is the marker + emitter.instruction("ldr x2, [sp, #40]"); // memcpy length is the marker length + emitter.bl_c("memmove"); // copy the marker alone + emitter.instruction("ldr x1, [sp, #80]"); // result pointer is the reservation start + emitter.instruction("ldr x2, [sp, #40]"); // result length is the marker length + emitter.instruction("bl __rt_concat_publish"); // publish the concat-scratch write offset + + emitter.label("__rt_mb_strimwidth_return"); + emitter.instruction("ldp x19, x20, [sp, #160]"); // restore callee-saved x19/x20 + emitter.instruction("ldp x21, x22, [sp, #176]"); // restore callee-saved x21/x22 + emitter.instruction("ldp x23, x24, [sp, #192]"); // restore callee-saved x23/x24 + emitter.instruction("ldp x25, x26, [sp, #208]"); // restore callee-saved x25/x26 + emitter.instruction("ldp x27, x28, [sp, #224]"); // restore callee-saved x27/x28 + emitter.instruction("ldp x29, x30, [sp, #240]"); // restore the caller frame and return address + emitter.instruction("add sp, sp, #256"); // release the helper frame + emitter.instruction("ret"); // return the trimmed string pointer/length + + emitter.label("__rt_mb_strimwidth_unknown_encoding"); + emitter.instruction("ldp x19, x20, [sp, #160]"); // restore callee-saved regs before throwing + emitter.instruction("ldp x21, x22, [sp, #176]"); // restore callee-saved x21/x22 + emitter.instruction("ldp x23, x24, [sp, #192]"); // restore callee-saved x23/x24 + emitter.instruction("ldp x25, x26, [sp, #208]"); // restore callee-saved x25/x26 + emitter.instruction("ldp x27, x28, [sp, #224]"); // restore callee-saved x27/x28 + emitter.instruction("ldp x29, x30, [sp, #240]"); // restore the caller frame before throwing + emitter.instruction("add sp, sp, #256"); // release the helper frame before unwinding + value_error::emit_throw_value_error_aarch64( + emitter, + "_mb_strimwidth_unknown_encoding_msg", + MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG.len(), + ); + + emitter.label("__rt_mb_strimwidth_start_error"); + emitter.instruction("ldp x19, x20, [sp, #160]"); // restore callee-saved regs before throwing + emitter.instruction("ldp x21, x22, [sp, #176]"); // restore callee-saved x21/x22 + emitter.instruction("ldp x23, x24, [sp, #192]"); // restore callee-saved x23/x24 + emitter.instruction("ldp x25, x26, [sp, #208]"); // restore callee-saved x25/x26 + emitter.instruction("ldp x27, x28, [sp, #224]"); // restore callee-saved x27/x28 + emitter.instruction("ldp x29, x30, [sp, #240]"); // restore the caller frame before throwing + emitter.instruction("add sp, sp, #256"); // release the helper frame before unwinding + value_error::emit_throw_value_error_aarch64( + emitter, + "_mb_strimwidth_start_range_msg", + MB_STRIMWIDTH_START_RANGE_MSG.len(), + ); + + emitter.label("__rt_mb_strimwidth_width_error"); + emitter.instruction("ldp x19, x20, [sp, #160]"); // restore callee-saved regs before throwing + emitter.instruction("ldp x21, x22, [sp, #176]"); // restore callee-saved x21/x22 + emitter.instruction("ldp x23, x24, [sp, #192]"); // restore callee-saved x23/x24 + emitter.instruction("ldp x25, x26, [sp, #208]"); // restore callee-saved x25/x26 + emitter.instruction("ldp x27, x28, [sp, #224]"); // restore callee-saved x27/x28 + emitter.instruction("ldp x29, x30, [sp, #240]"); // restore the caller frame before throwing + emitter.instruction("add sp, sp, #256"); // release the helper frame before unwinding + value_error::emit_throw_value_error_aarch64( + emitter, + "_mb_strimwidth_width_range_msg", + MB_STRIMWIDTH_WIDTH_RANGE_MSG.len(), + ); + + emit_mb_strimwidth_walkers_aarch64(emitter); +} + +/// Emits AArch64 character-walk helpers shared by the trim helper. +fn emit_mb_strimwidth_walkers_aarch64(emitter: &mut Emitter) { + // count_chars(x0=ptr, x1=len, x2=byte_mode) -> x0=count + emitter.label_shared("__rt_mb_strimwidth_count_chars"); + emitter.instruction("cbnz x2, __rt_mb_strimwidth_count_bytes"); // byte mode returns the raw length + emitter.instruction("mov x3, x0"); // x3 = scan pointer + emitter.instruction("add x4, x0, x1"); // x4 = one-past-end pointer + emitter.instruction("mov x0, #0"); // character count starts at zero + emitter.label("__rt_mb_strimwidth_count_loop"); + emitter.instruction("cmp x3, x4"); // scanned every byte? + emitter.instruction("b.hs __rt_mb_strimwidth_count_done"); // return the accumulated count + emitter.instruction("mov x5, x3"); // next-char input is the current pointer + emitter.instruction("mov x6, x4"); // next-char end is the one-past-end pointer + emitter.instruction("stp x0, x30, [sp, #-16]!"); // preserve the count and return address + emitter.instruction("bl __rt_mb_strimwidth_next"); // x5 advances past one character + emitter.instruction("ldp x0, x30, [sp], #16"); // restore the count and return address + emitter.instruction("mov x3, x5"); // continue after the consumed character + emitter.instruction("add x0, x0, #1"); // count one more character + emitter.instruction("b __rt_mb_strimwidth_count_loop"); // keep scanning + emitter.label("__rt_mb_strimwidth_count_bytes"); + emitter.instruction("mov x0, x1"); // byte encodings count one character per byte + emitter.label("__rt_mb_strimwidth_count_done"); + emitter.instruction("ret"); // return the character count + + // strwidth(x0=ptr, x1=len, x2=byte_mode) -> x0=width + emitter.label_shared("__rt_mb_strimwidth_strwidth"); + emitter.instruction("cbnz x2, __rt_mb_strimwidth_strwidth_bytes"); // byte mode returns the raw length + emitter.instruction("mov x3, x0"); // x3 = scan pointer + emitter.instruction("add x4, x0, x1"); // x4 = one-past-end pointer + emitter.instruction("mov x0, #0"); // display width starts at zero + emitter.label("__rt_mb_strimwidth_strwidth_loop"); + emitter.instruction("cmp x3, x4"); // scanned every byte? + emitter.instruction("b.hs __rt_mb_strimwidth_strwidth_done"); // return the accumulated width + emitter.instruction("mov x5, x3"); // next-char input is the current pointer + emitter.instruction("mov x6, x4"); // next-char end is the one-past-end pointer + emitter.instruction("stp x0, x30, [sp, #-16]!"); // preserve the width and return address + emitter.instruction("bl __rt_mb_strimwidth_next"); // x7 = codepoint, x5 advances + emitter.instruction("stp x4, x5, [sp, #-16]!"); // preserve the end pointer and advanced cursor + emitter.instruction("mov x0, x7"); // look up the consumed character's width + emitter.instruction("bl __rt_mb_strimwidth_char_width"); // x0 = 1 or 2 + emitter.instruction("mov x8, x0"); // stash the character width + emitter.instruction("ldp x4, x5, [sp], #16"); // restore the end pointer and advanced cursor + emitter.instruction("ldp x0, x30, [sp], #16"); // restore the total width and return address + emitter.instruction("add x0, x0, x8"); // accumulate the character width + emitter.instruction("mov x3, x5"); // continue after the consumed character + emitter.instruction("b __rt_mb_strimwidth_strwidth_loop"); // keep scanning + emitter.label("__rt_mb_strimwidth_strwidth_bytes"); + emitter.instruction("mov x0, x1"); // byte encodings have width equal to length + emitter.label("__rt_mb_strimwidth_strwidth_done"); + emitter.instruction("ret"); // return the display width + + // skip(x0=ptr, x1=len, x2=count, x3=byte_mode) -> x0=byte offset + emitter.label_shared("__rt_mb_strimwidth_skip"); + emitter.instruction("cbnz x3, __rt_mb_strimwidth_skip_bytes"); // byte mode skips `count` bytes + emitter.instruction("mov x5, x0"); // x5 = scan pointer + emitter.instruction("add x6, x0, x1"); // x6 = one-past-end pointer + emitter.instruction("mov x4, x0"); // remember the origin for the returned offset + emitter.instruction("mov x8, x2"); // remaining characters to skip + emitter.label("__rt_mb_strimwidth_skip_loop"); + emitter.instruction("cbz x8, __rt_mb_strimwidth_skip_done"); // finished skipping the requested count + emitter.instruction("cmp x5, x6"); // reached the end of the string? + emitter.instruction("b.hs __rt_mb_strimwidth_skip_done"); // cannot skip past the last character + emitter.instruction("stp x4, x8, [sp, #-16]!"); // preserve origin and remaining count + emitter.instruction("str x30, [sp, #-16]!"); // preserve the return address + emitter.instruction("bl __rt_mb_strimwidth_next"); // advance one character + emitter.instruction("ldr x30, [sp], #16"); // restore the return address + emitter.instruction("ldp x4, x8, [sp], #16"); // restore origin and remaining count + emitter.instruction("sub x8, x8, #1"); // one fewer character remains + emitter.instruction("b __rt_mb_strimwidth_skip_loop"); // keep skipping + emitter.label("__rt_mb_strimwidth_skip_done"); + emitter.instruction("sub x0, x5, x4"); // return the consumed byte count + emitter.instruction("ret"); // return the skip offset + emitter.label("__rt_mb_strimwidth_skip_bytes"); + emitter.instruction("cmp x2, x1"); // would the skip pass the last byte? + emitter.instruction("csel x0, x2, x1, lo"); // clamp the byte skip to the string length + emitter.instruction("ret"); // return the clamped byte offset + + // take(x0=ptr, x1=len, x2=budget, x3=byte_mode) -> x0=byte count + emitter.label_shared("__rt_mb_strimwidth_take"); + emitter.instruction("cbnz x3, __rt_mb_strimwidth_take_bytes"); // byte mode takes `budget` bytes + emitter.instruction("mov x5, x0"); // x5 = scan pointer + emitter.instruction("add x6, x0, x1"); // x6 = one-past-end pointer + emitter.instruction("mov x4, x0"); // remember the origin for the returned length + emitter.instruction("mov x8, x2"); // remaining display-width budget + emitter.label("__rt_mb_strimwidth_take_loop"); + emitter.instruction("cmp x5, x6"); // reached the end of the string? + emitter.instruction("b.hs __rt_mb_strimwidth_take_done"); // take the whole suffix + emitter.instruction("stp x4, x8, [sp, #-16]!"); // preserve origin and remaining budget + emitter.instruction("stp x6, x30, [sp, #-16]!"); // preserve the end pointer and return address + emitter.instruction("stp x5, xzr, [sp, #-16]!"); // preserve the cursor before this character + emitter.instruction("bl __rt_mb_strimwidth_next"); // x7 = codepoint, x5 tentatively advanced + emitter.instruction("ldr x9, [sp]"); // reload the pre-decode cursor; next clobbers x9 + emitter.instruction("stp x9, x5, [sp]"); // remember pre/post-decode cursors for the width check + emitter.instruction("mov x0, x7"); // look up the candidate character width + emitter.instruction("bl __rt_mb_strimwidth_char_width"); // x0 = 1 or 2 + emitter.instruction("ldp x9, x5, [sp], #16"); // restore the pre/post-decode cursors + emitter.instruction("ldp x6, x30, [sp], #16"); // restore the end pointer and return address + emitter.instruction("ldp x4, x8, [sp], #16"); // restore origin and remaining budget + emitter.instruction("cmp x8, x0"); // does the character still fit the budget? + emitter.instruction("b.lo __rt_mb_strimwidth_take_reject"); // no → leave this character out + emitter.instruction("sub x8, x8, x0"); // consume the character's display width + emitter.instruction("b __rt_mb_strimwidth_take_loop"); // keep taking + emitter.label("__rt_mb_strimwidth_take_reject"); + emitter.instruction("mov x5, x9"); // rewind to exclude the overflowing character + emitter.label("__rt_mb_strimwidth_take_done"); + emitter.instruction("sub x0, x5, x4"); // return the kept byte count + emitter.instruction("ret"); // return the taken prefix length + emitter.label("__rt_mb_strimwidth_take_bytes"); + emitter.instruction("cmp x2, x1"); // would the take pass the last byte? + emitter.instruction("csel x0, x2, x1, lo"); // clamp the byte take to the string length + emitter.instruction("ret"); // return the clamped byte count + + // next(x5=ptr, x6=end) -> x5=advanced ptr, x7=codepoint + emitter.label_shared("__rt_mb_strimwidth_next"); + emitter.instruction("ldrb w9, [x5]"); // load the next possible UTF-8 leading byte + emitter.instruction("cmp w9, #0x80"); // ASCII bytes are complete one-byte characters + emitter.instruction("b.lo __rt_mb_strimwidth_next_ascii"); // consume one ASCII byte + emitter.instruction("cmp w9, #0xc2"); // C0/C1 and continuation bytes are malformed leaders + emitter.instruction("b.lo __rt_mb_strimwidth_next_invalid"); // substitute one malformed byte + emitter.instruction("cmp w9, #0xe0"); // C2-DF introduce two-byte sequences + emitter.instruction("b.lo __rt_mb_strimwidth_next_two"); // validate a two-byte character + emitter.instruction("cmp w9, #0xf0"); // E0-EF introduce three-byte sequences + emitter.instruction("b.lo __rt_mb_strimwidth_next_three"); // validate a three-byte character + emitter.instruction("cmp w9, #0xf5"); // F0-F4 introduce Unicode-range four-byte sequences + emitter.instruction("b.lo __rt_mb_strimwidth_next_four"); // validate a four-byte character + emitter.instruction("b __rt_mb_strimwidth_next_invalid"); // F5-FF cannot begin valid UTF-8 + emitter.label("__rt_mb_strimwidth_next_ascii"); + emitter.instruction("uxtw x7, w9"); // the ASCII byte is the code point + emitter.instruction("add x5, x5, #1"); // consume the one-byte character + emitter.instruction("ret"); // return the ASCII character + emitter.label("__rt_mb_strimwidth_next_invalid"); + emitter.instruction("mov x7, #0xffffffff"); // malformed bytes have no Unicode scalar + emitter.instruction("add x5, x5, #1"); // substitute one malformed byte + emitter.instruction("ret"); // return the substitution character + emitter.label("__rt_mb_strimwidth_next_two"); + emitter.instruction("add x10, x5, #2"); // two-byte sequences need one continuation + emitter.instruction("cmp x10, x6"); // is the sequence truncated? + emitter.instruction("b.hi __rt_mb_strimwidth_next_trunc"); // group the truncated prefix as one character + emitter.instruction("ldrb w10, [x5, #1]"); // load the continuation byte + emitter.instruction("and w11, w10, #0xc0"); // isolate the continuation-byte prefix + emitter.instruction("cmp w11, #0x80"); // does the second byte have the required 10xxxxxx shape? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed continuation leaves the leader substituted + emitter.instruction("and w9, w9, #0x1f"); // keep the two-byte payload bits + emitter.instruction("lsl w9, w9, #6"); // shift the leader payload into place + emitter.instruction("and w10, w10, #0x3f"); // keep the continuation payload bits + emitter.instruction("orr w7, w9, w10"); // assemble the two-byte code point + emitter.instruction("add x5, x5, #2"); // consume the complete two-byte character + emitter.instruction("ret"); // return the two-byte character + emitter.label("__rt_mb_strimwidth_next_three"); + emitter.instruction("add x10, x5, #3"); // three-byte sequences need two continuations + emitter.instruction("cmp x10, x6"); // is the sequence truncated? + emitter.instruction("b.hi __rt_mb_strimwidth_next_trunc"); // group the truncated prefix as one character + emitter.instruction("ldrb w10, [x5, #1]"); // load the first continuation byte + emitter.instruction("ldrb w11, [x5, #2]"); // load the second continuation byte + emitter.instruction("and w12, w10, #0xc0"); // isolate the first continuation prefix + emitter.instruction("cmp w12, #0x80"); // is the first continuation structurally valid? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed continuation substitutes only the leader + emitter.instruction("and w12, w11, #0xc0"); // isolate the second continuation prefix + emitter.instruction("cmp w12, #0x80"); // is the second continuation structurally valid? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed final byte substitutes only the leader + emitter.instruction("and w9, w9, #0x0f"); // keep the three-byte leader payload + emitter.instruction("lsl w9, w9, #12"); // shift the leader payload into place + emitter.instruction("and w10, w10, #0x3f"); // keep the first continuation payload + emitter.instruction("lsl w10, w10, #6"); // shift the first continuation into place + emitter.instruction("and w11, w11, #0x3f"); // keep the second continuation payload + emitter.instruction("orr w9, w9, w10"); // merge leader and first continuation + emitter.instruction("orr w7, w9, w11"); // assemble the three-byte code point + emitter.instruction("add x5, x5, #3"); // consume the complete three-byte character + emitter.instruction("ret"); // return the three-byte character + emitter.label("__rt_mb_strimwidth_next_four"); + emitter.instruction("add x10, x5, #4"); // four-byte sequences need three continuations + emitter.instruction("cmp x10, x6"); // is the sequence truncated? + emitter.instruction("b.hi __rt_mb_strimwidth_next_trunc"); // group the truncated prefix as one character + emitter.instruction("ldrb w10, [x5, #1]"); // load the first continuation byte + emitter.instruction("ldrb w11, [x5, #2]"); // load the second continuation byte + emitter.instruction("ldrb w12, [x5, #3]"); // load the third continuation byte + emitter.instruction("and w13, w10, #0xc0"); // isolate the first continuation prefix + emitter.instruction("cmp w13, #0x80"); // is the first continuation structurally valid? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed continuation substitutes only the leader + emitter.instruction("and w13, w11, #0xc0"); // isolate the second continuation prefix + emitter.instruction("cmp w13, #0x80"); // is the second continuation structurally valid? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed middle byte substitutes only the leader + emitter.instruction("and w13, w12, #0xc0"); // isolate the third continuation prefix + emitter.instruction("cmp w13, #0x80"); // is the third continuation structurally valid? + emitter.instruction("b.ne __rt_mb_strimwidth_next_invalid"); // malformed final byte substitutes only the leader + emitter.instruction("and w9, w9, #0x07"); // keep the four-byte leader payload + emitter.instruction("lsl w9, w9, #18"); // shift the leader payload into place + emitter.instruction("and w10, w10, #0x3f"); // keep the first continuation payload + emitter.instruction("lsl w10, w10, #12"); // shift the first continuation into place + emitter.instruction("and w11, w11, #0x3f"); // keep the second continuation payload + emitter.instruction("lsl w11, w11, #6"); // shift the second continuation into place + emitter.instruction("and w12, w12, #0x3f"); // keep the third continuation payload + emitter.instruction("orr w9, w9, w10"); // merge leader and first continuation + emitter.instruction("orr w9, w9, w11"); // merge the second continuation + emitter.instruction("orr w7, w9, w12"); // assemble the four-byte code point + emitter.instruction("add x5, x5, #4"); // consume the complete four-byte character + emitter.instruction("ret"); // return the four-byte character + emitter.label("__rt_mb_strimwidth_next_trunc"); + emitter.instruction("mov x7, #0xffffffff"); // a truncated prefix is one substitution character + emitter.instruction("mov x5, x6"); // consume the remaining suffix bytes + emitter.instruction("ret"); // return the truncated-prefix character + + // char_width(x0=codepoint) -> x0=1 or 2 + emitter.label_shared("__rt_mb_strimwidth_char_width"); + abi::emit_load_int_immediate(emitter, "x1", 0x1100); + emitter.instruction("cmp x0, x1"); // code points below U+1100 are never fullwidth + emitter.instruction("b.lo __rt_mb_strimwidth_width_one"); // return width 1 for the fast path + abi::emit_symbol_address(emitter, "x2", "_mb_eaw_table"); + abi::emit_symbol_address(emitter, "x3", "_mb_eaw_table_count"); + emitter.instruction("ldr x3, [x3]"); // load the number of inclusive width-2 ranges + emitter.instruction("mov x4, #0"); // binary-search low index starts at zero + emitter.label("__rt_mb_strimwidth_width_search"); + emitter.instruction("cmp x4, x3"); // has the search range emptied? + emitter.instruction("b.hs __rt_mb_strimwidth_width_one"); // no range contained the code point + emitter.instruction("add x5, x4, x3"); // probe = (lo + hi) + emitter.instruction("lsr x5, x5, #1"); // probe = (lo + hi) / 2 + emitter.instruction("add x6, x2, x5, lsl #3"); // address the probe range (two 32-bit words) + emitter.instruction("ldr w7, [x6]"); // load the inclusive range begin + emitter.instruction("ldr w8, [x6, #4]"); // load the inclusive range end + emitter.instruction("cmp w0, w7"); // is the code point before this range? + emitter.instruction("b.lo __rt_mb_strimwidth_width_left"); // search the lower half + emitter.instruction("cmp w0, w8"); // is the code point after this range? + emitter.instruction("b.hi __rt_mb_strimwidth_width_right"); // search the upper half + emitter.instruction("mov x0, #2"); // a containing range means display width 2 + emitter.instruction("ret"); // return the fullwidth result + emitter.label("__rt_mb_strimwidth_width_left"); + emitter.instruction("mov x3, x5"); // hi = probe + emitter.instruction("b __rt_mb_strimwidth_width_search"); // continue the binary search + emitter.label("__rt_mb_strimwidth_width_right"); + emitter.instruction("add x4, x5, #1"); // lo = probe + 1 + emitter.instruction("b __rt_mb_strimwidth_width_search"); // continue the binary search + emitter.label("__rt_mb_strimwidth_width_one"); + emitter.instruction("mov x0, #1"); // every other code point has display width 1 + emitter.instruction("ret"); // return the halfwidth result +} + +/// Emits the Linux x86_64 implementation. +fn emit_mb_strimwidth_x86_64(emitter: &mut Emitter) { + emitter.blank(); + emitter.comment("--- runtime: mb_strimwidth (display-width trim) ---"); + emitter.label_global("__rt_mb_strimwidth"); + emitter.instruction("push rbp"); // preserve the caller frame pointer + emitter.instruction("mov rbp, rsp"); // establish a stable frame base for spills + emitter.instruction("push rbx"); // preserve callee-saved rbx + emitter.instruction("push r12"); // preserve callee-saved r12 + emitter.instruction("push r13"); // preserve callee-saved r13 + emitter.instruction("push r14"); // preserve callee-saved r14 + emitter.instruction("push r15"); // preserve callee-saved r15 + emitter.instruction("sub rsp, 200"); // reserve spills, encoding name, and 8-byte SysV call padding + emitter.instruction("mov QWORD PTR [rbp - 48], rax"); // spill the source string pointer + emitter.instruction("mov QWORD PTR [rbp - 56], rdx"); // spill the source string length + emitter.instruction("mov QWORD PTR [rbp - 64], rcx"); // spill the signed start offset + emitter.instruction("mov QWORD PTR [rbp - 72], r8"); // spill the signed display-width budget + emitter.instruction("mov QWORD PTR [rbp - 80], r9"); // spill the trim-marker pointer + emitter.instruction("mov QWORD PTR [rbp - 88], r10"); // spill the trim-marker length + emitter.instruction("mov QWORD PTR [rbp - 96], r11"); // spill the optional encoding pointer + emitter.instruction("mov QWORD PTR [rbp - 104], rdi"); // spill the optional encoding length + emitter.instruction("mov QWORD PTR [rbp - 112], 0"); // default to the UTF-8 display-width scanner + emitter.instruction("test r11, r11"); // omitted/null encoding is a null pointer + emitter.instruction("jz __rt_mb_strimwidth_count_x86"); // use UTF-8 when encoding is omitted/null + emitter.instruction(&format!("cmp rdi, {}", MAX_ENCODING_NAME_LEN)); // does the encoding name fit the stack C-string buffer? + emitter.instruction("ja __rt_mb_strimwidth_unknown_encoding_x86"); // reject names longer than every supported alias + emitter.instruction("lea rsi, [rbp - 208]"); // destination is the 64-byte encoding-name buffer + emitter.instruction("xor rcx, rcx"); // copied-byte index starts at zero + emitter.label("__rt_mb_strimwidth_encoding_copy_x86"); + emitter.instruction("cmp rcx, rdi"); // copied the whole explicit encoding name? + emitter.instruction("jae __rt_mb_strimwidth_encoding_copied_x86"); // terminate the C string once every byte is copied + emitter.instruction("mov r10b, BYTE PTR [r11 + rcx]"); // load one encoding-name byte from the PHP string + emitter.instruction("mov BYTE PTR [rsi + rcx], r10b"); // append the byte to the stack C string + emitter.instruction("inc rcx"); // advance the encoding-name byte index + emitter.instruction("jmp __rt_mb_strimwidth_encoding_copy_x86"); // continue copying the remaining encoding-name bytes + emitter.label("__rt_mb_strimwidth_encoding_copied_x86"); + emitter.instruction("mov BYTE PTR [rsi + rdi], 0"); // NUL-terminate the explicit encoding name + emitter.instruction("lea rdi, [rbp - 208]"); // first strcasecmp argument is the copied encoding name + abi::emit_symbol_address(emitter, "rsi", "_mb_strlen_utf8_name"); + emitter.instruction("call strcasecmp"); // compare the explicit encoding with UTF-8 + emitter.instruction("test eax, eax"); // did the encoding match UTF-8? + emitter.instruction("jz __rt_mb_strimwidth_count_x86"); // UTF-8 uses the display-width scanner + emitter.instruction("lea rdi, [rbp - 208]"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "rsi", "_mb_strlen_utf8_alias"); + emitter.instruction("call strcasecmp"); // compare the explicit encoding with UTF8 + emitter.instruction("test eax, eax"); // did the encoding match UTF8? + emitter.instruction("jz __rt_mb_strimwidth_count_x86"); // the UTF8 alias uses the same scanner + emitter.instruction("lea rdi, [rbp - 208]"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "rsi", "_mb_strlen_8bit_name"); + emitter.instruction("call strcasecmp"); // compare the explicit encoding with 8bit + emitter.instruction("test eax, eax"); // did the encoding match 8bit? + emitter.instruction("jz __rt_mb_strimwidth_byte_mode_x86"); // 8bit treats every byte as width 1 + emitter.instruction("lea rdi, [rbp - 208]"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "rsi", "_mb_strlen_binary_name"); + emitter.instruction("call strcasecmp"); // compare the explicit encoding with binary + emitter.instruction("test eax, eax"); // did the encoding match binary? + emitter.instruction("jz __rt_mb_strimwidth_byte_mode_x86"); // binary is PHP's alias for 8bit + emitter.instruction("lea rdi, [rbp - 208]"); // reload the copied encoding name + abi::emit_symbol_address(emitter, "rsi", "_mb_strlen_7bit_name"); + emitter.instruction("call strcasecmp"); // compare the explicit encoding with 7bit + emitter.instruction("test eax, eax"); // did the encoding match 7bit? + emitter.instruction("jz __rt_mb_strimwidth_byte_mode_x86"); // 7bit preserves one-character-per-byte width + emitter.instruction("jmp __rt_mb_strimwidth_unknown_encoding_x86"); // any other name is an unknown encoding + + emitter.label("__rt_mb_strimwidth_byte_mode_x86"); + emitter.instruction("mov QWORD PTR [rbp - 112], 1"); // mark the scanner as byte-width mode + + emitter.label("__rt_mb_strimwidth_count_x86"); + emitter.instruction("mov rdi, QWORD PTR [rbp - 48]"); // count characters in the whole source + emitter.instruction("mov rsi, QWORD PTR [rbp - 56]"); // pass the source length + emitter.instruction("mov rdx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_count_chars_x86"); // rax = character count + emitter.instruction("mov r12, rax"); // r12 = character count + emitter.instruction("mov rax, QWORD PTR [rbp - 64]"); // load the signed start offset + emitter.instruction("test rax, rax"); // is start zero? + emitter.instruction("jz __rt_mb_strimwidth_start_ok_x86"); // start 0 never needs a range check + emitter.instruction("cmp rax, 0"); // is start negative? + emitter.instruction("jge __rt_mb_strimwidth_start_nonneg_x86"); // a non-negative start is already an origin offset + emitter.instruction("add rax, r12"); // negative start counts from the end + emitter.label("__rt_mb_strimwidth_start_nonneg_x86"); + emitter.instruction("cmp rax, 0"); // did a negative start underflow the string? + emitter.instruction("jl __rt_mb_strimwidth_start_error_x86"); // start below zero is out of range + emitter.instruction("cmp rax, r12"); // is start past the last character? + emitter.instruction("jg __rt_mb_strimwidth_start_error_x86"); // start > char_count is out of range + emitter.instruction("mov QWORD PTR [rbp - 64], rax"); // persist the resolved non-negative start + emitter.label("__rt_mb_strimwidth_start_ok_x86"); + emitter.instruction("cmp QWORD PTR [rbp - 72], 0"); // is width negative? + emitter.instruction("jge __rt_mb_strimwidth_width_ok_x86"); // a non-negative width is already a remaining budget + emitter.instruction("mov rdi, QWORD PTR [rbp - 48]"); // measure the whole string's display width + emitter.instruction("mov rsi, QWORD PTR [rbp - 56]"); // pass the source length + emitter.instruction("mov rdx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_strwidth_x86"); // rax = total display width + emitter.instruction("add QWORD PTR [rbp - 72], rax"); // PHP adds the whole-string width first + emitter.instruction("cmp QWORD PTR [rbp - 64], 0"); // is start greater than zero? + emitter.instruction("jle __rt_mb_strimwidth_neg_width_checked_x86"); // start 0 has no prefix width to subtract + emitter.instruction("mov rdi, QWORD PTR [rbp - 48]"); // skip the prefix so we can measure it + emitter.instruction("mov rsi, QWORD PTR [rbp - 56]"); // pass the source length + emitter.instruction("mov rdx, QWORD PTR [rbp - 64]"); // skip this many characters + emitter.instruction("mov rcx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_skip_x86"); // rax = prefix byte length + emitter.instruction("mov rdi, QWORD PTR [rbp - 48]"); // prefix starts at the source pointer + emitter.instruction("mov rsi, rax"); // measure only the skipped prefix + emitter.instruction("mov rdx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_strwidth_x86"); // rax = prefix display width + emitter.instruction("sub QWORD PTR [rbp - 72], rax"); // subtract the skipped prefix width + emitter.label("__rt_mb_strimwidth_neg_width_checked_x86"); + emitter.instruction("cmp QWORD PTR [rbp - 72], 0"); // did the adjusted width underflow? + emitter.instruction("jl __rt_mb_strimwidth_width_error_x86"); // a still-negative width is out of range + emitter.label("__rt_mb_strimwidth_width_ok_x86"); + + emitter.instruction("mov rdi, QWORD PTR [rbp - 48]"); // skip `start` characters to find the kept suffix + emitter.instruction("mov rsi, QWORD PTR [rbp - 56]"); // pass the source length + emitter.instruction("mov rdx, QWORD PTR [rbp - 64]"); // pass the resolved start + emitter.instruction("mov rcx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_skip_x86"); // rax = start byte offset + emitter.instruction("mov QWORD PTR [rbp - 120], rax"); // persist the suffix origin + emitter.instruction("mov rbx, QWORD PTR [rbp - 48]"); // reload the source pointer + emitter.instruction("add rbx, rax"); // rbx = suffix pointer + emitter.instruction("mov r12, QWORD PTR [rbp - 56]"); // reload the source length + emitter.instruction("sub r12, rax"); // r12 = suffix byte length + emitter.instruction("mov rdi, rbx"); // measure the suffix display width + emitter.instruction("mov rsi, r12"); // pass the suffix length + emitter.instruction("mov rdx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_strwidth_x86"); // rax = suffix display width + emitter.instruction("cmp rax, QWORD PTR [rbp - 72]"); // does the suffix already fit? + emitter.instruction("jbe __rt_mb_strimwidth_copy_suffix_x86"); // yes → return the suffix unchanged + emitter.instruction("mov rdi, QWORD PTR [rbp - 80]"); // measure the trim marker's display width + emitter.instruction("mov rsi, QWORD PTR [rbp - 88]"); // pass the marker length + emitter.instruction("mov rdx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_strwidth_x86"); // rax = marker display width + emitter.instruction("cmp QWORD PTR [rbp - 72], rax"); // is the budget no larger than the marker? + emitter.instruction("jbe __rt_mb_strimwidth_copy_marker_x86"); // yes → PHP returns the marker alone + emitter.instruction("mov rdx, QWORD PTR [rbp - 72]"); // remaining budget starts as the requested width + emitter.instruction("sub rdx, rax"); // subtract the marker width + emitter.instruction("mov rdi, rbx"); // take a prefix of the suffix + emitter.instruction("mov rsi, r12"); // pass the suffix length + emitter.instruction("mov rcx, QWORD PTR [rbp - 112]"); // pass the width mode + emitter.instruction("call __rt_mb_strimwidth_take_x86"); // rax = kept source byte count + emitter.instruction("mov r13, rax"); // r13 = kept source bytes + emitter.instruction("mov rax, r13"); // reserve source prefix plus marker + emitter.instruction("add rax, QWORD PTR [rbp - 88]"); // add the marker length + emitter.instruction("call __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("mov QWORD PTR [rbp - 128], rax"); // persist the result start pointer + emitter.instruction("mov rdi, rax"); // memcpy destination is the reservation + emitter.instruction("mov rsi, rbx"); // memcpy source is the suffix + emitter.instruction("mov rdx, r13"); // memcpy length is the kept prefix + emitter.instruction("call memmove"); // copy the kept source prefix into the reservation + emitter.instruction("mov rdi, QWORD PTR [rbp - 128]"); // reload the result start + emitter.instruction("add rdi, r13"); // destination for the trim marker + emitter.instruction("mov rsi, QWORD PTR [rbp - 80]"); // memcpy source is the marker + emitter.instruction("mov rdx, QWORD PTR [rbp - 88]"); // memcpy length is the marker length + emitter.instruction("call memmove"); // append the trim marker + emitter.instruction("mov rax, QWORD PTR [rbp - 128]"); // result pointer is the reservation start + emitter.instruction("mov rdx, r13"); // start with the kept source bytes + emitter.instruction("add rdx, QWORD PTR [rbp - 88]"); // add the marker length + emitter.instruction("call __rt_concat_publish"); // publish the concat-scratch write offset + emitter.instruction("jmp __rt_mb_strimwidth_return_x86"); // restore callee-saved regs and return + + emitter.label("__rt_mb_strimwidth_copy_suffix_x86"); + emitter.instruction("mov rax, r12"); // reserve exactly the suffix bytes + emitter.instruction("call __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("mov QWORD PTR [rbp - 128], rax"); // persist the result start pointer + emitter.instruction("mov rdi, rax"); // memcpy destination is the reservation + emitter.instruction("mov rsi, rbx"); // memcpy source is the suffix + emitter.instruction("mov rdx, r12"); // memcpy length is the suffix length + emitter.instruction("call memmove"); // copy the untrimmed suffix + emitter.instruction("mov rax, QWORD PTR [rbp - 128]"); // result pointer is the reservation start + emitter.instruction("mov rdx, r12"); // result length is the suffix length + emitter.instruction("call __rt_concat_publish"); // publish the concat-scratch write offset + emitter.instruction("jmp __rt_mb_strimwidth_return_x86"); // restore callee-saved regs and return + + emitter.label("__rt_mb_strimwidth_copy_marker_x86"); + emitter.instruction("mov rax, QWORD PTR [rbp - 88]"); // reserve exactly the marker bytes + emitter.instruction("call __rt_concat_reserve"); // reserve scratch or heap storage + emitter.instruction("mov QWORD PTR [rbp - 128], rax"); // persist the result start pointer + emitter.instruction("mov rdi, rax"); // memcpy destination is the reservation + emitter.instruction("mov rsi, QWORD PTR [rbp - 80]"); // memcpy source is the marker + emitter.instruction("mov rdx, QWORD PTR [rbp - 88]"); // memcpy length is the marker length + emitter.instruction("call memmove"); // copy the marker alone + emitter.instruction("mov rax, QWORD PTR [rbp - 128]"); // result pointer is the reservation start + emitter.instruction("mov rdx, QWORD PTR [rbp - 88]"); // result length is the marker length + emitter.instruction("call __rt_concat_publish"); // publish the concat-scratch write offset + + emitter.label("__rt_mb_strimwidth_return_x86"); + emitter.instruction("add rsp, 200"); // release the argument-spill area + emitter.instruction("pop r15"); // restore callee-saved r15 + emitter.instruction("pop r14"); // restore callee-saved r14 + emitter.instruction("pop r13"); // restore callee-saved r13 + emitter.instruction("pop r12"); // restore callee-saved r12 + emitter.instruction("pop rbx"); // restore callee-saved rbx + emitter.instruction("pop rbp"); // restore the caller frame pointer + emitter.instruction("ret"); // return the trimmed string pointer/length + + emitter.label("__rt_mb_strimwidth_unknown_encoding_x86"); + emitter.instruction("add rsp, 200"); // release the helper frame before throwing + emitter.instruction("pop r15"); // restore callee-saved r15 + emitter.instruction("pop r14"); // restore callee-saved r14 + emitter.instruction("pop r13"); // restore callee-saved r13 + emitter.instruction("pop r12"); // restore callee-saved r12 + emitter.instruction("pop rbx"); // restore callee-saved rbx + emitter.instruction("pop rbp"); // restore the caller frame pointer + value_error::emit_throw_value_error_x86_64( + emitter, + "_mb_strimwidth_unknown_encoding_msg", + MB_STRIMWIDTH_UNKNOWN_ENCODING_MSG.len(), + ); + + emitter.label("__rt_mb_strimwidth_start_error_x86"); + emitter.instruction("add rsp, 200"); // release the helper frame before throwing + emitter.instruction("pop r15"); // restore callee-saved r15 + emitter.instruction("pop r14"); // restore callee-saved r14 + emitter.instruction("pop r13"); // restore callee-saved r13 + emitter.instruction("pop r12"); // restore callee-saved r12 + emitter.instruction("pop rbx"); // restore callee-saved rbx + emitter.instruction("pop rbp"); // restore the caller frame pointer + value_error::emit_throw_value_error_x86_64( + emitter, + "_mb_strimwidth_start_range_msg", + MB_STRIMWIDTH_START_RANGE_MSG.len(), + ); + + emitter.label("__rt_mb_strimwidth_width_error_x86"); + emitter.instruction("add rsp, 200"); // release the helper frame before throwing + emitter.instruction("pop r15"); // restore callee-saved r15 + emitter.instruction("pop r14"); // restore callee-saved r14 + emitter.instruction("pop r13"); // restore callee-saved r13 + emitter.instruction("pop r12"); // restore callee-saved r12 + emitter.instruction("pop rbx"); // restore callee-saved rbx + emitter.instruction("pop rbp"); // restore the caller frame pointer + value_error::emit_throw_value_error_x86_64( + emitter, + "_mb_strimwidth_width_range_msg", + MB_STRIMWIDTH_WIDTH_RANGE_MSG.len(), + ); + + emit_mb_strimwidth_walkers_x86_64(emitter); +} + +/// Emits x86_64 character-walk helpers shared by the trim helper. +fn emit_mb_strimwidth_walkers_x86_64(emitter: &mut Emitter) { + emitter.label_shared("__rt_mb_strimwidth_count_chars_x86"); + emitter.instruction("test rdx, rdx"); // byte mode returns the raw length + emitter.instruction("jnz __rt_mb_strimwidth_count_bytes_x86"); // return rsi unchanged as the count + emitter.instruction("push rbx"); // preserve callee-saved rbx + emitter.instruction("push r12"); // preserve the scan pointer owner + emitter.instruction("push r13"); // preserve the end pointer owner + emitter.instruction("mov r12, rdi"); // r12 = scan pointer + emitter.instruction("lea r13, [rdi + rsi]"); // r13 = one-past-end pointer + emitter.instruction("xor rbx, rbx"); // character count starts at zero + emitter.label("__rt_mb_strimwidth_count_loop_x86"); + emitter.instruction("cmp r12, r13"); // scanned every byte? + emitter.instruction("jae __rt_mb_strimwidth_count_done_x86"); // return the accumulated count + emitter.instruction("call __rt_mb_strimwidth_next_x86"); // r12 advances past one character + emitter.instruction("inc rbx"); // count one more character + emitter.instruction("jmp __rt_mb_strimwidth_count_loop_x86"); // keep scanning + emitter.label("__rt_mb_strimwidth_count_done_x86"); + emitter.instruction("mov rax, rbx"); // return the character count + emitter.instruction("pop r13"); // restore r13 + emitter.instruction("pop r12"); // restore r12 + emitter.instruction("pop rbx"); // restore rbx + emitter.instruction("ret"); // return the character count + emitter.label("__rt_mb_strimwidth_count_bytes_x86"); + emitter.instruction("mov rax, rsi"); // byte encodings count one character per byte + emitter.instruction("ret"); // return the byte length + + emitter.label_shared("__rt_mb_strimwidth_strwidth_x86"); + emitter.instruction("test rdx, rdx"); // byte mode returns the raw length + emitter.instruction("jnz __rt_mb_strimwidth_strwidth_bytes_x86"); // return rsi unchanged as the width + emitter.instruction("push rbx"); // preserve callee-saved rbx + emitter.instruction("push r12"); // preserve the scan pointer owner + emitter.instruction("push r13"); // preserve the end pointer owner + emitter.instruction("mov r12, rdi"); // r12 = scan pointer + emitter.instruction("lea r13, [rdi + rsi]"); // r13 = one-past-end pointer + emitter.instruction("xor rbx, rbx"); // display width starts at zero + emitter.label("__rt_mb_strimwidth_strwidth_loop_x86"); + emitter.instruction("cmp r12, r13"); // scanned every byte? + emitter.instruction("jae __rt_mb_strimwidth_strwidth_done_x86"); // return the accumulated width + emitter.instruction("call __rt_mb_strimwidth_next_x86"); // eax = codepoint, r12 advances + emitter.instruction("call __rt_mb_strimwidth_char_width_x86"); // ecx = 1 or 2 + emitter.instruction("add rbx, rcx"); // accumulate the character width + emitter.instruction("jmp __rt_mb_strimwidth_strwidth_loop_x86"); // keep scanning + emitter.label("__rt_mb_strimwidth_strwidth_done_x86"); + emitter.instruction("mov rax, rbx"); // return the display width + emitter.instruction("pop r13"); // restore r13 + emitter.instruction("pop r12"); // restore r12 + emitter.instruction("pop rbx"); // restore rbx + emitter.instruction("ret"); // return the display width + emitter.label("__rt_mb_strimwidth_strwidth_bytes_x86"); + emitter.instruction("mov rax, rsi"); // byte encodings have width equal to length + emitter.instruction("ret"); // return the byte length + + emitter.label_shared("__rt_mb_strimwidth_skip_x86"); + emitter.instruction("test rcx, rcx"); // byte mode skips `rdx` bytes + emitter.instruction("jnz __rt_mb_strimwidth_skip_bytes_x86"); // clamp the byte skip to the string length + emitter.instruction("push rbx"); // preserve callee-saved rbx + emitter.instruction("push r12"); // preserve the scan pointer owner + emitter.instruction("push r13"); // preserve the end pointer owner + emitter.instruction("sub rsp, 8"); // keep the in-loop push+call on a 16-byte boundary + emitter.instruction("mov r12, rdi"); // r12 = scan pointer + emitter.instruction("lea r13, [rdi + rsi]"); // r13 = one-past-end pointer + emitter.instruction("mov rbx, rdi"); // remember the origin for the returned offset + emitter.instruction("mov r8, rdx"); // remaining characters to skip + emitter.label("__rt_mb_strimwidth_skip_loop_x86"); + emitter.instruction("test r8, r8"); // finished skipping the requested count? + emitter.instruction("jz __rt_mb_strimwidth_skip_done_x86"); // return the consumed byte count + emitter.instruction("cmp r12, r13"); // reached the end of the string? + emitter.instruction("jae __rt_mb_strimwidth_skip_done_x86"); // cannot skip past the last character + emitter.instruction("push r8"); // preserve the remaining skip count + emitter.instruction("call __rt_mb_strimwidth_next_x86"); // advance one character + emitter.instruction("pop r8"); // restore the remaining skip count + emitter.instruction("dec r8"); // one fewer character remains + emitter.instruction("jmp __rt_mb_strimwidth_skip_loop_x86"); // keep skipping + emitter.label("__rt_mb_strimwidth_skip_done_x86"); + emitter.instruction("mov rax, r12"); // current pointer minus origin + emitter.instruction("sub rax, rbx"); // return the consumed byte count + emitter.instruction("add rsp, 8"); // release the SysV call-alignment pad + emitter.instruction("pop r13"); // restore r13 + emitter.instruction("pop r12"); // restore r12 + emitter.instruction("pop rbx"); // restore rbx + emitter.instruction("ret"); // return the skip offset + emitter.label("__rt_mb_strimwidth_skip_bytes_x86"); + emitter.instruction("cmp rdx, rsi"); // would the skip pass the last byte? + emitter.instruction("cmovb rax, rdx"); // use the requested count when it fits + emitter.instruction("cmovae rax, rsi"); // otherwise clamp to the string length + emitter.instruction("ret"); // return the clamped byte offset + + emitter.label_shared("__rt_mb_strimwidth_take_x86"); + emitter.instruction("test rcx, rcx"); // byte mode takes `rdx` bytes + emitter.instruction("jnz __rt_mb_strimwidth_take_bytes_x86"); // clamp the byte take to the string length + emitter.instruction("push rbx"); // preserve callee-saved rbx + emitter.instruction("push r12"); // preserve the scan pointer owner + emitter.instruction("push r13"); // preserve the end pointer owner + emitter.instruction("mov r12, rdi"); // r12 = scan pointer + emitter.instruction("lea r13, [rdi + rsi]"); // r13 = one-past-end pointer + emitter.instruction("mov rbx, rdi"); // remember the origin for the returned length + emitter.instruction("mov r8, rdx"); // remaining display-width budget + emitter.label("__rt_mb_strimwidth_take_loop_x86"); + emitter.instruction("cmp r12, r13"); // reached the end of the string? + emitter.instruction("jae __rt_mb_strimwidth_take_done_x86"); // take the whole suffix + emitter.instruction("push r8"); // preserve the remaining budget + emitter.instruction("push r12"); // remember the pointer before this character + emitter.instruction("call __rt_mb_strimwidth_next_x86"); // eax = codepoint, r12 tentatively advanced + emitter.instruction("call __rt_mb_strimwidth_char_width_x86"); // ecx = 1 or 2 + emitter.instruction("pop r9"); // r9 = pointer before this character + emitter.instruction("pop r8"); // restore the remaining budget + emitter.instruction("cmp r8, rcx"); // does the character still fit the budget? + emitter.instruction("jb __rt_mb_strimwidth_take_reject_x86"); // no → leave this character out + emitter.instruction("sub r8, rcx"); // consume the character's display width + emitter.instruction("jmp __rt_mb_strimwidth_take_loop_x86"); // keep taking + emitter.label("__rt_mb_strimwidth_take_reject_x86"); + emitter.instruction("mov r12, r9"); // rewind to exclude the overflowing character + emitter.label("__rt_mb_strimwidth_take_done_x86"); + emitter.instruction("mov rax, r12"); // current pointer minus origin + emitter.instruction("sub rax, rbx"); // return the kept byte count + emitter.instruction("pop r13"); // restore r13 + emitter.instruction("pop r12"); // restore r12 + emitter.instruction("pop rbx"); // restore rbx + emitter.instruction("ret"); // return the taken prefix length + emitter.label("__rt_mb_strimwidth_take_bytes_x86"); + emitter.instruction("cmp rdx, rsi"); // would the take pass the last byte? + emitter.instruction("cmovb rax, rdx"); // use the requested count when it fits + emitter.instruction("cmovae rax, rsi"); // otherwise clamp to the string length + emitter.instruction("ret"); // return the clamped byte count + + emitter.label_shared("__rt_mb_strimwidth_next_x86"); + emitter.instruction("movzx eax, BYTE PTR [r12]"); // load the next possible UTF-8 leading byte + emitter.instruction("cmp al, 0x80"); // ASCII bytes are complete one-byte characters + emitter.instruction("jb __rt_mb_strimwidth_next_ascii_x86"); // consume one ASCII byte + emitter.instruction("cmp al, 0xc2"); // C0/C1 and continuation bytes are malformed leaders + emitter.instruction("jb __rt_mb_strimwidth_next_invalid_x86"); // substitute one malformed byte + emitter.instruction("cmp al, 0xe0"); // C2-DF introduce two-byte sequences + emitter.instruction("jb __rt_mb_strimwidth_next_two_x86"); // validate a two-byte character + emitter.instruction("cmp al, 0xf0"); // E0-EF introduce three-byte sequences + emitter.instruction("jb __rt_mb_strimwidth_next_three_x86"); // validate a three-byte character + emitter.instruction("cmp al, 0xf5"); // F0-F4 introduce Unicode-range four-byte sequences + emitter.instruction("jb __rt_mb_strimwidth_next_four_x86"); // validate a four-byte character + emitter.instruction("jmp __rt_mb_strimwidth_next_invalid_x86"); // F5-FF cannot begin valid UTF-8 + emitter.label("__rt_mb_strimwidth_next_ascii_x86"); + emitter.instruction("inc r12"); // consume the one-byte character + emitter.instruction("ret"); // eax already holds the ASCII code point + emitter.label("__rt_mb_strimwidth_next_invalid_x86"); + emitter.instruction("mov eax, 0xffffffff"); // malformed bytes have no Unicode scalar + emitter.instruction("inc r12"); // substitute one malformed byte + emitter.instruction("ret"); // return the substitution character + emitter.label("__rt_mb_strimwidth_next_two_x86"); + emitter.instruction("lea r10, [r12 + 2]"); // two-byte sequences need one continuation + emitter.instruction("cmp r10, r13"); // is the sequence truncated? + emitter.instruction("ja __rt_mb_strimwidth_next_trunc_x86"); // group the truncated prefix as one character + emitter.instruction("movzx r11d, BYTE PTR [r12 + 1]"); // load the continuation byte + emitter.instruction("mov r10d, r11d"); // copy the continuation for the prefix check + emitter.instruction("and r10d, 0xc0"); // isolate the continuation-byte prefix + emitter.instruction("cmp r10d, 0x80"); // does the second byte have the required 10xxxxxx shape? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed continuation leaves the leader substituted + emitter.instruction("and eax, 0x1f"); // keep the two-byte payload bits + emitter.instruction("shl eax, 6"); // shift the leader payload into place + emitter.instruction("and r11d, 0x3f"); // keep the continuation payload bits + emitter.instruction("or eax, r11d"); // assemble the two-byte code point + emitter.instruction("add r12, 2"); // consume the complete two-byte character + emitter.instruction("ret"); // return the two-byte character + emitter.label("__rt_mb_strimwidth_next_three_x86"); + emitter.instruction("lea r10, [r12 + 3]"); // three-byte sequences need two continuations + emitter.instruction("cmp r10, r13"); // is the sequence truncated? + emitter.instruction("ja __rt_mb_strimwidth_next_trunc_x86"); // group the truncated prefix as one character + emitter.instruction("movzx r10d, BYTE PTR [r12 + 1]"); // load the first continuation byte + emitter.instruction("movzx r11d, BYTE PTR [r12 + 2]"); // load the second continuation byte + emitter.instruction("mov r9d, r10d"); // copy the first continuation for the prefix check + emitter.instruction("and r9d, 0xc0"); // isolate the first continuation prefix + emitter.instruction("cmp r9d, 0x80"); // is the first continuation structurally valid? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed continuation substitutes only the leader + emitter.instruction("mov r9d, r11d"); // copy the second continuation for the prefix check + emitter.instruction("and r9d, 0xc0"); // isolate the second continuation prefix + emitter.instruction("cmp r9d, 0x80"); // is the second continuation structurally valid? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed final byte substitutes only the leader + emitter.instruction("and eax, 0x0f"); // keep the three-byte leader payload + emitter.instruction("shl eax, 12"); // shift the leader payload into place + emitter.instruction("and r10d, 0x3f"); // keep the first continuation payload + emitter.instruction("shl r10d, 6"); // shift the first continuation into place + emitter.instruction("and r11d, 0x3f"); // keep the second continuation payload + emitter.instruction("or eax, r10d"); // merge leader and first continuation + emitter.instruction("or eax, r11d"); // assemble the three-byte code point + emitter.instruction("add r12, 3"); // consume the complete three-byte character + emitter.instruction("ret"); // return the three-byte character + emitter.label("__rt_mb_strimwidth_next_four_x86"); + emitter.instruction("lea r10, [r12 + 4]"); // four-byte sequences need three continuations + emitter.instruction("cmp r10, r13"); // is the sequence truncated? + emitter.instruction("ja __rt_mb_strimwidth_next_trunc_x86"); // group the truncated prefix as one character + emitter.instruction("movzx r10d, BYTE PTR [r12 + 1]"); // load the first continuation byte + emitter.instruction("movzx r11d, BYTE PTR [r12 + 2]"); // load the second continuation byte + emitter.instruction("movzx r9d, BYTE PTR [r12 + 3]"); // load the third continuation byte + emitter.instruction("mov r8d, r10d"); // copy the first continuation for the prefix check + emitter.instruction("and r8d, 0xc0"); // isolate the first continuation prefix + emitter.instruction("cmp r8d, 0x80"); // is the first continuation structurally valid? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed continuation substitutes only the leader + emitter.instruction("mov r8d, r11d"); // copy the second continuation for the prefix check + emitter.instruction("and r8d, 0xc0"); // isolate the second continuation prefix + emitter.instruction("cmp r8d, 0x80"); // is the second continuation structurally valid? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed middle byte substitutes only the leader + emitter.instruction("mov r8d, r9d"); // copy the third continuation for the prefix check + emitter.instruction("and r8d, 0xc0"); // isolate the third continuation prefix + emitter.instruction("cmp r8d, 0x80"); // is the third continuation structurally valid? + emitter.instruction("jne __rt_mb_strimwidth_next_invalid_x86"); // malformed final byte substitutes only the leader + emitter.instruction("and eax, 0x07"); // keep the four-byte leader payload + emitter.instruction("shl eax, 18"); // shift the leader payload into place + emitter.instruction("and r10d, 0x3f"); // keep the first continuation payload + emitter.instruction("shl r10d, 12"); // shift the first continuation into place + emitter.instruction("and r11d, 0x3f"); // keep the second continuation payload + emitter.instruction("shl r11d, 6"); // shift the second continuation into place + emitter.instruction("and r9d, 0x3f"); // keep the third continuation payload + emitter.instruction("or eax, r10d"); // merge leader and first continuation + emitter.instruction("or eax, r11d"); // merge the second continuation + emitter.instruction("or eax, r9d"); // assemble the four-byte code point + emitter.instruction("add r12, 4"); // consume the complete four-byte character + emitter.instruction("ret"); // return the four-byte character + emitter.label("__rt_mb_strimwidth_next_trunc_x86"); + emitter.instruction("mov eax, 0xffffffff"); // a truncated prefix is one substitution character + emitter.instruction("mov r12, r13"); // consume the remaining suffix bytes + emitter.instruction("ret"); // return the truncated-prefix character + + emitter.label_shared("__rt_mb_strimwidth_char_width_x86"); + emitter.instruction("cmp eax, 0x1100"); // code points below U+1100 are never fullwidth + emitter.instruction("jb __rt_mb_strimwidth_width_one_x86"); // return width 1 for the fast path + abi::emit_symbol_address(emitter, "r10", "_mb_eaw_table"); + abi::emit_symbol_address(emitter, "r11", "_mb_eaw_table_count"); + emitter.instruction("mov r11, QWORD PTR [r11]"); // load the number of inclusive width-2 ranges + emitter.instruction("xor r8, r8"); // binary-search low index starts at zero + emitter.label("__rt_mb_strimwidth_width_search_x86"); + emitter.instruction("cmp r8, r11"); // has the search range emptied? + emitter.instruction("jae __rt_mb_strimwidth_width_one_x86"); // no range contained the code point + emitter.instruction("mov r9, r8"); // probe = lo + emitter.instruction("add r9, r11"); // probe = lo + hi + emitter.instruction("shr r9, 1"); // probe = (lo + hi) / 2 + emitter.instruction("mov r14d, DWORD PTR [r10 + r9 * 8]"); // load the inclusive range begin + emitter.instruction("mov r15d, DWORD PTR [r10 + r9 * 8 + 4]"); // load the inclusive range end + emitter.instruction("cmp eax, r14d"); // is the code point before this range? + emitter.instruction("jb __rt_mb_strimwidth_width_left_x86"); // search the lower half + emitter.instruction("cmp eax, r15d"); // is the code point after this range? + emitter.instruction("ja __rt_mb_strimwidth_width_right_x86"); // search the upper half + emitter.instruction("mov ecx, 2"); // a containing range means display width 2 + emitter.instruction("ret"); // return the fullwidth result + emitter.label("__rt_mb_strimwidth_width_left_x86"); + emitter.instruction("mov r11, r9"); // hi = probe + emitter.instruction("jmp __rt_mb_strimwidth_width_search_x86"); // continue the binary search + emitter.label("__rt_mb_strimwidth_width_right_x86"); + emitter.instruction("lea r8, [r9 + 1]"); // lo = probe + 1 + emitter.instruction("jmp __rt_mb_strimwidth_width_search_x86"); // continue the binary search + emitter.label("__rt_mb_strimwidth_width_one_x86"); + emitter.instruction("mov ecx, 1"); // every other code point has display width 1 + emitter.instruction("ret"); // return the halfwidth result +} \ No newline at end of file diff --git a/src/codegen_support/runtime/strings/mod.rs b/src/codegen_support/runtime/strings/mod.rs index bb1e271ad8..ec241f240e 100644 --- a/src/codegen_support/runtime/strings/mod.rs +++ b/src/codegen_support/runtime/strings/mod.rs @@ -88,7 +88,9 @@ mod md5; mod sha1; mod crc32; mod iconv; +pub(crate) mod mb_eaw; mod mb_strlen; +mod mb_strimwidth; mod hash; pub(crate) mod hash_algos; mod hash_context; @@ -263,6 +265,7 @@ pub use crc32::emit_crc32; /// Emit mb_strlen UTF-8 code-point-count helper. pub use iconv::emit_iconv; pub use mb_strlen::emit_mb_strlen; +pub use mb_strimwidth::emit_mb_strimwidth; /// Emit SHA1 hash helper. pub use hash::emit_hash; /// Emit generic hash helper. diff --git a/src/ir/runtime_fn.rs b/src/ir/runtime_fn.rs index 829d26f6ca..32921c7bc3 100644 --- a/src/ir/runtime_fn.rs +++ b/src/ir/runtime_fn.rs @@ -585,6 +585,7 @@ pub enum RuntimeFnId { Long2ip, Ltrim, MbEregMatch, + MbStrimwidth, MbStrlen, Md5, NumberFormat, @@ -1128,6 +1129,11 @@ impl RuntimeFnId { | RuntimeFnId::ChunkSplit | RuntimeFnId::ParseUrl | RuntimeFnId::Wordwrap => crate::ir::Effects::MAY_THROW, + RuntimeFnId::MbStrimwidth => crate::ir::Effects::from_bits_retain( + crate::ir::Effects::MAY_THROW.bits() + | crate::ir::Effects::ALLOC_CONCAT.bits() + | crate::ir::Effects::ALLOC_HEAP.bits(), + ), RuntimeFnId::FunctionExists | RuntimeFnId::Defined | RuntimeFnId::JsonLastError @@ -1421,6 +1427,7 @@ impl RuntimeFnId { | RuntimeFnId::IconvStrrpos | RuntimeFnId::IconvSubstr | RuntimeFnId::Md5 + | RuntimeFnId::MbStrimwidth | RuntimeFnId::Sha1 | RuntimeFnId::StreamSocketEnableCrypto => MonitoringPolicy::GenericTiming, _ => MonitoringPolicy::Unspecified, @@ -1987,6 +1994,7 @@ impl RuntimeFnId { // release of an owned `$replacement` argument, so `array_splice($a, 1, 2, [9])` // leaked the literal replacement array on every call. | RuntimeFnId::ArraySplice + | RuntimeFnId::MbStrimwidth | RuntimeFnId::ZvalUnpack ) { BuiltinResultOwnership::Fresh @@ -2461,6 +2469,7 @@ impl RuntimeFnId { RuntimeFnId::Long2ip => "long2ip", RuntimeFnId::Ltrim => "ltrim", RuntimeFnId::MbEregMatch => "mb_ereg_match", + RuntimeFnId::MbStrimwidth => "mb_strimwidth", RuntimeFnId::MbStrlen => "mb_strlen", RuntimeFnId::Md5 => "md5", RuntimeFnId::NumberFormat => "number_format", diff --git a/tests/codegen/eval_builtin_parity.rs b/tests/codegen/eval_builtin_parity.rs index 88833a5c82..5f7dd88580 100644 --- a/tests/codegen/eval_builtin_parity.rs +++ b/tests/codegen/eval_builtin_parity.rs @@ -151,6 +151,23 @@ eval($source); assert_eq!(out, "6:2"); } +/// Verifies dynamic eval links and dispatches Magician's `mb_strimwidth()` path. +#[test] +fn test_eval_mb_strimwidth_parity() { + let out = compile_and_run( + r#" 0 ? "definitely-not-an-encoding" : "UTF-8"; +try { + mb_strimwidth("abc", 0, 1, "", $encoding); +} catch (\ValueError $error) { + echo "enc"; +} +echo ":"; +try { + mb_strimwidth("ab", 3, 1); +} catch (\ValueError $error) { + echo "start"; +} +echo ":"; +try { + mb_strimwidth("ab", 2, -1); +} catch (\ValueError $error) { + echo "width"; +}"#, + ); + assert_eq!(out, "日…:enc:start:width"); +} diff --git a/tests/error_tests/string_builtins.rs b/tests/error_tests/string_builtins.rs index b9f94676d2..84ee01936c 100644 --- a/tests/error_tests/string_builtins.rs +++ b/tests/error_tests/string_builtins.rs @@ -60,6 +60,12 @@ expect_builtin_arity_error!( "mb_strlen() takes 1 or 2 arguments" ); +expect_builtin_arity_error!( + test_error_mb_strimwidth_wrong_args, + "