we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement JS built-in Math, Date, and JSON

## Math
- Not a constructor (static methods only)
- Constants: E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2
- Methods: abs, ceil, floor, round, trunc, max, min, pow, sqrt, cbrt,
hypot, sin, cos, tan, asin, acos, atan, atan2, exp, log, log2, log10,
expm1, log1p, sign, clz32, fround, random, imul

## Date
- Constructor: Date(), Date(ms), Date(string), Date(y, m, d, h, min, s, ms)
- Static: Date.now(), Date.parse(string), Date.UTC(...)
- Getters: getFullYear, getMonth, getDate, getDay, getHours, getMinutes,
getSeconds, getMilliseconds, getTime, getTimezoneOffset + UTC variants
- Setters: setFullYear, setMonth, setDate, setHours, setMinutes,
setSeconds, setMilliseconds, setTime
- Formatting: toString, toISOString, toUTCString, toLocaleDateString, toJSON
- valueOf returns milliseconds since epoch

## JSON
- JSON.parse(text): full JSON tokenizer and recursive descent parser
supporting objects, arrays, strings, numbers, booleans, null, and
escape sequences including \uXXXX unicode escapes
- JSON.stringify(value, replacer?, space?): recursive serialization with
pretty-print support, circular reference detection (throws TypeError),
handles NaN/Infinity as null, Date toJSON support

## VM Changes
- Added date_prototype field to Vm
- Added RuntimeError::syntax_error constructor
- Made js_number_to_string pub(crate) for JSON.stringify

