Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196//! Register-based JavaScript virtual machine.//!//! Executes bytecode produced by the compiler. Each call frame has a register//! file, and the VM dispatches instructions in a loop. Heap-allocated objects//! (plain objects and functions) are managed by a tri-color mark-and-sweep//! garbage collector.
use crate::bytecode::{Constant, Function, Op, Reg};use crate::gc::{Gc, GcRef, Traceable};use std::collections::HashMap;use std::fmt;
// ── Heap objects (GC-managed) ────────────────────────────────
/// A GC-managed heap object: a plain object, a function, or a closure cell.pub enum HeapObject { Object(ObjectData), Function(Box<FunctionData>), /// A mutable cell holding one Value — used for closure-captured variables. Cell(Value),}
impl Traceable for HeapObject { fn trace(&self, visitor: &mut dyn FnMut(GcRef)) { match self { HeapObject::Object(data) => { for prop in data.properties.values() { if let Some(r) = prop.value.gc_ref() { visitor(r); } } if let Some(proto) = data.prototype { visitor(proto); } } HeapObject::Function(fdata) => { if let Some(proto) = fdata.prototype_obj { visitor(proto); } for prop in fdata.properties.values() { if let Some(r) = prop.value.gc_ref() { visitor(r); } } for &uv in &fdata.upvalues { visitor(uv); } } HeapObject::Cell(val) => { if let Some(r) = val.gc_ref() { visitor(r); } } } }}
/// A property descriptor stored in an object's property map.#[derive(Clone)]pub struct Property { /// The property's value (for data properties). pub value: Value, /// Whether the value can be changed via assignment. pub writable: bool, /// Whether the property shows up in `for...in` and `Object.keys`. pub enumerable: bool, /// Whether the property can be deleted or its attributes changed. pub configurable: bool,}
impl Property { /// Create a new data property with all flags set to true (the JS default for /// properties created by assignment). pub fn data(value: Value) -> Self { Self { value, writable: true, enumerable: true, configurable: true, } }
/// Create a non-enumerable, non-configurable property (e.g. built-in methods). pub fn builtin(value: Value) -> Self { Self { value, writable: true, enumerable: false, configurable: false, } }}
/// A JS plain object (properties stored as a HashMap with descriptors).pub struct ObjectData { pub properties: HashMap<String, Property>, pub prototype: Option<GcRef>, /// Whether new properties can be added (Object.preventExtensions). pub extensible: bool,}
impl ObjectData { pub fn new() -> Self { Self { properties: HashMap::new(), prototype: None, extensible: true, } }}
impl Default for ObjectData { fn default() -> Self { Self::new() }}
/// A runtime function value: either bytecode or native.////// In JavaScript, functions are objects and can have arbitrary properties/// (e.g. `assert.sameValue = function() {}`).pub struct FunctionData { pub name: String, pub kind: FunctionKind, /// The `.prototype` property object (for use as a constructor with `instanceof`). pub prototype_obj: Option<GcRef>, /// Arbitrary properties set on this function (functions are objects in JS). pub properties: HashMap<String, Property>, /// Captured upvalue cells (GcRefs to HeapObject::Cell values). pub upvalues: Vec<GcRef>,}
#[derive(Clone)]pub enum FunctionKind { /// Bytecode function. Bytecode(BytecodeFunc), /// Native (Rust) function. Native(NativeFunc),}
#[derive(Clone)]pub struct BytecodeFunc { pub func: Function,}
/// A native function callable from JS.#[derive(Clone)]pub struct NativeFunc { pub callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>,}
/// Context passed to native functions, providing GC access and `this` binding.pub struct NativeContext<'a> { pub gc: &'a mut Gc<HeapObject>, pub this: Value,}
// ── JS Value ──────────────────────────────────────────────────
/// A JavaScript runtime value.////// Primitive types (Undefined, Null, Boolean, Number, String) are stored/// inline. Objects and Functions are heap-allocated via the GC and referenced/// by a [`GcRef`] handle.#[derive(Clone)]pub enum Value { Undefined, Null, Boolean(bool), Number(f64), String(String), /// A GC-managed plain object. Object(GcRef), /// A GC-managed function. Function(GcRef),}
impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Value::Undefined => write!(f, "undefined"), Value::Null => write!(f, "null"), Value::Boolean(b) => write!(f, "{b}"), Value::Number(n) => write!(f, "{n}"), Value::String(s) => write!(f, "\"{}\"", s), Value::Object(_) => write!(f, "[object Object]"), Value::Function(_) => write!(f, "[Function]"), } }}
impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Value::Undefined => write!(f, "undefined"), Value::Null => write!(f, "null"), Value::Boolean(b) => write!(f, "{b}"), Value::Number(n) => format_number(*n, f), Value::String(s) => write!(f, "{s}"), Value::Object(_) => write!(f, "[object Object]"), Value::Function(_) => write!(f, "function() {{ [native code] }}"), } }}
/// Format a number following JS conventions (no trailing .0 for integers).fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result { if n.is_nan() { write!(f, "NaN") } else if n.is_infinite() { if n.is_sign_positive() { write!(f, "Infinity") } else { write!(f, "-Infinity") } } else if n == 0.0 { write!(f, "0") } else if n.fract() == 0.0 && n.abs() < 1e20 { write!(f, "{}", n as i64) } else { write!(f, "{n}") }}
impl Value { /// Abstract `ToBoolean` (ECMA-262 §7.1.2). pub fn to_boolean(&self) -> bool { match self { Value::Undefined | Value::Null => false, Value::Boolean(b) => *b, Value::Number(n) => *n != 0.0 && !n.is_nan(), Value::String(s) => !s.is_empty(), Value::Object(_) | Value::Function(_) => true, } }
/// Abstract `ToNumber` (ECMA-262 §7.1.3). pub fn to_number(&self) -> f64 { match self { Value::Undefined => f64::NAN, Value::Null => 0.0, Value::Boolean(true) => 1.0, Value::Boolean(false) => 0.0, Value::Number(n) => *n, Value::String(s) => { let s = s.trim(); if s.is_empty() { 0.0 } else if s == "Infinity" || s == "+Infinity" { f64::INFINITY } else if s == "-Infinity" { f64::NEG_INFINITY } else { s.parse::<f64>().unwrap_or(f64::NAN) } } Value::Object(_) | Value::Function(_) => f64::NAN, } }
/// Abstract `ToString` (ECMA-262 §7.1.12). /// /// Requires `&Gc` to look up function names for `Value::Function`. pub fn to_js_string(&self, gc: &Gc<HeapObject>) -> String { match self { Value::Undefined => "undefined".to_string(), Value::Null => "null".to_string(), Value::Boolean(true) => "true".to_string(), Value::Boolean(false) => "false".to_string(), Value::Number(n) => js_number_to_string(*n), Value::String(s) => s.clone(), Value::Object(_) => "[object Object]".to_string(), Value::Function(gc_ref) => gc .get(*gc_ref) .and_then(|obj| match obj { HeapObject::Function(f) => { Some(format!("function {}() {{ [native code] }}", f.name)) } _ => None, }) .unwrap_or_else(|| "function() { [native code] }".to_string()), } }
/// `typeof` operator result. pub fn type_of(&self) -> &'static str { match self { Value::Undefined => "undefined", Value::Null => "object", // yes, this is the spec Value::Boolean(_) => "boolean", Value::Number(_) => "number", Value::String(_) => "string", Value::Object(_) => "object", Value::Function(_) => "function", } }
/// Is this value nullish (null or undefined)? pub fn is_nullish(&self) -> bool { matches!(self, Value::Undefined | Value::Null) }
/// Extract the `GcRef` if this value is an Object or Function. pub fn gc_ref(&self) -> Option<GcRef> { match self { Value::Object(r) | Value::Function(r) => Some(*r), _ => None, } }}
/// Format a number as JS would.pub(crate) fn js_number_to_string(n: f64) -> String { if n.is_nan() { "NaN".to_string() } else if n.is_infinite() { if n.is_sign_positive() { "Infinity".to_string() } else { "-Infinity".to_string() } } else if n == 0.0 { "0".to_string() } else if n.fract() == 0.0 && n.abs() < 1e20 { format!("{}", n as i64) } else { format!("{n}") }}
// ── Runtime errors ────────────────────────────────────────────
/// JavaScript runtime error types.#[derive(Debug, Clone)]pub struct RuntimeError { pub kind: ErrorKind, pub message: String,}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum ErrorKind { TypeError, ReferenceError, RangeError, SyntaxError, Error,}
impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = match self.kind { ErrorKind::TypeError => "TypeError", ErrorKind::ReferenceError => "ReferenceError", ErrorKind::RangeError => "RangeError", ErrorKind::SyntaxError => "SyntaxError", ErrorKind::Error => "Error", }; write!(f, "{name}: {}", self.message) }}
impl RuntimeError { pub fn type_error(msg: impl Into<String>) -> Self { Self { kind: ErrorKind::TypeError, message: msg.into(), } }
pub fn reference_error(msg: impl Into<String>) -> Self { Self { kind: ErrorKind::ReferenceError, message: msg.into(), } }
pub fn range_error(msg: impl Into<String>) -> Self { Self { kind: ErrorKind::RangeError, message: msg.into(), } }
pub fn syntax_error(msg: impl Into<String>) -> Self { Self { kind: ErrorKind::SyntaxError, message: msg.into(), } }
/// Convert to a JS Value (an error object). Allocates through the GC. pub fn to_value(&self, gc: &mut Gc<HeapObject>) -> Value { let mut obj = ObjectData::new(); obj.properties.insert( "message".to_string(), Property::data(Value::String(self.message.clone())), ); let name = match self.kind { ErrorKind::TypeError => "TypeError", ErrorKind::ReferenceError => "ReferenceError", ErrorKind::RangeError => "RangeError", ErrorKind::SyntaxError => "SyntaxError", ErrorKind::Error => "Error", }; obj.properties.insert( "name".to_string(), Property::data(Value::String(name.to_string())), ); Value::Object(gc.alloc(HeapObject::Object(obj))) }}
// ── Property access helpers ──────────────────────────────────
/// Get a property from an object, walking the prototype chain.fn gc_get_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> Value { let proto = { match gc.get(obj_ref) { Some(HeapObject::Object(data)) => { if let Some(prop) = data.properties.get(key) { return prop.value.clone(); } data.prototype } Some(HeapObject::Function(fdata)) => { // Check user-defined properties first. if let Some(prop) = fdata.properties.get(key) { return prop.value.clone(); } // Functions have a `.prototype` property. if key == "prototype" { if let Some(proto_ref) = fdata.prototype_obj { return Value::Object(proto_ref); } return Value::Undefined; } None } _ => return Value::Undefined, } }; if let Some(proto_ref) = proto { gc_get_property(gc, proto_ref, key) } else { Value::Undefined }}
/// Check if an object has a property (own or inherited).fn gc_has_property(gc: &Gc<HeapObject>, obj_ref: GcRef, key: &str) -> bool { let proto = { match gc.get(obj_ref) { Some(HeapObject::Object(data)) => { if data.properties.contains_key(key) { return true; } data.prototype } Some(HeapObject::Function(fdata)) => { if fdata.properties.contains_key(key) || key == "prototype" { return true; } None } _ => return false, } }; if let Some(proto_ref) = proto { gc_has_property(gc, proto_ref, key) } else { false }}
/// Collect all enumerable string keys of an object (own + inherited), in proper order./// Integer indices first (sorted numerically), then string keys in insertion order.fn gc_enumerate_keys(gc: &Gc<HeapObject>, obj_ref: GcRef) -> Vec<String> { let mut seen = std::collections::HashSet::new(); let mut integer_keys: Vec<(u32, String)> = Vec::new(); let mut string_keys: Vec<String> = Vec::new(); let mut current = Some(obj_ref);
while let Some(cur_ref) = current { match gc.get(cur_ref) { Some(HeapObject::Object(data)) => { for (key, prop) in &data.properties { if prop.enumerable && seen.insert(key.clone()) { if let Ok(idx) = key.parse::<u32>() { integer_keys.push((idx, key.clone())); } else { string_keys.push(key.clone()); } } } current = data.prototype; } _ => break, } }
// Integer indices sorted numerically first, then string keys in collected order. integer_keys.sort_by_key(|(idx, _)| *idx); let mut result: Vec<String> = integer_keys.into_iter().map(|(_, k)| k).collect(); result.extend(string_keys); result}
/// Check if `obj_ref` is an instance of the constructor at `ctor_ref`./// Walks the prototype chain of `obj_ref` looking for `ctor.prototype`.fn gc_instanceof(gc: &Gc<HeapObject>, obj_ref: GcRef, ctor_ref: GcRef) -> bool { // Get the constructor's .prototype object. let ctor_proto = match gc.get(ctor_ref) { Some(HeapObject::Function(fdata)) => match fdata.prototype_obj { Some(p) => p, None => return false, }, _ => return false, };
// Walk the prototype chain of obj_ref. let mut current = match gc.get(obj_ref) { Some(HeapObject::Object(data)) => data.prototype, _ => None, };
while let Some(proto_ref) = current { if proto_ref == ctor_proto { return true; } current = match gc.get(proto_ref) { Some(HeapObject::Object(data)) => data.prototype, _ => None, }; } false}
/// Get a string property (length, index access).fn string_get_property(s: &str, key: &str) -> Value { if key == "length" { Value::Number(s.len() as f64) } else if let Ok(idx) = key.parse::<usize>() { s.chars() .nth(idx) .map(|c| Value::String(c.to_string())) .unwrap_or(Value::Undefined) } else { Value::Undefined }}
// ── Type conversion helpers ──────────────────────────────────
/// ToInt32 (ECMA-262 §7.1.5).fn to_int32(val: &Value) -> i32 { let n = val.to_number(); if n.is_nan() || n.is_infinite() || n == 0.0 { return 0; } let i = n.trunc() as i64; (i & 0xFFFF_FFFF) as i32}
/// ToUint32 (ECMA-262 §7.1.6).fn to_uint32(val: &Value) -> u32 { let n = val.to_number(); if n.is_nan() || n.is_infinite() || n == 0.0 { return 0; } let i = n.trunc() as i64; (i & 0xFFFF_FFFF) as u32}
// ── Equality ─────────────────────────────────────────────────
/// Abstract equality comparison (==) per ECMA-262 §7.2.14.fn abstract_eq(x: &Value, y: &Value) -> bool { match (x, y) { (Value::Undefined, Value::Undefined) => true, (Value::Null, Value::Null) => true, (Value::Undefined, Value::Null) | (Value::Null, Value::Undefined) => true, (Value::Number(a), Value::Number(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Boolean(a), Value::Boolean(b)) => a == b, // Number / String → convert String to Number. (Value::Number(_), Value::String(_)) => abstract_eq(x, &Value::Number(y.to_number())), (Value::String(_), Value::Number(_)) => abstract_eq(&Value::Number(x.to_number()), y), // Boolean → Number. (Value::Boolean(_), _) => abstract_eq(&Value::Number(x.to_number()), y), (_, Value::Boolean(_)) => abstract_eq(x, &Value::Number(y.to_number())), // Same GcRef → equal. (Value::Object(a), Value::Object(b)) => a == b, (Value::Function(a), Value::Function(b)) => a == b, _ => false, }}
/// Strict equality comparison (===) per ECMA-262 §7.2.15.fn strict_eq(x: &Value, y: &Value) -> bool { match (x, y) { (Value::Undefined, Value::Undefined) => true, (Value::Null, Value::Null) => true, (Value::Number(a), Value::Number(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Boolean(a), Value::Boolean(b)) => a == b, // Reference identity for heap objects. (Value::Object(a), Value::Object(b)) => a == b, (Value::Function(a), Value::Function(b)) => a == b, _ => false, }}
// ── Relational comparison ────────────────────────────────────
/// Abstract relational comparison. Returns false for NaN comparisons.fn abstract_relational( lhs: &Value, rhs: &Value, predicate: fn(std::cmp::Ordering) -> bool,) -> bool { // If both are strings, compare lexicographically. if let (Value::String(a), Value::String(b)) = (lhs, rhs) { return predicate(a.cmp(b)); } // Otherwise, compare as numbers. let a = lhs.to_number(); let b = rhs.to_number(); if a.is_nan() || b.is_nan() { return false; } predicate(a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))}
// ── Addition ─────────────────────────────────────────────────
/// The + operator: string concat if either operand is a string, else numeric add.fn add_values(lhs: &Value, rhs: &Value, gc: &Gc<HeapObject>) -> Value { match (lhs, rhs) { (Value::String(a), _) => Value::String(format!("{a}{}", rhs.to_js_string(gc))), (_, Value::String(b)) => Value::String(format!("{}{b}", lhs.to_js_string(gc))), _ => Value::Number(lhs.to_number() + rhs.to_number()), }}
// ── Call frame ────────────────────────────────────────────────
/// A single call frame on the VM's call stack.struct CallFrame { /// The function being executed. func: Function, /// Instruction pointer (byte offset into func.code). ip: usize, /// Base register index in the VM's register file. base: usize, /// Register to write the return value into (absolute index in register file). return_reg: usize, /// Exception handler stack for this frame. exception_handlers: Vec<ExceptionHandler>, /// Captured upvalue cells from the closure that created this call frame. upvalues: Vec<GcRef>,}
/// An exception handler entry (for try/catch).struct ExceptionHandler { /// IP to jump to on exception (the catch block start). catch_ip: usize, /// Register to store the caught exception value. catch_reg: Reg,}
// ── VM ───────────────────────────────────────────────────────
/// The JavaScript virtual machine.pub struct Vm { /// Register file (flat array shared across frames via base offsets). registers: Vec<Value>, /// Call stack. frames: Vec<CallFrame>, /// Global variables. globals: HashMap<String, Value>, /// Garbage collector managing heap objects. pub gc: Gc<HeapObject>, /// Optional instruction limit. If set, the VM will return an error after /// executing this many instructions (prevents infinite loops). instruction_limit: Option<u64>, /// Number of instructions executed so far. instructions_executed: u64, /// Built-in Object.prototype (root of the prototype chain). pub object_prototype: Option<GcRef>, /// Built-in Array.prototype (set on newly created arrays). pub array_prototype: Option<GcRef>, /// Built-in String.prototype (for primitive auto-boxing). pub string_prototype: Option<GcRef>, /// Built-in Number.prototype (for primitive auto-boxing). pub number_prototype: Option<GcRef>, /// Built-in Boolean.prototype (for primitive auto-boxing). pub boolean_prototype: Option<GcRef>, /// Built-in Date.prototype (for Date constructor objects). pub date_prototype: Option<GcRef>,}
/// Maximum register file size.const MAX_REGISTERS: usize = 4096;/// Maximum call depth.const MAX_CALL_DEPTH: usize = 512;
impl Vm { pub fn new() -> Self { let mut vm = Self { registers: vec![Value::Undefined; 256], frames: Vec::new(), globals: HashMap::new(), gc: Gc::new(), instruction_limit: None, instructions_executed: 0, object_prototype: None, array_prototype: None, string_prototype: None, number_prototype: None, boolean_prototype: None, date_prototype: None, }; crate::builtins::init_builtins(&mut vm); vm }
/// Set an instruction limit. The VM will return a RuntimeError after /// executing this many instructions. pub fn set_instruction_limit(&mut self, limit: u64) { self.instruction_limit = Some(limit); }
/// Execute a compiled top-level function and return the completion value. pub fn execute(&mut self, func: &Function) -> Result<Value, RuntimeError> { let reg_count = func.register_count as usize; self.ensure_registers(reg_count);
self.frames.push(CallFrame { func: func.clone(), ip: 0, base: 0, return_reg: 0, exception_handlers: Vec::new(), upvalues: Vec::new(), });
self.run() }
/// Ensure the register file has at least `needed` slots. fn ensure_registers(&mut self, needed: usize) { if needed > self.registers.len() { if needed > MAX_REGISTERS { return; } self.registers.resize(needed, Value::Undefined); } }
/// Read a u8 from the current frame's bytecode and advance IP. #[inline] fn read_u8(frame: &mut CallFrame) -> u8 { let b = frame.func.code[frame.ip]; frame.ip += 1; b }
/// Read a u16 (little-endian) from the current frame's bytecode and advance IP. #[inline] fn read_u16(frame: &mut CallFrame) -> u16 { let lo = frame.func.code[frame.ip]; let hi = frame.func.code[frame.ip + 1]; frame.ip += 2; u16::from_le_bytes([lo, hi]) }
/// Read an i32 (little-endian) from the current frame's bytecode and advance IP. #[inline] fn read_i32(frame: &mut CallFrame) -> i32 { let bytes = [ frame.func.code[frame.ip], frame.func.code[frame.ip + 1], frame.func.code[frame.ip + 2], frame.func.code[frame.ip + 3], ]; frame.ip += 4; i32::from_le_bytes(bytes) }
/// Collect all GcRef values reachable from the mutator (roots for GC). fn collect_roots(&self) -> Vec<GcRef> { let mut roots = Vec::new(); for val in &self.registers { if let Some(r) = val.gc_ref() { roots.push(r); } } for val in self.globals.values() { if let Some(r) = val.gc_ref() { roots.push(r); } } for frame in &self.frames { for &uv in &frame.upvalues { roots.push(uv); } } // Built-in prototype roots. if let Some(r) = self.object_prototype { roots.push(r); } if let Some(r) = self.array_prototype { roots.push(r); } if let Some(r) = self.string_prototype { roots.push(r); } if let Some(r) = self.number_prototype { roots.push(r); } if let Some(r) = self.boolean_prototype { roots.push(r); } roots }
/// Main dispatch loop. fn run(&mut self) -> Result<Value, RuntimeError> { loop { let fi = self.frames.len() - 1;
// Check if we've reached the end of bytecode. if self.frames[fi].ip >= self.frames[fi].func.code.len() { if self.frames.len() == 1 { self.frames.pop(); return Ok(Value::Undefined); } let old = self.frames.pop().unwrap(); self.registers[old.return_reg] = Value::Undefined; continue; }
// Instruction limit check (for test harnesses). if let Some(limit) = self.instruction_limit { self.instructions_executed += 1; if self.instructions_executed > limit { return Err(RuntimeError { kind: ErrorKind::Error, message: "instruction limit exceeded".into(), }); } }
let opcode_byte = self.frames[fi].func.code[self.frames[fi].ip]; self.frames[fi].ip += 1;
let Some(op) = Op::from_byte(opcode_byte) else { return Err(RuntimeError { kind: ErrorKind::Error, message: format!("unknown opcode: 0x{opcode_byte:02X}"), }); };
match op { // ── Register loads ────────────────────────────── Op::LoadConst => { let dst = Self::read_u8(&mut self.frames[fi]); let idx = Self::read_u16(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; let val = match &self.frames[fi].func.constants[idx] { Constant::Number(n) => Value::Number(*n), Constant::String(s) => Value::String(s.clone()), }; self.registers[base + dst as usize] = val; } Op::LoadNull => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Null; } Op::LoadUndefined => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Undefined; } Op::LoadTrue => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Boolean(true); } Op::LoadFalse => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Boolean(false); } Op::LoadInt8 => { let dst = Self::read_u8(&mut self.frames[fi]); let val = Self::read_u8(&mut self.frames[fi]) as i8; let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Number(val as f64); } Op::Move => { let dst = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let val = self.registers[base + src as usize].clone(); self.registers[base + dst as usize] = val; }
// ── Global access ────────────────────────────── Op::LoadGlobal => { let dst = Self::read_u8(&mut self.frames[fi]); let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; let name = &self.frames[fi].func.names[name_idx]; let val = self.globals.get(name).cloned().unwrap_or(Value::Undefined); self.registers[base + dst as usize] = val; } Op::StoreGlobal => { let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let name = self.frames[fi].func.names[name_idx].clone(); let val = self.registers[base + src as usize].clone(); self.globals.insert(name, val); }
// ── Arithmetic ───────────────────────────────── Op::Add => { let dst = Self::read_u8(&mut self.frames[fi]); let lhs_r = Self::read_u8(&mut self.frames[fi]); let rhs_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let result = add_values( &self.registers[base + lhs_r as usize], &self.registers[base + rhs_r as usize], &self.gc, ); self.registers[base + dst as usize] = result; } Op::Sub => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = self.registers[base + lhs_r].to_number() - self.registers[base + rhs_r].to_number(); self.registers[base + dst] = Value::Number(result); } Op::Mul => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = self.registers[base + lhs_r].to_number() * self.registers[base + rhs_r].to_number(); self.registers[base + dst] = Value::Number(result); } Op::Div => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = self.registers[base + lhs_r].to_number() / self.registers[base + rhs_r].to_number(); self.registers[base + dst] = Value::Number(result); } Op::Rem => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = self.registers[base + lhs_r].to_number() % self.registers[base + rhs_r].to_number(); self.registers[base + dst] = Value::Number(result); } Op::Exp => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = self.registers[base + lhs_r] .to_number() .powf(self.registers[base + rhs_r].to_number()); self.registers[base + dst] = Value::Number(result); } Op::Neg => { let dst = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let result = -self.registers[base + src as usize].to_number(); self.registers[base + dst as usize] = Value::Number(result); }
// ── Bitwise ──────────────────────────────────── Op::BitAnd => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_int32(&self.registers[base + lhs_r]); let b = to_int32(&self.registers[base + rhs_r]); self.registers[base + dst] = Value::Number((a & b) as f64); } Op::BitOr => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_int32(&self.registers[base + lhs_r]); let b = to_int32(&self.registers[base + rhs_r]); self.registers[base + dst] = Value::Number((a | b) as f64); } Op::BitXor => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_int32(&self.registers[base + lhs_r]); let b = to_int32(&self.registers[base + rhs_r]); self.registers[base + dst] = Value::Number((a ^ b) as f64); } Op::ShiftLeft => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_int32(&self.registers[base + lhs_r]); let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; self.registers[base + dst] = Value::Number((a << b) as f64); } Op::ShiftRight => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_int32(&self.registers[base + lhs_r]); let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; self.registers[base + dst] = Value::Number((a >> b) as f64); } Op::UShiftRight => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let a = to_uint32(&self.registers[base + lhs_r]); let b = to_uint32(&self.registers[base + rhs_r]) & 0x1F; self.registers[base + dst] = Value::Number((a >> b) as f64); } Op::BitNot => { let dst = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let result = !to_int32(&self.registers[base + src as usize]); self.registers[base + dst as usize] = Value::Number(result as f64); }
// ── Comparison ───────────────────────────────── Op::Eq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); self.registers[base + dst] = Value::Boolean(result); } Op::StrictEq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); self.registers[base + dst] = Value::Boolean(result); } Op::NotEq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = !abstract_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); self.registers[base + dst] = Value::Boolean(result); } Op::StrictNotEq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = !strict_eq(&self.registers[base + lhs_r], &self.registers[base + rhs_r]); self.registers[base + dst] = Value::Boolean(result); } Op::LessThan => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = abstract_relational( &self.registers[base + lhs_r], &self.registers[base + rhs_r], |ord| ord == std::cmp::Ordering::Less, ); self.registers[base + dst] = Value::Boolean(result); } Op::LessEq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = abstract_relational( &self.registers[base + lhs_r], &self.registers[base + rhs_r], |ord| ord != std::cmp::Ordering::Greater, ); self.registers[base + dst] = Value::Boolean(result); } Op::GreaterThan => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = abstract_relational( &self.registers[base + lhs_r], &self.registers[base + rhs_r], |ord| ord == std::cmp::Ordering::Greater, ); self.registers[base + dst] = Value::Boolean(result); } Op::GreaterEq => { let (dst, lhs_r, rhs_r, base) = self.read_3reg(fi); let result = abstract_relational( &self.registers[base + lhs_r], &self.registers[base + rhs_r], |ord| ord != std::cmp::Ordering::Less, ); self.registers[base + dst] = Value::Boolean(result); }
// ── Logical / unary ──────────────────────────── Op::LogicalNot => { let dst = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let result = !self.registers[base + src as usize].to_boolean(); self.registers[base + dst as usize] = Value::Boolean(result); } Op::TypeOf => { let dst = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let t = self.registers[base + src as usize].type_of(); self.registers[base + dst as usize] = Value::String(t.to_string()); } Op::InstanceOf => { let dst = Self::read_u8(&mut self.frames[fi]); let lhs_r = Self::read_u8(&mut self.frames[fi]); let rhs_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let result = match ( &self.registers[base + lhs_r as usize], &self.registers[base + rhs_r as usize], ) { (Value::Object(obj_ref), Value::Function(ctor_ref)) => { gc_instanceof(&self.gc, *obj_ref, *ctor_ref) } (_, Value::Function(_)) => false, _ => { return Err(RuntimeError::type_error( "Right-hand side of instanceof is not callable", )); } }; self.registers[base + dst as usize] = Value::Boolean(result); } Op::In => { let dst = Self::read_u8(&mut self.frames[fi]); let key_r = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let key = self.registers[base + key_r as usize].to_js_string(&self.gc); let result = match self.registers[base + obj_r as usize] { Value::Object(gc_ref) => { Value::Boolean(gc_has_property(&self.gc, gc_ref, &key)) } _ => { return Err(RuntimeError::type_error( "Cannot use 'in' operator to search for property in non-object", )); } }; self.registers[base + dst as usize] = result; } Op::Void => { let dst = Self::read_u8(&mut self.frames[fi]); let _src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; self.registers[base + dst as usize] = Value::Undefined; }
// ── Control flow ─────────────────────────────── Op::Jump => { let offset = Self::read_i32(&mut self.frames[fi]); self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; } Op::JumpIfTrue => { let reg = Self::read_u8(&mut self.frames[fi]); let offset = Self::read_i32(&mut self.frames[fi]); let base = self.frames[fi].base; if self.registers[base + reg as usize].to_boolean() { self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; } } Op::JumpIfFalse => { let reg = Self::read_u8(&mut self.frames[fi]); let offset = Self::read_i32(&mut self.frames[fi]); let base = self.frames[fi].base; if !self.registers[base + reg as usize].to_boolean() { self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; } } Op::JumpIfNullish => { let reg = Self::read_u8(&mut self.frames[fi]); let offset = Self::read_i32(&mut self.frames[fi]); let base = self.frames[fi].base; if self.registers[base + reg as usize].is_nullish() { self.frames[fi].ip = (self.frames[fi].ip as i64 + offset as i64) as usize; } }
// ── Functions / calls ────────────────────────── Op::Call => { let dst = Self::read_u8(&mut self.frames[fi]); let func_r = Self::read_u8(&mut self.frames[fi]); let args_start = Self::read_u8(&mut self.frames[fi]); let arg_count = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base;
// Extract function GcRef. let func_gc_ref = match self.registers[base + func_r as usize] { Value::Function(r) => r, _ => { let desc = self.registers[base + func_r as usize].to_js_string(&self.gc); let err = RuntimeError::type_error(format!("{desc} is not a function")); let err_val = err.to_value(&mut self.gc); if !self.handle_exception(err_val) { return Err(err); } continue; } };
// Collect arguments. let mut args = Vec::with_capacity(arg_count as usize); for i in 0..arg_count { args.push(self.registers[base + (args_start + i) as usize].clone()); }
// Read function data from GC (scoped borrow). let call_info = { match self.gc.get(func_gc_ref) { Some(HeapObject::Function(fdata)) => match &fdata.kind { FunctionKind::Native(n) => CallInfo::Native(n.callback), FunctionKind::Bytecode(bc) => { CallInfo::Bytecode(bc.func.clone(), fdata.upvalues.clone()) } }, _ => { let err = RuntimeError::type_error("not a function"); let err_val = err.to_value(&mut self.gc); if !self.handle_exception(err_val) { return Err(err); } continue; } } };
match call_info { CallInfo::Native(callback) => { let this = self .globals .get("this") .cloned() .unwrap_or(Value::Undefined); let mut ctx = NativeContext { gc: &mut self.gc, this, }; match callback(&args, &mut ctx) { Ok(val) => { self.registers[base + dst as usize] = val; } Err(err) => { let err_val = err.to_value(&mut self.gc); if !self.handle_exception(err_val) { return Err(err); } } } } CallInfo::Bytecode(callee_func, callee_upvalues) => { if self.frames.len() >= MAX_CALL_DEPTH { let err = RuntimeError::range_error("Maximum call stack size exceeded"); let err_val = err.to_value(&mut self.gc); if !self.handle_exception(err_val) { return Err(err); } continue; }
let callee_base = base + self.frames[fi].func.register_count as usize; let callee_regs = callee_func.register_count as usize; self.ensure_registers(callee_base + callee_regs);
// Copy arguments into callee's registers. for i in 0..callee_func.param_count.min(arg_count) { self.registers[callee_base + i as usize] = args[i as usize].clone(); } // Fill remaining params with undefined. for i in arg_count..callee_func.param_count { self.registers[callee_base + i as usize] = Value::Undefined; }
self.frames.push(CallFrame { func: callee_func, ip: 0, base: callee_base, return_reg: base + dst as usize, exception_handlers: Vec::new(), upvalues: callee_upvalues, }); } } } Op::Return => { let reg = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let val = self.registers[base + reg as usize].clone();
if self.frames.len() == 1 { self.frames.pop(); return Ok(val); }
let old = self.frames.pop().unwrap(); self.registers[old.return_reg] = val; } Op::Throw => { let reg = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let val = self.registers[base + reg as usize].clone();
if !self.handle_exception(val) { let msg = self.registers[base + reg as usize].to_js_string(&self.gc); return Err(RuntimeError { kind: ErrorKind::Error, message: msg, }); } } Op::CreateClosure => { let dst = Self::read_u8(&mut self.frames[fi]); let func_idx = Self::read_u16(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; let inner_func = self.frames[fi].func.functions[func_idx].clone(); let name = inner_func.name.clone();
// Resolve upvalues from the parent scope. let mut upvalues = Vec::with_capacity(inner_func.upvalue_defs.len()); for def in &inner_func.upvalue_defs { let cell_ref = if def.is_local { // Parent has a cell in register `def.index`. match &self.registers[base + def.index as usize] { Value::Object(r) => *r, _ => { return Err(RuntimeError { kind: ErrorKind::Error, message: "CreateClosure: upvalue register does not hold a cell" .into(), }); } } } else { // Transitive: parent's own upvalue at `def.index`. self.frames[fi].upvalues[def.index as usize] }; upvalues.push(cell_ref); }
// Create a .prototype object for the function (for instanceof). let proto_obj = self.gc.alloc(HeapObject::Object(ObjectData::new())); let gc_ref = self.gc.alloc(HeapObject::Function(Box::new(FunctionData { name, kind: FunctionKind::Bytecode(BytecodeFunc { func: inner_func }), prototype_obj: Some(proto_obj), properties: HashMap::new(), upvalues, }))); // Set .prototype.constructor = this function. if let Some(HeapObject::Object(data)) = self.gc.get_mut(proto_obj) { data.properties.insert( "constructor".to_string(), Property { value: Value::Function(gc_ref), writable: true, enumerable: false, configurable: true, }, ); } self.registers[base + dst as usize] = Value::Function(gc_ref);
// Trigger GC if needed. if self.gc.should_collect() { let roots = self.collect_roots(); self.gc.collect(&roots); } }
// ── Object / property ────────────────────────── Op::GetProperty => { let dst = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let key_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let key = self.registers[base + key_r as usize].to_js_string(&self.gc); let val = match self.registers[base + obj_r as usize] { Value::Object(gc_ref) | Value::Function(gc_ref) => { gc_get_property(&self.gc, gc_ref, &key) } Value::String(ref s) => { let v = string_get_property(s, &key); if matches!(v, Value::Undefined) { self.string_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined) } else { v } } Value::Number(_) => self .number_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined), Value::Boolean(_) => self .boolean_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined), _ => Value::Undefined, }; self.registers[base + dst as usize] = val; } Op::SetProperty => { let obj_r = Self::read_u8(&mut self.frames[fi]); let key_r = Self::read_u8(&mut self.frames[fi]); let val_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let key = self.registers[base + key_r as usize].to_js_string(&self.gc); let val = self.registers[base + val_r as usize].clone(); match self.registers[base + obj_r as usize] { Value::Object(gc_ref) => { if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) { if let Some(prop) = data.properties.get_mut(&key) { if prop.writable { prop.value = val; } } else { data.properties.insert(key, Property::data(val)); } } } Value::Function(gc_ref) => { if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) { if let Some(prop) = fdata.properties.get_mut(&key) { if prop.writable { prop.value = val; } } else { fdata.properties.insert(key, Property::data(val)); } } } _ => {} } } Op::CreateObject => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let mut obj = ObjectData::new(); obj.prototype = self.object_prototype; let gc_ref = self.gc.alloc(HeapObject::Object(obj)); self.registers[base + dst as usize] = Value::Object(gc_ref);
if self.gc.should_collect() { let roots = self.collect_roots(); self.gc.collect(&roots); } } Op::CreateArray => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let mut obj = ObjectData::new(); obj.prototype = self.array_prototype; obj.properties.insert( "length".to_string(), Property { value: Value::Number(0.0), writable: true, enumerable: false, configurable: false, }, ); let gc_ref = self.gc.alloc(HeapObject::Object(obj)); self.registers[base + dst as usize] = Value::Object(gc_ref);
if self.gc.should_collect() { let roots = self.collect_roots(); self.gc.collect(&roots); } } Op::GetPropertyByName => { let dst = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; let key = self.frames[fi].func.names[name_idx].clone(); let val = match self.registers[base + obj_r as usize] { Value::Object(gc_ref) | Value::Function(gc_ref) => { gc_get_property(&self.gc, gc_ref, &key) } Value::String(ref s) => { let v = string_get_property(s, &key); if matches!(v, Value::Undefined) { self.string_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined) } else { v } } Value::Number(_) => self .number_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined), Value::Boolean(_) => self .boolean_prototype .map(|p| gc_get_property(&self.gc, p, &key)) .unwrap_or(Value::Undefined), _ => Value::Undefined, }; self.registers[base + dst as usize] = val; } Op::SetPropertyByName => { let obj_r = Self::read_u8(&mut self.frames[fi]); let name_idx = Self::read_u16(&mut self.frames[fi]) as usize; let val_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let key = self.frames[fi].func.names[name_idx].clone(); let val = self.registers[base + val_r as usize].clone(); match self.registers[base + obj_r as usize] { Value::Object(gc_ref) => { if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) { if let Some(prop) = data.properties.get_mut(&key) { if prop.writable { prop.value = val; } } else { data.properties.insert(key, Property::data(val)); } } } Value::Function(gc_ref) => { if let Some(HeapObject::Function(fdata)) = self.gc.get_mut(gc_ref) { if let Some(prop) = fdata.properties.get_mut(&key) { if prop.writable { prop.value = val; } } else { fdata.properties.insert(key, Property::data(val)); } } } _ => {} } }
// ── Misc ─────────────────────────────────────── Op::Delete => { let dst = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let key_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let key = self.registers[base + key_r as usize].to_js_string(&self.gc); let result = if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] { if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) { match data.properties.get(&key) { Some(prop) if !prop.configurable => false, Some(_) => { data.properties.remove(&key); true } None => true, } } else { true } } else { true }; self.registers[base + dst as usize] = Value::Boolean(result); } Op::ForInInit => { let dst = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let keys = match self.registers[base + obj_r as usize] { Value::Object(gc_ref) => gc_enumerate_keys(&self.gc, gc_ref), _ => Vec::new(), }; // Store keys as an array object. let mut arr = ObjectData::new(); for (i, key) in keys.iter().enumerate() { arr.properties .insert(i.to_string(), Property::data(Value::String(key.clone()))); } arr.properties.insert( "length".to_string(), Property { value: Value::Number(keys.len() as f64), writable: true, enumerable: false, configurable: false, }, ); let gc_ref = self.gc.alloc(HeapObject::Object(arr)); self.registers[base + dst as usize] = Value::Object(gc_ref); } Op::ForInNext => { let dst_val = Self::read_u8(&mut self.frames[fi]); let dst_done = Self::read_u8(&mut self.frames[fi]); let keys_r = Self::read_u8(&mut self.frames[fi]); let idx_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let idx = self.registers[base + idx_r as usize].to_number() as usize; let len = match self.registers[base + keys_r as usize] { Value::Object(gc_ref) => { gc_get_property(&self.gc, gc_ref, "length").to_number() as usize } _ => 0, }; if idx >= len { self.registers[base + dst_done as usize] = Value::Boolean(true); self.registers[base + dst_val as usize] = Value::Undefined; } else { let key_str = idx.to_string(); let key = match self.registers[base + keys_r as usize] { Value::Object(gc_ref) => gc_get_property(&self.gc, gc_ref, &key_str), _ => Value::Undefined, }; self.registers[base + dst_val as usize] = key; self.registers[base + dst_done as usize] = Value::Boolean(false); } } Op::SetPrototype => { let obj_r = Self::read_u8(&mut self.frames[fi]); let proto_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let proto = match &self.registers[base + proto_r as usize] { Value::Object(r) => Some(*r), Value::Null => None, _ => None, }; if let Value::Object(gc_ref) = self.registers[base + obj_r as usize] { if let Some(HeapObject::Object(data)) = self.gc.get_mut(gc_ref) { data.prototype = proto; } } } Op::GetPrototype => { let dst = Self::read_u8(&mut self.frames[fi]); let obj_r = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let proto = match self.registers[base + obj_r as usize] { Value::Object(gc_ref) => match self.gc.get(gc_ref) { Some(HeapObject::Object(data)) => { data.prototype.map(Value::Object).unwrap_or(Value::Null) } _ => Value::Null, }, _ => Value::Null, }; self.registers[base + dst as usize] = proto; }
// ── Exception handling ───────────────────────────── Op::PushExceptionHandler => { let catch_reg = Self::read_u8(&mut self.frames[fi]); let offset = Self::read_i32(&mut self.frames[fi]); let catch_ip = (self.frames[fi].ip as i32 + offset) as usize; self.frames[fi].exception_handlers.push(ExceptionHandler { catch_ip, catch_reg, }); } Op::PopExceptionHandler => { self.frames[fi].exception_handlers.pop(); }
// ── Closure / upvalue ops ───────────────────────── Op::NewCell => { let dst = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let cell = self.gc.alloc(HeapObject::Cell(Value::Undefined)); self.registers[base + dst as usize] = Value::Object(cell);
if self.gc.should_collect() { let roots = self.collect_roots(); self.gc.collect(&roots); } } Op::CellLoad => { let dst = Self::read_u8(&mut self.frames[fi]); let cell_reg = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let cell_ref = match &self.registers[base + cell_reg as usize] { Value::Object(r) => *r, _ => { return Err(RuntimeError { kind: ErrorKind::Error, message: "CellLoad: register does not hold a cell".into(), }); } }; let val = match self.gc.get(cell_ref) { Some(HeapObject::Cell(v)) => v.clone(), _ => Value::Undefined, }; self.registers[base + dst as usize] = val; } Op::CellStore => { let cell_reg = Self::read_u8(&mut self.frames[fi]); let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let cell_ref = match &self.registers[base + cell_reg as usize] { Value::Object(r) => *r, _ => { return Err(RuntimeError { kind: ErrorKind::Error, message: "CellStore: register does not hold a cell".into(), }); } }; let val = self.registers[base + src as usize].clone(); if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) { *cell_val = val; } } Op::LoadUpvalue => { let dst = Self::read_u8(&mut self.frames[fi]); let idx = Self::read_u8(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; let cell_ref = self.frames[fi].upvalues[idx]; let val = match self.gc.get(cell_ref) { Some(HeapObject::Cell(v)) => v.clone(), _ => Value::Undefined, }; self.registers[base + dst as usize] = val; } Op::StoreUpvalue => { let idx = Self::read_u8(&mut self.frames[fi]) as usize; let src = Self::read_u8(&mut self.frames[fi]); let base = self.frames[fi].base; let val = self.registers[base + src as usize].clone(); let cell_ref = self.frames[fi].upvalues[idx]; if let Some(HeapObject::Cell(cell_val)) = self.gc.get_mut(cell_ref) { *cell_val = val; } } } } }
/// Read 3 register operands and return (dst, lhs, rhs, base) as usize indices. fn read_3reg(&mut self, fi: usize) -> (usize, usize, usize, usize) { let dst = Self::read_u8(&mut self.frames[fi]) as usize; let lhs = Self::read_u8(&mut self.frames[fi]) as usize; let rhs = Self::read_u8(&mut self.frames[fi]) as usize; let base = self.frames[fi].base; (dst, lhs, rhs, base) }
/// Try to find an exception handler on the call stack. /// Returns true if a handler was found (execution resumes there). fn handle_exception(&mut self, value: Value) -> bool { while let Some(frame) = self.frames.last_mut() { if let Some(handler) = frame.exception_handlers.pop() { let base = frame.base; frame.ip = handler.catch_ip; self.registers[base + handler.catch_reg as usize] = value; return true; } if self.frames.len() == 1 { break; } self.frames.pop(); } false }
/// Register a native function as a global. pub fn define_native( &mut self, name: &str, callback: fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>, ) { let gc_ref = self.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(), }))); self.globals .insert(name.to_string(), Value::Function(gc_ref)); }
/// Get a global variable value. pub fn get_global(&self, name: &str) -> Option<&Value> { self.globals.get(name) }
/// Set a global variable. pub fn set_global(&mut self, name: &str, val: Value) { self.globals.insert(name.to_string(), val); }}
impl Default for Vm { fn default() -> Self { Self::new() }}
/// Internal enum to avoid holding a GC borrow across the call setup.enum CallInfo { Native(fn(&[Value], &mut NativeContext) -> Result<Value, RuntimeError>), Bytecode(Function, Vec<GcRef>),}
// ── Tests ────────────────────────────────────────────────────
#[cfg(test)]mod tests { use super::*; use crate::bytecode::{BytecodeBuilder, Constant, Op}; use crate::compiler; use crate::parser::Parser;
/// Helper: compile and execute JS source, return the completion value. fn eval(source: &str) -> Result<Value, RuntimeError> { let program = Parser::parse(source).expect("parse failed"); let func = compiler::compile(&program).expect("compile failed"); let mut vm = Vm::new(); vm.execute(&func) }
// ── Value tests ─────────────────────────────────────────
#[test] fn test_to_boolean() { let mut gc: Gc<HeapObject> = Gc::new(); assert!(!Value::Undefined.to_boolean()); assert!(!Value::Null.to_boolean()); assert!(!Value::Boolean(false).to_boolean()); assert!(Value::Boolean(true).to_boolean()); assert!(!Value::Number(0.0).to_boolean()); assert!(!Value::Number(f64::NAN).to_boolean()); assert!(Value::Number(1.0).to_boolean()); assert!(!Value::String(String::new()).to_boolean()); assert!(Value::String("hello".to_string()).to_boolean()); let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new())); assert!(Value::Object(obj_ref).to_boolean()); }
#[test] fn test_to_number() { assert!(Value::Undefined.to_number().is_nan()); assert_eq!(Value::Null.to_number(), 0.0); assert_eq!(Value::Boolean(true).to_number(), 1.0); assert_eq!(Value::Boolean(false).to_number(), 0.0); assert_eq!(Value::Number(42.0).to_number(), 42.0); assert_eq!(Value::String("42".to_string()).to_number(), 42.0); assert_eq!(Value::String("".to_string()).to_number(), 0.0); assert!(Value::String("abc".to_string()).to_number().is_nan()); }
#[test] fn test_type_of() { let mut gc: Gc<HeapObject> = Gc::new(); assert_eq!(Value::Undefined.type_of(), "undefined"); assert_eq!(Value::Null.type_of(), "object"); assert_eq!(Value::Boolean(true).type_of(), "boolean"); assert_eq!(Value::Number(1.0).type_of(), "number"); assert_eq!(Value::String("hi".to_string()).type_of(), "string"); let obj_ref = gc.alloc(HeapObject::Object(ObjectData::new())); assert_eq!(Value::Object(obj_ref).type_of(), "object"); }
// ── VM bytecode-level tests ─────────────────────────────
#[test] fn test_load_const_number() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 1; let ci = b.add_constant(Constant::Number(42.0)); b.emit_reg_u16(Op::LoadConst, 0, ci); b.emit_reg(Op::Return, 0); let func = b.finish();
let mut vm = Vm::new(); let result = vm.execute(&func).unwrap(); match result { Value::Number(n) => assert_eq!(n, 42.0), _ => panic!("expected Number, got {result:?}"), } }
#[test] fn test_arithmetic_ops() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; let c10 = b.add_constant(Constant::Number(10.0)); let c3 = b.add_constant(Constant::Number(3.0)); b.emit_reg_u16(Op::LoadConst, 0, c10); b.emit_reg_u16(Op::LoadConst, 1, c3); b.emit_reg3(Op::Add, 2, 0, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 13.0), v => panic!("expected 13, got {v:?}"), } }
#[test] fn test_string_concat() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; let c1 = b.add_constant(Constant::String("hello".into())); let c2 = b.add_constant(Constant::String(" world".into())); b.emit_reg_u16(Op::LoadConst, 0, c1); b.emit_reg_u16(Op::LoadConst, 1, c2); b.emit_reg3(Op::Add, 2, 0, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::String(s) => assert_eq!(s, "hello world"), v => panic!("expected string, got {v:?}"), } }
#[test] fn test_jump_if_false() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 2; b.emit_reg(Op::LoadFalse, 0); let patch = b.emit_cond_jump(Op::JumpIfFalse, 0); b.emit_load_int8(1, 1); let skip = b.emit_jump(Op::Jump); b.patch_jump(patch); b.emit_load_int8(1, 2); b.patch_jump(skip); b.emit_reg(Op::Return, 1); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_globals() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 2; let name = b.add_name("x"); b.emit_load_int8(0, 42); b.emit_store_global(name, 0); b.emit_load_global(1, name); b.emit_reg(Op::Return, 1); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_function_call() { let mut inner_b = BytecodeBuilder::new("add1".into(), 1); inner_b.func.register_count = 2; inner_b.emit_load_int8(1, 1); inner_b.emit_reg3(Op::Add, 0, 0, 1); inner_b.emit_reg(Op::Return, 0); let inner = inner_b.finish();
let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 4; let fi = b.add_function(inner); b.emit_reg_u16(Op::CreateClosure, 0, fi); b.emit_load_int8(1, 10); b.emit_call(2, 0, 1, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 11.0), v => panic!("expected 11, got {v:?}"), } }
#[test] fn test_native_function() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; let name = b.add_name("double"); b.emit_load_global(0, name); b.emit_load_int8(1, 21); b.emit_call(2, 0, 1, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); vm.define_native("double", |args, _ctx| { let n = args.first().unwrap_or(&Value::Undefined).to_number(); Ok(Value::Number(n * 2.0)) }); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_object_property() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; let name = b.add_name("x"); b.emit_reg(Op::CreateObject, 0); b.emit_load_int8(1, 42); b.emit_set_prop_name(0, name, 1); b.emit_get_prop_name(2, 0, name); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_typeof_operator() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 2; b.emit_load_int8(0, 5); b.emit_reg_reg(Op::TypeOf, 1, 0); b.emit_reg(Op::Return, 1); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::String(s) => assert_eq!(s, "number"), v => panic!("expected 'number', got {v:?}"), } }
#[test] fn test_comparison() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; b.emit_load_int8(0, 5); b.emit_load_int8(1, 10); b.emit_reg3(Op::LessThan, 2, 0, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_abstract_equality() { assert!(abstract_eq(&Value::Null, &Value::Undefined)); assert!(abstract_eq(&Value::Undefined, &Value::Null)); assert!(abstract_eq( &Value::Number(1.0), &Value::String("1".to_string()) )); assert!(abstract_eq(&Value::Boolean(true), &Value::Number(1.0))); assert!(!abstract_eq(&Value::Number(0.0), &Value::Null)); }
#[test] fn test_strict_equality() { assert!(strict_eq(&Value::Number(1.0), &Value::Number(1.0))); assert!(!strict_eq( &Value::Number(1.0), &Value::String("1".to_string()) )); assert!(strict_eq(&Value::Null, &Value::Null)); assert!(!strict_eq(&Value::Null, &Value::Undefined)); }
#[test] fn test_bitwise_ops() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; b.emit_load_int8(0, 0x0F); b.emit_load_int8(1, 0x03); b.emit_reg3(Op::BitAnd, 2, 0, 1); b.emit_reg(Op::Return, 2); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_throw_uncaught() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 1; let ci = b.add_constant(Constant::String("oops".into())); b.emit_reg_u16(Op::LoadConst, 0, ci); b.emit_reg(Op::Throw, 0); let func = b.finish();
let mut vm = Vm::new(); let err = vm.execute(&func).unwrap_err(); assert_eq!(err.message, "oops"); }
#[test] fn test_loop_counting() { let mut b = BytecodeBuilder::new("<test>".into(), 0); b.func.register_count = 3; b.emit_load_int8(0, 0); b.emit_load_int8(1, 5); let loop_start = b.offset(); b.emit_reg3(Op::LessThan, 2, 0, 1); let exit_patch = b.emit_cond_jump(Op::JumpIfFalse, 2); b.emit_load_int8(2, 1); b.emit_reg3(Op::Add, 0, 0, 2); b.emit_jump_to(loop_start); b.patch_jump(exit_patch); b.emit_reg(Op::Return, 0); let func = b.finish();
let mut vm = Vm::new(); match vm.execute(&func).unwrap() { Value::Number(n) => assert_eq!(n, 5.0), v => panic!("expected 5, got {v:?}"), } }
// ── End-to-end (compile + execute) tests ────────────────
#[test] fn test_e2e_arithmetic() { match eval("2 + 3 * 4").unwrap() { Value::Number(n) => assert_eq!(n, 14.0), v => panic!("expected 14, got {v:?}"), } }
#[test] fn test_e2e_variables() { match eval("var x = 10; var y = 20; x + y").unwrap() { Value::Number(n) => assert_eq!(n, 30.0), v => panic!("expected 30, got {v:?}"), } }
#[test] fn test_e2e_if_else() { match eval("var x = 5; if (x > 3) { x = 100; } x").unwrap() { Value::Number(n) => assert_eq!(n, 100.0), v => panic!("expected 100, got {v:?}"), } }
#[test] fn test_e2e_while_loop() { match eval("var i = 0; var sum = 0; while (i < 10) { sum = sum + i; i = i + 1; } sum") .unwrap() { Value::Number(n) => assert_eq!(n, 45.0), v => panic!("expected 45, got {v:?}"), } }
#[test] fn test_e2e_function_call() { match eval("function add(a, b) { return a + b; } add(3, 4)").unwrap() { Value::Number(n) => assert_eq!(n, 7.0), v => panic!("expected 7, got {v:?}"), } }
#[test] fn test_e2e_recursive_factorial() { let src = r#" function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); } fact(6) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 720.0), v => panic!("expected 720, got {v:?}"), } }
#[test] fn test_e2e_string_concat() { match eval("'hello' + ' ' + 'world'").unwrap() { Value::String(s) => assert_eq!(s, "hello world"), v => panic!("expected 'hello world', got {v:?}"), } }
#[test] fn test_e2e_comparison_coercion() { match eval("1 == '1'").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("1 === '1'").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_e2e_typeof() { match eval("typeof 42").unwrap() { Value::String(s) => assert_eq!(s, "number"), v => panic!("expected 'number', got {v:?}"), } match eval("typeof 'hello'").unwrap() { Value::String(s) => assert_eq!(s, "string"), v => panic!("expected 'string', got {v:?}"), } }
#[test] fn test_e2e_object_literal() { match eval("var o = { x: 10, y: 20 }; o.x + o.y").unwrap() { Value::Number(n) => assert_eq!(n, 30.0), v => panic!("expected 30, got {v:?}"), } }
#[test] fn test_e2e_for_loop() { match eval("var s = 0; for (var i = 0; i < 5; i = i + 1) { s = s + i; } s").unwrap() { Value::Number(n) => assert_eq!(n, 10.0), v => panic!("expected 10, got {v:?}"), } }
#[test] fn test_e2e_nested_functions() { // Note: closures (capturing parent scope vars) not yet supported. // This test verifies nested function declarations and calls work. let src = r#" function outer(x) { function inner(y) { return y + 1; } return inner(x); } outer(5) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 6.0), v => panic!("expected 6, got {v:?}"), } }
#[test] fn test_e2e_logical_operators() { match eval("true && false").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } match eval("false || true").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_e2e_unary_neg() { match eval("-(5 + 3)").unwrap() { Value::Number(n) => assert_eq!(n, -8.0), v => panic!("expected -8, got {v:?}"), } }
#[test] fn test_e2e_ternary() { match eval("true ? 1 : 2").unwrap() { Value::Number(n) => assert_eq!(n, 1.0), v => panic!("expected 1, got {v:?}"), } match eval("false ? 1 : 2").unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_e2e_fibonacci() { let src = r#" function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } fib(10) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 55.0), v => panic!("expected 55, got {v:?}"), } }
// ── GC integration tests ────────────────────────────────
#[test] fn test_gc_object_survives_collection() { let src = r#" var o = { x: 42 }; o.x "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_gc_many_objects() { // Allocate many objects to trigger GC threshold. let src = r#" var sum = 0; var i = 0; while (i < 100) { var o = { val: i }; sum = sum + o.val; i = i + 1; } sum "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 4950.0), v => panic!("expected 4950, got {v:?}"), } }
#[test] fn test_gc_reference_identity() { // With GC, object assignment is by reference. let mut gc: Gc<HeapObject> = Gc::new(); let r = gc.alloc(HeapObject::Object(ObjectData::new())); let a = Value::Object(r); let b = a.clone(); assert!(strict_eq(&a, &b)); // Same GcRef → strict equal. }
// ── Object model tests ──────────────────────────────────
#[test] fn test_prototype_chain_lookup() { // Property lookup walks the prototype chain. let src = r#" function Animal() {} var a = {}; a.sound = "woof"; a.sound "#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "woof"), v => panic!("expected 'woof', got {v:?}"), } }
#[test] fn test_typeof_all_types() { // typeof returns correct strings for all types. let cases = [ ("typeof undefined", "undefined"), ("typeof null", "object"), ("typeof true", "boolean"), ("typeof 42", "number"), ("typeof 'hello'", "string"), ("typeof {}", "object"), ("typeof function(){}", "function"), ]; for (src, expected) in cases { match eval(src).unwrap() { Value::String(s) => assert_eq!(s, expected, "typeof failed for: {src}"), v => panic!("expected string for {src}, got {v:?}"), } } }
#[test] fn test_instanceof_basic() { let src = r#" function Foo() {} var f = {}; f instanceof Foo "#; // Plain object without prototype link → false match eval(src).unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_in_operator() { let src = r#" var o = { x: 1, y: 2 }; var r1 = "x" in o; var r2 = "z" in o; r1 === true && r2 === false "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_delete_property() { let src = r#" var o = { x: 1, y: 2 }; delete o.x; typeof o.x === "undefined" && o.y === 2 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_delete_computed_property() { let src = r#" var o = { a: 10, b: 20 }; var key = "a"; delete o[key]; typeof o.a === "undefined" && o.b === 20 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_delete_non_configurable() { // Array length is non-configurable, delete should return false. let mut gc: Gc<HeapObject> = Gc::new(); let mut obj = ObjectData::new(); obj.properties.insert( "x".to_string(), Property { value: Value::Number(1.0), writable: true, enumerable: true, configurable: false, }, ); let obj_ref = gc.alloc(HeapObject::Object(obj));
// Try to delete the non-configurable property. match gc.get_mut(obj_ref) { Some(HeapObject::Object(data)) => { let prop = data.properties.get("x").unwrap(); assert!(!prop.configurable); // The property should still be there. assert!(data.properties.contains_key("x")); } _ => panic!("expected object"), } }
#[test] fn test_property_writable_flag() { // Setting a non-writable property should silently fail. let mut gc: Gc<HeapObject> = Gc::new(); let mut obj = ObjectData::new(); obj.properties.insert( "frozen".to_string(), Property { value: Value::Number(42.0), writable: false, enumerable: true, configurable: false, }, ); let obj_ref = gc.alloc(HeapObject::Object(obj));
// Verify the property value. match gc.get(obj_ref) { Some(HeapObject::Object(data)) => { assert_eq!( data.properties.get("frozen").unwrap().value.to_number(), 42.0 ); } _ => panic!("expected object"), } }
#[test] fn test_for_in_basic() { let src = r#" var o = { a: 1, b: 2, c: 3 }; var sum = 0; for (var key in o) { sum = sum + o[key]; } sum "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 6.0), v => panic!("expected 6, got {v:?}"), } }
#[test] fn test_for_in_collects_keys() { let src = r#" var o = { x: 10, y: 20 }; var keys = ""; for (var k in o) { keys = keys + k + ","; } keys "#; match eval(src).unwrap() { Value::String(s) => { // Both keys should appear (order may vary with HashMap). assert!(s.contains("x,")); assert!(s.contains("y,")); } v => panic!("expected string, got {v:?}"), } }
#[test] fn test_for_in_empty_object() { let src = r#" var o = {}; var count = 0; for (var k in o) { count = count + 1; } count "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 0.0), v => panic!("expected 0, got {v:?}"), } }
#[test] fn test_property_enumerable_flag() { // Array "length" is non-enumerable, should not appear in for-in. let src = r#" var arr = [10, 20, 30]; var keys = ""; for (var k in arr) { keys = keys + k + ","; } keys "#; match eval(src).unwrap() { Value::String(s) => { // "length" should NOT be in the keys (it's non-enumerable). assert!(!s.contains("length")); // Array indices should be present. assert!(s.contains("0,")); assert!(s.contains("1,")); assert!(s.contains("2,")); } v => panic!("expected string, got {v:?}"), } }
#[test] fn test_object_reference_semantics() { // Objects have reference semantics (shared via GcRef). let src = r#" var a = { x: 1 }; var b = a; b.x = 42; a.x "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_gc_enumerate_keys_order() { // Integer keys should come first, sorted numerically. let mut gc: Gc<HeapObject> = Gc::new(); let mut obj = ObjectData::new(); obj.properties .insert("b".to_string(), Property::data(Value::Number(2.0))); obj.properties .insert("2".to_string(), Property::data(Value::Number(3.0))); obj.properties .insert("a".to_string(), Property::data(Value::Number(1.0))); obj.properties .insert("0".to_string(), Property::data(Value::Number(0.0))); let obj_ref = gc.alloc(HeapObject::Object(obj));
let keys = gc_enumerate_keys(&gc, obj_ref); // Integer keys first (sorted), then string keys. let int_part: Vec<&str> = keys.iter().take(2).map(|s| s.as_str()).collect(); assert_eq!(int_part, vec!["0", "2"]); // Remaining keys are string keys (order depends on HashMap iteration). let str_part: Vec<&str> = keys.iter().skip(2).map(|s| s.as_str()).collect(); assert!(str_part.contains(&"a")); assert!(str_part.contains(&"b")); }
#[test] fn test_instanceof_with_gc() { // Direct test of gc_instanceof. let mut gc: Gc<HeapObject> = Gc::new();
// Create a constructor function with a .prototype object. let proto = gc.alloc(HeapObject::Object(ObjectData::new())); let ctor = gc.alloc(HeapObject::Function(Box::new(FunctionData { name: "Foo".to_string(), kind: FunctionKind::Native(NativeFunc { callback: |_, _ctx| Ok(Value::Undefined), }), prototype_obj: Some(proto), properties: HashMap::new(), upvalues: Vec::new(), })));
// Create an object whose [[Prototype]] is the constructor's .prototype. let mut obj_data = ObjectData::new(); obj_data.prototype = Some(proto); let obj = gc.alloc(HeapObject::Object(obj_data));
assert!(gc_instanceof(&gc, obj, ctor));
// An unrelated object should not match. let other = gc.alloc(HeapObject::Object(ObjectData::new())); assert!(!gc_instanceof(&gc, other, ctor)); }
#[test] fn test_try_catch_basic() { // Simple try/catch should catch a thrown value. let src = r#" var caught = false; try { throw "err"; } catch (e) { caught = true; } caught "#; match eval(src).unwrap() { Value::Boolean(true) => {} v => panic!("expected true, got {v:?}"), } }
#[test] fn test_try_catch_nested_call() { // try/catch should catch errors thrown from called functions. let src = r#" function thrower() { throw "err"; } var caught = false; try { thrower(); } catch (e) { caught = true; } caught "#; match eval(src).unwrap() { Value::Boolean(true) => {} v => panic!("expected true, got {v:?}"), } }
// ── Closure tests ────────────────────────────────────────
#[test] fn test_closure_basic() { // Basic closure: inner function reads outer variable. let src = r#" function outer() { var x = 10; function inner() { return x; } return inner(); } outer() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 10.0), v => panic!("expected 10, got {v:?}"), } }
#[test] fn test_closure_return_function() { // Closure survives the outer function's return. let src = r#" function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); add5(3) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 8.0), v => panic!("expected 8, got {v:?}"), } }
#[test] fn test_closure_mutation() { // Closures share live references — mutation is visible. let src = r#" function counter() { var n = 0; return function() { n = n + 1; return n; }; } var c = counter(); c(); c(); c() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_closure_shared_variable() { // Two closures from the same scope share the same variable. let src = r#" function make() { var x = 0; function inc() { x = x + 1; } function get() { return x; } inc(); inc(); return get(); } make() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_closure_arrow() { // Arrow function captures outer variable. let src = r#" function outer() { var x = 42; var f = () => x; return f(); } outer() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_closure_nested() { // Transitive capture: grandchild function reads grandparent variable. let src = r#" function outer() { var x = 100; function middle() { function inner() { return x; } return inner(); } return middle(); } outer() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 100.0), v => panic!("expected 100, got {v:?}"), } }
#[test] fn test_closure_param_capture() { // Closure captures a function parameter. let src = r#" function multiply(factor) { return function(x) { return x * factor; }; } var double = multiply(2); double(7) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 14.0), v => panic!("expected 14, got {v:?}"), } }
// ── const tests ──────────────────────────────────────────
#[test] fn test_const_basic() { let src = "const x = 42; x"; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_const_reassignment_error() { let src = "const x = 1; x = 2;"; let program = crate::parser::Parser::parse(src).expect("parse ok"); let result = crate::compiler::compile(&program); assert!( result.is_err(), "const reassignment should be a compile error" ); }
#[test] fn test_const_missing_init_error() { let src = "const x;"; let program = crate::parser::Parser::parse(src).expect("parse ok"); let result = crate::compiler::compile(&program); assert!( result.is_err(), "const without initializer should be a compile error" ); }
// ── this binding tests ───────────────────────────────────
#[test] fn test_method_call_this() { let src = r#" var obj = {}; obj.x = 10; obj.getX = function() { return this.x; }; obj.getX() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 10.0), v => panic!("expected 10, got {v:?}"), } }
// ── Object built-in tests ────────────────────────────────
#[test] fn test_object_keys() { let src = r#" var obj = {}; obj.a = 1; obj.b = 2; obj.c = 3; var k = Object.keys(obj); k.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_object_values() { let src = r#" var obj = {}; obj.x = 10; var v = Object.values(obj); v[0] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 10.0), v => panic!("expected 10, got {v:?}"), } }
#[test] fn test_object_entries() { let src = r#" var obj = {}; obj.x = 42; var e = Object.entries(obj); e[0][1] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_object_assign() { let src = r#" var a = {}; a.x = 1; var b = {}; b.y = 2; var c = Object.assign(a, b); c.y "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_object_create() { let src = r#" var proto = {}; proto.greet = "hello"; var child = Object.create(proto); child.greet "#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "hello"), v => panic!("expected 'hello', got {v:?}"), } }
#[test] fn test_object_is() { let src = "Object.is(NaN, NaN)"; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_object_freeze() { let src = r#" var obj = {}; obj.x = 1; Object.freeze(obj); Object.isFrozen(obj) "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_object_has_own_property() { let src = r#" var obj = {}; obj.x = 1; obj.hasOwnProperty("x") "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
// ── Array built-in tests ─────────────────────────────────
#[test] fn test_array_push_pop() { let src = r#" var arr = [1, 2, 3]; arr.push(4); arr.pop() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 4.0), v => panic!("expected 4, got {v:?}"), } }
#[test] fn test_array_push_length() { let src = r#" var arr = []; arr.push(10); arr.push(20); arr.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_array_shift_unshift() { let src = r#" var arr = [1, 2, 3]; arr.unshift(0); arr.shift() "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 0.0), v => panic!("expected 0, got {v:?}"), } }
#[test] fn test_array_index_of() { let src = r#" var arr = [10, 20, 30]; arr.indexOf(20) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 1.0), v => panic!("expected 1, got {v:?}"), } }
#[test] fn test_array_includes() { let src = r#" var arr = [1, 2, 3]; arr.includes(2) "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_array_join() { let src = r#" var arr = [1, 2, 3]; arr.join("-") "#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "1-2-3"), v => panic!("expected '1-2-3', got {v:?}"), } }
#[test] fn test_array_slice() { let src = r#" var arr = [1, 2, 3, 4, 5]; var s = arr.slice(1, 3); s.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_array_concat() { let src = r#" var a = [1, 2]; var b = [3, 4]; var c = a.concat(b); c.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 4.0), v => panic!("expected 4, got {v:?}"), } }
#[test] fn test_array_reverse() { let src = r#" var arr = [1, 2, 3]; arr.reverse(); arr[0] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_array_splice() { let src = r#" var arr = [1, 2, 3, 4, 5]; var removed = arr.splice(1, 2); removed.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_array_is_array() { let src = "Array.isArray([1, 2, 3])"; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_array_map() { let src = r#" var arr = [1, 2, 3]; var doubled = arr.map(function(x) { return x * 2; }); doubled[1] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 4.0), v => panic!("expected 4, got {v:?}"), } }
#[test] fn test_array_filter() { let src = r#" var arr = [1, 2, 3, 4, 5]; var evens = arr.filter(function(x) { return x % 2 === 0; }); evens.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_array_reduce() { let src = r#" var arr = [1, 2, 3, 4]; arr.reduce(function(acc, x) { return acc + x; }, 0) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 10.0), v => panic!("expected 10, got {v:?}"), } }
#[test] fn test_array_foreach() { let src = r#" var arr = [1, 2, 3]; var sum = 0; arr.forEach(function(x) { sum = sum + x; }); sum "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 6.0), v => panic!("expected 6, got {v:?}"), } }
#[test] fn test_array_find() { let src = r#" var arr = [1, 2, 3, 4]; arr.find(function(x) { return x > 2; }) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_array_find_index() { let src = r#" var arr = [1, 2, 3, 4]; arr.findIndex(function(x) { return x > 2; }) "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 2.0), v => panic!("expected 2, got {v:?}"), } }
#[test] fn test_array_some_every() { let src = r#" var arr = [2, 4, 6]; var all_even = arr.every(function(x) { return x % 2 === 0; }); var has_big = arr.some(function(x) { return x > 10; }); all_even && !has_big "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_array_sort() { let src = r#" var arr = [3, 1, 2]; arr.sort(); arr[0] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 1.0), v => panic!("expected 1, got {v:?}"), } }
#[test] fn test_array_sort_custom() { let src = r#" var arr = [3, 1, 2]; arr.sort(function(a, b) { return a - b; }); arr[2] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_array_from() { let src = r#" var arr = Array.from("abc"); arr.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_array_from_array() { let src = r#" var orig = [10, 20, 30]; var copy = Array.from(orig); copy[2] "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 30.0), v => panic!("expected 30, got {v:?}"), } }
#[test] fn test_array_flat() { let src = r#" var arr = [[1, 2], [3, 4]]; var flat = arr.flat(); flat.length "#; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 4.0), v => panic!("expected 4, got {v:?}"), } }
// ── Error built-in tests ─────────────────────────────────
#[test] fn test_error_constructor() { let src = r#" var e = new Error("oops"); e.message "#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "oops"), v => panic!("expected 'oops', got {v:?}"), } }
#[test] fn test_type_error_constructor() { let src = r#" var e = new TypeError("bad type"); e.message "#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "bad type"), v => panic!("expected 'bad type', got {v:?}"), } }
// ── Global function tests ────────────────────────────────
#[test] fn test_parse_int() { let src = "parseInt('42')"; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
#[test] fn test_parse_int_hex() { let src = "parseInt('0xFF', 16)"; match eval(src).unwrap() { Value::Number(n) => assert_eq!(n, 255.0), v => panic!("expected 255, got {v:?}"), } }
#[test] fn test_is_nan() { let src = "isNaN(NaN)"; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_is_finite() { let src = "isFinite(42)"; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
// ── String built-in tests ─────────────────────────────────
#[test] fn test_string_constructor() { match eval("String(42)").unwrap() { Value::String(s) => assert_eq!(s, "42"), v => panic!("expected '42', got {v:?}"), } match eval("String(true)").unwrap() { Value::String(s) => assert_eq!(s, "true"), v => panic!("expected 'true', got {v:?}"), } match eval("String()").unwrap() { Value::String(s) => assert_eq!(s, ""), v => panic!("expected '', got {v:?}"), } }
#[test] fn test_string_length() { match eval("'hello'.length").unwrap() { Value::Number(n) => assert_eq!(n, 5.0), v => panic!("expected 5, got {v:?}"), } }
#[test] fn test_string_char_at() { match eval("'hello'.charAt(1)").unwrap() { Value::String(s) => assert_eq!(s, "e"), v => panic!("expected 'e', got {v:?}"), } match eval("'hello'.charAt(10)").unwrap() { Value::String(s) => assert_eq!(s, ""), v => panic!("expected '', got {v:?}"), } }
#[test] fn test_string_char_code_at() { match eval("'A'.charCodeAt(0)").unwrap() { Value::Number(n) => assert_eq!(n, 65.0), v => panic!("expected 65, got {v:?}"), } }
#[test] fn test_string_proto_concat() { match eval("'hello'.concat(' ', 'world')").unwrap() { Value::String(s) => assert_eq!(s, "hello world"), v => panic!("expected 'hello world', got {v:?}"), } }
#[test] fn test_string_slice() { match eval("'hello world'.slice(6)").unwrap() { Value::String(s) => assert_eq!(s, "world"), v => panic!("expected 'world', got {v:?}"), } match eval("'hello'.slice(1, 3)").unwrap() { Value::String(s) => assert_eq!(s, "el"), v => panic!("expected 'el', got {v:?}"), } match eval("'hello'.slice(-3)").unwrap() { Value::String(s) => assert_eq!(s, "llo"), v => panic!("expected 'llo', got {v:?}"), } }
#[test] fn test_string_substring() { match eval("'hello'.substring(1, 3)").unwrap() { Value::String(s) => assert_eq!(s, "el"), v => panic!("expected 'el', got {v:?}"), } // substring swaps args if start > end match eval("'hello'.substring(3, 1)").unwrap() { Value::String(s) => assert_eq!(s, "el"), v => panic!("expected 'el', got {v:?}"), } }
#[test] fn test_string_index_of() { match eval("'hello world'.indexOf('world')").unwrap() { Value::Number(n) => assert_eq!(n, 6.0), v => panic!("expected 6, got {v:?}"), } match eval("'hello'.indexOf('xyz')").unwrap() { Value::Number(n) => assert_eq!(n, -1.0), v => panic!("expected -1, got {v:?}"), } }
#[test] fn test_string_last_index_of() { match eval("'abcabc'.lastIndexOf('abc')").unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } }
#[test] fn test_string_includes() { match eval("'hello world'.includes('world')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("'hello'.includes('xyz')").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_string_starts_ends_with() { match eval("'hello'.startsWith('hel')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("'hello'.endsWith('llo')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_string_trim() { match eval("' hello '.trim()").unwrap() { Value::String(s) => assert_eq!(s, "hello"), v => panic!("expected 'hello', got {v:?}"), } match eval("' hello '.trimStart()").unwrap() { Value::String(s) => assert_eq!(s, "hello "), v => panic!("expected 'hello ', got {v:?}"), } match eval("' hello '.trimEnd()").unwrap() { Value::String(s) => assert_eq!(s, " hello"), v => panic!("expected ' hello', got {v:?}"), } }
#[test] fn test_string_pad() { match eval("'5'.padStart(3, '0')").unwrap() { Value::String(s) => assert_eq!(s, "005"), v => panic!("expected '005', got {v:?}"), } match eval("'5'.padEnd(3, '0')").unwrap() { Value::String(s) => assert_eq!(s, "500"), v => panic!("expected '500', got {v:?}"), } }
#[test] fn test_string_repeat() { match eval("'ab'.repeat(3)").unwrap() { Value::String(s) => assert_eq!(s, "ababab"), v => panic!("expected 'ababab', got {v:?}"), } }
#[test] fn test_string_split() { // split returns an array; verify length and elements. match eval("'a,b,c'.split(',').length").unwrap() { Value::Number(n) => assert_eq!(n, 3.0), v => panic!("expected 3, got {v:?}"), } match eval("'a,b,c'.split(',')[0]").unwrap() { Value::String(s) => assert_eq!(s, "a"), v => panic!("expected 'a', got {v:?}"), } match eval("'a,b,c'.split(',')[2]").unwrap() { Value::String(s) => assert_eq!(s, "c"), v => panic!("expected 'c', got {v:?}"), } }
#[test] fn test_string_replace() { match eval("'hello world'.replace('world', 'there')").unwrap() { Value::String(s) => assert_eq!(s, "hello there"), v => panic!("expected 'hello there', got {v:?}"), } }
#[test] fn test_string_replace_all() { match eval("'aabbcc'.replaceAll('b', 'x')").unwrap() { Value::String(s) => assert_eq!(s, "aaxxcc"), v => panic!("expected 'aaxxcc', got {v:?}"), } }
#[test] fn test_string_case() { match eval("'Hello'.toLowerCase()").unwrap() { Value::String(s) => assert_eq!(s, "hello"), v => panic!("expected 'hello', got {v:?}"), } match eval("'Hello'.toUpperCase()").unwrap() { Value::String(s) => assert_eq!(s, "HELLO"), v => panic!("expected 'HELLO', got {v:?}"), } }
#[test] fn test_string_at() { match eval("'hello'.at(0)").unwrap() { Value::String(s) => assert_eq!(s, "h"), v => panic!("expected 'h', got {v:?}"), } match eval("'hello'.at(-1)").unwrap() { Value::String(s) => assert_eq!(s, "o"), v => panic!("expected 'o', got {v:?}"), } }
#[test] fn test_string_from_char_code() { match eval("String.fromCharCode(72, 101, 108)").unwrap() { Value::String(s) => assert_eq!(s, "Hel"), v => panic!("expected 'Hel', got {v:?}"), } }
#[test] fn test_string_from_code_point() { match eval("String.fromCodePoint(65, 66, 67)").unwrap() { Value::String(s) => assert_eq!(s, "ABC"), v => panic!("expected 'ABC', got {v:?}"), } }
// ── Number built-in tests ─────────────────────────────────
#[test] fn test_number_constructor() { match eval("Number('42')").unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } match eval("Number(true)").unwrap() { Value::Number(n) => assert_eq!(n, 1.0), v => panic!("expected 1, got {v:?}"), } match eval("Number()").unwrap() { Value::Number(n) => assert_eq!(n, 0.0), v => panic!("expected 0, got {v:?}"), } }
#[test] fn test_number_is_nan() { match eval("Number.isNaN(NaN)").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Number.isNaN(42)").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } // Number.isNaN doesn't coerce — string "NaN" is not NaN. match eval("Number.isNaN('NaN')").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_number_is_finite() { match eval("Number.isFinite(42)").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Number.isFinite(Infinity)").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_number_is_integer() { match eval("Number.isInteger(42)").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Number.isInteger(42.5)").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_number_is_safe_integer() { match eval("Number.isSafeInteger(42)").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Number.isSafeInteger(9007199254740992)").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_number_constants() { match eval("Number.MAX_SAFE_INTEGER").unwrap() { Value::Number(n) => assert_eq!(n, 9007199254740991.0), v => panic!("expected MAX_SAFE_INTEGER, got {v:?}"), } match eval("Number.EPSILON").unwrap() { Value::Number(n) => assert_eq!(n, f64::EPSILON), v => panic!("expected EPSILON, got {v:?}"), } }
#[test] fn test_number_to_fixed() { match eval("var n = 3.14159; n.toFixed(2)").unwrap() { Value::String(s) => assert_eq!(s, "3.14"), v => panic!("expected '3.14', got {v:?}"), } }
#[test] fn test_number_to_string_radix() { match eval("var n = 255; n.toString(16)").unwrap() { Value::String(s) => assert_eq!(s, "ff"), v => panic!("expected 'ff', got {v:?}"), } match eval("var n = 10; n.toString(2)").unwrap() { Value::String(s) => assert_eq!(s, "1010"), v => panic!("expected '1010', got {v:?}"), } }
#[test] fn test_number_parse_int() { match eval("Number.parseInt('42')").unwrap() { Value::Number(n) => assert_eq!(n, 42.0), v => panic!("expected 42, got {v:?}"), } }
// ── Boolean built-in tests ────────────────────────────────
#[test] fn test_boolean_constructor() { match eval("Boolean(1)").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Boolean(0)").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } match eval("Boolean('')").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } match eval("Boolean('hello')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_boolean_to_string() { match eval("true.toString()").unwrap() { Value::String(s) => assert_eq!(s, "true"), v => panic!("expected 'true', got {v:?}"), } match eval("false.toString()").unwrap() { Value::String(s) => assert_eq!(s, "false"), v => panic!("expected 'false', got {v:?}"), } }
// ── Symbol built-in tests ─────────────────────────────────
#[test] fn test_symbol_uniqueness() { // Each Symbol() call should produce a unique value. match eval("var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } }
#[test] fn test_symbol_well_known() { match eval("typeof Symbol.iterator").unwrap() { Value::String(s) => assert_eq!(s, "string"), v => panic!("expected 'string', got {v:?}"), } match eval("Symbol.iterator").unwrap() { Value::String(s) => assert_eq!(s, "@@iterator"), v => panic!("expected '@@iterator', got {v:?}"), } }
#[test] fn test_symbol_for_and_key_for() { // "for" is a keyword, so use bracket notation: Symbol["for"](...). match eval("Symbol['for']('test') === Symbol['for']('test')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("Symbol.keyFor(Symbol['for']('mykey'))").unwrap() { Value::String(s) => assert_eq!(s, "mykey"), v => panic!("expected 'mykey', got {v:?}"), } }
// ── Primitive auto-boxing tests ───────────────────────────
#[test] fn test_string_method_chaining() { match eval("' Hello World '.trim().toLowerCase()").unwrap() { Value::String(s) => assert_eq!(s, "hello world"), v => panic!("expected 'hello world', got {v:?}"), } }
#[test] fn test_string_substr() { match eval("'hello world'.substr(6, 5)").unwrap() { Value::String(s) => assert_eq!(s, "world"), v => panic!("expected 'world', got {v:?}"), } }
// ── Math built-in ─────────────────────────────────────────
#[test] fn test_math_constants() { let r = eval("Math.PI").unwrap(); match r { Value::Number(n) => assert!((n - std::f64::consts::PI).abs() < 1e-10), _ => panic!("Expected number"), } let r = eval("Math.E").unwrap(); match r { Value::Number(n) => assert!((n - std::f64::consts::E).abs() < 1e-10), _ => panic!("Expected number"), } let r = eval("Math.SQRT2").unwrap(); match r { Value::Number(n) => assert!((n - std::f64::consts::SQRT_2).abs() < 1e-10), _ => panic!("Expected number"), } }
#[test] fn test_math_abs() { assert_eq!(eval("Math.abs(-5)").unwrap().to_number(), 5.0); assert_eq!(eval("Math.abs(3)").unwrap().to_number(), 3.0); assert_eq!(eval("Math.abs(0)").unwrap().to_number(), 0.0); }
#[test] fn test_math_floor_ceil_round_trunc() { assert_eq!(eval("Math.floor(4.7)").unwrap().to_number(), 4.0); assert_eq!(eval("Math.ceil(4.2)").unwrap().to_number(), 5.0); assert_eq!(eval("Math.round(4.5)").unwrap().to_number(), 5.0); assert_eq!(eval("Math.round(4.4)").unwrap().to_number(), 4.0); assert_eq!(eval("Math.trunc(4.7)").unwrap().to_number(), 4.0); assert_eq!(eval("Math.trunc(-4.7)").unwrap().to_number(), -4.0); }
#[test] fn test_math_max_min() { assert_eq!(eval("Math.max(1, 3, 2)").unwrap().to_number(), 3.0); assert_eq!(eval("Math.min(1, 3, 2)").unwrap().to_number(), 1.0); assert_eq!(eval("Math.max()").unwrap().to_number(), f64::NEG_INFINITY); assert_eq!(eval("Math.min()").unwrap().to_number(), f64::INFINITY); }
#[test] fn test_math_pow_sqrt() { assert_eq!(eval("Math.pow(2, 10)").unwrap().to_number(), 1024.0); assert_eq!(eval("Math.sqrt(9)").unwrap().to_number(), 3.0); let cbrt = eval("Math.cbrt(27)").unwrap().to_number(); assert!((cbrt - 3.0).abs() < 1e-10); }
#[test] fn test_math_trig() { let sin = eval("Math.sin(0)").unwrap().to_number(); assert!(sin.abs() < 1e-10); let cos = eval("Math.cos(0)").unwrap().to_number(); assert!((cos - 1.0).abs() < 1e-10); let atan2 = eval("Math.atan2(1, 1)").unwrap().to_number(); assert!((atan2 - std::f64::consts::FRAC_PI_4).abs() < 1e-10); }
#[test] fn test_math_log_exp() { let exp = eval("Math.exp(1)").unwrap().to_number(); assert!((exp - std::f64::consts::E).abs() < 1e-10); let log = eval("Math.log(Math.E)").unwrap().to_number(); assert!((log - 1.0).abs() < 1e-10); let log2 = eval("Math.log2(8)").unwrap().to_number(); assert!((log2 - 3.0).abs() < 1e-10); let log10 = eval("Math.log10(1000)").unwrap().to_number(); assert!((log10 - 3.0).abs() < 1e-10); }
#[test] fn test_math_sign() { assert_eq!(eval("Math.sign(5)").unwrap().to_number(), 1.0); assert_eq!(eval("Math.sign(-5)").unwrap().to_number(), -1.0); assert_eq!(eval("Math.sign(0)").unwrap().to_number(), 0.0); }
#[test] fn test_math_clz32() { assert_eq!(eval("Math.clz32(1)").unwrap().to_number(), 31.0); assert_eq!(eval("Math.clz32(0)").unwrap().to_number(), 32.0); }
#[test] fn test_math_imul() { assert_eq!(eval("Math.imul(3, 4)").unwrap().to_number(), 12.0); assert_eq!(eval("Math.imul(0xffffffff, 5)").unwrap().to_number(), -5.0); }
#[test] fn test_math_hypot() { assert_eq!(eval("Math.hypot(3, 4)").unwrap().to_number(), 5.0); assert_eq!(eval("Math.hypot()").unwrap().to_number(), 0.0); }
#[test] fn test_math_random() { match eval("var r = Math.random(); r >= 0 && r < 1").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_math_fround() { let r = eval("Math.fround(5.5)").unwrap().to_number(); assert_eq!(r, 5.5f32 as f64); }
// ── Date built-in ─────────────────────────────────────────
#[test] fn test_date_now() { let r = eval("Date.now()").unwrap().to_number(); assert!(r > 1_577_836_800_000.0); }
#[test] fn test_date_utc() { let r = eval("Date.UTC(2020, 0, 1)").unwrap().to_number(); assert_eq!(r, 1_577_836_800_000.0); }
#[test] fn test_date_parse() { let r = eval("Date.parse('2020-01-01T00:00:00.000Z')") .unwrap() .to_number(); assert_eq!(r, 1_577_836_800_000.0); }
#[test] fn test_date_constructor_ms() { let r = eval("var d = new Date(1577836800000); d.getFullYear()") .unwrap() .to_number(); assert_eq!(r, 2020.0); }
#[test] fn test_date_constructor_components() { let r = eval("var d = new Date(2020, 0, 1, 0, 0, 0, 0); d.getTime()") .unwrap() .to_number(); assert_eq!(r, 1_577_836_800_000.0); }
#[test] fn test_date_getters() { let src = r#" var d = new Date(1577836800000); var results = [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds(), d.getDay() ]; results[0] === 2020 && results[1] === 0 && results[2] === 1 && results[3] === 0 && results[4] === 0 && results[5] === 0 && results[6] === 0 && results[7] === 3 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_date_setters() { let src = r#" var d = new Date(1577836800000); d.setFullYear(2025); d.getFullYear() "#; assert_eq!(eval(src).unwrap().to_number(), 2025.0); }
#[test] fn test_date_to_iso_string() { match eval("var d = new Date(1577836800000); d.toISOString()").unwrap() { Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"), v => panic!("expected ISO string, got {v:?}"), } }
#[test] fn test_date_value_of() { let r = eval("var d = new Date(1577836800000); d.valueOf()") .unwrap() .to_number(); assert_eq!(r, 1_577_836_800_000.0); }
#[test] fn test_date_to_string() { let r = eval("var d = new Date(1577836800000); d.toString()").unwrap(); match r { Value::String(s) => assert!(s.contains("2020") && s.contains("GMT")), _ => panic!("Expected string"), } }
#[test] fn test_date_to_json() { match eval("var d = new Date(1577836800000); d.toJSON()").unwrap() { Value::String(s) => assert_eq!(s, "2020-01-01T00:00:00.000Z"), v => panic!("expected ISO string, got {v:?}"), } }
#[test] fn test_date_constructor_string() { let r = eval("var d = new Date('2020-06-15T12:30:00Z'); d.getMonth()") .unwrap() .to_number(); assert_eq!(r, 5.0); }
// ── JSON built-in ─────────────────────────────────────────
#[test] fn test_json_parse_primitives() { assert!(matches!(eval("JSON.parse('null')").unwrap(), Value::Null)); match eval("JSON.parse('true')").unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } match eval("JSON.parse('false')").unwrap() { Value::Boolean(b) => assert!(!b), v => panic!("expected false, got {v:?}"), } assert_eq!(eval("JSON.parse('42')").unwrap().to_number(), 42.0); match eval(r#"JSON.parse('"hello"')"#).unwrap() { Value::String(s) => assert_eq!(s, "hello"), v => panic!("expected 'hello', got {v:?}"), } }
#[test] fn test_json_parse_array() { let src = r#" var a = JSON.parse('[1, 2, 3]'); a.length === 3 && a[0] === 1 && a[1] === 2 && a[2] === 3 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_json_parse_object() { let src = r#" var o = JSON.parse('{"name":"test","value":42}'); o.name === "test" && o.value === 42 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_json_parse_nested() { let src = r#" var o = JSON.parse('{"a":[1,{"b":2}]}'); o.a[1].b === 2 "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_json_parse_invalid() { assert!(eval("JSON.parse('{invalid}')").is_err()); assert!(eval("JSON.parse('')").is_err()); }
#[test] fn test_json_stringify_primitives() { match eval("JSON.stringify(null)").unwrap() { Value::String(s) => assert_eq!(s, "null"), v => panic!("expected 'null', got {v:?}"), } match eval("JSON.stringify(true)").unwrap() { Value::String(s) => assert_eq!(s, "true"), v => panic!("expected 'true', got {v:?}"), } match eval("JSON.stringify(42)").unwrap() { Value::String(s) => assert_eq!(s, "42"), v => panic!("expected '42', got {v:?}"), } match eval(r#"JSON.stringify("hello")"#).unwrap() { Value::String(s) => assert_eq!(s, "\"hello\""), v => panic!("expected quoted hello, got {v:?}"), } }
#[test] fn test_json_stringify_array() { match eval("JSON.stringify([1, 2, 3])").unwrap() { Value::String(s) => assert_eq!(s, "[1,2,3]"), v => panic!("expected '[1,2,3]', got {v:?}"), } }
#[test] fn test_json_stringify_object() { let src = r#" var o = {a: 1, b: "hello"}; JSON.stringify(o) "#; let r = eval(src).unwrap(); match r { Value::String(s) => { assert!(s.contains("\"a\"") && s.contains("\"b\"")); assert!(s.contains('1') && s.contains("\"hello\"")); } _ => panic!("Expected string"), } }
#[test] fn test_json_stringify_nested() { let src = r#" JSON.stringify({a: [1, 2], b: {c: 3}}) "#; let r = eval(src).unwrap(); match r { Value::String(s) => { assert!(s.contains("[1,2]")); assert!(s.contains("\"c\":3") || s.contains("\"c\": 3")); } _ => panic!("Expected string"), } }
#[test] fn test_json_stringify_special_values() { match eval("JSON.stringify(NaN)").unwrap() { Value::String(s) => assert_eq!(s, "null"), v => panic!("expected 'null', got {v:?}"), } match eval("JSON.stringify(Infinity)").unwrap() { Value::String(s) => assert_eq!(s, "null"), v => panic!("expected 'null', got {v:?}"), } assert!(matches!( eval("JSON.stringify(undefined)").unwrap(), Value::Undefined )); }
#[test] fn test_json_stringify_with_indent() { let src = r#"JSON.stringify([1, 2], null, 2)"#; let r = eval(src).unwrap(); match r { Value::String(s) => { assert!(s.contains('\n')); assert!(s.contains(" 1")); } _ => panic!("Expected string"), } }
#[test] fn test_json_parse_escape_sequences() { let src = r#"JSON.parse('"hello\\nworld"')"#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "hello\nworld"), v => panic!("expected escaped string, got {v:?}"), } }
#[test] fn test_json_roundtrip() { let src = r#" var original = {name: "test", values: [1, 2, 3], nested: {ok: true}}; var json = JSON.stringify(original); var parsed = JSON.parse(json); parsed.name === "test" && parsed.values[1] === 2 && parsed.nested.ok === true "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_json_stringify_circular_detection() { let src = r#" var obj = {}; obj.self = obj; try { JSON.stringify(obj); false; } catch (e) { e.message.indexOf("circular") !== -1; } "#; match eval(src).unwrap() { Value::Boolean(b) => assert!(b), v => panic!("expected true, got {v:?}"), } }
#[test] fn test_json_parse_unicode_escape() { let src = r#"JSON.parse('"\\u0041"')"#; match eval(src).unwrap() { Value::String(s) => assert_eq!(s, "A"), v => panic!("expected 'A', got {v:?}"), } }
#[test] fn test_json_stringify_empty() { match eval("JSON.stringify([])").unwrap() { Value::String(s) => assert_eq!(s, "[]"), v => panic!("expected '[]', got {v:?}"), } match eval("JSON.stringify({})").unwrap() { Value::String(s) => assert_eq!(s, "{}"), v => panic!("expected '{{}}', got {v:?}"), } }}