feat: 支持 JSON 成员方法

This commit is contained in:
爱特每羊队
2026-06-07 23:41:52 +08:00
parent 4777783f92
commit 69945b9610
8 changed files with 572 additions and 1 deletions
+236
View File
@@ -0,0 +1,236 @@
use std::collections::HashSet;
use std::fmt::Write;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use crate::builtins::object::walk_own_keys;
use crate::native::NativeResult;
use crate::vm::Vm;
pub fn json_parse(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return Err(crate::builtins::error::create_syntax_error(
vm,
"JSON.parse requires 1 argument",
));
}
let val = vm.reg(args[1]);
if !val.is_string() {
return Err(crate::builtins::error::create_syntax_error(
vm,
"JSON.parse: argument is not a string",
));
}
let text = {
let si = val.as_string_index();
vm.kernel().string_forge().lookup(si).unwrap_or_default()
};
let parsed: serde_json::Value = serde_json::from_str(&text)
.map_err(|e| crate::builtins::error::create_syntax_error(vm, &format!("{}", e)))?;
Ok(value_to_jsvalue(vm, &parsed))
}
fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
match val {
serde_json::Value::Null => JsValue::null(),
serde_json::Value::Bool(b) => JsValue::bool(*b),
serde_json::Value::Number(n) => {
if let Some(f) = n.as_f64() {
JsValue::float(f)
} else {
JsValue::float(0.0)
}
}
serde_json::Value::String(s) => vm.intern(s),
serde_json::Value::Array(arr) => {
let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = arr.len();
let array_obj = vm.epoch().alloc(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
n,
vm.epoch().bump(),
));
for (i, v) in arr.iter().enumerate() {
let jsv = value_to_jsvalue(vm, v);
unsafe {
(*array_obj).set_prop_at(i as u8, jsv);
}
}
JsValue::from_js_object(array_obj)
}
serde_json::Value::Object(map) => {
let object_proto = vm.kernel().builtin_world().object_proto.as_ptr() as *mut JsObject;
let mut obj =
JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
for (key, val) in map {
let si = vm.kernel().string_forge().intern(key).0;
let jsv = value_to_jsvalue(vm, val);
let new_shape = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(new_shape);
obj.ensure_hash_props().push(Box::new(jsv));
}
let obj_ptr = vm.epoch().alloc(obj);
JsValue::from_js_object(obj_ptr)
}
}
}
pub fn json_stringify(vm: &mut Vm, args: &[u8]) -> NativeResult {
if args.len() < 2 {
return Ok(JsValue::undefined());
}
let value = vm.reg(args[1]);
if value.is_undefined() {
return Ok(JsValue::undefined());
}
let mut visited = HashSet::new();
let mut output = String::new();
jsvalue_to_json(vm, value, &mut visited, &mut output)
.map_err(|_| crate::builtins::error::create_type_error(vm, "cyclic object value"))?;
Ok(vm.intern(&output))
}
fn jsvalue_to_json(
vm: &Vm,
val: JsValue,
visited: &mut HashSet<*const u8>,
out: &mut String,
) -> Result<(), ()> {
if val.is_null() {
out.push_str("null");
} else if val.is_undefined() {
} else if val.is_bool() {
out.push_str(if val.as_bool() { "true" } else { "false" });
} else if val.is_int() {
write!(out, "{}", val.as_int()).unwrap();
} else if val.is_double() {
let n = val.as_double();
if !n.is_finite() {
out.push_str("null");
} else if n.fract() == 0.0 && n.abs() < 1e21 {
write!(out, "{}", n as i64).unwrap();
} else {
let mut buf = ryu::Buffer::new();
out.push_str(buf.format(n));
}
} else if val.is_string() {
let si = val.as_string_index();
let s = vm.kernel().string_forge().lookup(si).unwrap_or_default();
stringify_string(&s, out);
} else if val.is_object() {
let obj_ptr = val.as_js_object_ptr();
if obj_ptr.is_null() {
out.push_str("null");
return Ok(());
}
if visited.contains(&(obj_ptr as *const u8)) {
return Err(());
}
visited.insert(obj_ptr as *const u8);
let obj = unsafe { &*obj_ptr };
if obj.is_array() {
stringify_array(vm, obj, visited, out)?;
} else {
stringify_object(vm, obj, visited, out)?;
}
visited.remove(&(obj_ptr as *const u8));
}
Ok(())
}
fn stringify_string(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
let mut buf = [0u16; 2];
let encoded = c.encode_utf16(&mut buf);
for unit in encoded {
out.push_str(&format!("\\u{:04x}", unit));
}
}
_ => out.push(c),
}
}
out.push('"');
}
fn stringify_object(
vm: &Vm,
obj: &JsObject,
visited: &mut HashSet<*const u8>,
out: &mut String,
) -> Result<(), ()> {
out.push('{');
let keys = walk_own_keys(vm, obj);
let mut first = true;
for (si, pos) in keys {
let val = obj.get_prop_at(pos);
let is_function = val.is_object() && {
let ptr = val.as_js_object_ptr();
if ptr.is_null() {
false
} else {
unsafe { (*ptr).is_function() }
}
};
if val.is_undefined() || is_function {
continue;
}
if !first {
out.push(',');
}
first = false;
let name = vm.kernel().string_forge().lookup(si).unwrap_or_default();
stringify_string(&name, out);
out.push(':');
jsvalue_to_json(vm, val, visited, out)?;
}
out.push('}');
Ok(())
}
fn stringify_array(
vm: &Vm,
obj: &JsObject,
visited: &mut HashSet<*const u8>,
out: &mut String,
) -> Result<(), ()> {
out.push('[');
let len = obj.prop_vec_len();
for i in 0..len {
if i > 0 {
out.push(',');
}
let val = obj.get_prop_at(i as u8);
let is_function = val.is_object() && {
let ptr = val.as_js_object_ptr();
if ptr.is_null() {
false
} else {
unsafe { (*ptr).is_function() }
}
};
if is_function || val.is_undefined() {
out.push_str("null");
} else {
jsvalue_to_json(vm, val, visited, out)?;
}
}
out.push(']');
Ok(())
}
+1
View File
@@ -2,6 +2,7 @@ pub mod array;
pub mod error;
pub mod function;
pub mod global;
pub mod json;
pub mod math;
pub mod number;
pub mod object;
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::coercion;
use crate::native::NativeResult;
use crate::vm::Vm;
fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> {
pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u8)> {
let mut keys: Vec<(u32, u8)> = Vec::new();
let shape_id = obj.shape_id();
let mut pos: u8 = 0;
+33
View File
@@ -670,6 +670,38 @@ fn bind_math(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
global.bump_generation();
}
fn bind_json(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
let json_ptr = kernel.builtin_world().json_object.as_ptr() as *mut JsObject;
let json = unsafe { &mut *json_ptr };
bind_method!(
kernel.builtin_world(),
json,
kernel.string_forge().as_ref(),
kernel.shape_forge().as_ref(),
"parse",
crate::builtins::json::json_parse,
1
);
bind_method!(
kernel.builtin_world(),
json,
kernel.string_forge().as_ref(),
kernel.shape_forge().as_ref(),
"stringify",
crate::builtins::json::json_stringify,
1
);
let si_j = kernel.string_forge().intern("JSON").0;
let j_shape = kernel.shape_forge().make_shape(global.shape_id(), si_j);
let j_val =
JsValue::from_js_object(kernel.builtin_world().json_object.as_ptr() as *mut JsObject);
global.set_shape_id(j_shape);
global.ensure_hash_props().push(Box::new(j_val));
global.bump_generation();
}
fn bind_function(kernel: &Arc<OxideKernel>) {
let function_methods = FunctionMethods {
call: crate::builtins::function::function_call as *const (),
@@ -693,6 +725,7 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
bind_string(kernel, global);
bind_number(kernel, global);
bind_math(kernel, global);
bind_json(kernel, global);
bind_function(kernel);
}