41 new tests (369 total JS tests, all passing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1849 -2
+1398 -1
crates/js/src/builtins.rs
··· 1 - //! Built-in JavaScript objects and functions: Object, Array, Function, Error. 1 + //! Built-in JavaScript objects and functions: Object, Array, Function, Error, 2 + //! String, Number, Boolean, Symbol, Math, Date, JSON. 2 3 //! 3 4 //! Registers constructors, static methods, and prototype methods as globals 4 5 //! in the VM. Callback-based array methods (map, filter, etc.) are defined ··· 7 8 use crate::gc::{Gc, GcRef}; 8 9 use crate::vm::*; 9 10 use std::collections::HashMap; 11 + use std::time::{SystemTime, UNIX_EPOCH}; 10 12 11 13 /// Native callback type alias to satisfy clippy::type_complexity. 12 14 type NativeMethod = ( ··· 231 233 232 234 // Create and register Symbol factory. 233 235 init_symbol_builtins(vm); 236 + 237 + // Create and register Math object (static methods only, not a constructor). 238 + init_math_object(vm); 239 + 240 + // Create and register Date constructor. 241 + init_date_builtins(vm); 242 + 243 + // Create and register JSON object (static methods only). 244 + init_json_object(vm); 234 245 235 246 // Register global utility functions. 236 247 init_global_functions(vm); ··· 2208 2219 /// Global symbol ID counter. Each Symbol() call increments this. 2209 2220 static SYMBOL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); 2210 2221 2222 + // Thread-local Date prototype GcRef for use in date_constructor. 2223 + // The Date constructor callback doesn't have VM access, so we store the 2224 + // prototype here during init and read it back during construction. 2225 + thread_local! { 2226 + static DATE_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) }; 2227 + } 2228 + 2211 2229 fn init_symbol_builtins(vm: &mut Vm) { 2212 2230 // Create a function object so we can hang static props on it. 2213 2231 let gc_ref = make_native(&mut vm.gc, "Symbol", symbol_factory); ··· 2267 2285 } else { 2268 2286 Ok(Value::Undefined) 2269 2287 } 2288 + } 2289 + 2290 + // ── Math object ────────────────────────────────────────────── 2291 + 2292 + /// Simple xorshift64 PRNG state (no crypto requirement). 2293 + static PRNG_STATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); 2294 + 2295 + fn prng_next() -> f64 { 2296 + let mut s = PRNG_STATE.load(std::sync::atomic::Ordering::Relaxed); 2297 + if s == 0 { 2298 + // Seed from system time. 2299 + s = SystemTime::now() 2300 + .duration_since(UNIX_EPOCH) 2301 + .map(|d| d.as_nanos() as u64) 2302 + .unwrap_or(123456789); 2303 + } 2304 + s ^= s << 13; 2305 + s ^= s >> 7; 2306 + s ^= s << 17; 2307 + PRNG_STATE.store(s, std::sync::atomic::Ordering::Relaxed); 2308 + // Map to [0, 1). 2309 + (s >> 11) as f64 / ((1u64 << 53) as f64) 2310 + } 2311 + 2312 + fn init_math_object(vm: &mut Vm) { 2313 + let mut data = ObjectData::new(); 2314 + if let Some(proto) = vm.object_prototype { 2315 + data.prototype = Some(proto); 2316 + } 2317 + let math_ref = vm.gc.alloc(HeapObject::Object(data)); 2318 + 2319 + // Constants. 2320 + let constants: &[(&str, f64)] = &[ 2321 + ("E", std::f64::consts::E), 2322 + ("LN2", std::f64::consts::LN_2), 2323 + ("LN10", std::f64::consts::LN_10), 2324 + ("LOG2E", std::f64::consts::LOG2_E), 2325 + ("LOG10E", std::f64::consts::LOG10_E), 2326 + ("PI", std::f64::consts::PI), 2327 + ("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2), 2328 + ("SQRT2", std::f64::consts::SQRT_2), 2329 + ]; 2330 + for &(name, val) in constants { 2331 + set_builtin_prop(&mut vm.gc, math_ref, name, Value::Number(val)); 2332 + } 2333 + 2334 + // Methods. 2335 + let methods: &[NativeMethod] = &[ 2336 + ("abs", math_abs), 2337 + ("ceil", math_ceil), 2338 + ("floor", math_floor), 2339 + ("round", math_round), 2340 + ("trunc", math_trunc), 2341 + ("max", math_max), 2342 + ("min", math_min), 2343 + ("pow", math_pow), 2344 + ("sqrt", math_sqrt), 2345 + ("cbrt", math_cbrt), 2346 + ("hypot", math_hypot), 2347 + ("sin", math_sin), 2348 + ("cos", math_cos), 2349 + ("tan", math_tan), 2350 + ("asin", math_asin), 2351 + ("acos", math_acos), 2352 + ("atan", math_atan), 2353 + ("atan2", math_atan2), 2354 + ("exp", math_exp), 2355 + ("log", math_log), 2356 + ("log2", math_log2), 2357 + ("log10", math_log10), 2358 + ("expm1", math_expm1), 2359 + ("log1p", math_log1p), 2360 + ("sign", math_sign), 2361 + ("clz32", math_clz32), 2362 + ("fround", math_fround), 2363 + ("random", math_random), 2364 + ("imul", math_imul), 2365 + ]; 2366 + for &(name, cb) in methods { 2367 + let f = make_native(&mut vm.gc, name, cb); 2368 + set_builtin_prop(&mut vm.gc, math_ref, name, Value::Function(f)); 2369 + } 2370 + 2371 + vm.set_global("Math", Value::Object(math_ref)); 2372 + } 2373 + 2374 + fn math_abs(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2375 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2376 + Ok(Value::Number(n.abs())) 2377 + } 2378 + 2379 + fn math_ceil(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2380 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2381 + Ok(Value::Number(n.ceil())) 2382 + } 2383 + 2384 + fn math_floor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2385 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2386 + Ok(Value::Number(n.floor())) 2387 + } 2388 + 2389 + fn math_round(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2390 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2391 + // JS Math.round rounds .5 up (toward +Infinity). 2392 + Ok(Value::Number(n.round())) 2393 + } 2394 + 2395 + fn math_trunc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2396 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2397 + Ok(Value::Number(n.trunc())) 2398 + } 2399 + 2400 + fn math_max(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2401 + if args.is_empty() { 2402 + return Ok(Value::Number(f64::NEG_INFINITY)); 2403 + } 2404 + let mut result = f64::NEG_INFINITY; 2405 + for arg in args { 2406 + let n = arg.to_number(); 2407 + if n.is_nan() { 2408 + return Ok(Value::Number(f64::NAN)); 2409 + } 2410 + if n > result { 2411 + result = n; 2412 + } 2413 + } 2414 + Ok(Value::Number(result)) 2415 + } 2416 + 2417 + fn math_min(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2418 + if args.is_empty() { 2419 + return Ok(Value::Number(f64::INFINITY)); 2420 + } 2421 + let mut result = f64::INFINITY; 2422 + for arg in args { 2423 + let n = arg.to_number(); 2424 + if n.is_nan() { 2425 + return Ok(Value::Number(f64::NAN)); 2426 + } 2427 + if n < result { 2428 + result = n; 2429 + } 2430 + } 2431 + Ok(Value::Number(result)) 2432 + } 2433 + 2434 + fn math_pow(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2435 + let base = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2436 + let exp = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); 2437 + Ok(Value::Number(base.powf(exp))) 2438 + } 2439 + 2440 + fn math_sqrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2441 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2442 + Ok(Value::Number(n.sqrt())) 2443 + } 2444 + 2445 + fn math_cbrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2446 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2447 + Ok(Value::Number(n.cbrt())) 2448 + } 2449 + 2450 + fn math_hypot(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2451 + if args.is_empty() { 2452 + return Ok(Value::Number(0.0)); 2453 + } 2454 + let mut sum = 0.0f64; 2455 + for arg in args { 2456 + let n = arg.to_number(); 2457 + if n.is_infinite() { 2458 + return Ok(Value::Number(f64::INFINITY)); 2459 + } 2460 + if n.is_nan() { 2461 + return Ok(Value::Number(f64::NAN)); 2462 + } 2463 + sum += n * n; 2464 + } 2465 + Ok(Value::Number(sum.sqrt())) 2466 + } 2467 + 2468 + fn math_sin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2469 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2470 + Ok(Value::Number(n.sin())) 2471 + } 2472 + 2473 + fn math_cos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2474 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2475 + Ok(Value::Number(n.cos())) 2476 + } 2477 + 2478 + fn math_tan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2479 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2480 + Ok(Value::Number(n.tan())) 2481 + } 2482 + 2483 + fn math_asin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2484 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2485 + Ok(Value::Number(n.asin())) 2486 + } 2487 + 2488 + fn math_acos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2489 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2490 + Ok(Value::Number(n.acos())) 2491 + } 2492 + 2493 + fn math_atan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2494 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2495 + Ok(Value::Number(n.atan())) 2496 + } 2497 + 2498 + fn math_atan2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2499 + let y = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2500 + let x = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); 2501 + Ok(Value::Number(y.atan2(x))) 2502 + } 2503 + 2504 + fn math_exp(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2505 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2506 + Ok(Value::Number(n.exp())) 2507 + } 2508 + 2509 + fn math_log(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2510 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2511 + Ok(Value::Number(n.ln())) 2512 + } 2513 + 2514 + fn math_log2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2515 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2516 + Ok(Value::Number(n.log2())) 2517 + } 2518 + 2519 + fn math_log10(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2520 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2521 + Ok(Value::Number(n.log10())) 2522 + } 2523 + 2524 + fn math_expm1(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2525 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2526 + Ok(Value::Number(n.exp_m1())) 2527 + } 2528 + 2529 + fn math_log1p(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2530 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2531 + Ok(Value::Number(n.ln_1p())) 2532 + } 2533 + 2534 + fn math_sign(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2535 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2536 + if n.is_nan() { 2537 + Ok(Value::Number(f64::NAN)) 2538 + } else if n == 0.0 { 2539 + // Preserve -0 and +0. 2540 + Ok(Value::Number(n)) 2541 + } else if n > 0.0 { 2542 + Ok(Value::Number(1.0)) 2543 + } else { 2544 + Ok(Value::Number(-1.0)) 2545 + } 2546 + } 2547 + 2548 + fn math_clz32(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2549 + let n = args.first().map(|v| v.to_number()).unwrap_or(0.0); 2550 + let i = n as u32; 2551 + Ok(Value::Number(i.leading_zeros() as f64)) 2552 + } 2553 + 2554 + fn math_fround(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2555 + let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2556 + Ok(Value::Number((n as f32) as f64)) 2557 + } 2558 + 2559 + fn math_random(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2560 + Ok(Value::Number(prng_next())) 2561 + } 2562 + 2563 + fn math_imul(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2564 + // ToUint32 then reinterpret as i32 per spec. 2565 + let a = args.first().map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; 2566 + let b = args.get(1).map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; 2567 + Ok(Value::Number(a.wrapping_mul(b) as f64)) 2568 + } 2569 + 2570 + // ── Date built-in ──────────────────────────────────────────── 2571 + 2572 + /// Milliseconds since Unix epoch from SystemTime. 2573 + fn now_ms() -> f64 { 2574 + SystemTime::now() 2575 + .duration_since(UNIX_EPOCH) 2576 + .map(|d| d.as_millis() as f64) 2577 + .unwrap_or(0.0) 2578 + } 2579 + 2580 + /// Calendar components from a timestamp (milliseconds since epoch). 2581 + /// Returns (year, month0, day, hours, minutes, seconds, ms, weekday) in UTC. 2582 + fn ms_to_utc_components(ms: f64) -> (i64, i64, i64, i64, i64, i64, i64, i64) { 2583 + let total_ms = ms as i64; 2584 + let ms_part = ((total_ms % 1000) + 1000) % 1000; 2585 + let mut days = total_ms.div_euclid(86_400_000); 2586 + // Weekday: Jan 1 1970 was Thursday (4). 2587 + let weekday = ((days % 7) + 4 + 7) % 7; 2588 + 2589 + let secs_in_day = (total_ms.rem_euclid(86_400_000)) / 1000; 2590 + let hours = secs_in_day / 3600; 2591 + let minutes = (secs_in_day % 3600) / 60; 2592 + let seconds = secs_in_day % 60; 2593 + 2594 + // Convert days since epoch to year/month/day using a civil calendar algorithm. 2595 + // Shift epoch to March 1, 2000. 2596 + days += 719_468; 2597 + let era = if days >= 0 { 2598 + days / 146_097 2599 + } else { 2600 + (days - 146_096) / 146_097 2601 + }; 2602 + let doe = days - era * 146_097; // day of era [0, 146096] 2603 + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; 2604 + let y = yoe + era * 400; 2605 + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); 2606 + let mp = (5 * doy + 2) / 153; 2607 + let d = doy - (153 * mp + 2) / 5 + 1; 2608 + let m = if mp < 10 { mp + 3 } else { mp - 9 }; 2609 + let y = if m <= 2 { y + 1 } else { y }; 2610 + 2611 + (y, m - 1, d, hours, minutes, seconds, ms_part, weekday) 2612 + } 2613 + 2614 + /// Convert UTC components to milliseconds since epoch. 2615 + fn utc_components_to_ms(year: i64, month: i64, day: i64, h: i64, min: i64, s: i64, ms: i64) -> f64 { 2616 + // Normalize month overflow. 2617 + let y = year + month.div_euclid(12); 2618 + let m = month.rem_euclid(12) + 1; // 1-based 2619 + 2620 + // Civil calendar to days (inverse of above). 2621 + let (y2, m2) = if m <= 2 { (y - 1, m + 9) } else { (y, m - 3) }; 2622 + let era = if y2 >= 0 { y2 / 400 } else { (y2 - 399) / 400 }; 2623 + let yoe = y2 - era * 400; 2624 + let doy = (153 * m2 + 2) / 5 + day - 1; 2625 + let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; 2626 + let days = era * 146_097 + doe - 719_468; 2627 + 2628 + (days * 86_400_000 + h * 3_600_000 + min * 60_000 + s * 1000 + ms) as f64 2629 + } 2630 + 2631 + /// Parse a subset of ISO 8601 date strings (YYYY-MM-DDTHH:MM:SS.sssZ). 2632 + fn parse_date_string(s: &str) -> Option<f64> { 2633 + let s = s.trim(); 2634 + // Try YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD or YYYY 2635 + let parts: Vec<&str> = s.splitn(2, 'T').collect(); 2636 + let date_part = parts[0]; 2637 + let time_part = parts.get(1).copied().unwrap_or(""); 2638 + 2639 + let date_fields: Vec<&str> = date_part.split('-').collect(); 2640 + if date_fields.is_empty() { 2641 + return None; 2642 + } 2643 + 2644 + let year: i64 = date_fields[0].parse().ok()?; 2645 + let month: i64 = date_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(1); 2646 + let day: i64 = date_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(1); 2647 + 2648 + if !(1..=12).contains(&month) || !(1..=31).contains(&day) { 2649 + return None; 2650 + } 2651 + 2652 + let (h, min, sec, ms) = if time_part.is_empty() { 2653 + (0, 0, 0, 0) 2654 + } else { 2655 + // Strip trailing Z. 2656 + let tp = time_part.strip_suffix('Z').unwrap_or(time_part); 2657 + // Split seconds from milliseconds. 2658 + let (time_str, ms_val) = if let Some((t, m)) = tp.split_once('.') { 2659 + let ms_str = &m[..m.len().min(3)]; 2660 + let mut ms_val: i64 = ms_str.parse().unwrap_or(0); 2661 + // Pad to 3 digits if needed. 2662 + for _ in ms_str.len()..3 { 2663 + ms_val *= 10; 2664 + } 2665 + (t, ms_val) 2666 + } else { 2667 + (tp, 0i64) 2668 + }; 2669 + let time_fields: Vec<&str> = time_str.split(':').collect(); 2670 + let h: i64 = time_fields 2671 + .first() 2672 + .and_then(|s| s.parse().ok()) 2673 + .unwrap_or(0); 2674 + let min: i64 = time_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); 2675 + let sec: i64 = time_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(0); 2676 + (h, min, sec, ms_val) 2677 + }; 2678 + 2679 + Some(utc_components_to_ms(year, month - 1, day, h, min, sec, ms)) 2680 + } 2681 + 2682 + static DAY_NAMES: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 2683 + static MONTH_NAMES: [&str; 12] = [ 2684 + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 2685 + ]; 2686 + 2687 + fn init_date_builtins(vm: &mut Vm) { 2688 + // Date.prototype. 2689 + let mut date_proto_data = ObjectData::new(); 2690 + if let Some(proto) = vm.object_prototype { 2691 + date_proto_data.prototype = Some(proto); 2692 + } 2693 + let date_proto = vm.gc.alloc(HeapObject::Object(date_proto_data)); 2694 + init_date_prototype(&mut vm.gc, date_proto); 2695 + 2696 + // Store prototype for constructor access. 2697 + vm.date_prototype = Some(date_proto); 2698 + DATE_PROTO.with(|cell| cell.set(Some(date_proto))); 2699 + 2700 + // Date constructor function. 2701 + let ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { 2702 + name: "Date".to_string(), 2703 + kind: FunctionKind::Native(NativeFunc { 2704 + callback: date_constructor, 2705 + }), 2706 + prototype_obj: Some(date_proto), 2707 + properties: HashMap::new(), 2708 + upvalues: Vec::new(), 2709 + }))); 2710 + 2711 + // Static methods. 2712 + let now_fn = make_native(&mut vm.gc, "now", date_now); 2713 + set_func_prop(&mut vm.gc, ctor, "now", Value::Function(now_fn)); 2714 + 2715 + let parse_fn = make_native(&mut vm.gc, "parse", date_parse); 2716 + set_func_prop(&mut vm.gc, ctor, "parse", Value::Function(parse_fn)); 2717 + 2718 + let utc_fn = make_native(&mut vm.gc, "UTC", date_utc); 2719 + set_func_prop(&mut vm.gc, ctor, "UTC", Value::Function(utc_fn)); 2720 + 2721 + vm.set_global("Date", Value::Function(ctor)); 2722 + } 2723 + 2724 + /// Internal: create a Date object (plain object with __date_ms__ property). 2725 + fn make_date_obj(gc: &mut Gc<HeapObject>, ms: f64, proto: Option<GcRef>) -> Value { 2726 + let mut data = ObjectData::new(); 2727 + if let Some(p) = proto { 2728 + data.prototype = Some(p); 2729 + } 2730 + data.properties.insert( 2731 + "__date_ms__".to_string(), 2732 + Property::builtin(Value::Number(ms)), 2733 + ); 2734 + Value::Object(gc.alloc(HeapObject::Object(data))) 2735 + } 2736 + 2737 + /// Extract timestamp from a Date object. 2738 + fn date_get_ms(gc: &Gc<HeapObject>, this: &Value) -> f64 { 2739 + match this { 2740 + Value::Object(r) => match gc.get(*r) { 2741 + Some(HeapObject::Object(data)) => data 2742 + .properties 2743 + .get("__date_ms__") 2744 + .map(|p| p.value.to_number()) 2745 + .unwrap_or(f64::NAN), 2746 + _ => f64::NAN, 2747 + }, 2748 + _ => f64::NAN, 2749 + } 2750 + } 2751 + 2752 + /// Set timestamp on a Date object and return the new value. 2753 + fn date_set_ms(gc: &mut Gc<HeapObject>, this: &Value, ms: f64) -> f64 { 2754 + if let Value::Object(r) = this { 2755 + if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { 2756 + if let Some(prop) = data.properties.get_mut("__date_ms__") { 2757 + prop.value = Value::Number(ms); 2758 + } 2759 + } 2760 + } 2761 + ms 2762 + } 2763 + 2764 + fn date_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2765 + let proto = DATE_PROTO.with(|cell| cell.get()); 2766 + let ms = match args.len() { 2767 + 0 => now_ms(), 2768 + 1 => match &args[0] { 2769 + Value::String(s) => parse_date_string(s).unwrap_or(f64::NAN), 2770 + Value::Number(n) => *n, 2771 + v => v.to_number(), 2772 + }, 2773 + _ => { 2774 + // new Date(year, month, day?, hours?, min?, sec?, ms?) 2775 + let year = args[0].to_number() as i64; 2776 + let month = args[1].to_number() as i64; 2777 + let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); 2778 + let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); 2779 + let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); 2780 + let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); 2781 + let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); 2782 + // Years 0-99 map to 1900-1999 per spec. 2783 + let year = if (0..100).contains(&year) { 2784 + year + 1900 2785 + } else { 2786 + year 2787 + }; 2788 + utc_components_to_ms(year, month, day, h, min, sec, ms) 2789 + } 2790 + }; 2791 + Ok(make_date_obj(ctx.gc, ms, proto)) 2792 + } 2793 + 2794 + fn date_now(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2795 + Ok(Value::Number(now_ms())) 2796 + } 2797 + 2798 + fn date_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2799 + let s = args 2800 + .first() 2801 + .map(|v| v.to_js_string(ctx.gc)) 2802 + .unwrap_or_default(); 2803 + Ok(Value::Number(parse_date_string(&s).unwrap_or(f64::NAN))) 2804 + } 2805 + 2806 + fn date_utc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2807 + let year = args.first().map(|v| v.to_number() as i64).unwrap_or(1970); 2808 + let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(0); 2809 + let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); 2810 + let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); 2811 + let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); 2812 + let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); 2813 + let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); 2814 + let year = if (0..100).contains(&year) { 2815 + year + 1900 2816 + } else { 2817 + year 2818 + }; 2819 + Ok(Value::Number(utc_components_to_ms( 2820 + year, month, day, h, min, sec, ms, 2821 + ))) 2822 + } 2823 + 2824 + fn init_date_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { 2825 + let methods: &[NativeMethod] = &[ 2826 + ("getTime", date_get_time), 2827 + ("valueOf", date_get_time), // valueOf === getTime 2828 + ("getFullYear", date_get_full_year), 2829 + ("getMonth", date_get_month), 2830 + ("getDate", date_get_date), 2831 + ("getDay", date_get_day), 2832 + ("getHours", date_get_hours), 2833 + ("getMinutes", date_get_minutes), 2834 + ("getSeconds", date_get_seconds), 2835 + ("getMilliseconds", date_get_milliseconds), 2836 + ("getTimezoneOffset", date_get_timezone_offset), 2837 + ("getUTCFullYear", date_get_full_year), 2838 + ("getUTCMonth", date_get_month), 2839 + ("getUTCDate", date_get_date), 2840 + ("getUTCDay", date_get_day), 2841 + ("getUTCHours", date_get_hours), 2842 + ("getUTCMinutes", date_get_minutes), 2843 + ("getUTCSeconds", date_get_seconds), 2844 + ("getUTCMilliseconds", date_get_milliseconds), 2845 + ("setTime", date_set_time), 2846 + ("setFullYear", date_set_full_year), 2847 + ("setMonth", date_set_month), 2848 + ("setDate", date_set_date), 2849 + ("setHours", date_set_hours), 2850 + ("setMinutes", date_set_minutes), 2851 + ("setSeconds", date_set_seconds), 2852 + ("setMilliseconds", date_set_milliseconds), 2853 + ("toString", date_to_string), 2854 + ("toISOString", date_to_iso_string), 2855 + ("toUTCString", date_to_utc_string), 2856 + ("toLocaleDateString", date_to_locale_date_string), 2857 + ("toJSON", date_to_json), 2858 + ]; 2859 + for &(name, cb) in methods { 2860 + let f = make_native(gc, name, cb); 2861 + set_builtin_prop(gc, proto, name, Value::Function(f)); 2862 + } 2863 + } 2864 + 2865 + fn date_get_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2866 + let _ = args; 2867 + Ok(Value::Number(date_get_ms(ctx.gc, &ctx.this))) 2868 + } 2869 + 2870 + fn date_get_full_year(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2871 + let ms = date_get_ms(ctx.gc, &ctx.this); 2872 + if ms.is_nan() { 2873 + return Ok(Value::Number(f64::NAN)); 2874 + } 2875 + let (y, ..) = ms_to_utc_components(ms); 2876 + Ok(Value::Number(y as f64)) 2877 + } 2878 + 2879 + fn date_get_month(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2880 + let ms = date_get_ms(ctx.gc, &ctx.this); 2881 + if ms.is_nan() { 2882 + return Ok(Value::Number(f64::NAN)); 2883 + } 2884 + let (_, m, ..) = ms_to_utc_components(ms); 2885 + Ok(Value::Number(m as f64)) 2886 + } 2887 + 2888 + fn date_get_date(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2889 + let ms = date_get_ms(ctx.gc, &ctx.this); 2890 + if ms.is_nan() { 2891 + return Ok(Value::Number(f64::NAN)); 2892 + } 2893 + let (_, _, d, ..) = ms_to_utc_components(ms); 2894 + Ok(Value::Number(d as f64)) 2895 + } 2896 + 2897 + fn date_get_day(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2898 + let ms = date_get_ms(ctx.gc, &ctx.this); 2899 + if ms.is_nan() { 2900 + return Ok(Value::Number(f64::NAN)); 2901 + } 2902 + let (.., wd) = ms_to_utc_components(ms); 2903 + Ok(Value::Number(wd as f64)) 2904 + } 2905 + 2906 + fn date_get_hours(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2907 + let ms = date_get_ms(ctx.gc, &ctx.this); 2908 + if ms.is_nan() { 2909 + return Ok(Value::Number(f64::NAN)); 2910 + } 2911 + let (_, _, _, h, ..) = ms_to_utc_components(ms); 2912 + Ok(Value::Number(h as f64)) 2913 + } 2914 + 2915 + fn date_get_minutes(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2916 + let ms = date_get_ms(ctx.gc, &ctx.this); 2917 + if ms.is_nan() { 2918 + return Ok(Value::Number(f64::NAN)); 2919 + } 2920 + let (_, _, _, _, min, ..) = ms_to_utc_components(ms); 2921 + Ok(Value::Number(min as f64)) 2922 + } 2923 + 2924 + fn date_get_seconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2925 + let ms = date_get_ms(ctx.gc, &ctx.this); 2926 + if ms.is_nan() { 2927 + return Ok(Value::Number(f64::NAN)); 2928 + } 2929 + let (_, _, _, _, _, s, ..) = ms_to_utc_components(ms); 2930 + Ok(Value::Number(s as f64)) 2931 + } 2932 + 2933 + fn date_get_milliseconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2934 + let ms = date_get_ms(ctx.gc, &ctx.this); 2935 + if ms.is_nan() { 2936 + return Ok(Value::Number(f64::NAN)); 2937 + } 2938 + let (_, _, _, _, _, _, ms_part, _) = ms_to_utc_components(ms); 2939 + Ok(Value::Number(ms_part as f64)) 2940 + } 2941 + 2942 + fn date_get_timezone_offset( 2943 + _args: &[Value], 2944 + _ctx: &mut NativeContext, 2945 + ) -> Result<Value, RuntimeError> { 2946 + // We operate in UTC; return 0. 2947 + Ok(Value::Number(0.0)) 2948 + } 2949 + 2950 + fn date_set_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2951 + let ms = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); 2952 + date_set_ms(ctx.gc, &ctx.this, ms); 2953 + Ok(Value::Number(ms)) 2954 + } 2955 + 2956 + fn date_set_full_year(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2957 + let old = date_get_ms(ctx.gc, &ctx.this); 2958 + let (_, m, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 2959 + let year = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 2960 + let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(m); 2961 + let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(d); 2962 + let new_ms = utc_components_to_ms(year, month, day, h, min, s, ms); 2963 + date_set_ms(ctx.gc, &ctx.this, new_ms); 2964 + Ok(Value::Number(new_ms)) 2965 + } 2966 + 2967 + fn date_set_month(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2968 + let old = date_get_ms(ctx.gc, &ctx.this); 2969 + let (y, _, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 2970 + let month = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 2971 + let day = args.get(1).map(|v| v.to_number() as i64).unwrap_or(d); 2972 + let new_ms = utc_components_to_ms(y, month, day, h, min, s, ms); 2973 + date_set_ms(ctx.gc, &ctx.this, new_ms); 2974 + Ok(Value::Number(new_ms)) 2975 + } 2976 + 2977 + fn date_set_date(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2978 + let old = date_get_ms(ctx.gc, &ctx.this); 2979 + let (y, m, _, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 2980 + let day = args.first().map(|v| v.to_number() as i64).unwrap_or(1); 2981 + let new_ms = utc_components_to_ms(y, m, day, h, min, s, ms); 2982 + date_set_ms(ctx.gc, &ctx.this, new_ms); 2983 + Ok(Value::Number(new_ms)) 2984 + } 2985 + 2986 + fn date_set_hours(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2987 + let old = date_get_ms(ctx.gc, &ctx.this); 2988 + let (y, m, d, _, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 2989 + let h = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 2990 + let min_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(min); 2991 + let sec = args.get(2).map(|v| v.to_number() as i64).unwrap_or(s); 2992 + let ms_v = args.get(3).map(|v| v.to_number() as i64).unwrap_or(ms); 2993 + let new_ms = utc_components_to_ms(y, m, d, h, min_v, sec, ms_v); 2994 + date_set_ms(ctx.gc, &ctx.this, new_ms); 2995 + Ok(Value::Number(new_ms)) 2996 + } 2997 + 2998 + fn date_set_minutes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 2999 + let old = date_get_ms(ctx.gc, &ctx.this); 3000 + let (y, m, d, h, _, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3001 + let min = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3002 + let sec = args.get(1).map(|v| v.to_number() as i64).unwrap_or(s); 3003 + let ms_v = args.get(2).map(|v| v.to_number() as i64).unwrap_or(ms); 3004 + let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3005 + date_set_ms(ctx.gc, &ctx.this, new_ms); 3006 + Ok(Value::Number(new_ms)) 3007 + } 3008 + 3009 + fn date_set_seconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3010 + let old = date_get_ms(ctx.gc, &ctx.this); 3011 + let (y, m, d, h, min, _, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3012 + let sec = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3013 + let ms_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(ms); 3014 + let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); 3015 + date_set_ms(ctx.gc, &ctx.this, new_ms); 3016 + Ok(Value::Number(new_ms)) 3017 + } 3018 + 3019 + fn date_set_milliseconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3020 + let old = date_get_ms(ctx.gc, &ctx.this); 3021 + let (y, m, d, h, min, s, _, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); 3022 + let ms_v = args.first().map(|v| v.to_number() as i64).unwrap_or(0); 3023 + let new_ms = utc_components_to_ms(y, m, d, h, min, s, ms_v); 3024 + date_set_ms(ctx.gc, &ctx.this, new_ms); 3025 + Ok(Value::Number(new_ms)) 3026 + } 3027 + 3028 + fn date_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3029 + let ms = date_get_ms(ctx.gc, &ctx.this); 3030 + if ms.is_nan() { 3031 + return Ok(Value::String("Invalid Date".to_string())); 3032 + } 3033 + let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); 3034 + Ok(Value::String(format!( 3035 + "{} {} {:02} {} {:02}:{:02}:{:02} GMT", 3036 + DAY_NAMES[wd as usize], MONTH_NAMES[m as usize], d, y, h, min, s 3037 + ))) 3038 + } 3039 + 3040 + fn date_to_iso_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3041 + let ms = date_get_ms(ctx.gc, &ctx.this); 3042 + if ms.is_nan() { 3043 + return Err(RuntimeError::range_error("Invalid time value")); 3044 + } 3045 + let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); 3046 + Ok(Value::String(format!( 3047 + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", 3048 + y, 3049 + m + 1, 3050 + d, 3051 + h, 3052 + min, 3053 + s, 3054 + ms_part 3055 + ))) 3056 + } 3057 + 3058 + fn date_to_utc_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3059 + let ms = date_get_ms(ctx.gc, &ctx.this); 3060 + if ms.is_nan() { 3061 + return Ok(Value::String("Invalid Date".to_string())); 3062 + } 3063 + let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); 3064 + Ok(Value::String(format!( 3065 + "{}, {:02} {} {} {:02}:{:02}:{:02} GMT", 3066 + DAY_NAMES[wd as usize], d, MONTH_NAMES[m as usize], y, h, min, s 3067 + ))) 3068 + } 3069 + 3070 + fn date_to_locale_date_string( 3071 + _args: &[Value], 3072 + ctx: &mut NativeContext, 3073 + ) -> Result<Value, RuntimeError> { 3074 + // Simplified: same as toISOString date portion. 3075 + let ms = date_get_ms(ctx.gc, &ctx.this); 3076 + if ms.is_nan() { 3077 + return Ok(Value::String("Invalid Date".to_string())); 3078 + } 3079 + let (y, m, d, ..) = ms_to_utc_components(ms); 3080 + Ok(Value::String(format!("{:04}-{:02}-{:02}", y, m + 1, d))) 3081 + } 3082 + 3083 + fn date_to_json(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3084 + let ms = date_get_ms(ctx.gc, &ctx.this); 3085 + if ms.is_nan() { 3086 + return Ok(Value::Null); 3087 + } 3088 + date_to_iso_string(_args, ctx) 3089 + } 3090 + 3091 + // ── JSON object ────────────────────────────────────────────── 3092 + 3093 + fn init_json_object(vm: &mut Vm) { 3094 + let mut data = ObjectData::new(); 3095 + if let Some(proto) = vm.object_prototype { 3096 + data.prototype = Some(proto); 3097 + } 3098 + let json_ref = vm.gc.alloc(HeapObject::Object(data)); 3099 + 3100 + let parse_fn = make_native(&mut vm.gc, "parse", json_parse); 3101 + set_builtin_prop(&mut vm.gc, json_ref, "parse", Value::Function(parse_fn)); 3102 + 3103 + let stringify_fn = make_native(&mut vm.gc, "stringify", json_stringify); 3104 + set_builtin_prop( 3105 + &mut vm.gc, 3106 + json_ref, 3107 + "stringify", 3108 + Value::Function(stringify_fn), 3109 + ); 3110 + 3111 + vm.set_global("JSON", Value::Object(json_ref)); 3112 + } 3113 + 3114 + // ── JSON.parse ─────────────────────────────────────────────── 3115 + 3116 + /// Minimal JSON tokenizer. 3117 + #[derive(Debug, Clone, PartialEq)] 3118 + enum JsonToken { 3119 + LBrace, 3120 + RBrace, 3121 + LBracket, 3122 + RBracket, 3123 + Colon, 3124 + Comma, 3125 + String(String), 3126 + Number(f64), 3127 + True, 3128 + False, 3129 + Null, 3130 + } 3131 + 3132 + struct JsonLexer<'a> { 3133 + chars: &'a [u8], 3134 + pos: usize, 3135 + } 3136 + 3137 + impl<'a> JsonLexer<'a> { 3138 + fn new(input: &'a str) -> Self { 3139 + Self { 3140 + chars: input.as_bytes(), 3141 + pos: 0, 3142 + } 3143 + } 3144 + 3145 + fn skip_ws(&mut self) { 3146 + while self.pos < self.chars.len() { 3147 + match self.chars[self.pos] { 3148 + b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1, 3149 + _ => break, 3150 + } 3151 + } 3152 + } 3153 + 3154 + fn next_token(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 3155 + self.skip_ws(); 3156 + if self.pos >= self.chars.len() { 3157 + return Ok(None); 3158 + } 3159 + let ch = self.chars[self.pos]; 3160 + match ch { 3161 + b'{' => { 3162 + self.pos += 1; 3163 + Ok(Some(JsonToken::LBrace)) 3164 + } 3165 + b'}' => { 3166 + self.pos += 1; 3167 + Ok(Some(JsonToken::RBrace)) 3168 + } 3169 + b'[' => { 3170 + self.pos += 1; 3171 + Ok(Some(JsonToken::LBracket)) 3172 + } 3173 + b']' => { 3174 + self.pos += 1; 3175 + Ok(Some(JsonToken::RBracket)) 3176 + } 3177 + b':' => { 3178 + self.pos += 1; 3179 + Ok(Some(JsonToken::Colon)) 3180 + } 3181 + b',' => { 3182 + self.pos += 1; 3183 + Ok(Some(JsonToken::Comma)) 3184 + } 3185 + b'"' => self.read_string(), 3186 + b't' => self.read_keyword("true", JsonToken::True), 3187 + b'f' => self.read_keyword("false", JsonToken::False), 3188 + b'n' => self.read_keyword("null", JsonToken::Null), 3189 + b'-' | b'0'..=b'9' => self.read_number(), 3190 + _ => Err(RuntimeError::syntax_error(format!( 3191 + "Unexpected character '{}' in JSON", 3192 + ch as char 3193 + ))), 3194 + } 3195 + } 3196 + 3197 + fn read_string(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 3198 + self.pos += 1; // skip opening quote 3199 + let mut s = String::new(); 3200 + while self.pos < self.chars.len() { 3201 + let ch = self.chars[self.pos]; 3202 + self.pos += 1; 3203 + if ch == b'"' { 3204 + return Ok(Some(JsonToken::String(s))); 3205 + } 3206 + if ch == b'\\' { 3207 + if self.pos >= self.chars.len() { 3208 + return Err(RuntimeError::syntax_error("Unterminated string in JSON")); 3209 + } 3210 + let esc = self.chars[self.pos]; 3211 + self.pos += 1; 3212 + match esc { 3213 + b'"' => s.push('"'), 3214 + b'\\' => s.push('\\'), 3215 + b'/' => s.push('/'), 3216 + b'b' => s.push('\u{0008}'), 3217 + b'f' => s.push('\u{000C}'), 3218 + b'n' => s.push('\n'), 3219 + b'r' => s.push('\r'), 3220 + b't' => s.push('\t'), 3221 + b'u' => { 3222 + if self.pos + 4 > self.chars.len() { 3223 + return Err(RuntimeError::syntax_error("Invalid unicode escape")); 3224 + } 3225 + let hex = std::str::from_utf8(&self.chars[self.pos..self.pos + 4]) 3226 + .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; 3227 + self.pos += 4; 3228 + let code = u32::from_str_radix(hex, 16) 3229 + .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; 3230 + if let Some(c) = char::from_u32(code) { 3231 + s.push(c); 3232 + } else { 3233 + return Err(RuntimeError::syntax_error("Invalid unicode code point")); 3234 + } 3235 + } 3236 + _ => { 3237 + return Err(RuntimeError::syntax_error(format!( 3238 + "Invalid escape character '\\{}'", 3239 + esc as char 3240 + ))); 3241 + } 3242 + } 3243 + } else { 3244 + s.push(ch as char); 3245 + } 3246 + } 3247 + Err(RuntimeError::syntax_error("Unterminated string in JSON")) 3248 + } 3249 + 3250 + fn read_number(&mut self) -> Result<Option<JsonToken>, RuntimeError> { 3251 + let start = self.pos; 3252 + if self.pos < self.chars.len() && self.chars[self.pos] == b'-' { 3253 + self.pos += 1; 3254 + } 3255 + while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 3256 + self.pos += 1; 3257 + } 3258 + if self.pos < self.chars.len() && self.chars[self.pos] == b'.' { 3259 + self.pos += 1; 3260 + while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 3261 + self.pos += 1; 3262 + } 3263 + } 3264 + if self.pos < self.chars.len() 3265 + && (self.chars[self.pos] == b'e' || self.chars[self.pos] == b'E') 3266 + { 3267 + self.pos += 1; 3268 + if self.pos < self.chars.len() 3269 + && (self.chars[self.pos] == b'+' || self.chars[self.pos] == b'-') 3270 + { 3271 + self.pos += 1; 3272 + } 3273 + while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { 3274 + self.pos += 1; 3275 + } 3276 + } 3277 + let num_str = std::str::from_utf8(&self.chars[start..self.pos]) 3278 + .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; 3279 + let n: f64 = num_str 3280 + .parse() 3281 + .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; 3282 + Ok(Some(JsonToken::Number(n))) 3283 + } 3284 + 3285 + fn read_keyword( 3286 + &mut self, 3287 + keyword: &str, 3288 + token: JsonToken, 3289 + ) -> Result<Option<JsonToken>, RuntimeError> { 3290 + let end = self.pos + keyword.len(); 3291 + if end <= self.chars.len() && &self.chars[self.pos..end] == keyword.as_bytes() { 3292 + self.pos = end; 3293 + Ok(Some(token)) 3294 + } else { 3295 + Err(RuntimeError::syntax_error(format!( 3296 + "Unexpected token in JSON at position {}", 3297 + self.pos 3298 + ))) 3299 + } 3300 + } 3301 + } 3302 + 3303 + struct JsonParser<'a> { 3304 + lexer: JsonLexer<'a>, 3305 + current: Option<JsonToken>, 3306 + } 3307 + 3308 + impl<'a> JsonParser<'a> { 3309 + fn new(input: &'a str) -> Result<Self, RuntimeError> { 3310 + let mut lexer = JsonLexer::new(input); 3311 + let current = lexer.next_token()?; 3312 + Ok(Self { lexer, current }) 3313 + } 3314 + 3315 + fn advance(&mut self) -> Result<(), RuntimeError> { 3316 + self.current = self.lexer.next_token()?; 3317 + Ok(()) 3318 + } 3319 + 3320 + fn parse_value(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 3321 + match self.current.take() { 3322 + Some(JsonToken::Null) => { 3323 + self.advance()?; 3324 + Ok(Value::Null) 3325 + } 3326 + Some(JsonToken::True) => { 3327 + self.advance()?; 3328 + Ok(Value::Boolean(true)) 3329 + } 3330 + Some(JsonToken::False) => { 3331 + self.advance()?; 3332 + Ok(Value::Boolean(false)) 3333 + } 3334 + Some(JsonToken::Number(n)) => { 3335 + self.advance()?; 3336 + Ok(Value::Number(n)) 3337 + } 3338 + Some(JsonToken::String(s)) => { 3339 + self.advance()?; 3340 + Ok(Value::String(s)) 3341 + } 3342 + Some(JsonToken::LBracket) => self.parse_array(gc), 3343 + Some(JsonToken::LBrace) => self.parse_object(gc), 3344 + Some(other) => { 3345 + // Put it back for error context. 3346 + self.current = Some(other); 3347 + Err(RuntimeError::syntax_error("Unexpected token in JSON")) 3348 + } 3349 + None => Err(RuntimeError::syntax_error("Unexpected end of JSON input")), 3350 + } 3351 + } 3352 + 3353 + fn parse_array(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 3354 + // Current token was LBracket, already consumed via take(). 3355 + self.advance()?; // move past '[' 3356 + let mut items: Vec<Value> = Vec::new(); 3357 + if self.current == Some(JsonToken::RBracket) { 3358 + self.advance()?; 3359 + let mut obj = ObjectData::new(); 3360 + obj.properties.insert( 3361 + "length".to_string(), 3362 + Property { 3363 + value: Value::Number(0.0), 3364 + writable: true, 3365 + enumerable: false, 3366 + configurable: false, 3367 + }, 3368 + ); 3369 + return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); 3370 + } 3371 + loop { 3372 + let val = self.parse_value(gc)?; 3373 + items.push(val); 3374 + match &self.current { 3375 + Some(JsonToken::Comma) => { 3376 + self.advance()?; 3377 + } 3378 + Some(JsonToken::RBracket) => { 3379 + self.advance()?; 3380 + break; 3381 + } 3382 + _ => { 3383 + return Err(RuntimeError::syntax_error( 3384 + "Expected ',' or ']' in JSON array", 3385 + )); 3386 + } 3387 + } 3388 + } 3389 + let mut obj = ObjectData::new(); 3390 + for (i, v) in items.iter().enumerate() { 3391 + obj.properties 3392 + .insert(i.to_string(), Property::data(v.clone())); 3393 + } 3394 + obj.properties.insert( 3395 + "length".to_string(), 3396 + Property { 3397 + value: Value::Number(items.len() as f64), 3398 + writable: true, 3399 + enumerable: false, 3400 + configurable: false, 3401 + }, 3402 + ); 3403 + Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) 3404 + } 3405 + 3406 + fn parse_object(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { 3407 + self.advance()?; // move past '{' 3408 + let mut obj = ObjectData::new(); 3409 + if self.current == Some(JsonToken::RBrace) { 3410 + self.advance()?; 3411 + return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); 3412 + } 3413 + loop { 3414 + let key = match self.current.take() { 3415 + Some(JsonToken::String(s)) => s, 3416 + _ => { 3417 + return Err(RuntimeError::syntax_error( 3418 + "Expected string key in JSON object", 3419 + )); 3420 + } 3421 + }; 3422 + self.advance()?; 3423 + if self.current != Some(JsonToken::Colon) { 3424 + return Err(RuntimeError::syntax_error( 3425 + "Expected ':' after key in JSON object", 3426 + )); 3427 + } 3428 + self.advance()?; 3429 + let val = self.parse_value(gc)?; 3430 + obj.properties.insert(key, Property::data(val)); 3431 + match &self.current { 3432 + Some(JsonToken::Comma) => { 3433 + self.advance()?; 3434 + } 3435 + Some(JsonToken::RBrace) => { 3436 + self.advance()?; 3437 + break; 3438 + } 3439 + _ => { 3440 + return Err(RuntimeError::syntax_error( 3441 + "Expected ',' or '}}' in JSON object", 3442 + )); 3443 + } 3444 + } 3445 + } 3446 + Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) 3447 + } 3448 + } 3449 + 3450 + fn json_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3451 + let text = args 3452 + .first() 3453 + .map(|v| v.to_js_string(ctx.gc)) 3454 + .unwrap_or_default(); 3455 + let mut parser = JsonParser::new(&text)?; 3456 + let value = parser.parse_value(ctx.gc)?; 3457 + // Ensure no trailing content. 3458 + if parser.current.is_some() { 3459 + return Err(RuntimeError::syntax_error( 3460 + "Unexpected token after JSON value", 3461 + )); 3462 + } 3463 + // TODO: reviver support — skip for now. 3464 + Ok(value) 3465 + } 3466 + 3467 + // ── JSON.stringify ─────────────────────────────────────────── 3468 + 3469 + fn json_stringify(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { 3470 + let value = args.first().cloned().unwrap_or(Value::Undefined); 3471 + let indent = match args.get(2) { 3472 + Some(Value::Number(n)) => { 3473 + let n = *n as usize; 3474 + if n > 0 { 3475 + " ".repeat(n.min(10)) 3476 + } else { 3477 + String::new() 3478 + } 3479 + } 3480 + Some(Value::String(s)) => s[..s.len().min(10)].to_string(), 3481 + _ => String::new(), 3482 + }; 3483 + // Track visited objects for circular reference detection. 3484 + let mut visited: Vec<GcRef> = Vec::new(); 3485 + let result = json_stringify_value(&value, ctx.gc, &indent, "", &mut visited)?; 3486 + match result { 3487 + Some(s) => Ok(Value::String(s)), 3488 + None => Ok(Value::Undefined), 3489 + } 3490 + } 3491 + 3492 + fn json_stringify_value( 3493 + value: &Value, 3494 + gc: &Gc<HeapObject>, 3495 + indent: &str, 3496 + current_indent: &str, 3497 + visited: &mut Vec<GcRef>, 3498 + ) -> Result<Option<String>, RuntimeError> { 3499 + match value { 3500 + Value::Null => Ok(Some("null".to_string())), 3501 + Value::Boolean(true) => Ok(Some("true".to_string())), 3502 + Value::Boolean(false) => Ok(Some("false".to_string())), 3503 + Value::Number(n) => { 3504 + if n.is_nan() || n.is_infinite() { 3505 + Ok(Some("null".to_string())) 3506 + } else { 3507 + Ok(Some(js_number_to_string(*n))) 3508 + } 3509 + } 3510 + Value::String(s) => Ok(Some(json_quote_string(s))), 3511 + Value::Undefined | Value::Function(_) => Ok(None), 3512 + Value::Object(gc_ref) => { 3513 + // Circular reference check. 3514 + if visited.contains(gc_ref) { 3515 + return Err(RuntimeError::type_error( 3516 + "Converting circular structure to JSON", 3517 + )); 3518 + } 3519 + visited.push(*gc_ref); 3520 + 3521 + let result = match gc.get(*gc_ref) { 3522 + Some(HeapObject::Object(data)) => { 3523 + // Check for toJSON method. 3524 + if let Some(prop) = data.properties.get("toJSON") { 3525 + if matches!(prop.value, Value::Function(_)) { 3526 + // We can't call JS functions from here easily, 3527 + // but for Date objects we recognize the __date_ms__ pattern. 3528 + if let Some(date_prop) = data.properties.get("__date_ms__") { 3529 + let ms = date_prop.value.to_number(); 3530 + if ms.is_nan() { 3531 + visited.pop(); 3532 + return Ok(Some("null".to_string())); 3533 + } 3534 + let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); 3535 + visited.pop(); 3536 + return Ok(Some(format!( 3537 + "\"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z\"", 3538 + y, 3539 + m + 1, 3540 + d, 3541 + h, 3542 + min, 3543 + s, 3544 + ms_part 3545 + ))); 3546 + } 3547 + } 3548 + } 3549 + if array_length_exists(gc, *gc_ref) { 3550 + json_stringify_array(gc, *gc_ref, indent, current_indent, visited) 3551 + } else { 3552 + json_stringify_object(gc, data, indent, current_indent, visited) 3553 + } 3554 + } 3555 + _ => Ok(Some("{}".to_string())), 3556 + }; 3557 + 3558 + visited.pop(); 3559 + result 3560 + } 3561 + } 3562 + } 3563 + 3564 + fn json_stringify_array( 3565 + gc: &Gc<HeapObject>, 3566 + arr: GcRef, 3567 + indent: &str, 3568 + current_indent: &str, 3569 + visited: &mut Vec<GcRef>, 3570 + ) -> Result<Option<String>, RuntimeError> { 3571 + let len = array_length(gc, arr); 3572 + if len == 0 { 3573 + return Ok(Some("[]".to_string())); 3574 + } 3575 + let has_indent = !indent.is_empty(); 3576 + let next_indent = if has_indent { 3577 + format!("{}{}", current_indent, indent) 3578 + } else { 3579 + String::new() 3580 + }; 3581 + let mut parts: Vec<String> = Vec::new(); 3582 + for i in 0..len { 3583 + let val = array_get(gc, arr, i); 3584 + match json_stringify_value(&val, gc, indent, &next_indent, visited)? { 3585 + Some(s) => parts.push(s), 3586 + None => parts.push("null".to_string()), 3587 + } 3588 + } 3589 + if has_indent { 3590 + Ok(Some(format!( 3591 + "[\n{}{}\n{}]", 3592 + next_indent, 3593 + parts.join(&format!(",\n{}", next_indent)), 3594 + current_indent 3595 + ))) 3596 + } else { 3597 + Ok(Some(format!("[{}]", parts.join(",")))) 3598 + } 3599 + } 3600 + 3601 + fn json_stringify_object( 3602 + gc: &Gc<HeapObject>, 3603 + data: &ObjectData, 3604 + indent: &str, 3605 + current_indent: &str, 3606 + visited: &mut Vec<GcRef>, 3607 + ) -> Result<Option<String>, RuntimeError> { 3608 + let has_indent = !indent.is_empty(); 3609 + let next_indent = if has_indent { 3610 + format!("{}{}", current_indent, indent) 3611 + } else { 3612 + String::new() 3613 + }; 3614 + let mut parts: Vec<String> = Vec::new(); 3615 + // Collect and sort keys for deterministic output. 3616 + let mut keys: Vec<&String> = data.properties.keys().collect(); 3617 + keys.sort(); 3618 + for key in keys { 3619 + let prop = &data.properties[key]; 3620 + if !prop.enumerable { 3621 + continue; 3622 + } 3623 + if let Some(val_str) = json_stringify_value(&prop.value, gc, indent, &next_indent, visited)? 3624 + { 3625 + if has_indent { 3626 + parts.push(format!("{}: {}", json_quote_string(key), val_str)); 3627 + } else { 3628 + parts.push(format!("{}:{}", json_quote_string(key), val_str)); 3629 + } 3630 + } 3631 + } 3632 + if parts.is_empty() { 3633 + return Ok(Some("{}".to_string())); 3634 + } 3635 + if has_indent { 3636 + Ok(Some(format!( 3637 + "{{\n{}{}\n{}}}", 3638 + next_indent, 3639 + parts.join(&format!(",\n{}", next_indent)), 3640 + current_indent 3641 + ))) 3642 + } else { 3643 + Ok(Some(format!("{{{}}}", parts.join(",")))) 3644 + } 3645 + } 3646 + 3647 + fn json_quote_string(s: &str) -> String { 3648 + let mut out = String::with_capacity(s.len() + 2); 3649 + out.push('"'); 3650 + for ch in s.chars() { 3651 + match ch { 3652 + '"' => out.push_str("\\\""), 3653 + '\\' => out.push_str("\\\\"), 3654 + '\n' => out.push_str("\\n"), 3655 + '\r' => out.push_str("\\r"), 3656 + '\t' => out.push_str("\\t"), 3657 + '\u{0008}' => out.push_str("\\b"), 3658 + '\u{000C}' => out.push_str("\\f"), 3659 + c if c < '\u{0020}' => { 3660 + out.push_str(&format!("\\u{:04x}", c as u32)); 3661 + } 3662 + c => out.push(c), 3663 + } 3664 + } 3665 + out.push('"'); 3666 + out 2270 3667 } 2271 3668 2272 3669 // ── Global utility functions ─────────────────────────────────
+451 -1
crates/js/src/vm.rs
··· 310 310 } 311 311 312 312 /// Format a number as JS would. 313 - fn js_number_to_string(n: f64) -> String { 313 + pub(crate) fn js_number_to_string(n: f64) -> String { 314 314 if n.is_nan() { 315 315 "NaN".to_string() 316 316 } else if n.is_infinite() { ··· 377 377 pub fn range_error(msg: impl Into<String>) -> Self { 378 378 Self { 379 379 kind: ErrorKind::RangeError, 380 + message: msg.into(), 381 + } 382 + } 383 + 384 + pub fn syntax_error(msg: impl Into<String>) -> Self { 385 + Self { 386 + kind: ErrorKind::SyntaxError, 380 387 message: msg.into(), 381 388 } 382 389 } ··· 688 695 pub number_prototype: Option<GcRef>, 689 696 /// Built-in Boolean.prototype (for primitive auto-boxing). 690 697 pub boolean_prototype: Option<GcRef>, 698 + /// Built-in Date.prototype (for Date constructor objects). 699 + pub date_prototype: Option<GcRef>, 691 700 } 692 701 693 702 /// Maximum register file size. ··· 709 718 string_prototype: None, 710 719 number_prototype: None, 711 720 boolean_prototype: None, 721 + date_prototype: None, 712 722 }; 713 723 crate::builtins::init_builtins(&mut vm); 714 724 vm ··· 3740 3750 match eval("'hello world'.substr(6, 5)").unwrap() { 3741 3751 Value::String(s) => assert_eq!(s, "world"), 3742 3752 v => panic!("expected 'world', got {v:?}"), 3753 + } 3754 + } 3755 + 3756 + // ── Math built-in ───────────────────────────────────────── 3757 + 3758 + #[test] 3759 + fn test_math_constants() { 3760 + let r = eval("Math.PI").unwrap(); 3761 + match r { 3762 + Value::Number(n) => assert!((n - std::f64::consts::PI).abs() < 1e-10), 3763 + _ => panic!("Expected number"), 3764 + } 3765 + let r = eval("Math.E").unwrap(); 3766 + match r { 3767 + Value::Number(n) => assert!((n - std::f64::consts::E).abs() < 1e-10), 3768 + _ => panic!("Expected number"), 3769 + } 3770 + let r = eval("Math.SQRT2").unwrap(); 3771 + match r { 3772 + Value::Number(n) => assert!((n - std::f64::consts::SQRT_2).abs() < 1e-10), 3773 + _ => panic!("Expected number"), 3774 + } 3775 + } 3776 + 3777 + #[test] 3778 + fn test_math_abs() { 3779 + assert_eq!(eval("Math.abs(-5)").unwrap().to_number(), 5.0); 3780 + assert_eq!(eval("Math.abs(3)").unwrap().to_number(), 3.0); 3781 + assert_eq!(eval("Math.abs(0)").unwrap().to_number(), 0.0); 3782 + } 3783 + 3784 + #[test] 3785 + fn test_math_floor_ceil_round_trunc() { 3786 + assert_eq!(eval("Math.floor(4.7)").unwrap().to_number(), 4.0); 3787 + assert_eq!(eval("Math.ceil(4.2)").unwrap().to_number(), 5.0); 3788 + assert_eq!(eval("Math.round(4.5)").unwrap().to_number(), 5.0); 3789 + assert_eq!(eval("Math.round(4.4)").unwrap().to_number(), 4.0); 3790 + assert_eq!(eval("Math.trunc(4.7)").unwrap().to_number(), 4.0); 3791 + assert_eq!(eval("Math.trunc(-4.7)").unwrap().to_number(), -4.0); 3792 + } 3793 + 3794 + #[test] 3795 + fn test_math_max_min() { 3796 + assert_eq!(eval("Math.max(1, 3, 2)").unwrap().to_number(), 3.0); 3797 + assert_eq!(eval("Math.min(1, 3, 2)").unwrap().to_number(), 1.0); 3798 + assert_eq!(eval("Math.max()").unwrap().to_number(), f64::NEG_INFINITY); 3799 + assert_eq!(eval("Math.min()").unwrap().to_number(), f64::INFINITY); 3800 + } 3801 + 3802 + #[test] 3803 + fn test_math_pow_sqrt() { 3804 + assert_eq!(eval("Math.pow(2, 10)").unwrap().to_number(), 1024.0); 3805 + assert_eq!(eval("Math.sqrt(9)").unwrap().to_number(), 3.0); 3806 + let cbrt = eval("Math.cbrt(27)").unwrap().to_number(); 3807 + assert!((cbrt - 3.0).abs() < 1e-10); 3808 + } 3809 + 3810 + #[test] 3811 + fn test_math_trig() { 3812 + let sin = eval("Math.sin(0)").unwrap().to_number(); 3813 + assert!(sin.abs() < 1e-10); 3814 + let cos = eval("Math.cos(0)").unwrap().to_number(); 3815 + assert!((cos - 1.0).abs() < 1e-10); 3816 + let atan2 = eval("Math.atan2(1, 1)").unwrap().to_number(); 3817 + assert!((atan2 - std::f64::consts::FRAC_PI_4).abs() < 1e-10); 3818 + } 3819 + 3820 + #[test] 3821 + fn test_math_log_exp() { 3822 + let exp = eval("Math.exp(1)").unwrap().to_number(); 3823 + assert!((exp - std::f64::consts::E).abs() < 1e-10); 3824 + let log = eval("Math.log(Math.E)").unwrap().to_number(); 3825 + assert!((log - 1.0).abs() < 1e-10); 3826 + let log2 = eval("Math.log2(8)").unwrap().to_number(); 3827 + assert!((log2 - 3.0).abs() < 1e-10); 3828 + let log10 = eval("Math.log10(1000)").unwrap().to_number(); 3829 + assert!((log10 - 3.0).abs() < 1e-10); 3830 + } 3831 + 3832 + #[test] 3833 + fn test_math_sign() { 3834 + assert_eq!(eval("Math.sign(5)").unwrap().to_number(), 1.0); 3835 + assert_eq!(eval("Math.sign(-5)").unwrap().to_number(), -1.0); 3836 + assert_eq!(eval("Math.sign(0)").unwrap().to_number(), 0.0); 3837 + } 3838 + 3839 + #[test] 3840 + fn test_math_clz32() { 3841 + assert_eq!(eval("Math.clz32(1)").unwrap().to_number(), 31.0); 3842 + assert_eq!(eval("Math.clz32(0)").unwrap().to_number(), 32.0); 3843 + } 3844 + 3845 + #[test] 3846 + fn test_math_imul() { 3847 + assert_eq!(eval("Math.imul(3, 4)").unwrap().to_number(), 12.0); 3848 + assert_eq!(eval("Math.imul(0xffffffff, 5)").unwrap().to_number(), -5.0); 3849 + } 3850 + 3851 + #[test] 3852 + fn test_math_hypot() { 3853 + assert_eq!(eval("Math.hypot(3, 4)").unwrap().to_number(), 5.0); 3854 + assert_eq!(eval("Math.hypot()").unwrap().to_number(), 0.0); 3855 + } 3856 + 3857 + #[test] 3858 + fn test_math_random() { 3859 + match eval("var r = Math.random(); r >= 0 && r < 1").unwrap() { 3860 + Value::Boolean(b) => assert!(b), 3861 + v => panic!("expected true, got {v:?}"), 3862 + } 3863 + } 3864 + 3865 + #[test] 3866 + fn test_math_fround() { 3867 + let r = eval("Math.fround(5.5)").unwrap().to_number(); 3868 + assert_eq!(r, 5.5f32 as f64); 3869 + } 3870 + 3871 + // ── Date built-in ───────────────────────────────────────── 3872 + 3873 + #[test] 3874 + fn test_date_now() { 3875 + let r = eval("Date.now()").unwrap().to_number(); 3876 + assert!(r > 1_577_836_800_000.0); 3877 + } 3878 + 3879 + #[test] 3880 + fn test_date_utc() { 3881 + let r = eval("Date.UTC(2020, 0, 1)").unwrap().to_number(); 3882 + assert_eq!(r, 1_577_836_800_000.0); 3883 + } 3884 + 3885 + #[test] 3886 + fn test_date_parse() { 3887 + let r = eval("Date.parse('2020-01-01T00:00:00.000Z')") 3888 + .unwrap() 3889 + .to_number(); 3890 + assert_eq!(r, 1_577_836_800_000.0); 3891 + } 3892 + 3893 + #[test] 3894 + fn test_date_constructor_ms() { 3895 + let r = eval("var d = new Date(1577836800000); d.getFullYear()") 3896 + .unwrap() 3897 + .to_number(); 3898 + assert_eq!(r, 2020.0); 3899 + } 3900 + 3901 + #[test] 3902 + fn test_date_constructor_components() { 3903 + let r = eval("var d = new Date(2020, 0, 1, 0, 0, 0, 0); d.getTime()") 3904 + .unwrap() 3905 + .to_number(); 3906 + assert_eq!(r, 1_577_836_800_000.0); 3907 + } 3908 + 3909 + #[test] 3910 + fn test_date_getters() { 3911 + let src = r#" 3912 + var d = new Date(1577836800000); 3913 + var results = [ 3914 + d.getFullYear(), 3915 + d.getMonth(), 3916 + d.getDate(), 3917 + d.getHours(), 3918 + d.getMinutes(), 3919 + d.getSeconds(), 3920 + d.getMilliseconds(), 3921 + d.getDay() 3922 + ]; 3923 + results[0] === 2020 && results[1] === 0 && results[2] === 1 && 3924 + results[3] === 0 && results[4] === 0 && results[5] === 0 && 3925 + results[6] === 0 && results[7] === 3 3926 + "#; 3927 + match eval(src).unwrap() { 3928 + Value::Boolean(b) => assert!(b), 3929 + v => panic!("expected true, got {v:?}"), 3930 + } 3931 + } 3932 + 3933 + #[test] 3934 + fn test_date_setters() { 3935 + let src = r#" 3936 + var d = new Date(1577836800000); 3937 + d.setFullYear(2025); 3938 + d.getFullYear() 3939 + "#; 3940 + assert_eq!(eval(src).unwrap().to_number(), 2025.0); 3941 + } 3942 + 3943 + #[test] 3944 + fn test_date_to_iso_string() { 3945 + match eval("var d = new Date(1577836800000); d.toISOString()").unwrap() { 3946 + Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"), 3947 + v => panic!("expected ISO string, got {v:?}"), 3948 + } 3949 + } 3950 + 3951 + #[test] 3952 + fn test_date_value_of() { 3953 + let r = eval("var d = new Date(1577836800000); d.valueOf()") 3954 + .unwrap() 3955 + .to_number(); 3956 + assert_eq!(r, 1_577_836_800_000.0); 3957 + } 3958 + 3959 + #[test] 3960 + fn test_date_to_string() { 3961 + let r = eval("var d = new Date(1577836800000); d.toString()").unwrap(); 3962 + match r { 3963 + Value::String(s) => assert!(s.contains("2020") && s.contains("GMT")), 3964 + _ => panic!("Expected string"), 3965 + } 3966 + } 3967 + 3968 + #[test] 3969 + fn test_date_to_json() { 3970 + match eval("var d = new Date(1577836800000); d.toJSON()").unwrap() { 3971 + Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"), 3972 + v => panic!("expected ISO string, got {v:?}"), 3973 + } 3974 + } 3975 + 3976 + #[test] 3977 + fn test_date_constructor_string() { 3978 + let r = eval("var d = new Date('2020-06-15T12:30:00Z'); d.getMonth()") 3979 + .unwrap() 3980 + .to_number(); 3981 + assert_eq!(r, 5.0); 3982 + } 3983 + 3984 + // ── JSON built-in ───────────────────────────────────────── 3985 + 3986 + #[test] 3987 + fn test_json_parse_primitives() { 3988 + assert!(matches!(eval("JSON.parse('null')").unwrap(), Value::Null)); 3989 + match eval("JSON.parse('true')").unwrap() { 3990 + Value::Boolean(b) => assert!(b), 3991 + v => panic!("expected true, got {v:?}"), 3992 + } 3993 + match eval("JSON.parse('false')").unwrap() { 3994 + Value::Boolean(b) => assert!(!b), 3995 + v => panic!("expected false, got {v:?}"), 3996 + } 3997 + assert_eq!(eval("JSON.parse('42')").unwrap().to_number(), 42.0); 3998 + match eval(r#"JSON.parse('"hello"')"#).unwrap() { 3999 + Value::String(s) => assert_eq!(s, "hello"), 4000 + v => panic!("expected 'hello', got {v:?}"), 4001 + } 4002 + } 4003 + 4004 + #[test] 4005 + fn test_json_parse_array() { 4006 + let src = r#" 4007 + var a = JSON.parse('[1, 2, 3]'); 4008 + a.length === 3 && a[0] === 1 && a[1] === 2 && a[2] === 3 4009 + "#; 4010 + match eval(src).unwrap() { 4011 + Value::Boolean(b) => assert!(b), 4012 + v => panic!("expected true, got {v:?}"), 4013 + } 4014 + } 4015 + 4016 + #[test] 4017 + fn test_json_parse_object() { 4018 + let src = r#" 4019 + var o = JSON.parse('{"name":"test","value":42}'); 4020 + o.name === "test" && o.value === 42 4021 + "#; 4022 + match eval(src).unwrap() { 4023 + Value::Boolean(b) => assert!(b), 4024 + v => panic!("expected true, got {v:?}"), 4025 + } 4026 + } 4027 + 4028 + #[test] 4029 + fn test_json_parse_nested() { 4030 + let src = r#" 4031 + var o = JSON.parse('{"a":[1,{"b":2}]}'); 4032 + o.a[1].b === 2 4033 + "#; 4034 + match eval(src).unwrap() { 4035 + Value::Boolean(b) => assert!(b), 4036 + v => panic!("expected true, got {v:?}"), 4037 + } 4038 + } 4039 + 4040 + #[test] 4041 + fn test_json_parse_invalid() { 4042 + assert!(eval("JSON.parse('{invalid}')").is_err()); 4043 + assert!(eval("JSON.parse('')").is_err()); 4044 + } 4045 + 4046 + #[test] 4047 + fn test_json_stringify_primitives() { 4048 + match eval("JSON.stringify(null)").unwrap() { 4049 + Value::String(s) => assert_eq!(s, "null"), 4050 + v => panic!("expected 'null', got {v:?}"), 4051 + } 4052 + match eval("JSON.stringify(true)").unwrap() { 4053 + Value::String(s) => assert_eq!(s, "true"), 4054 + v => panic!("expected 'true', got {v:?}"), 4055 + } 4056 + match eval("JSON.stringify(42)").unwrap() { 4057 + Value::String(s) => assert_eq!(s, "42"), 4058 + v => panic!("expected '42', got {v:?}"), 4059 + } 4060 + match eval(r#"JSON.stringify("hello")"#).unwrap() { 4061 + Value::String(s) => assert_eq!(s, "\"hello\""), 4062 + v => panic!("expected quoted hello, got {v:?}"), 4063 + } 4064 + } 4065 + 4066 + #[test] 4067 + fn test_json_stringify_array() { 4068 + match eval("JSON.stringify([1, 2, 3])").unwrap() { 4069 + Value::String(s) => assert_eq!(s, "[1,2,3]"), 4070 + v => panic!("expected '[1,2,3]', got {v:?}"), 4071 + } 4072 + } 4073 + 4074 + #[test] 4075 + fn test_json_stringify_object() { 4076 + let src = r#" 4077 + var o = {a: 1, b: "hello"}; 4078 + JSON.stringify(o) 4079 + "#; 4080 + let r = eval(src).unwrap(); 4081 + match r { 4082 + Value::String(s) => { 4083 + assert!(s.contains("\"a\"") && s.contains("\"b\"")); 4084 + assert!(s.contains('1') && s.contains("\"hello\"")); 4085 + } 4086 + _ => panic!("Expected string"), 4087 + } 4088 + } 4089 + 4090 + #[test] 4091 + fn test_json_stringify_nested() { 4092 + let src = r#" 4093 + JSON.stringify({a: [1, 2], b: {c: 3}}) 4094 + "#; 4095 + let r = eval(src).unwrap(); 4096 + match r { 4097 + Value::String(s) => { 4098 + assert!(s.contains("[1,2]")); 4099 + assert!(s.contains("\"c\":3") || s.contains("\"c\": 3")); 4100 + } 4101 + _ => panic!("Expected string"), 4102 + } 4103 + } 4104 + 4105 + #[test] 4106 + fn test_json_stringify_special_values() { 4107 + match eval("JSON.stringify(NaN)").unwrap() { 4108 + Value::String(s) => assert_eq!(s, "null"), 4109 + v => panic!("expected 'null', got {v:?}"), 4110 + } 4111 + match eval("JSON.stringify(Infinity)").unwrap() { 4112 + Value::String(s) => assert_eq!(s, "null"), 4113 + v => panic!("expected 'null', got {v:?}"), 4114 + } 4115 + assert!(matches!( 4116 + eval("JSON.stringify(undefined)").unwrap(), 4117 + Value::Undefined 4118 + )); 4119 + } 4120 + 4121 + #[test] 4122 + fn test_json_stringify_with_indent() { 4123 + let src = r#"JSON.stringify([1, 2], null, 2)"#; 4124 + let r = eval(src).unwrap(); 4125 + match r { 4126 + Value::String(s) => { 4127 + assert!(s.contains('\n')); 4128 + assert!(s.contains(" 1")); 4129 + } 4130 + _ => panic!("Expected string"), 4131 + } 4132 + } 4133 + 4134 + #[test] 4135 + fn test_json_parse_escape_sequences() { 4136 + let src = r#"JSON.parse('"hello\\nworld"')"#; 4137 + match eval(src).unwrap() { 4138 + Value::String(s) => assert_eq!(s, "hello\nworld"), 4139 + v => panic!("expected escaped string, got {v:?}"), 4140 + } 4141 + } 4142 + 4143 + #[test] 4144 + fn test_json_roundtrip() { 4145 + let src = r#" 4146 + var original = {name: "test", values: [1, 2, 3], nested: {ok: true}}; 4147 + var json = JSON.stringify(original); 4148 + var parsed = JSON.parse(json); 4149 + parsed.name === "test" && parsed.values[1] === 2 && parsed.nested.ok === true 4150 + "#; 4151 + match eval(src).unwrap() { 4152 + Value::Boolean(b) => assert!(b), 4153 + v => panic!("expected true, got {v:?}"), 4154 + } 4155 + } 4156 + 4157 + #[test] 4158 + fn test_json_stringify_circular_detection() { 4159 + let src = r#" 4160 + var obj = {}; 4161 + obj.self = obj; 4162 + try { 4163 + JSON.stringify(obj); 4164 + false; 4165 + } catch (e) { 4166 + e.message.indexOf("circular") !== -1; 4167 + } 4168 + "#; 4169 + match eval(src).unwrap() { 4170 + Value::Boolean(b) => assert!(b), 4171 + v => panic!("expected true, got {v:?}"), 4172 + } 4173 + } 4174 + 4175 + #[test] 4176 + fn test_json_parse_unicode_escape() { 4177 + let src = r#"JSON.parse('"\\u0041"')"#; 4178 + match eval(src).unwrap() { 4179 + Value::String(s) => assert_eq!(s, "A"), 4180 + v => panic!("expected 'A', got {v:?}"), 4181 + } 4182 + } 4183 + 4184 + #[test] 4185 + fn test_json_stringify_empty() { 4186 + match eval("JSON.stringify([])").unwrap() { 4187 + Value::String(s) => assert_eq!(s, "[]"), 4188 + v => panic!("expected '[]', got {v:?}"), 4189 + } 4190 + match eval("JSON.stringify({})").unwrap() { 4191 + Value::String(s) => assert_eq!(s, "{}"), 4192 + v => panic!("expected '{{}}', got {v:?}"), 3743 4193 } 3744 4194 } 3745 4195 }