refactor: temporal 与 json 长参数收进结构体

This commit is contained in:
2026-10-12 18:21:31 +00:00
parent 92fd288b07
commit 4345d14a0b
13 changed files with 603 additions and 313 deletions
+123 -102
View File
@@ -977,46 +977,79 @@ pub fn json_stringify<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let mut visited = HashSet::new();
let mut output: Vec<u16> = Vec::new();
let indent_level: usize = 0;
match jsvalue_to_json(
vm,
value,
&mut visited,
&mut output,
let ctx = StringifyCtx {
visited: &mut visited,
out: &mut output,
replacer_fn,
replacer_whitelist.as_ref(),
&space,
indent_level,
) {
replacer_whitelist: replacer_whitelist.as_ref(),
space: &space,
indent_level: 0,
};
match jsvalue_to_json(vm, value, ctx) {
Ok(()) => NativeResult::Ok(vm.new_string_units_owned(output)),
Err(exc) => NativeResult::Err(exc),
}
}
/// JSON 序列化上下文:环检表、输出流、replacer 与缩进状态。
/// 递归传递时经 [`StringifyCtx::child`] 派生下一缩进层。
struct StringifyCtx<'data, 'w> {
visited: &'w mut HashSet<*const JsObject>,
out: &'w mut Vec<u16>,
replacer_fn: Option<JsValue>,
replacer_whitelist: Option<&'data Vec<String>>,
space: &'data [u16],
indent_level: usize,
}
impl<'data, 'w> StringifyCtx<'data, 'w> {
/// 派生同缩进层上下文:共享同一环检表与输出流,indent_level 不变
/// (值位序列化入口传给对象/数组展开时用)。
fn same(&mut self) -> StringifyCtx<'data, '_> {
StringifyCtx {
visited: &mut *self.visited,
out: &mut *self.out,
replacer_fn: self.replacer_fn,
replacer_whitelist: self.replacer_whitelist,
space: self.space,
indent_level: self.indent_level,
}
}
/// 派生下一缩进层上下文:共享同一环检表与输出流,indent_level 加一
/// (对象/数组展开内递归回值位序列化时用)。
fn child(&mut self) -> StringifyCtx<'data, '_> {
StringifyCtx {
visited: &mut *self.visited,
out: &mut *self.out,
replacer_fn: self.replacer_fn,
replacer_whitelist: self.replacer_whitelist,
space: self.space,
indent_level: self.indent_level + 1,
}
}
}
/// 值位序列化(SerializeValue 及对象/数组展开)。toJSON 钩子不在本入口应用——
/// 唯一应用点在调用方(顶层 `json_stringify` 与 `stringify_object`/`stringify_array`
/// 的属性循环内,Get 之后、replacer 之前),保证每值位恰一次且与 replacer 顺序
/// 符合规范。`Err` 携带的原始异常值(含环检 TypeError)原样上抛。
#[allow(clippy::too_many_arguments)]
fn jsvalue_to_json<H: VmHost>(
vm: &mut H, val: JsValue, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>, replacer_fn: Option<JsValue>,
replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
fn jsvalue_to_json<H: VmHost>(vm: &mut H, val: JsValue, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
if val.is_null() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
} else if val.is_undefined() {
} else if val.is_symbol() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
} else if val.is_bool() {
push_ascii(out, if val.as_bool() { "true" } else { "false" });
push_ascii(ctx.out, if val.as_bool() { "true" } else { "false" });
} else if val.is_int() {
push_ascii(out, &val.as_int().to_string());
push_ascii(ctx.out, &val.as_int().to_string());
} else if val.is_double() {
let n = val.as_double();
if !n.is_finite() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
} else {
push_number(out, n);
push_number(ctx.out, n);
}
} else if val.is_bigint() {
// SerializeJSONProperty 步 10:BigInt 无 JSON 文本形态,抛 TypeError。
@@ -1024,11 +1057,11 @@ fn jsvalue_to_json<H: VmHost>(
} else if val.is_string() {
// SAFETY: val 已确认是字符串值;单元视图按码单元流序列化(见 stringify_string_units)。
let units = unsafe { (*val.as_string_ptr()).units() };
stringify_string_units(&units, out);
stringify_string_units(&units, ctx.out);
} else if val.is_object() {
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
return Ok(());
}
@@ -1040,12 +1073,12 @@ fn jsvalue_to_json<H: VmHost>(
let raw_val = obj.get_prop_at(0);
// SAFETY: rawJSON 属性构造期即写入字符串值。
let raw_text = unsafe { (*raw_val.as_string_ptr()).to_owned_string() };
push_ascii(out, &raw_text);
push_ascii(ctx.out, &raw_text);
return Ok(());
}
}
if !visited.insert(obj_ptr as *const JsObject) {
if !ctx.visited.insert(obj_ptr as *const JsObject) {
return Err(crate::error::create_type_error(vm, "Converting circular structure to JSON"));
}
@@ -1058,23 +1091,23 @@ fn jsvalue_to_json<H: VmHost>(
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
visited.remove(&(obj_ptr as *const JsObject));
ctx.visited.remove(&(obj_ptr as *const JsObject));
return Err(exc);
}
};
if n.is_finite() {
push_number(out, n);
push_number(ctx.out, n);
} else {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
}
visited.remove(&(obj_ptr as *const JsObject));
ctx.visited.remove(&(obj_ptr as *const JsObject));
return Ok(());
}
// 步 4b:装箱 Boolean → [[BooleanData]]。
if obj.is_boolean_obj() {
let b = obj.boxed_value();
push_ascii(out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
visited.remove(&(obj_ptr as *const JsObject));
push_ascii(ctx.out, if b.is_bool() && b.as_bool() { "true" } else { "false" });
ctx.visited.remove(&(obj_ptr as *const JsObject));
return Ok(());
}
// 步 4d:装箱 BigInt 无条件解包 [[BigIntData]],后续步 10 抛 TypeError。
@@ -1090,21 +1123,21 @@ fn jsvalue_to_json<H: VmHost>(
let exc = vm
.take_uncaught_value()
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
visited.remove(&(obj_ptr as *const JsObject));
ctx.visited.remove(&(obj_ptr as *const JsObject));
return Err(exc);
}
};
let units = vm.string_units(s);
stringify_string_units(&units, out);
stringify_string_units(&units, ctx.out);
} else if obj.is_typed_array_obj() {
stringify_typed_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
stringify_typed_array(vm, obj, ctx.same())?;
} else if obj.is_array() {
stringify_array(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
stringify_array(vm, obj, ctx.same())?;
} else {
stringify_object(vm, obj, visited, out, replacer_fn, replacer_whitelist, space, indent_level)?;
stringify_object(vm, obj, ctx.same())?;
}
visited.remove(&(obj_ptr as *const JsObject));
ctx.visited.remove(&(obj_ptr as *const JsObject));
}
Ok(())
}
@@ -1153,17 +1186,13 @@ fn stringify_string_units(units: &[u16], out: &mut Vec<u16>) {
push_ascii(out, "\"");
}
#[allow(clippy::too_many_arguments)]
fn stringify_object<H: VmHost>(
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>,
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
push_ascii(out, "{");
fn stringify_object<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
let has_space = !ctx.space.is_empty();
push_ascii(ctx.out, "{");
// 键集:白名单给定时 K = P(列表序,不限自身可枚举,值读走 Get);
// 无白名单走自身可枚举序(EnumerableOwnPropertyNames)。
let entries: Vec<(Vec<u16>, u32, Option<u32>)> = if let Some(whitelist) = replacer_whitelist {
let entries: Vec<(Vec<u16>, u32, Option<u32>)> = if let Some(whitelist) = ctx.replacer_whitelist {
whitelist
.iter()
.map(|name| {
@@ -1198,7 +1227,7 @@ fn stringify_object<H: VmHost>(
let val = call_to_json(vm, val, &units)?;
// replacer 函数回调。
let val = if let Some(replacer) = replacer_fn {
let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string_units_owned(units.clone());
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
Ok(v) => {
@@ -1227,55 +1256,51 @@ fn stringify_object<H: VmHost>(
}
if !first {
push_ascii(out, ",");
push_ascii(ctx.out, ",");
}
first = false;
if has_space {
push_ascii(out, "\n");
for _ in 0..indent_level + 1 {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level + 1 {
ctx.out.extend_from_slice(ctx.space);
}
stringify_string_units(&units, out);
push_ascii(out, ":");
push_ascii(out, " ");
stringify_string_units(&units, ctx.out);
push_ascii(ctx.out, ":");
push_ascii(ctx.out, " ");
} else {
stringify_string_units(&units, out);
push_ascii(out, ":");
stringify_string_units(&units, ctx.out);
push_ascii(ctx.out, ":");
}
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
jsvalue_to_json(vm, val, ctx.child())?;
}
if has_space && !first {
push_ascii(out, "\n");
for _ in 0..indent_level {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level {
ctx.out.extend_from_slice(ctx.space);
}
}
push_ascii(out, "}");
push_ascii(ctx.out, "}");
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn stringify_array<H: VmHost>(
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>,
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
push_ascii(out, "[");
fn stringify_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
let has_space = !ctx.space.is_empty();
push_ascii(ctx.out, "[");
let obj_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let len = obj.prop_count() as usize;
for i in 0..len {
if i > 0 {
push_ascii(out, ",");
push_ascii(ctx.out, ",");
}
if has_space {
push_ascii(out, "\n");
for _ in 0..indent_level + 1 {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level + 1 {
ctx.out.extend_from_slice(ctx.space);
}
}
@@ -1287,12 +1312,12 @@ fn stringify_array<H: VmHost>(
let val = call_to_json(vm, val, &index_units)?;
// replacer 函数回调。
let val = if let Some(replacer) = replacer_fn {
let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string(&index_str);
match vm.call_function_sync(replacer, obj_val, &[key_val, val]) {
Ok(v) => {
if v.is_undefined() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
continue;
}
v
@@ -1313,39 +1338,35 @@ fn stringify_array<H: VmHost>(
!ptr.is_null() && unsafe { (*ptr).is_function() }
};
if is_function || val.is_undefined() {
push_ascii(out, "null");
push_ascii(ctx.out, "null");
} else {
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
jsvalue_to_json(vm, val, ctx.child())?;
}
}
if has_space && len > 0 {
push_ascii(out, "\n");
for _ in 0..indent_level {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level {
ctx.out.extend_from_slice(ctx.space);
}
}
push_ascii(out, "]");
push_ascii(ctx.out, "]");
Ok(())
}
/// TypedArray 序列化:整数索引是可枚举自身属性(元素存于原生载荷,不在形状链),
/// 逐元素按对象臂规范序 Get → toJSON → replacer 处理,输出形态与普通对象一致。
#[allow(clippy::too_many_arguments)]
fn stringify_typed_array<H: VmHost>(
vm: &mut H, obj: &JsObject, visited: &mut HashSet<*const JsObject>, out: &mut Vec<u16>,
replacer_fn: Option<JsValue>, replacer_whitelist: Option<&Vec<String>>, space: &[u16], indent_level: usize,
) -> Result<(), JsValue> {
let has_space = !space.is_empty();
fn stringify_typed_array<H: VmHost>(vm: &mut H, obj: &JsObject, mut ctx: StringifyCtx<'_, '_>) -> Result<(), JsValue> {
let has_space = !ctx.space.is_empty();
let this_val = JsValue::from_js_object(obj as *const JsObject as *mut JsObject);
let view = crate::typed_array::get_typed_array_data(vm, this_val)?;
// live 口径:buffer 收缩/detach 后按当前 live 长度枚举(detached 空对象形态)。
let len = crate::typed_array::ta_live_length(view);
push_ascii(out, "{");
push_ascii(ctx.out, "{");
// 键集:白名单给定时 K = P(列表序,整数索引名读元素、越界名跳过、
// 非索引名经 Get 读命名键如 length);无白名单走升序元素序。
let entries: Vec<(Vec<u16>, JsValue)> = if let Some(whitelist) = replacer_whitelist {
let entries: Vec<(Vec<u16>, JsValue)> = if let Some(whitelist) = ctx.replacer_whitelist {
let mut list: Vec<(Vec<u16>, JsValue)> = Vec::new();
for name in whitelist {
let si = vm.string_key_si(name);
@@ -1377,7 +1398,7 @@ fn stringify_typed_array<H: VmHost>(
let val = call_to_json(vm, val, &index_units)?;
// replacer 函数回调。
let val = if let Some(replacer) = replacer_fn {
let val = if let Some(replacer) = ctx.replacer_fn {
let key_val = vm.new_string_units_owned(index_units.clone());
match vm.call_function_sync(replacer, this_val, &[key_val, val]) {
Ok(v) => {
@@ -1406,32 +1427,32 @@ fn stringify_typed_array<H: VmHost>(
}
if !first {
push_ascii(out, ",");
push_ascii(ctx.out, ",");
}
first = false;
if has_space {
push_ascii(out, "\n");
for _ in 0..indent_level + 1 {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level + 1 {
ctx.out.extend_from_slice(ctx.space);
}
stringify_string_units(&index_units, out);
push_ascii(out, ":");
push_ascii(out, " ");
stringify_string_units(&index_units, ctx.out);
push_ascii(ctx.out, ":");
push_ascii(ctx.out, " ");
} else {
stringify_string_units(&index_units, out);
push_ascii(out, ":");
stringify_string_units(&index_units, ctx.out);
push_ascii(ctx.out, ":");
}
jsvalue_to_json(vm, val, visited, out, replacer_fn, replacer_whitelist, space, indent_level + 1)?;
jsvalue_to_json(vm, val, ctx.child())?;
}
if has_space && !first {
push_ascii(out, "\n");
for _ in 0..indent_level {
out.extend_from_slice(space);
push_ascii(ctx.out, "\n");
for _ in 0..ctx.indent_level {
ctx.out.extend_from_slice(ctx.space);
}
}
push_ascii(out, "}");
push_ascii(ctx.out, "}");
Ok(())
}