mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 支持 JSON 成员方法
This commit is contained in:
Generated
+1
@@ -850,6 +850,7 @@ dependencies = [
|
|||||||
"proptest",
|
"proptest",
|
||||||
"regex",
|
"regex",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ pub struct BuiltinWorld {
|
|||||||
pub uri_error_proto: P<JsObject>,
|
pub uri_error_proto: P<JsObject>,
|
||||||
pub eval_error_proto: P<JsObject>,
|
pub eval_error_proto: P<JsObject>,
|
||||||
pub math_object: P<JsObject>,
|
pub math_object: P<JsObject>,
|
||||||
|
pub json_object: P<JsObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
|
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
|
||||||
@@ -242,6 +243,8 @@ impl BuiltinWorld {
|
|||||||
|
|
||||||
let math_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
let math_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||||
|
|
||||||
|
let json_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||||
|
|
||||||
/// Overwrite placeholder slots (set up by make_pair) with real values.
|
/// Overwrite placeholder slots (set up by make_pair) with real values.
|
||||||
/// ctor.vec[0] = constructor.prototype -> proto
|
/// ctor.vec[0] = constructor.prototype -> proto
|
||||||
/// proto.vec[0] = proto.constructor -> ctor
|
/// proto.vec[0] = proto.constructor -> ctor
|
||||||
@@ -293,6 +296,7 @@ impl BuiltinWorld {
|
|||||||
uri_error_proto,
|
uri_error_proto,
|
||||||
eval_error_proto,
|
eval_error_proto,
|
||||||
math_object,
|
math_object,
|
||||||
|
json_object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ fast-float.workspace = true
|
|||||||
ryu.workspace = true
|
ryu.workspace = true
|
||||||
memchr.workspace = true
|
memchr.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest.workspace = true
|
proptest.workspace = true
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ pub mod array;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
|
pub mod json;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod number;
|
pub mod number;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::coercion;
|
|||||||
use crate::native::NativeResult;
|
use crate::native::NativeResult;
|
||||||
use crate::vm::Vm;
|
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 mut keys: Vec<(u32, u8)> = Vec::new();
|
||||||
let shape_id = obj.shape_id();
|
let shape_id = obj.shape_id();
|
||||||
let mut pos: u8 = 0;
|
let mut pos: u8 = 0;
|
||||||
|
|||||||
@@ -670,6 +670,38 @@ fn bind_math(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
|||||||
global.bump_generation();
|
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>) {
|
fn bind_function(kernel: &Arc<OxideKernel>) {
|
||||||
let function_methods = FunctionMethods {
|
let function_methods = FunctionMethods {
|
||||||
call: crate::builtins::function::function_call as *const (),
|
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_string(kernel, global);
|
||||||
bind_number(kernel, global);
|
bind_number(kernel, global);
|
||||||
bind_math(kernel, global);
|
bind_math(kernel, global);
|
||||||
|
bind_json(kernel, global);
|
||||||
bind_function(kernel);
|
bind_function(kernel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
||||||
|
let allocator = oxide_parser::Allocator::default();
|
||||||
|
let program =
|
||||||
|
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||||
|
let module = Compiler::new()
|
||||||
|
.compile(&program)
|
||||||
|
.map_err(|e| format!("Compile error: {}", e))?;
|
||||||
|
vm.run(&module)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_value(vm: &Vm, val: JsValue) -> String {
|
||||||
|
vm.kernel()
|
||||||
|
.string_forge()
|
||||||
|
.lookup(val.as_string_index())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- JSON.parse --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_object() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('{\"a\":1,\"b\":2}')").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||||
|
let mut count = 0;
|
||||||
|
for i in 0..obj.prop_vec_len() {
|
||||||
|
if !obj.get_prop_at(i as u8).is_undefined() {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(count, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_array() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('[1,2,3]')").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||||
|
assert!(obj.is_array());
|
||||||
|
assert_eq!(obj.prop_vec_len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_string() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('\"hello\"')").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('42')").unwrap();
|
||||||
|
assert!(result.is_double());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_float() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('3.14')").unwrap();
|
||||||
|
assert!(result.is_double());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_true() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('true')").unwrap();
|
||||||
|
assert!(result.is_bool());
|
||||||
|
assert!(result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_false() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('false')").unwrap();
|
||||||
|
assert!(result.is_bool());
|
||||||
|
assert!(!result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_null() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('null')").unwrap();
|
||||||
|
assert!(result.is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_nested() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(
|
||||||
|
&mut vm,
|
||||||
|
"JSON.parse('{\"a\":1,\"b\":[1,2],\"c\":{\"d\":\"hello\"}}')",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_invalid_throws() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let err = eval(&mut vm, "JSON.parse('not json')");
|
||||||
|
assert!(err.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- JSON.stringify --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_object() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify({a:1})").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "{\"a\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_array() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify([1,2,3])").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "[1,2,3]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_string() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify('hello')").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "\"hello\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(42)").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_float() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(3.14)").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert!(s.starts_with("3.14"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_true() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(true)").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_false() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(false)").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_null() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(null)").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_undefined() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify(undefined)").unwrap();
|
||||||
|
assert!(result.is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Roundtrip --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_stringify_roundtrip() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse(JSON.stringify({a:1,b:2}))").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||||
|
assert_eq!(obj.prop_vec_len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_roundtrip_array() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let _ = eval(
|
||||||
|
&mut vm,
|
||||||
|
"var arr = JSON.parse(JSON.stringify([1,2,3])); arr",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Edge cases --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_empty_object() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify({})").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_empty_array() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify([])").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_escape_quotes() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify('he\"llo')").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "\"he\\\"llo\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_escape_backslash() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify('a\\\\b')").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "\"a\\\\b\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_nested_objects() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.stringify({a: {b: 1}})").unwrap();
|
||||||
|
assert!(result.is_string());
|
||||||
|
let s = string_value(&vm, result);
|
||||||
|
assert_eq!(s, "{\"a\":{\"b\":1}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_empty_object() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('{}')").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||||
|
assert_eq!(obj.prop_vec_len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_empty_array() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('[]')").unwrap();
|
||||||
|
assert!(result.is_object());
|
||||||
|
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||||
|
assert!(obj.is_array());
|
||||||
|
assert_eq!(obj.prop_vec_len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_parse_int_is_double() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "JSON.parse('123')").unwrap();
|
||||||
|
assert!((result.as_double() - 123.0).abs() < 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Cycle detection --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_stringify_cycle_throws_type_error() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let err = eval(&mut vm, "var a = {}; a.self = a; JSON.stringify(a)");
|
||||||
|
assert!(err.is_err());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user