Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941//! Built-in JavaScript objects and functions: Object, Array, Function, Error,//! String, Number, Boolean, Symbol, Math, Date, JSON.//!//! Registers constructors, static methods, and prototype methods as globals//! in the VM. Callback-based array methods (map, filter, etc.) are defined//! via a JS preamble executed at init time.
use crate::gc::{Gc, GcRef};use crate::vm::*;use std::collections::HashMap;use std::time::{SystemTime, UNIX_EPOCH};
/// Native callback type alias to satisfy clippy::type_complexity.type NativeMethod = ( &'static str, fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,);
// ── Helpers ──────────────────────────────────────────────────
/// Create a native function GcRef.fn make_native( gc: &mut Gc<HeapObject>, name: &str, callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,) -> GcRef { gc.alloc(HeapObject::Function(Box::new(FunctionData { name: name.to_string(), kind: FunctionKind::Native(NativeFunc { callback }), prototype_obj: None, properties: HashMap::new(), upvalues: Vec::new(), })))}
/// Set a non-enumerable property on an object.fn set_builtin_prop(gc: &mut Gc<HeapObject>, obj: GcRef, key: &str, val: Value) { if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { data.properties .insert(key.to_string(), Property::builtin(val)); }}
/// Set a non-enumerable property on a function.fn set_func_prop(gc: &mut Gc<HeapObject>, func: GcRef, key: &str, val: Value) { if let Some(HeapObject::Function(fdata)) = gc.get_mut(func) { fdata .properties .insert(key.to_string(), Property::builtin(val)); }}
/// Get own enumerable string keys of an object (for Object.keys, etc.).fn own_enumerable_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { match gc.get(obj_ref) { Some(HeapObject::Object(data)) => { let mut int_keys: Vec<(u32, String)> = Vec::new(); let mut str_keys: Vec<String> = Vec::new(); for (k, prop) in &data.properties { if prop.enumerable { if let Ok(idx) = k.parse::<u32>() { int_keys.push((idx, k.clone())); } else { str_keys.push(k.clone()); } } } int_keys.sort_by_key(|(idx, _)| *idx); let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); result.extend(str_keys); result } _ => Vec::new(), }}
/// Get all own property names (enumerable or not) of an object.fn own_property_names(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { match gc.get(obj_ref) { Some(HeapObject::Object(data)) => { let mut int_keys: Vec<(u32, String)> = Vec::new(); let mut str_keys: Vec<String> = Vec::new(); for k in data.properties.keys() { if let Ok(idx) = k.parse::<u32>() { int_keys.push((idx, k.clone())); } else { str_keys.push(k.clone()); } } int_keys.sort_by_key(|(idx, _)| *idx); let mut result: Vec<String> = int_keys.into_iter().map(|(_, k)| k).collect(); result.extend(str_keys); result } _ => Vec::new(), }}
/// Read the "length" property of an array-like object as usize.fn array_length(gc: &Gc<HeapObject>, obj: GcRef) -> usize { match gc.get(obj) { Some(HeapObject::Object(data)) => match data.properties.get("length") { Some(prop) => prop.value.to_number() as usize, None => 0, }, _ => 0, }}
/// Set the "length" property on an array-like object.fn set_array_length(gc: &mut Gc<HeapObject>, obj: GcRef, len: usize) { if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { if let Some(prop) = data.properties.get_mut("length") { prop.value = Value::Number(len as f64); } else { data.properties.insert( "length".to_string(), Property { value: Value::Number(len as f64), writable: true, enumerable: false, configurable: false, }, ); } }}
/// Check whether an object has a "length" property (i.e. is array-like).fn array_length_exists(gc: &Gc<HeapObject>, obj: GcRef) -> bool { match gc.get(obj) { Some(HeapObject::Object(data)) => data.properties.contains_key("length"), _ => false, }}
/// Get an element by index from an array-like object.fn array_get(gc: &Gc<HeapObject>, obj: GcRef, idx: usize) -> Value { match gc.get(obj) { Some(HeapObject::Object(data)) => { let key = idx.to_string(); data.properties .get(&key) .map(|p| p.value.clone()) .unwrap_or(Value::Undefined) } _ => Value::Undefined, }}
/// Set an element by index on an array-like object.fn array_set(gc: &mut Gc<HeapObject>, obj: GcRef, idx: usize, val: Value) { if let Some(HeapObject::Object(data)) = gc.get_mut(obj) { data.properties.insert(idx.to_string(), Property::data(val)); }}
// ── Initialization ───────────────────────────────────────────
/// Initialize all built-in objects and register them in the VM.pub fn init_builtins(vm: &mut Vm) { // Create Object.prototype first (root of the prototype chain). let obj_proto = vm.gc.alloc(HeapObject::Object(ObjectData::new())); init_object_prototype(&mut vm.gc, obj_proto);
// Create Array.prototype (inherits from Object.prototype). let mut arr_proto_data = ObjectData::new(); arr_proto_data.prototype = Some(obj_proto); let arr_proto = vm.gc.alloc(HeapObject::Object(arr_proto_data)); init_array_prototype(&mut vm.gc, arr_proto);
// Create Error.prototype (inherits from Object.prototype). let mut err_proto_data = ObjectData::new(); err_proto_data.prototype = Some(obj_proto); err_proto_data.properties.insert( "name".to_string(), Property::builtin(Value::String("Error".to_string())), ); err_proto_data.properties.insert( "message".to_string(), Property::builtin(Value::String(String::new())), ); let err_proto = vm.gc.alloc(HeapObject::Object(err_proto_data)); init_error_prototype(&mut vm.gc, err_proto);
// Create String.prototype (inherits from Object.prototype). let mut str_proto_data = ObjectData::new(); str_proto_data.prototype = Some(obj_proto); let str_proto = vm.gc.alloc(HeapObject::Object(str_proto_data)); init_string_prototype(&mut vm.gc, str_proto);
// Create Number.prototype (inherits from Object.prototype). let mut num_proto_data = ObjectData::new(); num_proto_data.prototype = Some(obj_proto); let num_proto = vm.gc.alloc(HeapObject::Object(num_proto_data)); init_number_prototype(&mut vm.gc, num_proto);
// Create Boolean.prototype (inherits from Object.prototype). let mut bool_proto_data = ObjectData::new(); bool_proto_data.prototype = Some(obj_proto); let bool_proto = vm.gc.alloc(HeapObject::Object(bool_proto_data)); init_boolean_prototype(&mut vm.gc, bool_proto);
// Store prototypes in VM for use by CreateArray/CreateObject and auto-boxing. vm.object_prototype = Some(obj_proto); vm.array_prototype = Some(arr_proto); vm.string_prototype = Some(str_proto); vm.number_prototype = Some(num_proto); vm.boolean_prototype = Some(bool_proto);
// Create and register Object constructor. let obj_ctor = init_object_constructor(&mut vm.gc, obj_proto); vm.set_global("Object", Value::Function(obj_ctor));
// Create and register Array constructor. let arr_ctor = init_array_constructor(&mut vm.gc, arr_proto); vm.set_global("Array", Value::Function(arr_ctor));
// Create and register Error constructors. init_error_constructors(vm, err_proto);
// Create and register String constructor. let str_ctor = init_string_constructor(&mut vm.gc, str_proto); vm.set_global("String", Value::Function(str_ctor));
// Create and register Number constructor. let num_ctor = init_number_constructor(&mut vm.gc, num_proto); vm.set_global("Number", Value::Function(num_ctor));
// Create and register Boolean constructor. let bool_ctor = init_boolean_constructor(&mut vm.gc, bool_proto); vm.set_global("Boolean", Value::Function(bool_ctor));
// Create and register Symbol factory. init_symbol_builtins(vm);
// Create and register Math object (static methods only, not a constructor). init_math_object(vm);
// Create and register Date constructor. init_date_builtins(vm);
// Create and register JSON object (static methods only). init_json_object(vm);
// Register global utility functions. init_global_functions(vm);
// Execute JS preamble for callback-based methods. init_js_preamble(vm);}
// ── Object.prototype ─────────────────────────────────────────
fn init_object_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let has_own = make_native(gc, "hasOwnProperty", object_proto_has_own_property); set_builtin_prop(gc, proto, "hasOwnProperty", Value::Function(has_own));
let to_string = make_native(gc, "toString", object_proto_to_string); set_builtin_prop(gc, proto, "toString", Value::Function(to_string));
let value_of = make_native(gc, "valueOf", object_proto_value_of); set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of));}
fn object_proto_has_own_property( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let key = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); match ctx.this.gc_ref() { Some(obj_ref) => match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => { Ok(Value::Boolean(data.properties.contains_key(&key))) } Some(HeapObject::Function(fdata)) => { Ok(Value::Boolean(fdata.properties.contains_key(&key))) } _ => Ok(Value::Boolean(false)), }, None => Ok(Value::Boolean(false)), }}
fn object_proto_to_string( _args: &[Value], _ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { Ok(Value::String("[object Object]".to_string()))}
fn object_proto_value_of(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { Ok(ctx.this.clone())}
// ── Object constructor + static methods ──────────────────────
fn init_object_constructor(gc: &mut Gc<HeapObject>, obj_proto: GcRef) -> GcRef { let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Object".to_string(), kind: FunctionKind::Native(NativeFunc { callback: object_constructor, }), prototype_obj: Some(obj_proto), properties: HashMap::new(), upvalues: Vec::new(), })));
// Static methods. let keys = make_native(gc, "keys", object_keys); set_func_prop(gc, ctor, "keys", Value::Function(keys));
let values = make_native(gc, "values", object_values); set_func_prop(gc, ctor, "values", Value::Function(values));
let entries = make_native(gc, "entries", object_entries); set_func_prop(gc, ctor, "entries", Value::Function(entries));
let assign = make_native(gc, "assign", object_assign); set_func_prop(gc, ctor, "assign", Value::Function(assign));
let create = make_native(gc, "create", object_create); set_func_prop(gc, ctor, "create", Value::Function(create));
let is = make_native(gc, "is", object_is); set_func_prop(gc, ctor, "is", Value::Function(is));
let get_proto = make_native(gc, "getPrototypeOf", object_get_prototype_of); set_func_prop(gc, ctor, "getPrototypeOf", Value::Function(get_proto));
let get_own_names = make_native(gc, "getOwnPropertyNames", object_get_own_property_names); set_func_prop( gc, ctor, "getOwnPropertyNames", Value::Function(get_own_names), );
let get_own_desc = make_native(gc, "getOwnPropertyDescriptor", object_get_own_prop_desc); set_func_prop( gc, ctor, "getOwnPropertyDescriptor", Value::Function(get_own_desc), );
let define_prop = make_native(gc, "defineProperty", object_define_property); set_func_prop(gc, ctor, "defineProperty", Value::Function(define_prop));
let freeze = make_native(gc, "freeze", object_freeze); set_func_prop(gc, ctor, "freeze", Value::Function(freeze));
let seal = make_native(gc, "seal", object_seal); set_func_prop(gc, ctor, "seal", Value::Function(seal));
let is_frozen = make_native(gc, "isFrozen", object_is_frozen); set_func_prop(gc, ctor, "isFrozen", Value::Function(is_frozen));
let is_sealed = make_native(gc, "isSealed", object_is_sealed); set_func_prop(gc, ctor, "isSealed", Value::Function(is_sealed));
let from_entries = make_native(gc, "fromEntries", object_from_entries); set_func_prop(gc, ctor, "fromEntries", Value::Function(from_entries));
let has_own = make_native(gc, "hasOwn", object_has_own); set_func_prop(gc, ctor, "hasOwn", Value::Function(has_own));
ctor}
fn object_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { match args.first() { Some(Value::Object(_)) | Some(Value::Function(_)) => Ok(args[0].clone()), Some(Value::Null) | Some(Value::Undefined) | None => { let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); Ok(Value::Object(obj)) } _ => { // Primitive wrapping (simplified): return a new object. let obj = ctx.gc.alloc(HeapObject::Object(ObjectData::new())); Ok(Value::Object(obj)) } }}
fn object_keys(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) | Some(Value::Function(r)) => *r, _ => return Err(RuntimeError::type_error("Object.keys requires an object")), }; let keys = own_enumerable_keys(ctx.gc, obj_ref); Ok(make_string_array(ctx.gc, &keys))}
fn object_values(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => return Err(RuntimeError::type_error("Object.values requires an object")), }; let keys = own_enumerable_keys(ctx.gc, obj_ref); let values: Vec<Value> = keys .iter() .map(|k| { ctx.gc .get(obj_ref) .and_then(|ho| match ho { HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), _ => None, }) .unwrap_or(Value::Undefined) }) .collect(); Ok(make_value_array(ctx.gc, &values))}
fn object_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.entries requires an object", )) } }; let keys = own_enumerable_keys(ctx.gc, obj_ref); let mut entries = Vec::new(); for k in &keys { let val = ctx .gc .get(obj_ref) .and_then(|ho| match ho { HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), _ => None, }) .unwrap_or(Value::Undefined); // Create a [key, value] pair array. let pair = make_value_array(ctx.gc, &[Value::String(k.clone()), val]); entries.push(pair); } Ok(make_value_array(ctx.gc, &entries))}
fn object_assign(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let target_ref = match args.first() { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.assign requires an object target", )) } }; for source in args.iter().skip(1) { let src_ref = match source { Value::Object(r) => *r, Value::Null | Value::Undefined => continue, _ => continue, }; let keys = own_enumerable_keys(ctx.gc, src_ref); for k in &keys { let val = ctx .gc .get(src_ref) .and_then(|ho| match ho { HeapObject::Object(data) => data.properties.get(k).map(|p| p.value.clone()), _ => None, }) .unwrap_or(Value::Undefined); if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(target_ref) { data.properties.insert(k.clone(), Property::data(val)); } } } Ok(Value::Object(target_ref))}
fn object_create(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let proto = match args.first() { Some(Value::Object(r)) => Some(*r), Some(Value::Null) => None, _ => { return Err(RuntimeError::type_error( "Object.create prototype must be an object or null", )) } }; let mut obj = ObjectData::new(); obj.prototype = proto; let gc_ref = ctx.gc.alloc(HeapObject::Object(obj)); Ok(Value::Object(gc_ref))}
fn object_is(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let x = args.first().cloned().unwrap_or(Value::Undefined); let y = args.get(1).cloned().unwrap_or(Value::Undefined); Ok(Value::Boolean(same_value(&x, &y)))}
/// SameValue algorithm (ECMA-262 §7.2.10).fn same_value(x: &Value, y: &Value) -> bool { match (x, y) { (Value::Number(a), Value::Number(b)) => { if a.is_nan() && b.is_nan() { return true; } if *a == 0.0 && *b == 0.0 { return a.is_sign_positive() == b.is_sign_positive(); } a == b } (Value::Undefined, Value::Undefined) => true, (Value::Null, Value::Null) => true, (Value::Boolean(a), Value::Boolean(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Object(a), Value::Object(b)) => a == b, (Value::Function(a), Value::Function(b)) => a == b, _ => false, }}
fn object_get_prototype_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.getPrototypeOf requires an object", )) } }; match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => match data.prototype { Some(proto) => Ok(Value::Object(proto)), None => Ok(Value::Null), }, _ => Ok(Value::Null), }}
fn object_get_own_property_names( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) | Some(Value::Function(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.getOwnPropertyNames requires an object", )) } }; let names = own_property_names(ctx.gc, obj_ref); Ok(make_string_array(ctx.gc, &names))}
fn object_get_own_prop_desc( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => return Ok(Value::Undefined), }; let key = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let prop = match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => data.properties.get(&key).cloned(), _ => None, }; match prop { Some(p) => { let mut desc = ObjectData::new(); desc.properties .insert("value".to_string(), Property::data(p.value)); desc.properties.insert( "writable".to_string(), Property::data(Value::Boolean(p.writable)), ); desc.properties.insert( "enumerable".to_string(), Property::data(Value::Boolean(p.enumerable)), ); desc.properties.insert( "configurable".to_string(), Property::data(Value::Boolean(p.configurable)), ); Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(desc)))) } None => Ok(Value::Undefined), }}
fn object_define_property(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.defineProperty requires an object", )) } }; let key = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let desc_ref = match args.get(2) { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Property descriptor must be an object", )) } };
// Read descriptor properties. let (value, writable, enumerable, configurable) = { match ctx.gc.get(desc_ref) { Some(HeapObject::Object(desc_data)) => { let value = desc_data .properties .get("value") .map(|p| p.value.clone()) .unwrap_or(Value::Undefined); let writable = desc_data .properties .get("writable") .map(|p| p.value.to_boolean()) .unwrap_or(false); let enumerable = desc_data .properties .get("enumerable") .map(|p| p.value.to_boolean()) .unwrap_or(false); let configurable = desc_data .properties .get("configurable") .map(|p| p.value.to_boolean()) .unwrap_or(false); (value, writable, enumerable, configurable) } _ => (Value::Undefined, false, false, false), } };
if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { data.properties.insert( key, Property { value, writable, enumerable, configurable, }, ); } Ok(Value::Object(obj_ref))}
fn object_freeze(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, Some(other) => return Ok(other.clone()), None => return Ok(Value::Undefined), }; if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { data.extensible = false; for prop in data.properties.values_mut() { prop.writable = false; prop.configurable = false; } } Ok(Value::Object(obj_ref))}
fn object_seal(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, Some(other) => return Ok(other.clone()), None => return Ok(Value::Undefined), }; if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { data.extensible = false; for prop in data.properties.values_mut() { prop.configurable = false; } } Ok(Value::Object(obj_ref))}
fn object_is_frozen(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => return Ok(Value::Boolean(true)), }; match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => { if data.extensible { return Ok(Value::Boolean(false)); } let frozen = data .properties .values() .all(|p| !p.writable && !p.configurable); Ok(Value::Boolean(frozen)) } _ => Ok(Value::Boolean(true)), }}
fn object_is_sealed(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => return Ok(Value::Boolean(true)), }; match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => { if data.extensible { return Ok(Value::Boolean(false)); } let sealed = data.properties.values().all(|p| !p.configurable); Ok(Value::Boolean(sealed)) } _ => Ok(Value::Boolean(true)), }}
fn object_from_entries(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let arr_ref = match args.first() { Some(Value::Object(r)) => *r, _ => { return Err(RuntimeError::type_error( "Object.fromEntries requires an iterable", )) } }; let len = array_length(ctx.gc, arr_ref); let mut obj = ObjectData::new(); for i in 0..len { let pair_val = array_get(ctx.gc, arr_ref, i); if let Value::Object(pair_ref) = pair_val { let key = array_get(ctx.gc, pair_ref, 0); let val = array_get(ctx.gc, pair_ref, 1); let key_str = key.to_js_string(ctx.gc); obj.properties.insert(key_str, Property::data(val)); } } Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj))))}
fn object_has_own(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match args.first() { Some(Value::Object(r)) => *r, _ => return Ok(Value::Boolean(false)), }; let key = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => Ok(Value::Boolean(data.properties.contains_key(&key))), _ => Ok(Value::Boolean(false)), }}
// ── Array helpers ────────────────────────────────────────────
/// Create a JS array from a slice of string values.fn make_string_array(gc: &mut Gc<HeapObject>, items: &[String]) -> Value { let mut obj = ObjectData::new(); for (i, s) in items.iter().enumerate() { obj.properties .insert(i.to_string(), Property::data(Value::String(s.clone()))); } obj.properties.insert( "length".to_string(), Property { value: Value::Number(items.len() as f64), writable: true, enumerable: false, configurable: false, }, ); Value::Object(gc.alloc(HeapObject::Object(obj)))}
/// Create a JS array from a slice of Values.fn make_value_array(gc: &mut Gc<HeapObject>, items: &[Value]) -> Value { let mut obj = ObjectData::new(); for (i, v) in items.iter().enumerate() { obj.properties .insert(i.to_string(), Property::data(v.clone())); } obj.properties.insert( "length".to_string(), Property { value: Value::Number(items.len() as f64), writable: true, enumerable: false, configurable: false, }, ); Value::Object(gc.alloc(HeapObject::Object(obj)))}
// ── Array.prototype ──────────────────────────────────────────
fn init_array_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let push = make_native(gc, "push", array_push); set_builtin_prop(gc, proto, "push", Value::Function(push));
let pop = make_native(gc, "pop", array_pop); set_builtin_prop(gc, proto, "pop", Value::Function(pop));
let shift = make_native(gc, "shift", array_shift); set_builtin_prop(gc, proto, "shift", Value::Function(shift));
let unshift = make_native(gc, "unshift", array_unshift); set_builtin_prop(gc, proto, "unshift", Value::Function(unshift));
let index_of = make_native(gc, "indexOf", array_index_of); set_builtin_prop(gc, proto, "indexOf", Value::Function(index_of));
let last_index_of = make_native(gc, "lastIndexOf", array_last_index_of); set_builtin_prop(gc, proto, "lastIndexOf", Value::Function(last_index_of));
let includes = make_native(gc, "includes", array_includes); set_builtin_prop(gc, proto, "includes", Value::Function(includes));
let join = make_native(gc, "join", array_join); set_builtin_prop(gc, proto, "join", Value::Function(join));
let slice = make_native(gc, "slice", array_slice); set_builtin_prop(gc, proto, "slice", Value::Function(slice));
let concat = make_native(gc, "concat", array_concat); set_builtin_prop(gc, proto, "concat", Value::Function(concat));
let reverse = make_native(gc, "reverse", array_reverse); set_builtin_prop(gc, proto, "reverse", Value::Function(reverse));
let splice = make_native(gc, "splice", array_splice); set_builtin_prop(gc, proto, "splice", Value::Function(splice));
let fill = make_native(gc, "fill", array_fill); set_builtin_prop(gc, proto, "fill", Value::Function(fill));
let to_string = make_native(gc, "toString", array_to_string); set_builtin_prop(gc, proto, "toString", Value::Function(to_string));
let at = make_native(gc, "at", array_at); set_builtin_prop(gc, proto, "at", Value::Function(at));}
fn array_push(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Err(RuntimeError::type_error("push called on non-object")), }; let mut len = array_length(ctx.gc, obj_ref); for val in args { array_set(ctx.gc, obj_ref, len, val.clone()); len += 1; } set_array_length(ctx.gc, obj_ref, len); Ok(Value::Number(len as f64))}
fn array_pop(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Err(RuntimeError::type_error("pop called on non-object")), }; let len = array_length(ctx.gc, obj_ref); if len == 0 { return Ok(Value::Undefined); } let val = array_get(ctx.gc, obj_ref, len - 1); // Remove the last element. if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { data.properties.remove(&(len - 1).to_string()); } set_array_length(ctx.gc, obj_ref, len - 1); Ok(val)}
fn array_shift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Err(RuntimeError::type_error("shift called on non-object")), }; let len = array_length(ctx.gc, obj_ref); if len == 0 { return Ok(Value::Undefined); } let first = array_get(ctx.gc, obj_ref, 0); // Shift all elements down. let mut vals = Vec::with_capacity(len - 1); for i in 1..len { vals.push(array_get(ctx.gc, obj_ref, i)); } if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { // Remove all numeric keys. for i in 0..len { data.properties.remove(&i.to_string()); } // Re-insert shifted values. for (i, v) in vals.into_iter().enumerate() { data.properties.insert(i.to_string(), Property::data(v)); } } set_array_length(ctx.gc, obj_ref, len - 1); Ok(first)}
fn array_unshift(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Err(RuntimeError::type_error("unshift called on non-object")), }; let len = array_length(ctx.gc, obj_ref); let insert_count = args.len(); // Read existing values. let mut existing = Vec::with_capacity(len); for i in 0..len { existing.push(array_get(ctx.gc, obj_ref, i)); } // Write new values at the start, then existing values after. if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { for i in 0..len { data.properties.remove(&i.to_string()); } for (i, v) in args.iter().enumerate() { data.properties .insert(i.to_string(), Property::data(v.clone())); } for (i, v) in existing.into_iter().enumerate() { data.properties .insert((i + insert_count).to_string(), Property::data(v)); } } let new_len = len + insert_count; set_array_length(ctx.gc, obj_ref, new_len); Ok(Value::Number(new_len as f64))}
fn array_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Number(-1.0)), }; let search = args.first().cloned().unwrap_or(Value::Undefined); let from = args .get(1) .map(|v| { let n = v.to_number() as i64; let len = array_length(ctx.gc, obj_ref) as i64; if n < 0 { (len + n).max(0) as usize } else { n as usize } }) .unwrap_or(0); let len = array_length(ctx.gc, obj_ref); for i in from..len { let elem = array_get(ctx.gc, obj_ref, i); if strict_eq_values(&elem, &search) { return Ok(Value::Number(i as f64)); } } Ok(Value::Number(-1.0))}
fn array_last_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Number(-1.0)), }; let search = args.first().cloned().unwrap_or(Value::Undefined); let len = array_length(ctx.gc, obj_ref); if len == 0 { return Ok(Value::Number(-1.0)); } let from = args .get(1) .map(|v| { let n = v.to_number() as i64; if n < 0 { (len as i64 + n) as usize } else { (n as usize).min(len - 1) } }) .unwrap_or(len - 1); for i in (0..=from).rev() { let elem = array_get(ctx.gc, obj_ref, i); if strict_eq_values(&elem, &search) { return Ok(Value::Number(i as f64)); } } Ok(Value::Number(-1.0))}
fn array_includes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Boolean(false)), }; let search = args.first().cloned().unwrap_or(Value::Undefined); let len = array_length(ctx.gc, obj_ref); let from = args .get(1) .map(|v| { let n = v.to_number() as i64; if n < 0 { (len as i64 + n).max(0) as usize } else { n as usize } }) .unwrap_or(0); for i in from..len { let elem = array_get(ctx.gc, obj_ref, i); // includes uses SameValueZero (like === but NaN === NaN). if same_value_zero(&elem, &search) { return Ok(Value::Boolean(true)); } } Ok(Value::Boolean(false))}
fn array_join(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::String(String::new())), }; let sep = args .first() .map(|v| { if matches!(v, Value::Undefined) { ",".to_string() } else { v.to_js_string(ctx.gc) } }) .unwrap_or_else(|| ",".to_string()); let len = array_length(ctx.gc, obj_ref); let mut parts = Vec::with_capacity(len); for i in 0..len { let elem = array_get(ctx.gc, obj_ref, i); if matches!(elem, Value::Undefined | Value::Null) { parts.push(String::new()); } else { parts.push(elem.to_js_string(ctx.gc)); } } Ok(Value::String(parts.join(&sep)))}
fn array_slice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let len = array_length(ctx.gc, obj_ref) as i64; let start = args .first() .map(|v| { let n = v.to_number() as i64; if n < 0 { (len + n).max(0) } else { n.min(len) } }) .unwrap_or(0) as usize; let end = args .get(1) .map(|v| { if matches!(v, Value::Undefined) { len } else { let n = v.to_number() as i64; if n < 0 { (len + n).max(0) } else { n.min(len) } } }) .unwrap_or(len) as usize;
let mut items = Vec::new(); for i in start..end { items.push(array_get(ctx.gc, obj_ref, i)); } Ok(make_value_array(ctx.gc, &items))}
fn array_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let mut items = Vec::new(); // First, add elements from `this`. let len = array_length(ctx.gc, obj_ref); for i in 0..len { items.push(array_get(ctx.gc, obj_ref, i)); } // Then add from each argument. for arg in args { match arg { Value::Object(r) => { let arg_len = array_length(ctx.gc, *r); // Only spread array-like objects (those with a length property). if array_length_exists(ctx.gc, *r) { for i in 0..arg_len { items.push(array_get(ctx.gc, *r, i)); } } else { items.push(arg.clone()); } } _ => items.push(arg.clone()), } } Ok(make_value_array(ctx.gc, &items))}
fn array_reverse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let len = array_length(ctx.gc, obj_ref); // Read all values. let mut vals: Vec<Value> = (0..len).map(|i| array_get(ctx.gc, obj_ref, i)).collect(); vals.reverse(); // Write back. if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { for (i, v) in vals.into_iter().enumerate() { data.properties.insert(i.to_string(), Property::data(v)); } } Ok(ctx.this.clone())}
fn array_splice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let len = array_length(ctx.gc, obj_ref) as i64; let start = args .first() .map(|v| { let n = v.to_number() as i64; if n < 0 { (len + n).max(0) } else { n.min(len) } }) .unwrap_or(0) as usize; let delete_count = args .get(1) .map(|v| { let n = v.to_number() as i64; n.max(0).min(len - start as i64) as usize }) .unwrap_or((len - start as i64).max(0) as usize); let insert_items: Vec<Value> = args.iter().skip(2).cloned().collect();
// Collect current values. let all_vals: Vec<Value> = (0..len as usize) .map(|i| array_get(ctx.gc, obj_ref, i)) .collect();
// Build removed slice. let removed: Vec<Value> = all_vals[start..start + delete_count].to_vec();
// Build new array content. let mut new_vals = Vec::new(); new_vals.extend_from_slice(&all_vals[..start]); new_vals.extend(insert_items); new_vals.extend_from_slice(&all_vals[start + delete_count..]);
// Write back. if let Some(HeapObject::Object(data)) = ctx.gc.get_mut(obj_ref) { // Remove all numeric keys. let old_keys: Vec<String> = data .properties .keys() .filter(|k| k.parse::<usize>().is_ok()) .cloned() .collect(); for k in old_keys { data.properties.remove(&k); } // Write new values. for (i, v) in new_vals.iter().enumerate() { data.properties .insert(i.to_string(), Property::data(v.clone())); } } set_array_length(ctx.gc, obj_ref, new_vals.len()); Ok(make_value_array(ctx.gc, &removed))}
fn array_fill(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let val = args.first().cloned().unwrap_or(Value::Undefined); let len = array_length(ctx.gc, obj_ref) as i64; let start = args .get(1) .map(|v| { let n = v.to_number() as i64; if n < 0 { (len + n).max(0) } else { n.min(len) } }) .unwrap_or(0) as usize; let end = args .get(2) .map(|v| { if matches!(v, Value::Undefined) { len } else { let n = v.to_number() as i64; if n < 0 { (len + n).max(0) } else { n.min(len) } } }) .unwrap_or(len) as usize; for i in start..end { array_set(ctx.gc, obj_ref, i, val.clone()); } Ok(ctx.this.clone())}
fn array_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { // Array.prototype.toString is the same as join(","). array_join( &[Value::String(",".to_string())], &mut NativeContext { gc: ctx.gc, this: ctx.this.clone(), }, ) .or_else(|_| Ok(Value::String(String::new())))}
fn array_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::Undefined), }; let len = array_length(ctx.gc, obj_ref) as i64; let index = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let actual = if index < 0 { len + index } else { index }; if actual < 0 || actual >= len { Ok(Value::Undefined) } else { Ok(array_get(ctx.gc, obj_ref, actual as usize)) }}
// ── Array constructor + static methods ───────────────────────
fn init_array_constructor(gc: &mut Gc<HeapObject>, arr_proto: GcRef) -> GcRef { let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Array".to_string(), kind: FunctionKind::Native(NativeFunc { callback: array_constructor, }), prototype_obj: Some(arr_proto), properties: HashMap::new(), upvalues: Vec::new(), })));
let is_array = make_native(gc, "isArray", array_is_array); set_func_prop(gc, ctor, "isArray", Value::Function(is_array));
let from = make_native(gc, "from", array_from); set_func_prop(gc, ctor, "from", Value::Function(from));
let of = make_native(gc, "of", array_of); set_func_prop(gc, ctor, "of", Value::Function(of));
ctor}
fn array_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { if args.len() == 1 { if let Value::Number(n) = &args[0] { let len = *n as usize; let mut obj = ObjectData::new(); obj.properties.insert( "length".to_string(), Property { value: Value::Number(len as f64), writable: true, enumerable: false, configurable: false, }, ); return Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj)))); } } Ok(make_value_array(ctx.gc, args))}
fn array_is_array(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { // An "array" is an object that has a numeric length property. // This is a simplified check — real JS uses an internal [[Class]] slot. match args.first() { Some(Value::Object(r)) => match ctx.gc.get(*r) { Some(HeapObject::Object(data)) => { Ok(Value::Boolean(data.properties.contains_key("length"))) } _ => Ok(Value::Boolean(false)), }, _ => Ok(Value::Boolean(false)), }}
fn array_from(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let iterable = args.first().cloned().unwrap_or(Value::Undefined); match iterable { Value::Object(r) => { let len = array_length(ctx.gc, r); let mut items = Vec::with_capacity(len); for i in 0..len { items.push(array_get(ctx.gc, r, i)); } Ok(make_value_array(ctx.gc, &items)) } Value::String(s) => { let chars: Vec<Value> = s.chars().map(|c| Value::String(c.to_string())).collect(); Ok(make_value_array(ctx.gc, &chars)) } _ => Ok(make_value_array(ctx.gc, &[])), }}
fn array_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { Ok(make_value_array(ctx.gc, args))}
// ── Error constructors ───────────────────────────────────────
fn init_error_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let to_string = make_native(gc, "toString", error_proto_to_string); set_builtin_prop(gc, proto, "toString", Value::Function(to_string));}
fn init_error_constructors(vm: &mut Vm, err_proto: GcRef) { // Base Error. let error_ctor = make_error_constructor(&mut vm.gc, "Error", err_proto); vm.set_global("Error", Value::Function(error_ctor));
// TypeError. let te_proto = make_error_subclass_proto(&mut vm.gc, "TypeError", err_proto); let te_ctor = make_error_constructor(&mut vm.gc, "TypeError", te_proto); vm.set_global("TypeError", Value::Function(te_ctor));
// ReferenceError. let re_proto = make_error_subclass_proto(&mut vm.gc, "ReferenceError", err_proto); let re_ctor = make_error_constructor(&mut vm.gc, "ReferenceError", re_proto); vm.set_global("ReferenceError", Value::Function(re_ctor));
// SyntaxError. let se_proto = make_error_subclass_proto(&mut vm.gc, "SyntaxError", err_proto); let se_ctor = make_error_constructor(&mut vm.gc, "SyntaxError", se_proto); vm.set_global("SyntaxError", Value::Function(se_ctor));
// RangeError. let rae_proto = make_error_subclass_proto(&mut vm.gc, "RangeError", err_proto); let rae_ctor = make_error_constructor(&mut vm.gc, "RangeError", rae_proto); vm.set_global("RangeError", Value::Function(rae_ctor));
// URIError. let ue_proto = make_error_subclass_proto(&mut vm.gc, "URIError", err_proto); let ue_ctor = make_error_constructor(&mut vm.gc, "URIError", ue_proto); vm.set_global("URIError", Value::Function(ue_ctor));
// EvalError. let ee_proto = make_error_subclass_proto(&mut vm.gc, "EvalError", err_proto); let ee_ctor = make_error_constructor(&mut vm.gc, "EvalError", ee_proto); vm.set_global("EvalError", Value::Function(ee_ctor));}
fn make_error_subclass_proto(gc: &mut Gc<HeapObject>, name: &str, parent_proto: GcRef) -> GcRef { let mut data = ObjectData::new(); data.prototype = Some(parent_proto); data.properties.insert( "name".to_string(), Property::builtin(Value::String(name.to_string())), ); data.properties.insert( "message".to_string(), Property::builtin(Value::String(String::new())), ); gc.alloc(HeapObject::Object(data))}
fn make_error_constructor(gc: &mut Gc<HeapObject>, name: &str, proto: GcRef) -> GcRef { gc.alloc(HeapObject::Function(Box::new(FunctionData { name: name.to_string(), kind: FunctionKind::Native(NativeFunc { callback: error_constructor, }), prototype_obj: Some(proto), properties: HashMap::new(), upvalues: Vec::new(), })))}
fn error_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let message = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let mut obj = ObjectData::new(); obj.properties.insert( "message".to_string(), Property::data(Value::String(message)), ); // The "name" property comes from the prototype chain. Ok(Value::Object(ctx.gc.alloc(HeapObject::Object(obj))))}
fn error_proto_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let obj_ref = match ctx.this.gc_ref() { Some(r) => r, None => return Ok(Value::String("Error".to_string())), }; let name = match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => data .properties .get("name") .map(|p| p.value.to_js_string(ctx.gc)) .unwrap_or_else(|| "Error".to_string()), _ => "Error".to_string(), }; let message = match ctx.gc.get(obj_ref) { Some(HeapObject::Object(data)) => data .properties .get("message") .map(|p| p.value.to_js_string(ctx.gc)) .unwrap_or_default(), _ => String::new(), }; if message.is_empty() { Ok(Value::String(name)) } else { Ok(Value::String(format!("{name}: {message}"))) }}
// ── String built-in ──────────────────────────────────────────
fn init_string_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let methods: &[NativeMethod] = &[ ("charAt", string_proto_char_at), ("charCodeAt", string_proto_char_code_at), ("codePointAt", string_proto_code_point_at), ("concat", string_proto_concat), ("slice", string_proto_slice), ("substring", string_proto_substring), ("substr", string_proto_substr), ("indexOf", string_proto_index_of), ("lastIndexOf", string_proto_last_index_of), ("includes", string_proto_includes), ("startsWith", string_proto_starts_with), ("endsWith", string_proto_ends_with), ("trim", string_proto_trim), ("trimStart", string_proto_trim_start), ("trimEnd", string_proto_trim_end), ("padStart", string_proto_pad_start), ("padEnd", string_proto_pad_end), ("repeat", string_proto_repeat), ("split", string_proto_split), ("replace", string_proto_replace), ("replaceAll", string_proto_replace_all), ("toLowerCase", string_proto_to_lower_case), ("toUpperCase", string_proto_to_upper_case), ("at", string_proto_at), ("toString", string_proto_to_string), ("valueOf", string_proto_value_of), ]; for &(name, callback) in methods { let f = make_native(gc, name, callback); set_builtin_prop(gc, proto, name, Value::Function(f)); }}
fn init_string_constructor(gc: &mut Gc<HeapObject>, str_proto: GcRef) -> GcRef { let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "String".to_string(), kind: FunctionKind::Native(NativeFunc { callback: string_constructor, }), prototype_obj: Some(str_proto), properties: HashMap::new(), upvalues: Vec::new(), }))); let from_char_code = make_native(gc, "fromCharCode", string_from_char_code); set_func_prop(gc, ctor, "fromCharCode", Value::Function(from_char_code)); let from_code_point = make_native(gc, "fromCodePoint", string_from_code_point); set_func_prop(gc, ctor, "fromCodePoint", Value::Function(from_code_point)); ctor}
fn string_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); Ok(Value::String(s))}
fn string_from_char_code(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s: String = args .iter() .filter_map(|v| { let code = v.to_number() as u32; char::from_u32(code) }) .collect(); Ok(Value::String(s))}
fn string_from_code_point(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let mut s = String::new(); for v in args { let code = v.to_number() as u32; match char::from_u32(code) { Some(c) => s.push(c), None => { return Err(RuntimeError::range_error(format!( "Invalid code point {code}" ))) } } } Ok(Value::String(s))}
/// Helper: extract the string from `this` for String.prototype methods.fn this_string(ctx: &NativeContext) -> String { ctx.this.to_js_string(ctx.gc)}
/// Helper: get chars as a Vec for index-based operations.fn str_chars(s: &str) -> Vec<char> { s.chars().collect()}
fn string_proto_char_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); if idx < 0 || idx as usize >= chars.len() { Ok(Value::String(String::new())) } else { Ok(Value::String(chars[idx as usize].to_string())) }}
fn string_proto_char_code_at( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); if idx < 0 || idx as usize >= chars.len() { Ok(Value::Number(f64::NAN)) } else { Ok(Value::Number(chars[idx as usize] as u32 as f64)) }}
fn string_proto_code_point_at( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); if idx < 0 || idx as usize >= chars.len() { Ok(Value::Undefined) } else { Ok(Value::Number(chars[idx as usize] as u32 as f64)) }}
fn string_proto_concat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let mut s = this_string(ctx); for arg in args { s.push_str(&arg.to_js_string(ctx.gc)); } Ok(Value::String(s))}
fn string_proto_slice(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let len = chars.len() as i64; let start = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let end = args.get(1).map(|v| v.to_number() as i64).unwrap_or(len); let start = if start < 0 { (len + start).max(0) as usize } else { start.min(len) as usize }; let end = if end < 0 { (len + end).max(0) as usize } else { end.min(len) as usize }; if start >= end { Ok(Value::String(String::new())) } else { Ok(Value::String(chars[start..end].iter().collect())) }}
fn string_proto_substring(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let len = chars.len() as i64; let a = args .first() .map(|v| v.to_number() as i64) .unwrap_or(0) .clamp(0, len) as usize; let b = args .get(1) .map(|v| v.to_number() as i64) .unwrap_or(len) .clamp(0, len) as usize; let (start, end) = if a <= b { (a, b) } else { (b, a) }; Ok(Value::String(chars[start..end].iter().collect()))}
fn string_proto_substr(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let len = chars.len() as i64; let start = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let start = if start < 0 { (len + start).max(0) as usize } else { start.min(len) as usize }; let count = args.get(1).map(|v| v.to_number() as i64).unwrap_or(len) as usize; let end = (start + count).min(chars.len()); Ok(Value::String(chars[start..end].iter().collect()))}
fn string_proto_index_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let from = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); let chars = str_chars(&s); let search_chars = str_chars(&search); if search_chars.is_empty() { return Ok(Value::Number(from.min(chars.len()) as f64)); } for i in from..chars.len() { if i + search_chars.len() <= chars.len() && chars[i..i + search_chars.len()] == search_chars[..] { return Ok(Value::Number(i as f64)); } } Ok(Value::Number(-1.0))}
fn string_proto_last_index_of( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let chars = str_chars(&s); let search_chars = str_chars(&search); let from = args .get(1) .map(|v| { let n = v.to_number(); if n.is_nan() { chars.len() } else { n as usize } }) .unwrap_or(chars.len()); if search_chars.is_empty() { return Ok(Value::Number(from.min(chars.len()) as f64)); } let max_start = from.min(chars.len().saturating_sub(search_chars.len())); for i in (0..=max_start).rev() { if i + search_chars.len() <= chars.len() && chars[i..i + search_chars.len()] == search_chars[..] { return Ok(Value::Number(i as f64)); } } Ok(Value::Number(-1.0))}
fn string_proto_includes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let from = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); let chars = str_chars(&s); let search_chars = str_chars(&search); if search_chars.is_empty() { return Ok(Value::Boolean(true)); } for i in from..chars.len() { if i + search_chars.len() <= chars.len() && chars[i..i + search_chars.len()] == search_chars[..] { return Ok(Value::Boolean(true)); } } Ok(Value::Boolean(false))}
fn string_proto_starts_with( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let pos = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0); let chars = str_chars(&s); let search_chars = str_chars(&search); if pos + search_chars.len() > chars.len() { return Ok(Value::Boolean(false)); } Ok(Value::Boolean( chars[pos..pos + search_chars.len()] == search_chars[..], ))}
fn string_proto_ends_with(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let chars = str_chars(&s); let search_chars = str_chars(&search); let end_pos = args .get(1) .map(|v| (v.to_number() as usize).min(chars.len())) .unwrap_or(chars.len()); if search_chars.len() > end_pos { return Ok(Value::Boolean(false)); } let start = end_pos - search_chars.len(); Ok(Value::Boolean(chars[start..end_pos] == search_chars[..]))}
fn string_proto_trim(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx).trim().to_string()))}
fn string_proto_trim_start(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx).trim_start().to_string()))}
fn string_proto_trim_end(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx).trim_end().to_string()))}
fn string_proto_pad_start(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let target_len = args.first().map(|v| v.to_number() as usize).unwrap_or(0); let fill = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_else(|| " ".to_string()); let chars = str_chars(&s); if chars.len() >= target_len || fill.is_empty() { return Ok(Value::String(s)); } let fill_chars = str_chars(&fill); let needed = target_len - chars.len(); let mut pad = String::new(); for i in 0..needed { pad.push(fill_chars[i % fill_chars.len()]); } pad.push_str(&s); Ok(Value::String(pad))}
fn string_proto_pad_end(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let target_len = args.first().map(|v| v.to_number() as usize).unwrap_or(0); let fill = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_else(|| " ".to_string()); let chars = str_chars(&s); if chars.len() >= target_len || fill.is_empty() { return Ok(Value::String(s)); } let fill_chars = str_chars(&fill); let needed = target_len - chars.len(); let mut result = s; for i in 0..needed { result.push(fill_chars[i % fill_chars.len()]); } Ok(Value::String(result))}
fn string_proto_repeat(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let count = args.first().map(|v| v.to_number()).unwrap_or(0.0); if count < 0.0 || count.is_infinite() { return Err(RuntimeError::range_error("Invalid count value")); } Ok(Value::String(s.repeat(count as usize)))}
fn string_proto_split(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); if args.is_empty() || matches!(args.first(), Some(Value::Undefined)) { return Ok(make_value_array(ctx.gc, &[Value::String(s)])); } let sep = args[0].to_js_string(ctx.gc); let limit = args .get(1) .map(|v| v.to_number() as usize) .unwrap_or(usize::MAX); if sep.is_empty() { let items: Vec<Value> = str_chars(&s) .into_iter() .take(limit) .map(|c| Value::String(c.to_string())) .collect(); return Ok(make_value_array(ctx.gc, &items)); } let mut items = Vec::new(); let mut start = 0; let sep_len = sep.len(); while let Some(pos) = s[start..].find(&sep) { if items.len() >= limit { break; } items.push(Value::String(s[start..start + pos].to_string())); start += pos + sep_len; } if items.len() < limit { items.push(Value::String(s[start..].to_string())); } Ok(make_value_array(ctx.gc, &items))}
fn string_proto_replace(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let replacement = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); // Replace only the first occurrence. if let Some(pos) = s.find(&search) { let mut result = String::with_capacity(s.len()); result.push_str(&s[..pos]); result.push_str(&replacement); result.push_str(&s[pos + search.len()..]); Ok(Value::String(result)) } else { Ok(Value::String(s)) }}
fn string_proto_replace_all( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let s = this_string(ctx); let search = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let replacement = args .get(1) .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); Ok(Value::String(s.replace(&search, &replacement)))}
fn string_proto_to_lower_case( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx).to_lowercase()))}
fn string_proto_to_upper_case( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx).to_uppercase()))}
fn string_proto_at(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = this_string(ctx); let chars = str_chars(&s); let idx = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let actual = if idx < 0 { chars.len() as i64 + idx } else { idx }; if actual < 0 || actual as usize >= chars.len() { Ok(Value::Undefined) } else { Ok(Value::String(chars[actual as usize].to_string())) }}
fn string_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx)))}
fn string_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String(this_string(ctx)))}
// ── Number built-in ──────────────────────────────────────────
fn init_number_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let methods: &[NativeMethod] = &[ ("toString", number_proto_to_string), ("valueOf", number_proto_value_of), ("toFixed", number_proto_to_fixed), ("toPrecision", number_proto_to_precision), ("toExponential", number_proto_to_exponential), ]; for &(name, callback) in methods { let f = make_native(gc, name, callback); set_builtin_prop(gc, proto, name, Value::Function(f)); }}
fn init_number_constructor(gc: &mut Gc<HeapObject>, num_proto: GcRef) -> GcRef { let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Number".to_string(), kind: FunctionKind::Native(NativeFunc { callback: number_constructor, }), prototype_obj: Some(num_proto), properties: HashMap::new(), upvalues: Vec::new(), }))); // Static methods. let is_nan = make_native(gc, "isNaN", number_is_nan); set_func_prop(gc, ctor, "isNaN", Value::Function(is_nan)); let is_finite = make_native(gc, "isFinite", number_is_finite); set_func_prop(gc, ctor, "isFinite", Value::Function(is_finite)); let is_integer = make_native(gc, "isInteger", number_is_integer); set_func_prop(gc, ctor, "isInteger", Value::Function(is_integer)); let is_safe_integer = make_native(gc, "isSafeInteger", number_is_safe_integer); set_func_prop(gc, ctor, "isSafeInteger", Value::Function(is_safe_integer)); let parse_int_fn = make_native(gc, "parseInt", crate::builtins::parse_int); set_func_prop(gc, ctor, "parseInt", Value::Function(parse_int_fn)); let parse_float_fn = make_native(gc, "parseFloat", crate::builtins::parse_float); set_func_prop(gc, ctor, "parseFloat", Value::Function(parse_float_fn)); // Constants. set_func_prop(gc, ctor, "EPSILON", Value::Number(f64::EPSILON)); set_func_prop( gc, ctor, "MAX_SAFE_INTEGER", Value::Number(9007199254740991.0), ); set_func_prop( gc, ctor, "MIN_SAFE_INTEGER", Value::Number(-9007199254740991.0), ); set_func_prop(gc, ctor, "MAX_VALUE", Value::Number(f64::MAX)); set_func_prop(gc, ctor, "MIN_VALUE", Value::Number(f64::MIN_POSITIVE)); set_func_prop(gc, ctor, "NaN", Value::Number(f64::NAN)); set_func_prop(gc, ctor, "POSITIVE_INFINITY", Value::Number(f64::INFINITY)); set_func_prop( gc, ctor, "NEGATIVE_INFINITY", Value::Number(f64::NEG_INFINITY), ); ctor}
fn number_constructor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(0.0); Ok(Value::Number(n))}
fn number_is_nan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { match args.first() { Some(Value::Number(n)) => Ok(Value::Boolean(n.is_nan())), _ => Ok(Value::Boolean(false)), }}
fn number_is_finite(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { match args.first() { Some(Value::Number(n)) => Ok(Value::Boolean(n.is_finite())), _ => Ok(Value::Boolean(false)), }}
fn number_is_integer(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { match args.first() { Some(Value::Number(n)) => Ok(Value::Boolean(n.is_finite() && n.trunc() == *n)), _ => Ok(Value::Boolean(false)), }}
fn number_is_safe_integer(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { match args.first() { Some(Value::Number(n)) => { let safe = n.is_finite() && n.trunc() == *n && n.abs() <= 9007199254740991.0; Ok(Value::Boolean(safe)) } _ => Ok(Value::Boolean(false)), }}
/// Helper: extract the number from `this` for Number.prototype methods.fn this_number(ctx: &NativeContext) -> f64 { ctx.this.to_number()}
fn number_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = this_number(ctx); let radix = args.first().map(|v| v.to_number() as u32).unwrap_or(10); if !(2..=36).contains(&radix) { return Err(RuntimeError::range_error( "toString() radix must be between 2 and 36", )); } if radix == 10 { return Ok(Value::String(Value::Number(n).to_js_string(ctx.gc))); } if n.is_nan() { return Ok(Value::String("NaN".to_string())); } if n.is_infinite() { return Ok(Value::String(if n > 0.0 { "Infinity".to_string() } else { "-Infinity".to_string() })); } // Integer path for non-decimal radix. let neg = n < 0.0; let abs = n.abs() as u64; let mut digits = Vec::new(); let mut val = abs; if val == 0 { digits.push('0'); } else { while val > 0 { let d = (val % radix as u64) as u32; digits.push(char::from_digit(d, radix).unwrap_or('?')); val /= radix as u64; } } digits.reverse(); let mut result = String::new(); if neg { result.push('-'); } result.extend(digits); Ok(Value::String(result))}
fn number_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::Number(this_number(ctx)))}
fn number_proto_to_fixed(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = this_number(ctx); let digits = args.first().map(|v| v.to_number() as usize).unwrap_or(0); if digits > 100 { return Err(RuntimeError::range_error( "toFixed() digits argument must be between 0 and 100", )); } Ok(Value::String(format!("{n:.digits$}")))}
fn number_proto_to_precision( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let n = this_number(ctx); if args.is_empty() || matches!(args.first(), Some(Value::Undefined)) { return Ok(Value::String(Value::Number(n).to_js_string(ctx.gc))); } let prec = args[0].to_number() as usize; if !(1..=100).contains(&prec) { return Err(RuntimeError::range_error( "toPrecision() argument must be between 1 and 100", )); } Ok(Value::String(format!("{n:.prec$e}")))}
fn number_proto_to_exponential( args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { let n = this_number(ctx); let digits = args.first().map(|v| v.to_number() as usize).unwrap_or(6); if digits > 100 { return Err(RuntimeError::range_error( "toExponential() argument must be between 0 and 100", )); } Ok(Value::String(format!("{n:.digits$e}")))}
// ── Boolean built-in ─────────────────────────────────────────
fn init_boolean_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let to_string = make_native(gc, "toString", boolean_proto_to_string); set_builtin_prop(gc, proto, "toString", Value::Function(to_string)); let value_of = make_native(gc, "valueOf", boolean_proto_value_of); set_builtin_prop(gc, proto, "valueOf", Value::Function(value_of));}
fn init_boolean_constructor(gc: &mut Gc<HeapObject>, bool_proto: GcRef) -> GcRef { gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Boolean".to_string(), kind: FunctionKind::Native(NativeFunc { callback: boolean_constructor, }), prototype_obj: Some(bool_proto), properties: HashMap::new(), upvalues: Vec::new(), })))}
fn boolean_constructor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let b = args.first().map(|v| v.to_boolean()).unwrap_or(false); Ok(Value::Boolean(b))}
fn boolean_proto_to_string(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::String( if ctx.this.to_boolean() { "true" } else { "false" } .to_string(), ))}
fn boolean_proto_value_of(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::Boolean(ctx.this.to_boolean()))}
// ── Symbol built-in ──────────────────────────────────────────
/// Global symbol ID counter. Each Symbol() call increments this.static SYMBOL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
// Thread-local Date prototype GcRef for use in date_constructor.// The Date constructor callback doesn't have VM access, so we store the// prototype here during init and read it back during construction.thread_local! { static DATE_PROTO: std::cell::Cell<Option<GcRef>> = const { std::cell::Cell::new(None) };}
fn init_symbol_builtins(vm: &mut Vm) { // Create a function object so we can hang static props on it. let gc_ref = make_native(&mut vm.gc, "Symbol", symbol_factory);
// Well-known symbols as string constants. let well_known = [ ("iterator", "@@iterator"), ("toPrimitive", "@@toPrimitive"), ("toStringTag", "@@toStringTag"), ("hasInstance", "@@hasInstance"), ]; for (name, value) in well_known { set_func_prop(&mut vm.gc, gc_ref, name, Value::String(value.to_string())); }
// Symbol.for() and Symbol.keyFor(). let sym_for = make_native(&mut vm.gc, "for", symbol_for); set_func_prop(&mut vm.gc, gc_ref, "for", Value::Function(sym_for)); let sym_key_for = make_native(&mut vm.gc, "keyFor", symbol_key_for); set_func_prop(&mut vm.gc, gc_ref, "keyFor", Value::Function(sym_key_for));
vm.set_global("Symbol", Value::Function(gc_ref));
// Register global NaN and Infinity constants. vm.set_global("NaN", Value::Number(f64::NAN)); vm.set_global("Infinity", Value::Number(f64::INFINITY)); vm.set_global("undefined", Value::Undefined);}
fn symbol_factory(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let desc = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let id = SYMBOL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); // Return a unique string representation. Real Symbol is a distinct type, // but this pragmatic approach works for property keys and identity checks. Ok(Value::String(format!("@@sym_{id}_{desc}")))}
fn symbol_for(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let key = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); // Deterministic: same key always produces same symbol string. Ok(Value::String(format!("@@global_{key}")))}
fn symbol_key_for(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let sym = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); if let Some(key) = sym.strip_prefix("@@global_") { Ok(Value::String(key.to_string())) } else { Ok(Value::Undefined) }}
// ── Math object ──────────────────────────────────────────────
/// Simple xorshift64 PRNG state (no crypto requirement).static PRNG_STATE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn prng_next() -> f64 { let mut s = PRNG_STATE.load(std::sync::atomic::Ordering::Relaxed); if s == 0 { // Seed from system time. s = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_nanos() as u64) .unwrap_or(123456789); } s ^= s << 13; s ^= s >> 7; s ^= s << 17; PRNG_STATE.store(s, std::sync::atomic::Ordering::Relaxed); // Map to [0, 1). (s >> 11) as f64 / ((1u64 << 53) as f64)}
fn init_math_object(vm: &mut Vm) { let mut data = ObjectData::new(); if let Some(proto) = vm.object_prototype { data.prototype = Some(proto); } let math_ref = vm.gc.alloc(HeapObject::Object(data));
// Constants. let constants: &[(&str, f64)] = &[ ("E", std::f64::consts::E), ("LN2", std::f64::consts::LN_2), ("LN10", std::f64::consts::LN_10), ("LOG2E", std::f64::consts::LOG2_E), ("LOG10E", std::f64::consts::LOG10_E), ("PI", std::f64::consts::PI), ("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2), ("SQRT2", std::f64::consts::SQRT_2), ]; for &(name, val) in constants { set_builtin_prop(&mut vm.gc, math_ref, name, Value::Number(val)); }
// Methods. let methods: &[NativeMethod] = &[ ("abs", math_abs), ("ceil", math_ceil), ("floor", math_floor), ("round", math_round), ("trunc", math_trunc), ("max", math_max), ("min", math_min), ("pow", math_pow), ("sqrt", math_sqrt), ("cbrt", math_cbrt), ("hypot", math_hypot), ("sin", math_sin), ("cos", math_cos), ("tan", math_tan), ("asin", math_asin), ("acos", math_acos), ("atan", math_atan), ("atan2", math_atan2), ("exp", math_exp), ("log", math_log), ("log2", math_log2), ("log10", math_log10), ("expm1", math_expm1), ("log1p", math_log1p), ("sign", math_sign), ("clz32", math_clz32), ("fround", math_fround), ("random", math_random), ("imul", math_imul), ]; for &(name, cb) in methods { let f = make_native(&mut vm.gc, name, cb); set_builtin_prop(&mut vm.gc, math_ref, name, Value::Function(f)); }
vm.set_global("Math", Value::Object(math_ref));}
fn math_abs(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.abs()))}
fn math_ceil(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.ceil()))}
fn math_floor(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.floor()))}
fn math_round(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); // JS Math.round rounds .5 up (toward +Infinity). Ok(Value::Number(n.round()))}
fn math_trunc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.trunc()))}
fn math_max(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { if args.is_empty() { return Ok(Value::Number(f64::NEG_INFINITY)); } let mut result = f64::NEG_INFINITY; for arg in args { let n = arg.to_number(); if n.is_nan() { return Ok(Value::Number(f64::NAN)); } if n > result { result = n; } } Ok(Value::Number(result))}
fn math_min(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { if args.is_empty() { return Ok(Value::Number(f64::INFINITY)); } let mut result = f64::INFINITY; for arg in args { let n = arg.to_number(); if n.is_nan() { return Ok(Value::Number(f64::NAN)); } if n < result { result = n; } } Ok(Value::Number(result))}
fn math_pow(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let base = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); let exp = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(base.powf(exp)))}
fn math_sqrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.sqrt()))}
fn math_cbrt(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.cbrt()))}
fn math_hypot(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { if args.is_empty() { return Ok(Value::Number(0.0)); } let mut sum = 0.0f64; for arg in args { let n = arg.to_number(); if n.is_infinite() { return Ok(Value::Number(f64::INFINITY)); } if n.is_nan() { return Ok(Value::Number(f64::NAN)); } sum += n * n; } Ok(Value::Number(sum.sqrt()))}
fn math_sin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.sin()))}
fn math_cos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.cos()))}
fn math_tan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.tan()))}
fn math_asin(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.asin()))}
fn math_acos(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.acos()))}
fn math_atan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.atan()))}
fn math_atan2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let y = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); let x = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(y.atan2(x)))}
fn math_exp(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.exp()))}
fn math_log(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.ln()))}
fn math_log2(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.log2()))}
fn math_log10(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.log10()))}
fn math_expm1(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.exp_m1()))}
fn math_log1p(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number(n.ln_1p()))}
fn math_sign(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); if n.is_nan() { Ok(Value::Number(f64::NAN)) } else if n == 0.0 { // Preserve -0 and +0. Ok(Value::Number(n)) } else if n > 0.0 { Ok(Value::Number(1.0)) } else { Ok(Value::Number(-1.0)) }}
fn math_clz32(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(0.0); let i = n as u32; Ok(Value::Number(i.leading_zeros() as f64))}
fn math_fround(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Number((n as f32) as f64))}
fn math_random(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { Ok(Value::Number(prng_next()))}
fn math_imul(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { // ToUint32 then reinterpret as i32 per spec. let a = args.first().map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; let b = args.get(1).map(|v| v.to_number()).unwrap_or(0.0) as u32 as i32; Ok(Value::Number(a.wrapping_mul(b) as f64))}
// ── Date built-in ────────────────────────────────────────────
/// Milliseconds since Unix epoch from SystemTime.fn now_ms() -> f64 { SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_millis() as f64) .unwrap_or(0.0)}
/// Calendar components from a timestamp (milliseconds since epoch)./// Returns (year, month0, day, hours, minutes, seconds, ms, weekday) in UTC.fn ms_to_utc_components(ms: f64) -> (i64, i64, i64, i64, i64, i64, i64, i64) { let total_ms = ms as i64; let ms_part = ((total_ms % 1000) + 1000) % 1000; let mut days = total_ms.div_euclid(86_400_000); // Weekday: Jan 1 1970 was Thursday (4). let weekday = ((days % 7) + 4 + 7) % 7;
let secs_in_day = (total_ms.rem_euclid(86_400_000)) / 1000; let hours = secs_in_day / 3600; let minutes = (secs_in_day % 3600) / 60; let seconds = secs_in_day % 60;
// Convert days since epoch to year/month/day using a civil calendar algorithm. // Shift epoch to March 1, 2000. days += 719_468; let era = if days >= 0 { days / 146_097 } else { (days - 146_096) / 146_097 }; let doe = days - era * 146_097; // day of era [0, 146096] let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; let y = yoe + era * 400; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let y = if m <= 2 { y + 1 } else { y };
(y, m - 1, d, hours, minutes, seconds, ms_part, weekday)}
/// Convert UTC components to milliseconds since epoch.fn utc_components_to_ms(year: i64, month: i64, day: i64, h: i64, min: i64, s: i64, ms: i64) -> f64 { // Normalize month overflow. let y = year + month.div_euclid(12); let m = month.rem_euclid(12) + 1; // 1-based
// Civil calendar to days (inverse of above). let (y2, m2) = if m <= 2 { (y - 1, m + 9) } else { (y, m - 3) }; let era = if y2 >= 0 { y2 / 400 } else { (y2 - 399) / 400 }; let yoe = y2 - era * 400; let doy = (153 * m2 + 2) / 5 + day - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; let days = era * 146_097 + doe - 719_468;
(days * 86_400_000 + h * 3_600_000 + min * 60_000 + s * 1000 + ms) as f64}
/// Parse a subset of ISO 8601 date strings (YYYY-MM-DDTHH:MM:SS.sssZ).fn parse_date_string(s: &str) -> Option<f64> { let s = s.trim(); // Try YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD or YYYY let parts: Vec<&str> = s.splitn(2, 'T').collect(); let date_part = parts[0]; let time_part = parts.get(1).copied().unwrap_or("");
let date_fields: Vec<&str> = date_part.split('-').collect(); if date_fields.is_empty() { return None; }
let year: i64 = date_fields[0].parse().ok()?; let month: i64 = date_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(1); let day: i64 = date_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
if !(1..=12).contains(&month) || !(1..=31).contains(&day) { return None; }
let (h, min, sec, ms) = if time_part.is_empty() { (0, 0, 0, 0) } else { // Strip trailing Z. let tp = time_part.strip_suffix('Z').unwrap_or(time_part); // Split seconds from milliseconds. let (time_str, ms_val) = if let Some((t, m)) = tp.split_once('.') { let ms_str = &m[..m.len().min(3)]; let mut ms_val: i64 = ms_str.parse().unwrap_or(0); // Pad to 3 digits if needed. for _ in ms_str.len()..3 { ms_val *= 10; } (t, ms_val) } else { (tp, 0i64) }; let time_fields: Vec<&str> = time_str.split(':').collect(); let h: i64 = time_fields .first() .and_then(|s| s.parse().ok()) .unwrap_or(0); let min: i64 = time_fields.get(1).and_then(|s| s.parse().ok()).unwrap_or(0); let sec: i64 = time_fields.get(2).and_then(|s| s.parse().ok()).unwrap_or(0); (h, min, sec, ms_val) };
Some(utc_components_to_ms(year, month - 1, day, h, min, sec, ms))}
static DAY_NAMES: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];static MONTH_NAMES: [&str; 12] = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",];
fn init_date_builtins(vm: &mut Vm) { // Date.prototype. let mut date_proto_data = ObjectData::new(); if let Some(proto) = vm.object_prototype { date_proto_data.prototype = Some(proto); } let date_proto = vm.gc.alloc(HeapObject::Object(date_proto_data)); init_date_prototype(&mut vm.gc, date_proto);
// Store prototype for constructor access. vm.date_prototype = Some(date_proto); DATE_PROTO.with(|cell| cell.set(Some(date_proto)));
// Date constructor function. let ctor = vm.gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Date".to_string(), kind: FunctionKind::Native(NativeFunc { callback: date_constructor, }), prototype_obj: Some(date_proto), properties: HashMap::new(), upvalues: Vec::new(), })));
// Static methods. let now_fn = make_native(&mut vm.gc, "now", date_now); set_func_prop(&mut vm.gc, ctor, "now", Value::Function(now_fn));
let parse_fn = make_native(&mut vm.gc, "parse", date_parse); set_func_prop(&mut vm.gc, ctor, "parse", Value::Function(parse_fn));
let utc_fn = make_native(&mut vm.gc, "UTC", date_utc); set_func_prop(&mut vm.gc, ctor, "UTC", Value::Function(utc_fn));
vm.set_global("Date", Value::Function(ctor));}
/// Internal: create a Date object (plain object with __date_ms__ property).fn make_date_obj(gc: &mut Gc<HeapObject>, ms: f64, proto: Option<GcRef>) -> Value { let mut data = ObjectData::new(); if let Some(p) = proto { data.prototype = Some(p); } data.properties.insert( "__date_ms__".to_string(), Property::builtin(Value::Number(ms)), ); Value::Object(gc.alloc(HeapObject::Object(data)))}
/// Extract timestamp from a Date object.fn date_get_ms(gc: &Gc<HeapObject>, this: &Value) -> f64 { match this { Value::Object(r) => match gc.get(*r) { Some(HeapObject::Object(data)) => data .properties .get("__date_ms__") .map(|p| p.value.to_number()) .unwrap_or(f64::NAN), _ => f64::NAN, }, _ => f64::NAN, }}
/// Set timestamp on a Date object and return the new value.fn date_set_ms(gc: &mut Gc<HeapObject>, this: &Value, ms: f64) -> f64 { if let Value::Object(r) = this { if let Some(HeapObject::Object(data)) = gc.get_mut(*r) { if let Some(prop) = data.properties.get_mut("__date_ms__") { prop.value = Value::Number(ms); } } } ms}
fn date_constructor(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let proto = DATE_PROTO.with(|cell| cell.get()); let ms = match args.len() { 0 => now_ms(), 1 => match &args[0] { Value::String(s) => parse_date_string(s).unwrap_or(f64::NAN), Value::Number(n) => *n, v => v.to_number(), }, _ => { // new Date(year, month, day?, hours?, min?, sec?, ms?) let year = args[0].to_number() as i64; let month = args[1].to_number() as i64; let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); // Years 0-99 map to 1900-1999 per spec. let year = if (0..100).contains(&year) { year + 1900 } else { year }; utc_components_to_ms(year, month, day, h, min, sec, ms) } }; Ok(make_date_obj(ctx.gc, ms, proto))}
fn date_now(_args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { Ok(Value::Number(now_ms()))}
fn date_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); Ok(Value::Number(parse_date_string(&s).unwrap_or(f64::NAN)))}
fn date_utc(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let year = args.first().map(|v| v.to_number() as i64).unwrap_or(1970); let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(0); let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(1); let h = args.get(3).map(|v| v.to_number() as i64).unwrap_or(0); let min = args.get(4).map(|v| v.to_number() as i64).unwrap_or(0); let sec = args.get(5).map(|v| v.to_number() as i64).unwrap_or(0); let ms = args.get(6).map(|v| v.to_number() as i64).unwrap_or(0); let year = if (0..100).contains(&year) { year + 1900 } else { year }; Ok(Value::Number(utc_components_to_ms( year, month, day, h, min, sec, ms, )))}
fn init_date_prototype(gc: &mut Gc<HeapObject>, proto: GcRef) { let methods: &[NativeMethod] = &[ ("getTime", date_get_time), ("valueOf", date_get_time), // valueOf === getTime ("getFullYear", date_get_full_year), ("getMonth", date_get_month), ("getDate", date_get_date), ("getDay", date_get_day), ("getHours", date_get_hours), ("getMinutes", date_get_minutes), ("getSeconds", date_get_seconds), ("getMilliseconds", date_get_milliseconds), ("getTimezoneOffset", date_get_timezone_offset), ("getUTCFullYear", date_get_full_year), ("getUTCMonth", date_get_month), ("getUTCDate", date_get_date), ("getUTCDay", date_get_day), ("getUTCHours", date_get_hours), ("getUTCMinutes", date_get_minutes), ("getUTCSeconds", date_get_seconds), ("getUTCMilliseconds", date_get_milliseconds), ("setTime", date_set_time), ("setFullYear", date_set_full_year), ("setMonth", date_set_month), ("setDate", date_set_date), ("setHours", date_set_hours), ("setMinutes", date_set_minutes), ("setSeconds", date_set_seconds), ("setMilliseconds", date_set_milliseconds), ("toString", date_to_string), ("toISOString", date_to_iso_string), ("toUTCString", date_to_utc_string), ("toLocaleDateString", date_to_locale_date_string), ("toJSON", date_to_json), ]; for &(name, cb) in methods { let f = make_native(gc, name, cb); set_builtin_prop(gc, proto, name, Value::Function(f)); }}
fn date_get_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let _ = args; Ok(Value::Number(date_get_ms(ctx.gc, &ctx.this)))}
fn date_get_full_year(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (y, ..) = ms_to_utc_components(ms); Ok(Value::Number(y as f64))}
fn date_get_month(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, m, ..) = ms_to_utc_components(ms); Ok(Value::Number(m as f64))}
fn date_get_date(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, _, d, ..) = ms_to_utc_components(ms); Ok(Value::Number(d as f64))}
fn date_get_day(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (.., wd) = ms_to_utc_components(ms); Ok(Value::Number(wd as f64))}
fn date_get_hours(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, _, _, h, ..) = ms_to_utc_components(ms); Ok(Value::Number(h as f64))}
fn date_get_minutes(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, _, _, _, min, ..) = ms_to_utc_components(ms); Ok(Value::Number(min as f64))}
fn date_get_seconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, _, _, _, _, s, ..) = ms_to_utc_components(ms); Ok(Value::Number(s as f64))}
fn date_get_milliseconds(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Number(f64::NAN)); } let (_, _, _, _, _, _, ms_part, _) = ms_to_utc_components(ms); Ok(Value::Number(ms_part as f64))}
fn date_get_timezone_offset( _args: &[Value], _ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { // We operate in UTC; return 0. Ok(Value::Number(0.0))}
fn date_set_time(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); date_set_ms(ctx.gc, &ctx.this, ms); Ok(Value::Number(ms))}
fn date_set_full_year(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (_, m, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let year = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let month = args.get(1).map(|v| v.to_number() as i64).unwrap_or(m); let day = args.get(2).map(|v| v.to_number() as i64).unwrap_or(d); let new_ms = utc_components_to_ms(year, month, day, h, min, s, ms); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_month(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, _, d, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let month = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let day = args.get(1).map(|v| v.to_number() as i64).unwrap_or(d); let new_ms = utc_components_to_ms(y, month, day, h, min, s, ms); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_date(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, m, _, h, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let day = args.first().map(|v| v.to_number() as i64).unwrap_or(1); let new_ms = utc_components_to_ms(y, m, day, h, min, s, ms); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_hours(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, m, d, _, min, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let h = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let min_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(min); let sec = args.get(2).map(|v| v.to_number() as i64).unwrap_or(s); let ms_v = args.get(3).map(|v| v.to_number() as i64).unwrap_or(ms); let new_ms = utc_components_to_ms(y, m, d, h, min_v, sec, ms_v); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_minutes(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, m, d, h, _, s, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let min = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let sec = args.get(1).map(|v| v.to_number() as i64).unwrap_or(s); let ms_v = args.get(2).map(|v| v.to_number() as i64).unwrap_or(ms); let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_seconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, m, d, h, min, _, ms, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let sec = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let ms_v = args.get(1).map(|v| v.to_number() as i64).unwrap_or(ms); let new_ms = utc_components_to_ms(y, m, d, h, min, sec, ms_v); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_set_milliseconds(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let old = date_get_ms(ctx.gc, &ctx.this); let (y, m, d, h, min, s, _, _) = ms_to_utc_components(if old.is_nan() { 0.0 } else { old }); let ms_v = args.first().map(|v| v.to_number() as i64).unwrap_or(0); let new_ms = utc_components_to_ms(y, m, d, h, min, s, ms_v); date_set_ms(ctx.gc, &ctx.this, new_ms); Ok(Value::Number(new_ms))}
fn date_to_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::String("Invalid Date".to_string())); } let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); Ok(Value::String(format!( "{} {} {:02} {} {:02}:{:02}:{:02} GMT", DAY_NAMES[wd as usize], MONTH_NAMES[m as usize], d, y, h, min, s )))}
fn date_to_iso_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Err(RuntimeError::range_error("Invalid time value")); } let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); Ok(Value::String(format!( "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", y, m + 1, d, h, min, s, ms_part )))}
fn date_to_utc_string(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::String("Invalid Date".to_string())); } let (y, m, d, h, min, s, _, wd) = ms_to_utc_components(ms); Ok(Value::String(format!( "{}, {:02} {} {} {:02}:{:02}:{:02} GMT", DAY_NAMES[wd as usize], d, MONTH_NAMES[m as usize], y, h, min, s )))}
fn date_to_locale_date_string( _args: &[Value], ctx: &mut NativeContext,) -> Result<Value, RuntimeError> { // Simplified: same as toISOString date portion. let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::String("Invalid Date".to_string())); } let (y, m, d, ..) = ms_to_utc_components(ms); Ok(Value::String(format!("{:04}-{:02}-{:02}", y, m + 1, d)))}
fn date_to_json(_args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let ms = date_get_ms(ctx.gc, &ctx.this); if ms.is_nan() { return Ok(Value::Null); } date_to_iso_string(_args, ctx)}
// ── JSON object ──────────────────────────────────────────────
fn init_json_object(vm: &mut Vm) { let mut data = ObjectData::new(); if let Some(proto) = vm.object_prototype { data.prototype = Some(proto); } let json_ref = vm.gc.alloc(HeapObject::Object(data));
let parse_fn = make_native(&mut vm.gc, "parse", json_parse); set_builtin_prop(&mut vm.gc, json_ref, "parse", Value::Function(parse_fn));
let stringify_fn = make_native(&mut vm.gc, "stringify", json_stringify); set_builtin_prop( &mut vm.gc, json_ref, "stringify", Value::Function(stringify_fn), );
vm.set_global("JSON", Value::Object(json_ref));}
// ── JSON.parse ───────────────────────────────────────────────
/// Minimal JSON tokenizer.#[derive(Debug, Clone, PartialEq)]enum JsonToken { LBrace, RBrace, LBracket, RBracket, Colon, Comma, String(String), Number(f64), True, False, Null,}
struct JsonLexer<'a> { chars: &'a [u8], pos: usize,}
impl<'a> JsonLexer<'a> { fn new(input: &'a str) -> Self { Self { chars: input.as_bytes(), pos: 0, } }
fn skip_ws(&mut self) { while self.pos < self.chars.len() { match self.chars[self.pos] { b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1, _ => break, } } }
fn next_token(&mut self) -> Result<Option<JsonToken>, RuntimeError> { self.skip_ws(); if self.pos >= self.chars.len() { return Ok(None); } let ch = self.chars[self.pos]; match ch { b'{' => { self.pos += 1; Ok(Some(JsonToken::LBrace)) } b'}' => { self.pos += 1; Ok(Some(JsonToken::RBrace)) } b'[' => { self.pos += 1; Ok(Some(JsonToken::LBracket)) } b']' => { self.pos += 1; Ok(Some(JsonToken::RBracket)) } b':' => { self.pos += 1; Ok(Some(JsonToken::Colon)) } b',' => { self.pos += 1; Ok(Some(JsonToken::Comma)) } b'"' => self.read_string(), b't' => self.read_keyword("true", JsonToken::True), b'f' => self.read_keyword("false", JsonToken::False), b'n' => self.read_keyword("null", JsonToken::Null), b'-' | b'0'..=b'9' => self.read_number(), _ => Err(RuntimeError::syntax_error(format!( "Unexpected character '{}' in JSON", ch as char ))), } }
fn read_string(&mut self) -> Result<Option<JsonToken>, RuntimeError> { self.pos += 1; // skip opening quote let mut s = String::new(); while self.pos < self.chars.len() { let ch = self.chars[self.pos]; self.pos += 1; if ch == b'"' { return Ok(Some(JsonToken::String(s))); } if ch == b'\\' { if self.pos >= self.chars.len() { return Err(RuntimeError::syntax_error("Unterminated string in JSON")); } let esc = self.chars[self.pos]; self.pos += 1; match esc { b'"' => s.push('"'), b'\\' => s.push('\\'), b'/' => s.push('/'), b'b' => s.push('\u{0008}'), b'f' => s.push('\u{000C}'), b'n' => s.push('\n'), b'r' => s.push('\r'), b't' => s.push('\t'), b'u' => { if self.pos + 4 > self.chars.len() { return Err(RuntimeError::syntax_error("Invalid unicode escape")); } let hex = std::str::from_utf8(&self.chars[self.pos..self.pos + 4]) .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; self.pos += 4; let code = u32::from_str_radix(hex, 16) .map_err(|_| RuntimeError::syntax_error("Invalid unicode escape"))?; if let Some(c) = char::from_u32(code) { s.push(c); } else { return Err(RuntimeError::syntax_error("Invalid unicode code point")); } } _ => { return Err(RuntimeError::syntax_error(format!( "Invalid escape character '\\{}'", esc as char ))); } } } else if ch < 0x80 { // ASCII byte — safe to cast directly. s.push(ch as char); } else { // Multi-byte UTF-8: determine sequence length from lead byte. let byte_len = if ch < 0xE0 { 2 } else if ch < 0xF0 { 3 } else { 4 }; let start = self.pos - 1; // already advanced past lead byte let end = start + byte_len; if end > self.chars.len() { return Err(RuntimeError::syntax_error("Invalid UTF-8 in JSON string")); } if let Ok(slice) = std::str::from_utf8(&self.chars[start..end]) { s.push_str(slice); } else { return Err(RuntimeError::syntax_error("Invalid UTF-8 in JSON string")); } self.pos = end; } } Err(RuntimeError::syntax_error("Unterminated string in JSON")) }
fn read_number(&mut self) -> Result<Option<JsonToken>, RuntimeError> { let start = self.pos; if self.pos < self.chars.len() && self.chars[self.pos] == b'-' { self.pos += 1; } while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { self.pos += 1; } if self.pos < self.chars.len() && self.chars[self.pos] == b'.' { self.pos += 1; while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { self.pos += 1; } } if self.pos < self.chars.len() && (self.chars[self.pos] == b'e' || self.chars[self.pos] == b'E') { self.pos += 1; if self.pos < self.chars.len() && (self.chars[self.pos] == b'+' || self.chars[self.pos] == b'-') { self.pos += 1; } while self.pos < self.chars.len() && self.chars[self.pos].is_ascii_digit() { self.pos += 1; } } let num_str = std::str::from_utf8(&self.chars[start..self.pos]) .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; let n: f64 = num_str .parse() .map_err(|_| RuntimeError::syntax_error("Invalid number in JSON"))?; Ok(Some(JsonToken::Number(n))) }
fn read_keyword( &mut self, keyword: &str, token: JsonToken, ) -> Result<Option<JsonToken>, RuntimeError> { let end = self.pos + keyword.len(); if end <= self.chars.len() && &self.chars[self.pos..end] == keyword.as_bytes() { self.pos = end; Ok(Some(token)) } else { Err(RuntimeError::syntax_error(format!( "Unexpected token in JSON at position {}", self.pos ))) } }}
struct JsonParser<'a> { lexer: JsonLexer<'a>, current: Option<JsonToken>,}
impl<'a> JsonParser<'a> { fn new(input: &'a str) -> Result<Self, RuntimeError> { let mut lexer = JsonLexer::new(input); let current = lexer.next_token()?; Ok(Self { lexer, current }) }
fn advance(&mut self) -> Result<(), RuntimeError> { self.current = self.lexer.next_token()?; Ok(()) }
fn parse_value(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { match self.current.take() { Some(JsonToken::Null) => { self.advance()?; Ok(Value::Null) } Some(JsonToken::True) => { self.advance()?; Ok(Value::Boolean(true)) } Some(JsonToken::False) => { self.advance()?; Ok(Value::Boolean(false)) } Some(JsonToken::Number(n)) => { self.advance()?; Ok(Value::Number(n)) } Some(JsonToken::String(s)) => { self.advance()?; Ok(Value::String(s)) } Some(JsonToken::LBracket) => self.parse_array(gc), Some(JsonToken::LBrace) => self.parse_object(gc), Some(other) => { // Put it back for error context. self.current = Some(other); Err(RuntimeError::syntax_error("Unexpected token in JSON")) } None => Err(RuntimeError::syntax_error("Unexpected end of JSON input")), } }
fn parse_array(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { // Current token was LBracket, already consumed via take(). self.advance()?; // move past '[' let mut items: Vec<Value> = Vec::new(); if self.current == Some(JsonToken::RBracket) { self.advance()?; let mut obj = ObjectData::new(); obj.properties.insert( "length".to_string(), Property { value: Value::Number(0.0), writable: true, enumerable: false, configurable: false, }, ); return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); } loop { let val = self.parse_value(gc)?; items.push(val); match &self.current { Some(JsonToken::Comma) => { self.advance()?; } Some(JsonToken::RBracket) => { self.advance()?; break; } _ => { return Err(RuntimeError::syntax_error( "Expected ',' or ']' in JSON array", )); } } } let mut obj = ObjectData::new(); for (i, v) in items.iter().enumerate() { obj.properties .insert(i.to_string(), Property::data(v.clone())); } obj.properties.insert( "length".to_string(), Property { value: Value::Number(items.len() as f64), writable: true, enumerable: false, configurable: false, }, ); Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) }
fn parse_object(&mut self, gc: &mut Gc<HeapObject>) -> Result<Value, RuntimeError> { self.advance()?; // move past '{' let mut obj = ObjectData::new(); if self.current == Some(JsonToken::RBrace) { self.advance()?; return Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))); } loop { let key = match self.current.take() { Some(JsonToken::String(s)) => s, _ => { return Err(RuntimeError::syntax_error( "Expected string key in JSON object", )); } }; self.advance()?; if self.current != Some(JsonToken::Colon) { return Err(RuntimeError::syntax_error( "Expected ':' after key in JSON object", )); } self.advance()?; let val = self.parse_value(gc)?; obj.properties.insert(key, Property::data(val)); match &self.current { Some(JsonToken::Comma) => { self.advance()?; } Some(JsonToken::RBrace) => { self.advance()?; break; } _ => { return Err(RuntimeError::syntax_error( "Expected ',' or '}}' in JSON object", )); } } } Ok(Value::Object(gc.alloc(HeapObject::Object(obj)))) }}
fn json_parse(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let text = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let mut parser = JsonParser::new(&text)?; let value = parser.parse_value(ctx.gc)?; // Ensure no trailing content. if parser.current.is_some() { return Err(RuntimeError::syntax_error( "Unexpected token after JSON value", )); } // TODO: reviver support — skip for now. Ok(value)}
// ── JSON.stringify ───────────────────────────────────────────
fn json_stringify(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let value = args.first().cloned().unwrap_or(Value::Undefined); let indent = match args.get(2) { Some(Value::Number(n)) => { let n = *n as usize; if n > 0 { " ".repeat(n.min(10)) } else { String::new() } } Some(Value::String(s)) => s[..s.len().min(10)].to_string(), _ => String::new(), }; // Track visited objects for circular reference detection. let mut visited: Vec<GcRef> = Vec::new(); let result = json_stringify_value(&value, ctx.gc, &indent, "", &mut visited)?; match result { Some(s) => Ok(Value::String(s)), None => Ok(Value::Undefined), }}
fn json_stringify_value( value: &Value, gc: &Gc<HeapObject>, indent: &str, current_indent: &str, visited: &mut Vec<GcRef>,) -> Result<Option<String>, RuntimeError> { match value { Value::Null => Ok(Some("null".to_string())), Value::Boolean(true) => Ok(Some("true".to_string())), Value::Boolean(false) => Ok(Some("false".to_string())), Value::Number(n) => { if n.is_nan() || n.is_infinite() { Ok(Some("null".to_string())) } else { Ok(Some(js_number_to_string(*n))) } } Value::String(s) => Ok(Some(json_quote_string(s))), Value::Undefined | Value::Function(_) => Ok(None), Value::Object(gc_ref) => { // Circular reference check. if visited.contains(gc_ref) { return Err(RuntimeError::type_error( "Converting circular structure to JSON", )); } visited.push(*gc_ref);
let result = match gc.get(*gc_ref) { Some(HeapObject::Object(data)) => { // Check for toJSON method. if let Some(prop) = data.properties.get("toJSON") { if matches!(prop.value, Value::Function(_)) { // We can't call JS functions from here easily, // but for Date objects we recognize the __date_ms__ pattern. if let Some(date_prop) = data.properties.get("__date_ms__") { let ms = date_prop.value.to_number(); if ms.is_nan() { visited.pop(); return Ok(Some("null".to_string())); } let (y, m, d, h, min, s, ms_part, _) = ms_to_utc_components(ms); visited.pop(); return Ok(Some(format!( "\"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z\"", y, m + 1, d, h, min, s, ms_part ))); } } } if array_length_exists(gc, *gc_ref) { json_stringify_array(gc, *gc_ref, indent, current_indent, visited) } else { json_stringify_object(gc, data, indent, current_indent, visited) } } _ => Ok(Some("{}".to_string())), };
visited.pop(); result } }}
fn json_stringify_array( gc: &Gc<HeapObject>, arr: GcRef, indent: &str, current_indent: &str, visited: &mut Vec<GcRef>,) -> Result<Option<String>, RuntimeError> { let len = array_length(gc, arr); if len == 0 { return Ok(Some("[]".to_string())); } let has_indent = !indent.is_empty(); let next_indent = if has_indent { format!("{}{}", current_indent, indent) } else { String::new() }; let mut parts: Vec<String> = Vec::new(); for i in 0..len { let val = array_get(gc, arr, i); match json_stringify_value(&val, gc, indent, &next_indent, visited)? { Some(s) => parts.push(s), None => parts.push("null".to_string()), } } if has_indent { Ok(Some(format!( "[\n{}{}\n{}]", next_indent, parts.join(&format!(",\n{}", next_indent)), current_indent ))) } else { Ok(Some(format!("[{}]", parts.join(",")))) }}
fn json_stringify_object( gc: &Gc<HeapObject>, data: &ObjectData, indent: &str, current_indent: &str, visited: &mut Vec<GcRef>,) -> Result<Option<String>, RuntimeError> { let has_indent = !indent.is_empty(); let next_indent = if has_indent { format!("{}{}", current_indent, indent) } else { String::new() }; let mut parts: Vec<String> = Vec::new(); // Collect and sort keys for deterministic output. let mut keys: Vec<&String> = data.properties.keys().collect(); keys.sort(); for key in keys { let prop = &data.properties[key]; if !prop.enumerable { continue; } if let Some(val_str) = json_stringify_value(&prop.value, gc, indent, &next_indent, visited)? { if has_indent { parts.push(format!("{}: {}", json_quote_string(key), val_str)); } else { parts.push(format!("{}:{}", json_quote_string(key), val_str)); } } } if parts.is_empty() { return Ok(Some("{}".to_string())); } if has_indent { Ok(Some(format!( "{{\n{}{}\n{}}}", next_indent, parts.join(&format!(",\n{}", next_indent)), current_indent ))) } else { Ok(Some(format!("{{{}}}", parts.join(",")))) }}
fn json_quote_string(s: &str) -> String { let mut out = String::with_capacity(s.len() + 2); out.push('"'); for ch in s.chars() { match ch { '"' => out.push_str("\\\""), '\\' => out.push_str("\\\\"), '\n' => out.push_str("\\n"), '\r' => out.push_str("\\r"), '\t' => out.push_str("\\t"), '\u{0008}' => out.push_str("\\b"), '\u{000C}' => out.push_str("\\f"), c if c < '\u{0020}' => { out.push_str(&format!("\\u{:04x}", c as u32)); } c => out.push(c), } } out.push('"'); out}
// ── Global utility functions ─────────────────────────────────
fn init_global_functions(vm: &mut Vm) { vm.define_native("parseInt", parse_int); vm.define_native("parseFloat", parse_float); vm.define_native("isNaN", is_nan); vm.define_native("isFinite", is_finite);}
fn parse_int(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let radix = args.get(1).map(|v| v.to_number() as u32).unwrap_or(0); let s = s.trim(); if s.is_empty() { return Ok(Value::Number(f64::NAN)); } let (neg, s) = if let Some(rest) = s.strip_prefix('-') { (true, rest) } else if let Some(rest) = s.strip_prefix('+') { (false, rest) } else { (false, s) }; // Auto-detect hex. let (radix, s) = if radix == 0 || radix == 16 { if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { (16, rest) } else { (if radix == 0 { 10 } else { radix }, s) } } else { (radix, s) }; if !(2..=36).contains(&radix) { return Ok(Value::Number(f64::NAN)); } // Parse digits until invalid. let mut result: f64 = 0.0; let mut found = false; for c in s.chars() { let digit = match c.to_digit(radix) { Some(d) => d, None => break, }; result = result * radix as f64 + digit as f64; found = true; } if !found { return Ok(Value::Number(f64::NAN)); } if neg { result = -result; } Ok(Value::Number(result))}
fn parse_float(args: &[Value], ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let s = args .first() .map(|v| v.to_js_string(ctx.gc)) .unwrap_or_default(); let s = s.trim(); match s.parse::<f64>() { Ok(n) => Ok(Value::Number(n)), Err(_) => Ok(Value::Number(f64::NAN)), }}
fn is_nan(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Boolean(n.is_nan()))}
fn is_finite(args: &[Value], _ctx: &mut NativeContext) -> Result<Value, RuntimeError> { let n = args.first().map(|v| v.to_number()).unwrap_or(f64::NAN); Ok(Value::Boolean(n.is_finite()))}
// ── Strict equality (for Array.indexOf etc.) ─────────────────
fn strict_eq_values(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Undefined, Value::Undefined) => true, (Value::Null, Value::Null) => true, (Value::Number(x), Value::Number(y)) => x == y, (Value::String(x), Value::String(y)) => x == y, (Value::Boolean(x), Value::Boolean(y)) => x == y, (Value::Object(x), Value::Object(y)) => x == y, (Value::Function(x), Value::Function(y)) => x == y, _ => false, }}
/// SameValueZero (like === but NaN === NaN is true).fn same_value_zero(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Number(x), Value::Number(y)) => { if x.is_nan() && y.is_nan() { return true; } x == y } _ => strict_eq_values(a, b), }}
// ── JS preamble for callback-based methods ───────────────────
fn init_js_preamble(vm: &mut Vm) { // NOTE: All preamble methods capture `this` into a local `_t` variable // because nested method calls (e.g. result.push()) clobber the global `this`. let preamble = r#"Array.prototype.forEach = function(cb) { var _t = this; for (var i = 0; i < _t.length; i = i + 1) { cb(_t[i], i, _t); }};Array.prototype.map = function(cb) { var _t = this; var result = []; for (var i = 0; i < _t.length; i = i + 1) { result.push(cb(_t[i], i, _t)); } return result;};Array.prototype.filter = function(cb) { var _t = this; var result = []; for (var i = 0; i < _t.length; i = i + 1) { if (cb(_t[i], i, _t)) { result.push(_t[i]); } } return result;};Array.prototype.reduce = function(cb, init) { var _t = this; var acc = init; var start = 0; if (acc === undefined) { acc = _t[0]; start = 1; } for (var i = start; i < _t.length; i = i + 1) { acc = cb(acc, _t[i], i, _t); } return acc;};Array.prototype.reduceRight = function(cb, init) { var _t = this; var acc = init; var start = _t.length - 1; if (acc === undefined) { acc = _t[start]; start = start - 1; } for (var i = start; i >= 0; i = i - 1) { acc = cb(acc, _t[i], i, _t); } return acc;};Array.prototype.find = function(cb) { var _t = this; for (var i = 0; i < _t.length; i = i + 1) { if (cb(_t[i], i, _t)) { return _t[i]; } } return undefined;};Array.prototype.findIndex = function(cb) { var _t = this; for (var i = 0; i < _t.length; i = i + 1) { if (cb(_t[i], i, _t)) { return i; } } return -1;};Array.prototype.some = function(cb) { var _t = this; for (var i = 0; i < _t.length; i = i + 1) { if (cb(_t[i], i, _t)) { return true; } } return false;};Array.prototype.every = function(cb) { var _t = this; for (var i = 0; i < _t.length; i = i + 1) { if (!cb(_t[i], i, _t)) { return false; } } return true;};Array.prototype.sort = function(cmp) { var _t = this; var len = _t.length; for (var i = 0; i < len; i = i + 1) { for (var j = 0; j < len - i - 1; j = j + 1) { var a = _t[j]; var b = _t[j + 1]; var order; if (cmp) { order = cmp(a, b); } else { var sa = "" + a; var sb = "" + b; if (sa > sb) { order = 1; } else if (sa < sb) { order = -1; } else { order = 0; } } if (order > 0) { _t[j] = b; _t[j + 1] = a; } } } return _t;};Array.prototype.flat = function(depth) { var _t = this; if (depth === undefined) { depth = 1; } var result = []; for (var i = 0; i < _t.length; i = i + 1) { var elem = _t[i]; if (depth > 0 && Array.isArray(elem)) { var sub = elem.flat(depth - 1); for (var j = 0; j < sub.length; j = j + 1) { result.push(sub[j]); } } else { result.push(elem); } } return result;};Array.prototype.flatMap = function(cb) { var _t = this; return _t.map(cb).flat();};"#; // Compile and execute the preamble. let ast = match crate::parser::Parser::parse(preamble) { Ok(ast) => ast, Err(_) => return, // Silently skip if preamble fails to parse. }; let func = match crate::compiler::compile(&ast) { Ok(func) => func, Err(_) => return, }; let _ = vm.execute(&func);}