Something went wrong. Try again.
A general purpose .. programming language written in zig ijadux2.tngl.sh/Rinnescript
zig language
Something went wrong. Try again.
212 kB · 4614 lines
Zig
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615const std = @import("std");const parser_mod = @import("parser.zig");const compiler_mod = @import("compiler.zig");const value_mod = @import("value.zig");const mmap_mod = @import("mmap.zig");const st_mod = @import("ai_safetensors.zig");const gguf_mod = @import("ai_gguf.zig");const llm_mod = @import("llm.zig");
// System dynamic-loader FFI for load_lib (POSIX). The binary links libc so// these resolve from libc/libdl.extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*anyopaque;extern "c" fn dlsym(handle: ?*anyopaque, sym: [*:0]const u8) ?*anyopaque;extern "c" fn dlclose(handle: ?*anyopaque) c_int;const RTLD_NOW: c_int = 2;
/// Casts a dlsym result into a callable C function pointer of type `T`.fn dlsymFn(comptime T: type, handle: *anyopaque, sym: [*:0]const u8) ?T { const addr = dlsym(handle, sym) orelse return null; return @ptrCast(@alignCast(addr));}const loader_mod = @import("loader.zig");const pkg_mod = @import("pkg.zig");pub const Value = value_mod.Value;pub const Module = compiler_mod.Module;pub const Op = compiler_mod.Op;
/// Total-ish ordering for `std.collections.sort`/`min_of`/`max_of`: numbers/// compare by real value, strings lexically, bools false<true; incomparable/// tag mixes compare equal (so sort stays total and stable).fn value_less_than(a: Value, b: Value) bool { if (a.isNumber() and b.isNumber()) { if (a == .int_val and b == .int_val) return a.int_val < b.int_val; return a.toF64() < b.toF64(); } if (a == .str_val and b == .str_val) return std.mem.lessThan(u8, a.str_val, b.str_val); if (a == .bool_val and b == .bool_val) return !a.bool_val and b.bool_val; return false;}
pub const RuntimeError = error{ OutOfMemory, TypeMismatch, UndefinedVariable, DivideByZero, IndexOutOfBounds, ArityMismatch, UnknownBuiltin, WriteFailed, IoFailed,};
pub const RunError = parser_mod.Parser.ParseError || compiler_mod.CompileError || RuntimeError;
/// Stack-based bytecode virtual machine implementing Scripting Mode/// (`rinne run`, see PIVOT.md section 2).pub const Vm = struct { allocator: std.mem.Allocator, /// Active module while executing; set by `runModule`. module: ?*const Module = null, stack: std.ArrayList(Value) = .empty, frames: std.ArrayList(Frame) = .empty, globals: std.StringHashMapUnmanaged(Value) = .empty, /// LIFO registry of deferred calls; drained at frame return. defers: std.ArrayList(DeferredCall) = .empty, out: *std.Io.Writer, errw: *std.Io.Writer, environ: ?*const std.process.Environ.Map = null, /// Optional Io handle enabling the @clock builtin (monotonic timing). io: ?std.Io = null, start_ts: ?std.Io.Timestamp = null, err_msg: ?[]u8 = null, /// CLI arguments exposed to scripts via args(). script_args: []const []const u8 = &.{}, /// Byte offset of the instruction currently being executed (set in /// `step`), used to translate runtime failures back to source lines. cur_op_ip: usize = 0, /// Options for the @fetch package loader applied by runSource between /// parsing and compiling (cache dir override, offline mode, test seam). pkg_opts: loader_mod.Options = .{}, /// Lazily-seeded CSPRNG backing the std.random builtins (seeded from OS /// entropy the first time one of them runs). rng: ?std.Random.DefaultCsprng = null, /// std.test accounting: assertions executed and failures encountered. /// runModule returns 70 when any assertion failed, so test scripts can /// `return 0` unconditionally and still signal failure via the runner. test_assertions: u64 = 0, test_failures: u64 = 0,
pub const Frame = struct { fn_idx: usize, ip: usize, /// Stack index of local slot 0 (arguments occupy base..base+argc). base: usize, /// Set once a `ret` was issued; remaining steps drain pending defers. retiring: bool = false, };
const DeferredCall = struct { fn_idx: usize, /// Frame base the defer was registered under. base: usize, };
pub fn init(allocator: std.mem.Allocator, out: *std.Io.Writer, err_out: *std.Io.Writer) Vm { return .{ .allocator = allocator, .out = out, .errw = err_out, }; }
pub fn deinit(self: *Vm) void { self.stack.deinit(self.allocator); self.frames.deinit(self.allocator); self.globals.deinit(self.allocator); self.defers.deinit(self.allocator); if (self.err_msg) |m| self.allocator.free(m); }
fn fail(self: *Vm, e: RuntimeError, comptime fmt: []const u8, args: anytype) RuntimeError { if (self.err_msg == null) { var buf: [256]u8 = undefined; const suffix = self.locationSuffix(&buf); if (suffix.len > 0) { self.err_msg = std.fmt.allocPrint(self.allocator, fmt ++ "{s}", args ++ .{suffix}) catch null; } else { self.err_msg = std.fmt.allocPrint(self.allocator, fmt, args) catch null; } } return e; }
/// Renders " (at line N in `fn`)" for the currently executing /// instruction, or an empty string when unavailable. fn locationSuffix(self: *Vm, buf: []u8) []const u8 { const module = self.module orelse return ""; if (self.frames.items.len == 0) return ""; const frame = self.frames.items[self.frames.items.len - 1]; if (frame.fn_idx >= module.fns.len) return ""; const chunk = &module.fns[frame.fn_idx].chunk; const line = chunk.lineAt(self.cur_op_ip); if (line == 0) { return std.fmt.bufPrint(buf, " (in {s})", .{module.fns[frame.fn_idx].name}) catch ""; } return std.fmt.bufPrint(buf, " (at line {d} in {s})", .{ line, module.fns[frame.fn_idx].name }) catch ""; }
/// Parse + compile + execute convenience used by tests and `Vm.execute`. pub fn runSource(self: *Vm, source: []const u8, args: []const []const u8) RunError!u8 { var parser = parser_mod.Parser.init(self.allocator, source); var program = try parser.parseProgram(); defer program.deinit(self.allocator);
// Resolve module-level @fetch directives before compilation. if (program.directives.len != 0) { var pkg_detail: ?[]u8 = null; loader_mod.resolveFetches(self.allocator, self.io, &program, self.pkg_opts, &pkg_detail) catch |e| { if (self.err_msg == null) { self.err_msg = pkg_detail orelse std.fmt.allocPrint(self.allocator, "{s}", .{@errorName(e)}) catch null; } else if (pkg_detail) |d| self.allocator.free(d); return error.PackageFetch; }; }
var compiler = compiler_mod.Compiler.init(self.allocator); defer compiler.deinit(); var module = compiler.compileProgram(&program) catch |e| { // Surface compile-time diagnostics through the VM like runtime // ones, so callers only ever inspect `vm.err_msg`. if (self.err_msg == null) { if (compiler.err_msg) |m| { self.err_msg = self.allocator.dupe(u8, m) catch null; } } return e; }; defer module.deinit();
return self.runModule(&module, args); }
/// Back-compat entry point: executes source and discards the exit code. pub fn execute(self: *Vm, source: []const u8) RunError!void { _ = try self.runSource(source, &.{}); }
/// Executes a compiled module: runs global statements, then calls /// `main` (if present) with the CLI argument vector. Returns exit code. pub fn runModule(self: *Vm, module: *const Module, args: []const []const u8) RuntimeError!u8 { self.module = module; self.script_args = args; if (self.io) |io| { self.start_ts = std.Io.Timestamp.now(io, .awake); } // fns[0] is the implicit "<global>" initialization chunk. try self.callFrame(0, 0); try self.execLoop();
var exit_code: u8 = 0; if (module.main_idx) |mi| { const mark = self.stack.items.len; switch (module.fns[mi].arity) { 0 => {}, 1 => { const argv = try self.allocator.alloc(Value, args.len); for (args, 0..) |arg, i| argv[i] = .{ .str_val = arg }; try self.stack.append(self.allocator, .{ .array_val = argv }); }, else => return self.fail(error.ArityMismatch, "main() must declare 0 or 1 parameter(s)", .{}), } try self.callFrame(mi, module.fns[mi].arity); try self.execLoop(); if (self.stack.items.len > mark) { const rv = self.stack.pop().?; exit_code = valueToExitCode(rv); } } if (self.test_failures != 0) { return 70; } return exit_code; }
fn valueToExitCode(v: Value) u8 { return switch (v) { .int_val => |i| @truncate(@as(u64, @bitCast(i))), .float_val => |f| @truncate(std.math.lossyCast(u64, f)), .bool_val => |b| if (b) 1 else 0, else => 0, }; }
// ---- Execution core ----
fn execLoop(self: *Vm) RuntimeError!void { while (self.frames.items.len > 0) { try self.step(); } }
fn callFrame(self: *Vm, fn_idx: usize, argc: usize) RuntimeError!void { const proto = &self.module.?.fns[fn_idx]; if (proto.arity != argc) { return self.fail(error.ArityMismatch, "'{s}' expects {d} argument(s), got {d}", .{ proto.name, proto.arity, argc }); } const base = self.stack.items.len - argc; try self.frames.append(self.allocator, .{ .fn_idx = fn_idx, .ip = 0, .base = base }); while (self.stack.items.len < base + proto.locals_count) { try self.stack.append(self.allocator, .null_val); } }
/// Handles a `ret`: ensures a return value exists, then enters the /// retiring state to drain pending defers as synthetic frames (LIFO) /// before the final unwind. fn doReturn(self: *Vm) RuntimeError!void { const module = self.module.?; const last = self.frames.items.len - 1; var cur = self.frames.items[last]; if (!cur.retiring) { const region_end = cur.base + module.fns[cur.fn_idx].locals_count; if (self.stack.items.len <= region_end) { // Bare return: materialize an implicit null return value. try self.stack.append(self.allocator, .null_val); } cur.retiring = true; self.frames.items[last] = cur; } try self.retireStep(); }
/// One drain step of a retiring frame: run the next due defer, or unwind /// the frame pushing its return value (which already sits at exactly /// base + locals_count). fn retireStep(self: *Vm) RuntimeError!void { const module = self.module.?; const last = self.frames.items.len - 1; const cur = self.frames.items[last];
if (self.defers.getLastOrNull()) |d| { if (d.base >= cur.base) { _ = self.defers.pop(); try self.callFrame(d.fn_idx, 0); return; } }
const region_end = cur.base + module.fns[cur.fn_idx].locals_count; var retval: Value = .null_val; if (region_end < self.stack.items.len) { retval = self.stack.items[region_end]; } _ = self.frames.pop(); self.stack.shrinkRetainingCapacity(cur.base); try self.stack.append(self.allocator, retval); }
/// Single instruction fetch/decode/execute pass. O(1) per instruction: /// fixed-size operand decoding via direct byte reads. fn step(self: *Vm) RuntimeError!void { const module = self.module.?; const last = self.frames.items.len - 1; var frame = self.frames.items[last]; // copy; writeback at end const chunk = &module.fns[frame.fn_idx].chunk; const code = chunk.code.items;
// Retiring frames resume draining defers / final unwind instead of // fetching instructions. if (frame.retiring) { return self.retireStep(); }
if (frame.ip >= code.len) { return self.doReturn(); }
const op: Op = @enumFromInt(code[frame.ip]); self.cur_op_ip = frame.ip; frame.ip += 1;
switch (op) { .constant => { const idx = readU32(code, &frame.ip); if (idx >= chunk.constants.items.len) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "corrupt constant index", .{}); } try self.stack.append(self.allocator, chunk.constants.items[idx]); }, .null_const => try self.stack.append(self.allocator, .null_val), .true_const => try self.stack.append(self.allocator, .{ .bool_val = true }), .false_const => try self.stack.append(self.allocator, .{ .bool_val = false }), .pop => { if (self.stack.items.len <= frame.base + module.fns[frame.fn_idx].locals_count) { // Never pop below the frame's reserved region. self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "stack underflow", .{}); } _ = self.stack.pop(); },
.get_local => { const slot = readU32(code, &frame.ip); const abs = frame.base + @as(usize, slot); if (abs >= self.stack.items.len or abs < frame.base) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "invalid local slot {d}", .{slot}); } try self.stack.append(self.allocator, self.stack.items[abs]); }, .set_local => { const slot = readU32(code, &frame.ip); const abs = frame.base + @as(usize, slot); if (self.stack.items.len == 0 or abs >= self.stack.items.len) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "invalid local slot {d}", .{slot}); } self.stack.items[abs] = self.stack.pop().?; },
.def_global => { const name_idx = readU32(code, &frame.ip); const v = self.stack.pop().?; try self.globals.put(self.allocator, constantStr(chunk, name_idx), v); }, .get_global => { const name_idx = readU32(code, &frame.ip); const name = constantStr(chunk, name_idx); const g = self.globals.get(name) orelse { self.frames.items[last].ip = frame.ip; return self.fail(error.UndefinedVariable, "undefined variable '{s}'", .{name}); }; try self.stack.append(self.allocator, g); }, .set_global => { const name_idx = readU32(code, &frame.ip); const name = constantStr(chunk, name_idx); if (!self.globals.contains(name)) { self.frames.items[last].ip = frame.ip; return self.fail(error.UndefinedVariable, "undefined variable '{s}'", .{name}); } const v = self.stack.pop().?; try self.globals.put(self.allocator, name, v); },
.array_lit => { const count = readU32(code, &frame.ip); if (count > self.stack.items.len - frame.base - module.fns[frame.fn_idx].locals_count) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "stack underflow building array", .{}); } const start = self.stack.items.len - count; const items = try self.allocator.dupe(Value, self.stack.items[start..]); self.stack.shrinkRetainingCapacity(start); try self.stack.append(self.allocator, .{ .array_val = items }); }, .get_index => { const idx_v = self.stack.pop().?; const obj = self.stack.pop().?; // Maps index by string key before the int coercion. if (obj == .map_val and idx_v == .str_val) { const mp = obj.map_val; if (mp.dead) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "use of dead map", .{}); } const got: Value = mp.map.get(idx_v.str_val) orelse .null_val; self.frames.items[last].ip = frame.ip; try self.stack.append(self.allocator, got); return; } if (obj == .map_val) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "map keys must be strings", .{}); } const idx = switch (idx_v) { .int_val => |i| i, else => { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "index must be an int", .{}); }, }; const result: Value = switch (obj) { .array_val => |items| blk: { if (idx < 0 or @as(usize, @intCast(idx)) >= items.len) { self.frames.items[last].ip = frame.ip; return self.fail(error.IndexOutOfBounds, "index {d} out of bounds for length {d}", .{ idx, items.len }); } break :blk items[@intCast(idx)]; }, .str_val => |s| blk: { if (idx < 0 or @as(usize, @intCast(idx)) >= s.len) { self.frames.items[last].ip = frame.ip; return self.fail(error.IndexOutOfBounds, "index {d} out of bounds for length {d}", .{ idx, s.len }); } break :blk .{ .int_val = s[@intCast(idx)] }; }, .tensor_val => |t| blk: { if (t.dead) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "use of deinitialized tensor", .{}); } const n = t.numel() catch 0; if (idx < 0 or @as(u64, @intCast(idx)) >= n) { self.frames.items[last].ip = frame.ip; return self.fail(error.IndexOutOfBounds, "index {d} out of bounds for numel {d}", .{ idx, n }); } break :blk t.readElem(@intCast(idx)); }, else => { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "cannot index this value", .{}); }, }; try self.stack.append(self.allocator, result); }, .set_index => { const val = self.stack.pop().?; const idx_v = self.stack.pop().?; const obj = self.stack.pop().?; if (obj == .map_val) { if (idx_v != .str_val) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "map keys must be strings", .{}); } const mp = obj.map_val; if (mp.dead) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "use of dead map", .{}); } const owned = self.allocator.dupe(u8, idx_v.str_val) catch return error.OutOfMemory; mp.map.put(owned, val) catch return error.OutOfMemory; self.frames.items[last].ip = frame.ip; return; } const idx = switch (idx_v) { .int_val => |i| i, else => { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "index must be an int", .{}); }, }; switch (obj) { .array_val => |items| { if (idx < 0 or @as(usize, @intCast(idx)) >= items.len) { self.frames.items[last].ip = frame.ip; return self.fail(error.IndexOutOfBounds, "index {d} out of bounds for length {d}", .{ idx, items.len }); } items[@intCast(idx)] = val; }, .tensor_val => |t| { if (t.dead) { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "use of deinitialized tensor", .{}); } const n = t.numel() catch 0; if (idx < 0 or @as(u64, @intCast(idx)) >= n) { self.frames.items[last].ip = frame.ip; return self.fail(error.IndexOutOfBounds, "index {d} out of bounds for numel {d}", .{ idx, n }); } t.writeElem(@intCast(idx), val) catch { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "cannot modify memory-mapped tensor '{s}' (loaded from file, read-only)", .{t.name}); }; }, else => { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "only arrays support index assignment", .{}); }, } },
.add, .sub, .mul, .div, .mod_op => { const b = self.stack.pop().?; const a = self.stack.pop().?; const r = try self.arith(op, a, b); try self.stack.append(self.allocator, r); }, .neg => { const v = self.stack.pop().?; const r: Value = switch (v) { .int_val => |i| .{ .int_val = -%i }, .float_val => |f| .{ .float_val = -f }, else => { self.frames.items[last].ip = frame.ip; return self.fail(error.TypeMismatch, "cannot negate this value", .{}); }, }; try self.stack.append(self.allocator, r); }, .not_op => { const v = self.stack.pop().?; try self.stack.append(self.allocator, .{ .bool_val = !v.truthy() }); },
.eq => { const b = self.stack.pop().?; const a = self.stack.pop().?; try self.stack.append(self.allocator, .{ .bool_val = Value.eql(a, b) }); }, .neq => { const b = self.stack.pop().?; const a = self.stack.pop().?; try self.stack.append(self.allocator, .{ .bool_val = !Value.eql(a, b) }); }, .lt, .le, .gt, .ge => { const b = self.stack.pop().?; const a = self.stack.pop().?; const r = try self.compare(op, a, b); try self.stack.append(self.allocator, r); }, .and_op => { const b = self.stack.pop().?; const a = self.stack.pop().?; try self.stack.append(self.allocator, .{ .bool_val = a.truthy() and b.truthy() }); }, .or_op => { const b = self.stack.pop().?; const a = self.stack.pop().?; try self.stack.append(self.allocator, .{ .bool_val = a.truthy() or b.truthy() }); },
.jump => { frame.ip = readU32(code, &frame.ip); }, .jump_if_false => { const target = readU32(code, &frame.ip); const cond = self.stack.pop().?; if (!cond.truthy()) frame.ip = target; }, .loop => { frame.ip = readU32(code, &frame.ip); },
.call_fn => { const fn_idx = readU32(code, &frame.ip); const argc = code[frame.ip]; frame.ip += 1; self.frames.items[last].ip = frame.ip; return self.callFrame(fn_idx, argc); }, .call_builtin => { const name_idx = readU32(code, &frame.ip); const argc = code[frame.ip]; frame.ip += 1; self.frames.items[last].ip = frame.ip; const name = constantStr(chunk, name_idx); const stack_len = self.stack.items.len; if (argc > stack_len) { return self.fail(error.TypeMismatch, "stack underflow in builtin '{s}'", .{name}); } const args = self.stack.items[stack_len - argc ..]; const result = try self.callBuiltin(name, args); self.stack.shrinkRetainingCapacity(stack_len - argc); try self.stack.append(self.allocator, result); }, .call_method => { const name_idx = readU32(code, &frame.ip); const argc = code[frame.ip]; frame.ip += 1; self.frames.items[last].ip = frame.ip; const name = constantStr(chunk, name_idx); const stack_len = self.stack.items.len; if (argc + 1 > stack_len) { return self.fail(error.TypeMismatch, "stack underflow in method '{s}'", .{name}); } const recv = self.stack.items[stack_len - argc - 1]; const result = try self.callMethod(name, recv, self.stack.items[stack_len - argc ..]); self.stack.shrinkRetainingCapacity(stack_len - argc - 1); try self.stack.append(self.allocator, result); }, .tensor_simd => { const kernel_idx = readU32(code, &frame.ip); self.frames.items[last].ip = frame.ip; if (self.stack.items.len == 0) { return self.fail(error.TypeMismatch, "stack underflow in apply_simd", .{}); } const recv = self.stack.pop().?; try self.runTensorSimd(kernel_idx, recv); // Expression position expects one result value (apply_simd // transforms in place); expr_stmt's trailing .pop eats this. try self.stack.append(self.allocator, .null_val); // Kernel frames may have reallocated `frames`; `frame` is // stale here. The saved ip above is already written back. return; }, .try_call => { const kernel_idx = readU32(code, &frame.ip); self.frames.items[last].ip = frame.ip; const result = try self.runTry(kernel_idx); // Frames were rewritten by runTry; `frame` is stale. The ip // writeback above already landed. try self.stack.append(self.allocator, result); return; }, .push_defer => { const fn_idx = readU32(code, &frame.ip); try self.defers.append(self.allocator, .{ .fn_idx = fn_idx, .base = frame.base }); },
.ret => return self.doReturn(), }
// Writeback for non-returning paths. self.frames.items[self.frames.items.len - 1].ip = frame.ip; }
fn constantStr(chunk: *const compiler_mod.Chunk, idx: u32) []const u8 { const c = chunk.constants.items[idx]; return switch (c) { .str_val => |s| s, else => "", }; }
fn parseDTypeStr(s: []const u8) ?value_mod.DType { if (std.mem.eql(u8, s, "f32")) return .f32; if (std.mem.eql(u8, s, "f64")) return .f64; if (std.mem.eql(u8, s, "i32")) return .i32; if (std.mem.eql(u8, s, "i64")) return .i64; if (std.mem.eql(u8, s, "u8")) return .u8; if (std.mem.eql(u8, s, "bool")) return .boolean; return null; }
fn readU32(code: []const u8, ip: *usize) u32 { const bytes = code[ip.*..][0..4]; ip.* += 4; return std.mem.readInt(u32, bytes, .little); }
// ---- Operators ----
fn arith(self: *Vm, op: Op, a: Value, b: Value) RuntimeError!Value { // Element-wise vector math: arrays combine with arrays of equal // length, and numeric scalars broadcast across arrays. if (a == .array_val or b == .array_val) { return self.arrayArith(op, a, b); } switch (op) { .add => { if (a == .str_val and b == .str_val) { const joined = std.mem.concat(self.allocator, u8, &.{ a.str_val, b.str_val }) catch return error.OutOfMemory; return .{ .str_val = joined }; } if (a.isNumber() and b.isNumber()) { if (a == .int_val and b == .int_val) { return .{ .int_val = a.int_val +| b.int_val }; } return .{ .float_val = a.toF64() + b.toF64() }; } return self.fail(error.TypeMismatch, "invalid operands for '+'", .{}); }, .sub => { if (a == .int_val and b == .int_val) return .{ .int_val = a.int_val -| b.int_val }; if (a.isNumber() and b.isNumber()) return .{ .float_val = a.toF64() - b.toF64() }; return self.fail(error.TypeMismatch, "invalid operands for '-'", .{}); }, .mul => { if (a == .int_val and b == .int_val) return .{ .int_val = a.int_val *| b.int_val }; if (a.isNumber() and b.isNumber()) return .{ .float_val = a.toF64() * b.toF64() }; return self.fail(error.TypeMismatch, "invalid operands for '*'", .{}); }, .div => { if (a == .int_val and b == .int_val) { if (b.int_val == 0) return self.fail(error.DivideByZero, "division by zero", .{}); return .{ .int_val = @divTrunc(a.int_val, b.int_val) }; } if (a.isNumber() and b.isNumber()) { return .{ .float_val = a.toF64() / b.toF64() }; } return self.fail(error.TypeMismatch, "invalid operands for '/'", .{}); }, .mod_op => { if (a == .int_val and b == .int_val) { if (b.int_val == 0) return self.fail(error.DivideByZero, "modulo by zero", .{}); return .{ .int_val = @rem(a.int_val, b.int_val) }; } if (a.isNumber() and b.isNumber()) { return .{ .float_val = @mod(a.toF64(), b.toF64()) }; } return self.fail(error.TypeMismatch, "invalid operands for '%'", .{}); }, else => unreachable, } }
/// SIMD-style element-wise arithmetic on arrays with scalar broadcast. fn arrayArith(self: *Vm, op: Op, a: Value, b: Value) RuntimeError!Value { const arr_a: []Value = if (a == .array_val) a.array_val else &.{}; const arr_b: []Value = if (b == .array_val) b.array_val else &.{}; const len: usize = if (arr_a.len > 0) arr_a.len else arr_b.len;
if ((a == .array_val and b == .array_val) and arr_a.len != arr_b.len) { return self.fail(error.TypeMismatch, "vector length mismatch ({d} vs {d})", .{ arr_a.len, arr_b.len }); } if (len == 0) { return self.fail(error.TypeMismatch, "arithmetic on empty vector", .{}); }
const out = self.allocator.alloc(Value, len) catch return error.OutOfMemory; for (0..len) |i| { const ea: Value = if (a == .array_val) arr_a[i] else a; const eb: Value = if (b == .array_val) arr_b[i] else b; if (!ea.isNumber() or !eb.isNumber()) { return self.fail(error.TypeMismatch, "vector elements must be numeric", .{}); } out[i] = switch (op) { .add => if (ea == .int_val and eb == .int_val) .{ .int_val = ea.int_val +| eb.int_val } else .{ .float_val = ea.toF64() + eb.toF64() }, .sub => if (ea == .int_val and eb == .int_val) .{ .int_val = ea.int_val -| eb.int_val } else .{ .float_val = ea.toF64() - eb.toF64() }, .mul => if (ea == .int_val and eb == .int_val) .{ .int_val = ea.int_val *| eb.int_val } else .{ .float_val = ea.toF64() * eb.toF64() }, .div => blk: { if (eb.toF64() == 0) return self.fail(error.DivideByZero, "division by zero in vector op", .{}); if (ea == .int_val and eb == .int_val) break :blk .{ .int_val = @divTrunc(ea.int_val, eb.int_val) }; break :blk .{ .float_val = ea.toF64() / eb.toF64() }; }, else => unreachable, }; } return .{ .array_val = out }; }
fn compare(self: *Vm, op: Op, a: Value, b: Value) RuntimeError!Value { // Element-wise comparisons produce bool vectors; numeric scalars // broadcast (e.g. `v > zero` for SIMD masks). if (a == .array_val or b == .array_val) { const arr_a: []Value = if (a == .array_val) a.array_val else &.{}; const arr_b: []Value = if (b == .array_val) b.array_val else &.{}; if (a == .array_val and b == .array_val and arr_a.len != arr_b.len) { return self.fail(error.TypeMismatch, "vector length mismatch ({d} vs {d})", .{ arr_a.len, arr_b.len }); } const len = if (arr_a.len > 0) arr_a.len else arr_b.len; if (len == 0) return self.fail(error.TypeMismatch, "comparison on empty vector", .{}); const out = self.allocator.alloc(Value, len) catch return error.OutOfMemory; for (0..len) |i| { out[i] = try self.compare(op, if (a == .array_val) arr_a[i] else a, if (b == .array_val) arr_b[i] else b); } return .{ .array_val = out }; }
var ordering: std.math.Order = undefined; if (a.isNumber() and b.isNumber()) { if (a == .int_val and b == .int_val) { ordering = std.math.order(a.int_val, b.int_val); } else { ordering = std.math.order(a.toF64(), b.toF64()); } } else if (a == .str_val and b == .str_val) { ordering = std.mem.order(u8, a.str_val, b.str_val); } else if (a == .bool_val and b == .bool_val) { const ai: i64 = @intFromBool(a.bool_val); const bi: i64 = @intFromBool(b.bool_val); ordering = std.math.order(ai, bi); } else { return self.fail(error.TypeMismatch, "cannot compare these values", .{}); } const result = switch (op) { .lt => ordering == .lt, .le => ordering != .gt, .gt => ordering == .gt, .ge => ordering != .lt, else => unreachable, }; return .{ .bool_val = result }; }
/// Returns the module's CSPRNG, seeding it from OS entropy (or, as a /// fallback, from wall clock + thread id) on first use. fn ensureRng(self: *Vm) RuntimeError!*std.Random.DefaultCsprng { if (self.rng) |*r| return r; var seed: [std.Random.DefaultCsprng.secret_seed_length]u8 = undefined; var seeded = false; if (self.io) |io| { const f = std.Io.Dir.cwd().openFile(io, "/dev/urandom", .{}) catch null; if (f) |file| { defer file.close(io); const got = file.readPositionalAll(io, &seed, 0) catch 0; seeded = got == seed.len; } } if (!seeded) { const ts = std.Io.Timestamp.now(self.io.?, .awake); var x: u64 = @intCast(@as(u96, @bitCast(ts.nanoseconds))); x ^= std.Thread.getCurrentId(); x ^= x >> 33; x *= 0xff51afd7ed558ccd; x ^= x >> 33; for (&seed) |*b| { x ^= x >> 13; x *= 0xbf58476d1ce4e5b9; b.* = @intCast(x & 0xff); } } self.rng = std.Random.DefaultCsprng.init(seed); return &self.rng.?; }
// ---- Builtins ----
/// Renders `fmt` into `w`, substituting each `{}` with the next value /// from `vals`. `{{` and `}}` are escapes for literal braces. Argument /// count must match placeholder count exactly. fn writeFmt(self: *Vm, w: *std.Io.Writer, fmt: []const u8, vals: []const Value) RuntimeError!void { var vi: usize = 0; var i: usize = 0; while (i < fmt.len) { const c = fmt[i]; if (c == '}') { if (i + 1 < fmt.len and fmt[i + 1] == '}') { w.writeByte('}') catch return error.WriteFailed; i += 2; continue; } return self.fail(error.TypeMismatch, "unescaped '}}' in format string (use '}}' for a literal brace)", .{}); } if (c != '{') { w.writeByte(c) catch return error.WriteFailed; i += 1; continue; } if (i + 1 < fmt.len and fmt[i + 1] == '{') { w.writeByte('{') catch return error.WriteFailed; i += 2; continue; } const close = std.mem.indexOfScalarPos(u8, fmt, i, '}') orelse return self.fail(error.TypeMismatch, "unclosed '{{' in format string", .{}); const spec = fmt[i + 1 .. close]; if (spec.len != 0) { return self.fail(error.TypeMismatch, "unsupported format specifier '{{{s}}}': only '{{{{}}}}' is supported", .{spec}); } if (vi >= vals.len) { return self.fail(error.TypeMismatch, "format string has more '{{}}' placeholders than arguments ({d})", .{vals.len}); } vals[vi].format(w) catch return error.WriteFailed; vi += 1; i = close + 1; } if (vi < vals.len) { return self.fail(error.TypeMismatch, "format string has fewer '{{}}' placeholders than arguments ({d})", .{vals.len}); } }
fn callFmtBuiltin(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .str_val or args[1] != .array_val) { return self.fail(error.TypeMismatch, "@{s} expects a format string and tuple: @{s}(\"x = {{}}\\n\", .{{x}})", .{ name, name }); } switch (name[0]) { 's', 'p' => { // stdout, print try self.writeFmt(self.out, args[0].str_val, args[1].array_val); }, 'l', 'e' => { // log, stderr try self.writeFmt(self.errw, args[0].str_val, args[1].array_val); }, else => unreachable, } return .null_val; }
fn callBuiltin(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (std.mem.eql(u8, name, "stdout") or std.mem.eql(u8, name, "log") or std.mem.eql(u8, name, "print") or std.mem.eql(u8, name, "stderr")) { return self.callFmtBuiltin(name, args); } if (std.mem.eql(u8, name, "env")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "@env expects one string argument", .{}); } if (self.environ) |map| { if (map.get(args[0].str_val)) |val| { return .{ .str_val = val }; } } return .{ .str_val = "" }; } if (std.mem.eql(u8, name, "clock")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "@clock takes no arguments", .{}); } if (self.io != null and self.start_ts != null) { const now_ts = std.Io.Timestamp.now(self.io.?, .awake); const elapsed_ns = self.start_ts.?.durationTo(now_ts).nanoseconds; const elapsed_f: f64 = @floatFromInt(elapsed_ns); return .{ .float_val = elapsed_f / 1_000_000.0 }; } return .{ .float_val = 0 }; } if (std.mem.eql(u8, name, "len")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "len expects exactly one argument", .{}); } return switch (args[0]) { .str_val => |s| .{ .int_val = @intCast(s.len) }, .array_val => |arr| .{ .int_val = @intCast(arr.len) }, .map_val => |mp| .{ .int_val = @intCast(mp.map.count()) }, else => self.fail(error.TypeMismatch, "len expects a str, array or map", .{}), }; } if (std.mem.eql(u8, name, "int")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "int expects exactly one argument", .{}); } return switch (args[0]) { .int_val => args[0], .float_val => |f| .{ .int_val = std.math.lossyCast(i64, @trunc(f)) }, .bool_val => |b2| .{ .int_val = if (b2) 1 else 0 }, .str_val => |s| blk: { const t = std.mem.trim(u8, s, " \t\r\n"); const parsed = std.fmt.parseInt(i64, t, 10) catch { break :blk self.fail(error.TypeMismatch, "cannot parse '{s}' as int", .{s}); }; break :blk .{ .int_val = parsed }; }, else => self.fail(error.TypeMismatch, "cannot convert this value to int", .{}), }; } if (std.mem.eql(u8, name, "float")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "float expects exactly one argument", .{}); } return switch (args[0]) { .float_val => args[0], .int_val => |i| .{ .float_val = @floatFromInt(i) }, .bool_val => |b2| .{ .float_val = if (b2) 1.0 else 0.0 }, .str_val => |s| blk: { const t = std.mem.trim(u8, s, " \t\r\n"); const parsed = std.fmt.parseFloat(f64, t) catch { break :blk self.fail(error.TypeMismatch, "cannot parse '{s}' as float", .{s}); }; break :blk .{ .float_val = parsed }; }, else => self.fail(error.TypeMismatch, "cannot convert this value to float", .{}), }; } if (std.mem.eql(u8, name, "str")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "str expects exactly one argument", .{}); } const rendered = args[0].display(self.allocator) catch return error.OutOfMemory; return .{ .str_val = rendered }; } // Type cast: `@as(float, 7)`. The compiler passes bare type names // through as strings, so args[0] is the target type name. if (std.mem.eql(u8, name, "as")) { if (args.len != 2 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "@as expects (type, value), e.g. @as(float, 7)", .{}); } const t = args[0].str_val; const int_aliases = [_][]const u8{ "int", "i32", "i64", "u8", "u32", "u64", "usize" }; for (int_aliases) |a| { if (std.mem.eql(u8, t, a)) return self.callBuiltin("int", args[1..2]); } const float_aliases = [_][]const u8{ "float", "f32", "f64" }; for (float_aliases) |a| { if (std.mem.eql(u8, t, a)) return self.callBuiltin("float", args[1..2]); } if (std.mem.eql(u8, t, "str") or std.mem.eql(u8, t, "string")) { return self.callBuiltin("str", args[1..2]); } if (std.mem.eql(u8, t, "bool")) { return .{ .bool_val = args[1].truthy() }; } return self.fail(error.TypeMismatch, "unknown type '{s}' in @as", .{t}); }
// ---- Phase 2: SIMD-style vector intrinsics ---- // @vector(Len, Type) creates a zero-filled vector; @splat(Len, v) // fills with v; @reduce(op, vec) folds to a scalar. Real 128/256/512-bit // codegen arrives with the Phase 3 native backend. if (std.mem.eql(u8, name, "splat")) { if (args.len != 2 or args[0] != .int_val) { return self.fail(error.TypeMismatch, "@splat expects (len: int, value)", .{}); } const n = args[0].int_val; if (n < 0) return self.fail(error.TypeMismatch, "@splat length must be >= 0", .{}); const out = self.allocator.alloc(Value, @intCast(n)) catch return error.OutOfMemory; for (out) |*slot| slot.* = args[1]; return .{ .array_val = out }; } if (std.mem.eql(u8, name, "vector")) { if (args.len != 2 or args[0] != .int_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "@vector expects (len: int, type: str), e.g. @vector(16, \"f32\")", .{}); } const n = args[0].int_val; if (n < 0) return self.fail(error.TypeMismatch, "@vector length must be >= 0", .{}); const zero: Value = blk: { const t = args[1].str_val; if (std.mem.eql(u8, t, "float") or std.mem.eql(u8, t, "f32") or std.mem.eql(u8, t, "f64")) break :blk .{ .float_val = 0.0 }; if (std.mem.eql(u8, t, "bool")) break :blk .{ .bool_val = false }; break :blk .{ .int_val = 0 }; }; const out = self.allocator.alloc(Value, @intCast(n)) catch return error.OutOfMemory; for (out) |*slot| slot.* = zero; return .{ .array_val = out }; } if (std.mem.eql(u8, name, "reduce")) { if (args.len != 2 or args[0] != .str_val or args[1] != .array_val) { return self.fail(error.TypeMismatch, "@reduce expects (op: str, vector), e.g. @reduce(\"add\", xs)", .{}); } const vec = args[1].array_val; if (vec.len == 0) return self.fail(error.TypeMismatch, "@reduce on empty vector", .{}); const op_name = args[0].str_val; var acc: Value = vec[0]; for (vec[1..]) |v| { if (!acc.isNumber() or !v.isNumber()) { return self.fail(error.TypeMismatch, "@reduce elements must be numeric", .{}); } if (std.mem.eql(u8, op_name, "add") or std.mem.eql(u8, op_name, "+")) { acc = if (acc == .int_val and v == .int_val) .{ .int_val = acc.int_val +| v.int_val } else .{ .float_val = acc.toF64() + v.toF64() }; } else if (std.mem.eql(u8, op_name, "mul") or std.mem.eql(u8, op_name, "*")) { acc = if (acc == .int_val and v == .int_val) .{ .int_val = acc.int_val *| v.int_val } else .{ .float_val = acc.toF64() * v.toF64() }; } else if (std.mem.eql(u8, op_name, "min")) { acc = if (acc.toF64() < v.toF64()) acc else v; } else if (std.mem.eql(u8, op_name, "max")) { acc = if (acc.toF64() > v.toF64()) acc else v; } else { return self.fail(error.TypeMismatch, "unknown reduce op '{s}' (use add/mul/min/max)", .{op_name}); } } return acc; }
if (std.mem.eql(u8, name, "select")) { if (args.len != 3 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "@select expects (mask: []bool, a, b)", .{}); } const mask = args[0].array_val; if (mask.len == 0) return self.fail(error.TypeMismatch, "@select on empty mask", .{}); const out = self.allocator.alloc(Value, mask.len) catch return error.OutOfMemory; for (mask, 0..) |m, i| { if (m != .bool_val) return self.fail(error.TypeMismatch, "@select mask must be bool vector", .{}); const from_a = args[1] == .array_val; const from_b = args[2] == .array_val; const av: Value = if (from_a) args[1].array_val[i] else args[1]; const bv: Value = if (from_b) args[2].array_val[i] else args[2]; out[i] = if (m.bool_val) av else bv; } return .{ .array_val = out }; }
// ---- Phase 2: std.fs / std.io slice — file I/O builtins ---- if (self.io == null and (std.mem.eql(u8, name, "read_file") or std.mem.eql(u8, name, "write_file") or std.mem.eql(u8, name, "file_exists") or std.mem.eql(u8, name, "make_dir") or std.mem.eql(u8, name, "make_dir_all") or std.mem.eql(u8, name, "remove_file") or std.mem.eql(u8, name, "remove_dir") or std.mem.eql(u8, name, "rename_file") or std.mem.eql(u8, name, "append_file") or std.mem.eql(u8, name, "list_dir") or std.mem.eql(u8, name, "load_safetensors") or std.mem.eql(u8, name, "load_gguf") or std.mem.eql(u8, name, "llm_load") or std.mem.eql(u8, name, "tcp_listen") or std.mem.eql(u8, name, "tcp_connect") or std.mem.eql(u8, name, "sleep") or std.mem.eql(u8, name, "wall_time_ms") or std.mem.eql(u8, name, "current_dir") or std.mem.eql(u8, name, "home_dir") or std.mem.eql(u8, name, "cpu_count") or std.mem.eql(u8, name, "random_int") or std.mem.eql(u8, name, "random_float") or std.mem.eql(u8, name, "random_chance") or std.mem.eql(u8, name, "random_pick") or std.mem.eql(u8, name, "shuffle"))) { return self.fail(error.IoFailed, "@{s} requires an Io context (unavailable here)", .{name}); } if (std.mem.eql(u8, name, "read_file")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "read_file expects (path: str)", .{}); } const contents = std.Io.Dir.cwd().readFileAlloc(self.io.?, args[0].str_val, self.allocator, .limited(64 * 1024 * 1024)) catch |e| { return self.fail(error.IoFailed, "read_file('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; return .{ .str_val = contents }; } if (std.mem.eql(u8, name, "write_file")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "write_file expects (path: str, contents: str)", .{}); } std.Io.Dir.cwd().writeFile(self.io.?, .{ .sub_path = args[0].str_val, .data = args[1].str_val }) catch |e| { return self.fail(error.IoFailed, "write_file('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; return .{ .bool_val = true }; } if (std.mem.eql(u8, name, "file_exists")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "file_exists expects (path: str)", .{}); } std.Io.Dir.cwd().access(self.io.?, args[0].str_val, .{}) catch |e| switch (e) { error.FileNotFound => return .{ .bool_val = false }, else => return self.fail(error.IoFailed, "file_exists('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }), }; return .{ .bool_val = true }; } // std.io filesystem helpers (extend the read/write/query trio above). if (std.mem.eql(u8, name, "make_dir") or std.mem.eql(u8, name, "make_dir_all")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "@{s} expects (path: str)", .{name}); } if (std.mem.eql(u8, name, "make_dir_all")) { std.Io.Dir.cwd().createDirPath(self.io.?, args[0].str_val) catch |e| { return self.fail(error.IoFailed, "make_dir_all('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; } else { std.Io.Dir.cwd().createDir(self.io.?, args[0].str_val, .default_dir) catch |e| { return self.fail(error.IoFailed, "make_dir('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; } return .{ .bool_val = true }; } if (std.mem.eql(u8, name, "remove_file") or std.mem.eql(u8, name, "remove_dir")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "@{s} expects (path: str)", .{name}); } const cwd = std.Io.Dir.cwd(); const is_file = std.mem.eql(u8, name, "remove_file"); if (is_file) { cwd.deleteFile(self.io.?, args[0].str_val) catch |e| switch (e) { error.FileNotFound => return .{ .bool_val = false }, else => return self.fail(error.IoFailed, "remove_file('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }), }; } else { cwd.deleteDir(self.io.?, args[0].str_val) catch |e| switch (e) { error.FileNotFound => return .{ .bool_val = false }, else => return self.fail(error.IoFailed, "remove_dir('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }), }; } return .{ .bool_val = true }; } if (std.mem.eql(u8, name, "rename_file")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "rename_file expects (from: str, to: str)", .{}); } const cwd = std.Io.Dir.cwd(); std.Io.Dir.rename(cwd, args[0].str_val, cwd, args[1].str_val, self.io.?) catch |e| { return self.fail(error.IoFailed, "rename_file('{s}' -> '{s}') failed: {s}", .{ args[0].str_val, args[1].str_val, @errorName(e) }); }; return .{ .bool_val = true }; } if (std.mem.eql(u8, name, "append_file")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "append_file expects (path: str, data: str)", .{}); } const f = std.Io.Dir.cwd().createFile(self.io.?, args[0].str_val, .{ .truncate = false }) catch |e| { return self.fail(error.IoFailed, "append_file('{s}') failed to open: {s}", .{ args[0].str_val, @errorName(e) }); }; const end = f.length(self.io.?) catch |e| { f.close(self.io.?); return self.fail(error.IoFailed, "append_file('{s}') failed to size: {s}", .{ args[0].str_val, @errorName(e) }); }; f.writePositionalAll(self.io.?, args[1].str_val, end) catch |e| { f.close(self.io.?); return self.fail(error.IoFailed, "append_file('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; f.close(self.io.?); return .{ .bool_val = true }; } if (std.mem.eql(u8, name, "list_dir")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "list_dir expects (path: str)", .{}); } const dir = std.Io.Dir.cwd().openDir(self.io.?, args[0].str_val, .{ .iterate = true }) catch |e| { return self.fail(error.IoFailed, "list_dir('{s}') failed: {s}", .{ args[0].str_val, @errorName(e) }); }; defer dir.close(self.io.?); var it = dir.iterate(); var out: std.ArrayList(Value) = .empty; while (it.next(self.io.?)) |maybe_entry| { const child = maybe_entry orelse break; const dup = self.allocator.dupe(u8, child.name) catch return error.OutOfMemory; out.append(self.allocator, .{ .str_val = dup }) catch return error.OutOfMemory; } else |e| { return self.fail(error.IoFailed, "list_dir('{s}') iteration failed: {s}", .{ args[0].str_val, @errorName(e) }); } return .{ .array_val = out.toOwnedSlice(self.allocator) catch return error.OutOfMemory }; }
// ---- Phase 4: ai library — zero-copy model readers ---- // load_safetensors(path)/load_gguf(path): mmap the file, validate the // header, and expose every tensor as a read-only TensorObj view into // the mapping (no data is copied). if (std.mem.eql(u8, name, "load_safetensors") or std.mem.eql(u8, name, "load_gguf")) { return self.callAiLoader(name, args); }
// ---- Net library ---- // TCP client/server via std.Io.net (cross-platform through the Io // vtable). Listeners expose accept/port/close; streams read/write/close. if (std.mem.eql(u8, name, "tcp_listen")) { if (args.len != 1 or args[0] != .int_val or args[0].int_val < 0 or args[0].int_val > 65535) { return self.fail(error.TypeMismatch, "tcp_listen expects (port: int 0..65535); port 0 picks a free port", .{}); } var addr = std.Io.net.IpAddress.parseIp4("0.0.0.0", @intCast(args[0].int_val)) catch return self.fail(error.IoFailed, "tcp_listen: bad bind address", .{}); const srv = addr.listen(self.io.?, .{ .mode = .stream }) catch |e| { return self.fail(error.IoFailed, "tcp_listen: cannot listen on port {d} ({s})", .{ args[0].int_val, @errorName(e) }); }; const obj = self.allocator.create(value_mod.NetObj) catch return error.OutOfMemory; obj.* = .{ .kind = .listener, .server = srv }; return .{ .net_val = obj }; } if (std.mem.eql(u8, name, "tcp_connect")) { if (args.len != 2 or args[0] != .str_val or args[1] != .int_val or args[1].int_val <= 0 or args[1].int_val > 65535) { return self.fail(error.TypeMismatch, "tcp_connect expects (host: str, port: int)", .{}); } var addr = std.Io.net.IpAddress.resolve(self.io.?, args[0].str_val, @intCast(args[1].int_val)) catch |e| { return self.fail(error.IoFailed, "tcp_connect: cannot resolve '{s}' ({s})", .{ args[0].str_val, @errorName(e) }); }; const st = std.Io.net.IpAddress.connect(&addr, self.io.?, .{ .mode = .stream }) catch |e| { return self.fail(error.IoFailed, "tcp_connect: cannot connect to {s}:{d} ({s})", .{ args[0].str_val, args[1].int_val, @errorName(e) }); }; const obj = self.allocator.create(value_mod.NetObj) catch return error.OutOfMemory; obj.* = .{ .kind = .stream, .stream = st }; return .{ .net_val = obj }; }
// ---- C FFI library ---- // load_lib(path): dlopen a shared library; methods call_int / // call_float invoke C symbols with int/float args. if (std.mem.eql(u8, name, "load_lib")) { return self.callLibLoader(name, args); }
// ---- LLM library ---- // llm_load(path): mmap a GGUF model (rinne.llm.* convention) and hand // back a zero-copy model object; forward passes run on the VM thread. if (std.mem.eql(u8, name, "llm_load")) { return self.callLlmLoader(name, args); }
// ---- CLI (utility) ---- // args(): the argument vector passed to runSource/run. if (std.mem.eql(u8, name, "args")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "args takes no arguments", .{}); } const out = self.allocator.alloc(Value, self.script_args.len) catch return error.OutOfMemory; for (self.script_args, 0..) |a, i| { out[i] = .{ .str_val = self.allocator.dupe(u8, a) catch return error.OutOfMemory }; } return .{ .array_val = out }; }
// ---- Assertions (utility) ---- if (std.mem.eql(u8, name, "assert")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "assert expects one condition", .{}); } if (!args[0].truthy()) { return self.fail(error.TypeMismatch, "assertion failed", .{}); } return .null_val; } if (std.mem.eql(u8, name, "assert_eq")) { if (args.len != 2) { return self.fail(error.TypeMismatch, "assert_eq expects two values", .{}); } if (!Value.eql(args[0], args[1])) { const da = args[0].display(self.allocator) catch return error.OutOfMemory; const db = args[1].display(self.allocator) catch return error.OutOfMemory; return self.fail(error.TypeMismatch, "assertion failed: {s} != {s}", .{ da, db }); } return .null_val; }
// ---- Maps (utility) ---- // map_new(k0, v0, k1, v1, ...): builds a string-keyed map; also the // lowering target for `.{ .key = value }` literals. if (std.mem.eql(u8, name, "map_new")) { if (args.len % 2 != 0) { return self.fail(error.TypeMismatch, "map_new expects key/value pairs", .{}); } const m = value_mod.MapObj.create(self.allocator) catch return error.OutOfMemory; var i: usize = 0; while (i < args.len) : (i += 2) { if (args[i] != .str_val) { return self.fail(error.TypeMismatch, "map keys must be strings", .{}); } const kdup = self.allocator.dupe(u8, args[i].str_val) catch return error.OutOfMemory; m.map.put(kdup, args[i + 1]) catch return error.OutOfMemory; } return .{ .map_val = m }; }
// ---- Strings (utility) ---- if (std.mem.eql(u8, name, "split")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "split expects (s: str, sep: str)", .{}); } if (args[1].str_val.len == 0) { return self.fail(error.TypeMismatch, "split separator must not be empty", .{}); } const A = self.allocator; var out: std.ArrayList(Value) = .empty; var it = std.mem.splitSequence(u8, args[0].str_val, args[1].str_val); while (it.next()) |part| { const d = A.dupe(u8, part) catch return error.OutOfMemory; out.append(A, .{ .str_val = d }) catch return error.OutOfMemory; } return .{ .array_val = out.toOwnedSlice(A) catch return error.OutOfMemory }; } if (std.mem.eql(u8, name, "join")) { if (args.len != 2 or args[0] != .array_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "join expects (parts: array, sep: str)", .{}); } const A = self.allocator; var out: std.ArrayList(u8) = .empty; for (args[0].array_val, 0..) |v, i| { if (i > 0) out.appendSlice(A, args[1].str_val) catch return error.OutOfMemory; switch (v) { .str_val => |sv| out.appendSlice(A, sv) catch return error.OutOfMemory, else => { const d = v.display(A) catch return error.OutOfMemory; out.appendSlice(A, d) catch return error.OutOfMemory; }, } } return .{ .str_val = out.toOwnedSlice(A) catch return error.OutOfMemory }; } if (std.mem.eql(u8, name, "trim")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "trim expects one string", .{}); } const t = std.mem.trim(u8, args[0].str_val, " \t\r\n"); return .{ .str_val = self.allocator.dupe(u8, t) catch return error.OutOfMemory }; } if (std.mem.eql(u8, name, "replace")) { if (args.len != 3 or args[0] != .str_val or args[1] != .str_val or args[2] != .str_val) { return self.fail(error.TypeMismatch, "replace expects (s, old, new)", .{}); } if (args[1].str_val.len == 0) { return self.fail(error.TypeMismatch, "replace pattern must not be empty", .{}); } const r = std.mem.replaceOwned(u8, self.allocator, args[0].str_val, args[1].str_val, args[2].str_val) catch return error.OutOfMemory; return .{ .str_val = r }; } if (std.mem.eql(u8, name, "contains")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "contains expects two strings", .{}); } return .{ .bool_val = std.mem.indexOf(u8, args[0].str_val, args[1].str_val) != null }; } if (std.mem.eql(u8, name, "starts_with") or std.mem.eql(u8, name, "ends_with")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "{s} expects two strings", .{name}); } const b = if (name[0] == 's') std.mem.startsWith(u8, args[0].str_val, args[1].str_val) else std.mem.endsWith(u8, args[0].str_val, args[1].str_val); return .{ .bool_val = b }; } if (std.mem.eql(u8, name, "to_lower") or std.mem.eql(u8, name, "to_upper")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "{s} expects one string", .{name}); } const buf = self.allocator.dupe(u8, args[0].str_val) catch return error.OutOfMemory; for (buf) |*c| { c.* = if (name[3] == 'l') std.ascii.toLower(c.*) else std.ascii.toUpper(c.*); } return .{ .str_val = buf }; } // repeat(s, n): concatenate s n times. if (std.mem.eql(u8, name, "repeat")) { if (args.len != 2 or args[0] != .str_val or args[1] != .int_val or args[1].int_val < 0) { return self.fail(error.TypeMismatch, "repeat expects (s: str, count: int >= 0)", .{}); } const n: usize = @intCast(args[1].int_val); const out = self.allocator.alloc(u8, args[0].str_val.len * n) catch return error.OutOfMemory; for (0..n) |i| { @memcpy(out[i * args[0].str_val.len .. (i + 1) * args[0].str_val.len], args[0].str_val); } return .{ .str_val = out }; } // chars(s): each byte (rune) as a one-char string; also accepts arrays // (identity copy) for symmetric use in list contexts. if (std.mem.eql(u8, name, "chars")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "chars expects one string", .{}); } switch (args[0]) { .str_val => |sv| { const out = self.allocator.alloc(Value, sv.len) catch return error.OutOfMemory; for (sv, 0..) |b, i| { const buf = self.allocator.alloc(u8, 1) catch return error.OutOfMemory; buf[0] = b; out[i] = .{ .str_val = buf }; } return .{ .array_val = out }; }, .array_val => |av| { const out = self.allocator.alloc(Value, av.len) catch return error.OutOfMemory; for (av, 0..) |v, i| out[i] = v; return .{ .array_val = out }; }, else => return self.fail(error.TypeMismatch, "chars expects a str or array", .{}), } } // reverse(s|arr): reverse a string's bytes or an array in place of copy. if (std.mem.eql(u8, name, "reverse")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "reverse expects one str or array", .{}); } switch (args[0]) { .str_val => |sv| { const buf = self.allocator.dupe(u8, sv) catch return error.OutOfMemory; std.mem.reverse(u8, buf); return .{ .str_val = buf }; }, .array_val => |av| { const out = self.allocator.dupe(Value, av) catch return error.OutOfMemory; std.mem.reverse(Value, out); return .{ .array_val = out }; }, else => return self.fail(error.TypeMismatch, "reverse expects a str or array", .{}), } } // pad_start/pad_end(s, n[, ch]): pad a string to length n with ch. if (std.mem.eql(u8, name, "pad_start") or std.mem.eql(u8, name, "pad_end")) { if (args.len < 2 or args.len > 3 or args[0] != .str_val or args[1] != .int_val or (args.len == 3 and args[2] != .str_val)) { return self.fail(error.TypeMismatch, "@{s} expects (s: str, length: int, pad?: str)", .{name}); } const s = args[0].str_val; const n: i64 = args[1].int_val; const pad_ch: []const u8 = if (args.len == 3) args[2].str_val else " "; if (pad_ch.len == 0) return self.fail(error.TypeMismatch, "pad character must not be empty", .{}); if (n <= @as(i64, @intCast(s.len))) { return .{ .str_val = self.allocator.dupe(u8, s) catch return error.OutOfMemory }; } const pad_len: usize = @intCast(n - @as(i64, @intCast(s.len))); const total: usize = @intCast(n); const out = self.allocator.alloc(u8, total) catch return error.OutOfMemory; const pad_start_result = std.mem.eql(u8, name, "pad_start"); const pad_dest = if (pad_start_result) out[0..pad_len] else out[s.len..]; var off: usize = 0; while (off < pad_len) { const chunk = @min(pad_ch.len, pad_len - off); @memcpy(pad_dest[off .. off + chunk], pad_ch[0..chunk]); off += chunk; } if (pad_start_result) { @memcpy(out[pad_len..], s); } else { @memcpy(out[0..s.len], s); } return .{ .str_val = out }; } // capitalize(s): first byte upper-cased, rest unchanged. if (std.mem.eql(u8, name, "capitalize")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "capitalize expects one string", .{}); } const buf = self.allocator.dupe(u8, args[0].str_val) catch return error.OutOfMemory; if (buf.len > 0) buf[0] = std.ascii.toUpper(buf[0]); return .{ .str_val = buf }; } // count(s, sub): number of non-overlapping occurrences. if (std.mem.eql(u8, name, "count")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "count expects two strings", .{}); } if (args[1].str_val.len == 0) { return self.fail(error.TypeMismatch, "count pattern must not be empty", .{}); } var cnt: usize = 0; var idx: usize = 0; while (std.mem.findPos(u8, args[0].str_val, idx, args[1].str_val)) |pos| { cnt += 1; idx = pos + args[1].str_val.len; } return .{ .int_val = @intCast(cnt) }; } // index_of(s, sub[, start]): null when absent. if (std.mem.eql(u8, name, "index_of") or std.mem.eql(u8, name, "last_index_of")) { if (args.len < 2 or args.len > 3 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "@{s} expects (s: str, sub: str[, start: int])", .{name}); } if (args.len == 3 and args[2] != .int_val) { return self.fail(error.TypeMismatch, "@{s} start offset must be an int", .{name}); } const hay = args[0].str_val; const needle = args[1].str_val; var start: usize = 0; if (args.len == 3) { var off: i64 = args[2].int_val; if (off < 0) off = @as(i64, @intCast(hay.len)) + off; if (off < 0) off = 0; start = @min(@as(usize, @intCast(off)), hay.len); } const is_last = std.mem.eql(u8, name, "last_index_of"); const search_len = hay.len - start; const found = if (is_last) std.mem.lastIndexOf(u8, hay[0..search_len], needle) else std.mem.indexOf(u8, hay[start..], needle); const pos = found orelse return .null_val; if (is_last) return .{ .int_val = @intCast(pos) }; return .{ .int_val = @intCast(pos + start) }; } // lines(s): split on newlines, dropping a trailing newline. if (std.mem.eql(u8, name, "lines")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "lines expects one string", .{}); } const s = args[0].str_val; const A = self.allocator; var out: std.ArrayList(Value) = .empty; if (s.len == 0) return .{ .array_val = &.{} }; var it = std.mem.splitScalar(u8, s, '\n'); while (it.next()) |line_raw| { const line = if (line_raw.len > 0 and line_raw[line_raw.len - 1] == '\r') line_raw[0 .. line_raw.len - 1] else line_raw; // A terminal newline yields a trailing empty segment; drop it. if (line.len == 0 and it.peek() == null) break; out.append(A, .{ .str_val = A.dupe(u8, line) catch return error.OutOfMemory }) catch return error.OutOfMemory; } return .{ .array_val = out.toOwnedSlice(A) catch return error.OutOfMemory }; } // contains_val(collection, v): does the array hold an equal value, or // does the map hold it as one of its values? if (std.mem.eql(u8, name, "contains_val")) { const coll = args[0]; switch (coll) { .array_val => |arr| { for (arr) |v| { if (Value.eql(v, args[1])) return .{ .bool_val = true }; } }, .map_val => |mp| { var it = mp.map.valueIterator(); while (it.next()) |v| { if (Value.eql(v.*, args[1])) return .{ .bool_val = true }; } }, else => return self.fail(error.TypeMismatch, "contains_val expects (array|map, value)", .{}), } return .{ .bool_val = false }; }
// ---- std.test (assertions) ---- // Asserts never abort the script: a failure is reported to stderr and // counted, and runModule exits 70 once the run finishes. `rinne test` // aggregates the per-file counters into a final summary. if (std.mem.eql(u8, name, "test_eq") or std.mem.eql(u8, name, "test_ne") or std.mem.eql(u8, name, "test_true") or std.mem.eql(u8, name, "test_false")) { if (args.len < 1 or args.len > 2) return self.fail(error.ArityMismatch, "std.test.{s} expects 1 or 2 argument(s)", .{name}); self.test_assertions += 1; const pass: bool = if (std.mem.eql(u8, name, "test_eq")) Value.eql(args[0], args[1]) else if (std.mem.eql(u8, name, "test_ne")) !Value.eql(args[0], args[1]) else if (std.mem.eql(u8, name, "test_true")) args[0] == .bool_val and args[0].bool_val else args[0] == .bool_val and !args[0].bool_val; if (!pass) { self.test_failures += 1; var buf: [256]u8 = undefined; const loc = self.locationSuffix(&buf); self.errw.print("test: FAIL std.test.{s}{s}\n", .{ name, loc }) catch return error.WriteFailed; } return .{ .bool_val = pass }; }
// ---- std.collections (array reducers / views) ---- // sort(arr): new array sorted ascending. Numbers compare numerically; // strings compare lexically; mixed numeric arrays compare by real value. if (std.mem.eql(u8, name, "sort")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "sort expects one array", .{}); } const out = self.allocator.dupe(Value, args[0].array_val) catch return error.OutOfMemory; std.mem.sort(Value, out, {}, struct { fn lessThan(_: void, a: Value, b: Value) bool { return value_less_than(a, b); } }.lessThan); return .{ .array_val = out }; } // unique(arr): new array with adjacent duplicates collapsed (pair it // with sort() to de-duplicate fully). if (std.mem.eql(u8, name, "unique")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "unique expects one array", .{}); } const src = args[0].array_val; var out: std.ArrayList(Value) = .empty; for (src, 0..) |v, i| { if (i == 0 or !Value.eql(v, src[i - 1])) { out.append(self.allocator, v) catch return error.OutOfMemory; } } return .{ .array_val = out.toOwnedSlice(self.allocator) catch return error.OutOfMemory }; } // sum/mean of numeric arrays. if (std.mem.eql(u8, name, "sum") or std.mem.eql(u8, name, "mean")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "@{s} expects one array", .{name}); } const arr = args[0].array_val; if (arr.len == 0) return .{ .float_val = 0 }; var all_int = true; var acc: f64 = 0; for (arr) |v| { if (!v.isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects an array of numbers", .{name}); } if (all_int and v != .int_val) all_int = false; acc += v.toF64(); } if (std.mem.eql(u8, name, "sum")) { if (all_int) return .{ .int_val = std.math.lossyCast(i64, acc) }; return .{ .float_val = acc }; } return .{ .float_val = acc / @as(f64, @floatFromInt(arr.len)) }; } // min_of/max_of: optional-returning reducers (null on empty array). if (std.mem.eql(u8, name, "min_of") or std.mem.eql(u8, name, "max_of")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "@{s} expects one array", .{name}); } const arr = args[0].array_val; if (arr.len == 0) return .null_val; const want_min = std.mem.eql(u8, name, "min_of"); var best = arr[0]; for (arr) |v| { if (value_less_than(v, best)) { if (want_min) best = v; } else if (value_less_than(best, v)) { if (!want_min) best = v; } } return best; } // first/last: boundary views; last supports negative offsets (-1 = end). if (std.mem.eql(u8, name, "first") or std.mem.eql(u8, name, "last")) { if (args.len < 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "@{s} expects (array[, index])", .{name}); } const arr = args[0].array_val; var idx: i64 = if (std.mem.eql(u8, name, "last")) -1 else 0; if (args.len >= 2) { if (args[1] != .int_val) { return self.fail(error.TypeMismatch, "@{s} index must be an int", .{name}); } idx = args[1].int_val; } if (arr.len == 0) return .null_val; var i = idx; if (i < 0) i = @as(i64, @intCast(arr.len)) + i; if (i < 0 or i >= @as(i64, @intCast(arr.len))) return .null_val; return arr[@intCast(i)]; } // empty(arr|str|map): true when it has no elements. if (std.mem.eql(u8, name, "empty")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "empty expects one str, array or map", .{}); } const b = switch (args[0]) { .str_val => |s| s.len == 0, .array_val => |arr| arr.len == 0, .map_val => |mp| mp.map.count() == 0, else => return self.fail(error.TypeMismatch, "empty expects a str, array or map", .{}), }; return .{ .bool_val = b }; }
// ---- JSON (utility) ---- if (std.mem.eql(u8, name, "json_parse")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "json_parse expects one string", .{}); } var parsed = std.json.parseFromSlice(std.json.Value, self.allocator, args[0].str_val, .{}) catch { return self.fail(error.TypeMismatch, "json_parse: invalid JSON input", .{}); }; defer parsed.deinit(); return try self.jsonToValue(parsed.value); } if (std.mem.eql(u8, name, "json_stringify")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "json_stringify expects one value", .{}); } var buf: std.Io.Writer.Allocating = .init(self.allocator); try self.jsonStringify(args[0], &buf.writer); return .{ .str_val = buf.toOwnedSlice() catch return error.OutOfMemory }; } // json_pretty(v): same as json_stringify but indented, multi-line. if (std.mem.eql(u8, name, "json_pretty")) { if (args.len != 1) { return self.fail(error.TypeMismatch, "json_pretty expects one value", .{}); } var buf: std.Io.Writer.Allocating = .init(self.allocator); try self.jsonStringifyPretty(args[0], 0, &buf.writer); return .{ .str_val = buf.toOwnedSlice() catch return error.OutOfMemory }; }
// ---- Array/string utilities ---- // push(arr, v): returns a NEW array with v appended (arrays are // values; functional growth keeps aliasing semantics simple). if (std.mem.eql(u8, name, "push")) { if (args.len != 2 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "push expects (array, value)", .{}); } const src_arr = args[0].array_val; const out = self.allocator.alloc(Value, src_arr.len + 1) catch return error.OutOfMemory; @memcpy(out[0..src_arr.len], src_arr); out[src_arr.len] = args[1]; return .{ .array_val = out }; } // slice(v, start, end): substring or sub-array, end exclusive. // Negative indices count from the end; clamped. if (std.mem.eql(u8, name, "slice")) { if (args.len != 3 or !(args[0] == .str_val or args[0] == .array_val) or !args[1].isNumber() or !args[2].isNumber()) { return self.fail(error.TypeMismatch, "slice expects (str|array, start: int, end: int)", .{}); } const start_i = args[1].int_val; const end_i = args[2].int_val; switch (args[0]) { .str_val => |sv| { const total: i64 = @intCast(sv.len); const a = std.math.clamp(if (start_i < 0) total + start_i else start_i, 0, total); var b = std.math.clamp(if (end_i < 0) total + end_i else end_i, 0, total); if (b < a) b = a; const s = sv[@intCast(a)..@intCast(b)]; return .{ .str_val = self.allocator.dupe(u8, s) catch return error.OutOfMemory }; }, .array_val => |av| { const total: i64 = @intCast(av.len); const a = std.math.clamp(if (start_i < 0) total + start_i else start_i, 0, total); var b = std.math.clamp(if (end_i < 0) total + end_i else end_i, 0, total); if (b < a) b = a; const n: usize = @intCast(b - a); const out = self.allocator.alloc(Value, n) catch return error.OutOfMemory; @memcpy(out, av[@intCast(a)..@intCast(b)]); return .{ .array_val = out }; }, else => return self.fail(error.TypeMismatch, "slice target must be str or array", .{}), } }
// ---- Control (utility) ---- // panic(msg): unconditionally raises a runtime error carrying msg. // The primary use is under try(): `const r = try(may_panic());` if (std.mem.eql(u8, name, "panic")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "panic expects one string", .{}); } return self.fail(error.TypeMismatch, "{s}", .{args[0].str_val}); }
// ---- CSV (utility) ---- if (std.mem.eql(u8, name, "csv_parse")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "csv_parse expects one string", .{}); } return self.parseCsv(args[0].str_val); } if (std.mem.eql(u8, name, "csv_format")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "csv_format expects an array of rows", .{}); } const A = self.allocator; var out: std.ArrayList(u8) = .empty; defer out.deinit(A); for (args[0].array_val, 0..) |row_v, ri| { if (row_v != .array_val) { return self.fail(error.TypeMismatch, "csv_format row {d} is not an array", .{ri}); } for (row_v.array_val, 0..) |cell, ci| { if (ci > 0) out.append(A, ',') catch return error.OutOfMemory; const piece = try self.csvCell(cell); out.appendSlice(A, piece) catch return error.OutOfMemory; A.free(piece); } if (ri + 1 < args[0].array_val.len) out.append(A, '\n') catch return error.OutOfMemory; } return .{ .str_val = out.toOwnedSlice(A) catch return error.OutOfMemory }; }
// ---- Logging (utility) ---- // log_write(level, msg): structured line on stderr, e.g. "[INFO] hi". // Level is an int (0=TRACE 1=DEBUG 2=INFO 3=WARN 4=ERROR) or a string. if (std.mem.eql(u8, name, "log_write")) { if (args.len != 2 or args[1] != .str_val) { return self.fail(error.TypeMismatch, "log_write expects (level, message: str)", .{}); } const lvl: []const u8 = switch (args[0]) { .int_val => |v| switch (v) { 0 => "TRACE", 1 => "DEBUG", 2 => "INFO", 3 => "WARN", 4 => "ERROR", else => return self.fail(error.TypeMismatch, "log_write int level must be 0..4", .{}), }, .str_val => |sv| sv, else => return self.fail(error.TypeMismatch, "log_write level must be an int or str", .{}), }; self.errw.print("[{s}] {s}\n", .{ lvl, args[1].str_val }) catch return error.WriteFailed; return .null_val; }
// ---- Crypto (utility) ---- // Hashing: sha256/sha1/md5/sha384/sha512(s) -> lowercase hex digest. if (std.mem.eql(u8, name, "sha256") or std.mem.eql(u8, name, "sha1") or std.mem.eql(u8, name, "md5") or std.mem.eql(u8, name, "sha384") or std.mem.eql(u8, name, "sha512")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "@{s} expects (data: str)", .{name}); } const data = args[0].str_val; var digest: [64]u8 = undefined; const digest_len: usize = if (std.mem.eql(u8, name, "sha1")) blk: { std.crypto.hash.Sha1.hash(data, digest[0..std.crypto.hash.Sha1.digest_length], .{}); break :blk std.crypto.hash.Sha1.digest_length; } else if (std.mem.eql(u8, name, "md5")) blk: { std.crypto.hash.Md5.hash(data, digest[0..std.crypto.hash.Md5.digest_length], .{}); break :blk std.crypto.hash.Md5.digest_length; } else if (std.mem.eql(u8, name, "sha384")) blk: { std.crypto.hash.sha2.Sha384.hash(data, digest[0..std.crypto.hash.sha2.Sha384.digest_length], .{}); break :blk std.crypto.hash.sha2.Sha384.digest_length; } else if (std.mem.eql(u8, name, "sha512")) blk: { std.crypto.hash.sha2.Sha512.hash(data, digest[0..std.crypto.hash.sha2.Sha512.digest_length], .{}); break :blk std.crypto.hash.sha2.Sha512.digest_length; } else blk: { std.crypto.hash.sha2.Sha256.hash(data, digest[0..std.crypto.hash.sha2.Sha256.digest_length], .{}); break :blk std.crypto.hash.sha2.Sha256.digest_length; }; const charset = std.fmt.hex_charset; const out = self.allocator.alloc(u8, digest_len * 2) catch return error.OutOfMemory; for (digest[0..digest_len], 0..) |b, i| { out[i * 2] = charset[b >> 4]; out[i * 2 + 1] = charset[b & 15]; } return .{ .str_val = out }; } // hmac_sha256(data, key) -> keyed SHA-256 MAC, hex string. if (std.mem.eql(u8, name, "hmac_sha256")) { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "hmac_sha256 expects (data: str, key: str)", .{}); } var mac: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; std.crypto.auth.hmac.sha2.HmacSha256.create(&mac, args[0].str_val, args[1].str_val); const charset = std.fmt.hex_charset; const hex = self.allocator.alloc(u8, 64) catch return error.OutOfMemory; for (mac, 0..) |b, i| { hex[i * 2] = charset[b >> 4]; hex[i * 2 + 1] = charset[b & 15]; } return .{ .str_val = hex }; } // hex_encode(s) -> ASCII hex; hex_decode(s) -> raw bytes. if (std.mem.eql(u8, name, "hex_encode")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "hex_encode expects (data: str)", .{}); } const data = args[0].str_val; const charset = std.fmt.hex_charset; const hex = self.allocator.alloc(u8, data.len * 2) catch return error.OutOfMemory; for (data, 0..) |b, i| { hex[i * 2] = charset[b >> 4]; hex[i * 2 + 1] = charset[b & 15]; } return .{ .str_val = hex }; } if (std.mem.eql(u8, name, "hex_decode")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "hex_decode expects (hex: str)", .{}); } const hex = args[0].str_val; if (hex.len % 2 != 0) { return self.fail(error.TypeMismatch, "hex_decode: odd-length hex string", .{}); } const out = self.allocator.alloc(u8, hex.len / 2) catch return error.OutOfMemory; for (out, 0..) |*b, i| { const hi = std.fmt.charToDigit(hex[i * 2], 16) catch { return self.fail(error.TypeMismatch, "hex_decode: invalid hex digit '{c}'", .{hex[i * 2]}); }; const lo = std.fmt.charToDigit(hex[i * 2 + 1], 16) catch { return self.fail(error.TypeMismatch, "hex_decode: invalid hex digit '{c}'", .{hex[i * 2 + 1]}); }; b.* = @intCast((hi << 4) | lo); } return .{ .str_val = out }; } // base64_encode(s) -> standard-alphabet base64. if (std.mem.eql(u8, name, "base64_encode")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "base64_encode expects (data: str)", .{}); } const enc = std.base64.standard.Encoder; const out = self.allocator.alloc(u8, enc.calcSize(args[0].str_val.len)) catch return error.OutOfMemory; _ = enc.encode(out, args[0].str_val); return .{ .str_val = out }; } // base64_decode(s) -> decoded bytes as a string. if (std.mem.eql(u8, name, "base64_decode")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "base64_decode expects (data: str)", .{}); } const dec = std.base64.standard.Decoder; const n = dec.calcSizeForSlice(args[0].str_val) catch return self.fail(error.TypeMismatch, "invalid base64 input", .{}); const out = self.allocator.alloc(u8, n) catch return error.OutOfMemory; dec.decode(out, args[0].str_val) catch return self.fail(error.TypeMismatch, "invalid base64 input", .{}); return .{ .str_val = out }; }
// ---- std.math (numeric helpers) ---- // abs preserves the input's type; min/max/clamp preserve the "winning" // operand; the transcendental helpers always return float. if (std.mem.eql(u8, name, "abs") or std.mem.eql(u8, name, "floor") or std.mem.eql(u8, name, "ceil") or std.mem.eql(u8, name, "round") or std.mem.eql(u8, name, "sqrt")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects one numeric argument", .{name}); } const v = args[0]; if (std.mem.eql(u8, name, "abs")) { return switch (v) { .int_val => |i| .{ .int_val = if (i < 0) -i else i }, .float_val => |f| .{ .float_val = @abs(f) }, else => .{ .int_val = 0 }, }; } const f = v.toF64(); const out: f64 = if (std.mem.eql(u8, name, "floor")) @floor(f) else if (std.mem.eql(u8, name, "ceil")) @ceil(f) else if (std.mem.eql(u8, name, "round")) @round(f) else @sqrt(f); return .{ .float_val = out }; } if (std.mem.eql(u8, name, "pow")) { if (args.len != 2 or !args[0].isNumber() or !args[1].isNumber()) { return self.fail(error.TypeMismatch, "@pow expects (base: num, exp: num)", .{}); } return .{ .float_val = std.math.pow(f64, args[0].toF64(), args[1].toF64()) }; } if (std.mem.eql(u8, name, "min") or std.mem.eql(u8, name, "max")) { if (args.len != 2 or !args[0].isNumber() or !args[1].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects two numeric arguments", .{name}); } const want_min = std.mem.eql(u8, name, "min"); const a = args[0]; const b = args[1]; return if (a == .int_val and b == .int_val) if (want_min) (if (a.int_val < b.int_val) a else b) else (if (a.int_val > b.int_val) a else b) else if (want_min) (if (a.toF64() < b.toF64()) a else b) else (if (a.toF64() > b.toF64()) a else b); } if (std.mem.eql(u8, name, "clamp")) { if (args.len != 3 or !args[0].isNumber() or !args[1].isNumber() or !args[2].isNumber()) { return self.fail(error.TypeMismatch, "@clamp expects (value, lo, hi)", .{}); } const v = args[0]; const lo = args[1]; const hi = args[2]; if (v == .int_val and lo == .int_val and hi == .int_val) { const c = std.math.clamp(v.int_val, lo.int_val, hi.int_val); return .{ .int_val = c }; } const f = std.math.clamp(v.toF64(), lo.toF64(), hi.toF64()); return .{ .float_val = f }; } // sign(v): -1 / 0 / 1 preserving the input's numeric type. if (std.mem.eql(u8, name, "sign")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "sign expects one numeric argument", .{}); } const v = args[0]; if (v == .int_val) return .{ .int_val = std.math.sign(v.int_val) }; return .{ .float_val = std.math.sign(v.toF64()) }; } // trunc/fract: split a float into its integer and fractional parts. if (std.mem.eql(u8, name, "trunc") or std.mem.eql(u8, name, "fract")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects one numeric argument", .{name}); } const f = args[0].toF64(); const t = @trunc(f); return if (std.mem.eql(u8, name, "trunc")) .{ .int_val = std.math.lossyCast(i64, t) } else .{ .float_val = f - t }; } // exp/ln/log2/log10: transcendental functions returning float. if (std.mem.eql(u8, name, "exp") or std.mem.eql(u8, name, "ln") or std.mem.eql(u8, name, "log2") or std.mem.eql(u8, name, "log10")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects one numeric argument", .{name}); } const x = args[0].toF64(); const out: f64 = if (std.mem.eql(u8, name, "exp")) std.math.exp(x) else if (std.mem.eql(u8, name, "ln")) std.math.log(f64, std.math.e, x) else if (std.mem.eql(u8, name, "log2")) @log2(x) else @log10(x); return .{ .float_val = out }; } // deg_to_rad/rad_to_deg: angle conversion. if (std.mem.eql(u8, name, "deg_to_rad") or std.mem.eql(u8, name, "rad_to_deg")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects one numeric argument", .{name}); } const deg = std.mem.eql(u8, name, "deg_to_rad"); const v = args[0].toF64(); return .{ .float_val = if (deg) v * (std.math.pi / 180.0) else v * (180.0 / std.math.pi) }; } // is_even/is_odd: integral parity checks (works on any number). if (std.mem.eql(u8, name, "is_even") or std.mem.eql(u8, name, "is_odd")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "@{s} expects one numeric argument", .{name}); } const n = @mod(args[0].toF64(), 2.0); const even = n == 0; return .{ .bool_val = if (std.mem.eql(u8, name, "is_even")) even else !even }; }
// ---- Tensors ---- // Constructed by the compiler lowering of `@tensor(...)`; // signature: tensor_new(dtype_str, shape_arr [, device_str] [, fill]). if (std.mem.eql(u8, name, "tensor_new")) { if (args.len < 2 or args.len > 4 or args[0] != .str_val or args[1] != .array_val) { return self.fail(error.TypeMismatch, "@tensor expects (dtype, shape, options?), e.g. @tensor(f32, [2, 3], .{{.device = .cpu}})", .{}); } const dt = parseDTypeStr(args[0].str_val) orelse return self.fail(error.TypeMismatch, "unknown tensor dtype '{s}' (known: f32, f64, i32, i64, u8, bool)", .{args[0].str_val});
var device: value_mod.TensorDevice = .cpu; if (args.len >= 3) { if (args[2] != .str_val) { return self.fail(error.TypeMismatch, "@tensor .device must be a string like \"cpu\"", .{}); } device = std.meta.stringToEnum(value_mod.TensorDevice, args[2].str_val) orelse return self.fail(error.TypeMismatch, "unknown tensor device '{s}' (known: cpu, cuda, rocm)", .{args[2].str_val}); if (device != .cpu) { return self.fail(error.TypeMismatch, "device '{s}' is not available in this build (v1 is CPU-only); re-run on .cpu", .{args[2].str_val}); } }
const shape_src = args[1].array_val; if (shape_src.len == 0 or shape_src.len > 8) { return self.fail(error.TypeMismatch, "@tensor shape must have 1..8 dimensions", .{}); } var total: u64 = 1; const shape = self.allocator.alloc(u64, shape_src.len) catch return error.OutOfMemory; for (shape_src, 0..) |sv, i| { if (sv != .int_val or sv.int_val <= 0) { return self.fail(error.TypeMismatch, "@tensor dimensions must be positive ints", .{}); } shape[i] = @intCast(sv.int_val); total = std.math.mul(u64, total, shape[i]) catch return self.fail(error.TypeMismatch, "@tensor element count overflows", .{}); } const byte_len = std.math.mul(u64, total, dt.size()) catch return self.fail(error.TypeMismatch, "@tensor byte size overflows", .{}); if (byte_len > 1 << 40) { return self.fail(error.TypeMismatch, "@tensor allocation exceeds the 1 TiB safety cap", .{}); }
const bytes = self.allocator.alloc(u8, @intCast(byte_len)) catch return error.OutOfMemory; @memset(bytes, 0); const t = self.allocator.create(value_mod.TensorObj) catch return error.OutOfMemory; t.* = .{ .shape = shape, .dtype = dt, .device = device, .storage = .{ .owned = bytes } }; if (args.len == 4) { const n: usize = @intCast(total); var i: usize = 0; while (i < n) : (i += 1) t.writeElem(i, args[3]) catch return self.fail(error.TypeMismatch, "internal error: @tensor fill rejected a fresh buffer", .{}); } return .{ .tensor_val = t }; }
// ---- std.os (host interaction) ---- if (std.mem.eql(u8, name, "sleep")) { if (args.len != 1 or args[0] != .int_val or args[0].int_val < 0) { return self.fail(error.TypeMismatch, "sleep expects (ms: int >= 0)", .{}); } const ms: i64 = args[0].int_val; std.Io.sleep(self.io.?, std.Io.Duration.fromMilliseconds(ms), .awake) catch |e| { return self.fail(error.IoFailed, "@sleep interrupted: {s}", .{@errorName(e)}); }; return .null_val; } if (std.mem.eql(u8, name, "wall_time_ms")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "wall_time_ms takes no arguments", .{}); } const ts = std.Io.Timestamp.now(self.io.?, .real); return .{ .int_val = ts.toMilliseconds() }; } if (std.mem.eql(u8, name, "current_dir")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "current_dir takes no arguments", .{}); } const cwd = std.process.currentPathAlloc(self.io.?, self.allocator) catch |e| { return self.fail(error.IoFailed, "current_dir failed: {s}", .{@errorName(e)}); }; const dup = self.allocator.dupe(u8, cwd) catch return error.OutOfMemory; return .{ .str_val = dup }; } if (std.mem.eql(u8, name, "home_dir")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "home_dir takes no arguments", .{}); } if (self.environ) |map| { if (map.get("HOME")) |h| { return .{ .str_val = h }; } } return .{ .str_val = "" }; } if (std.mem.eql(u8, name, "cpu_count")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "cpu_count takes no arguments", .{}); } const n = std.Thread.getCpuCount() catch 1; return .{ .int_val = @intCast(n) }; }
// ---- std.random (CSPRNG-backed) ---- if (std.mem.eql(u8, name, "random_int")) { if (args.len != 2 or args[0] != .int_val or args[1] != .int_val) { return self.fail(error.TypeMismatch, "random_int expects (lo: int, hi: int)", .{}); } const lo = args[0].int_val; const hi = args[1].int_val; if (hi < lo) { return self.fail(error.TypeMismatch, "random_int requires hi >= lo", .{}); } const rng = try self.ensureRng(); const v = rng.random().intRangeAtMost(i64, lo, hi); return .{ .int_val = v }; } if (std.mem.eql(u8, name, "random_float")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "random_float takes no arguments", .{}); } const rng = try self.ensureRng(); return .{ .float_val = rng.random().float(f64) }; } if (std.mem.eql(u8, name, "random_chance")) { if (args.len != 1 or !args[0].isNumber()) { return self.fail(error.TypeMismatch, "random_chance expects (p: float)", .{}); } const p = args[0].toF64(); if (p < 0 or p > 1) { return self.fail(error.TypeMismatch, "random_chance requires 0.0 <= p <= 1.0", .{}); } const rng = try self.ensureRng(); return .{ .bool_val = rng.random().float(f64) < p }; } if (std.mem.eql(u8, name, "random_pick")) { if (args.len != 1 or args[0] != .array_val or args[0].array_val.len == 0) { return self.fail(error.TypeMismatch, "random_pick expects a non-empty array", .{}); } const arr = args[0].array_val; const rng = try self.ensureRng(); const i: usize = @intCast(rng.random().uintAtMost(u64, arr.len - 1)); return arr[i]; } if (std.mem.eql(u8, name, "shuffle")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "shuffle expects one array", .{}); } const out = self.allocator.dupe(Value, args[0].array_val) catch return error.OutOfMemory; const rng = try self.ensureRng(); std.Random.shuffleWithIndex(rng.random(), Value, out, usize); return .{ .array_val = out }; }
return self.fail(error.UnknownBuiltin, "unknown builtin '{s}'", .{name}); }
/// Implements load_safetensors(path)/load_gguf(path): memory-map the file, /// parse its header (no tensor data is read), and hand back one read-only /// TensorObj view per tensor, each pointing into the shared mapping. fn callAiLoader(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "{s} expects (path: str)", .{name}); } const path = args[0].str_val; var mf = mmap_mod.mapFile(self.io.?, path) catch |e| switch (e) { error.FileOpenFailed => return self.fail(error.IoFailed, "{s}: cannot open '{s}'", .{ name, path }), error.EmptyFile => return self.fail(error.IoFailed, "{s}: '{s}' is empty", .{ name, path }), error.MmapFailed => return self.fail(error.IoFailed, "{s}: mmap of '{s}' failed", .{ name, path }), }; errdefer mf.deinit();
const views = if (std.mem.eql(u8, name, "load_safetensors")) st_mod.parse(self.allocator, mf.inner.memory) catch |e| return self.aiParseFail(name, path, e) else gguf_mod.parse(self.allocator, mf.inner.memory) catch |e| return self.aiParseFail(name, path, e); // Views' names/shapes move into the TensorObjs below; the slice itself // is arena-owned here, so nothing to free on success. if (views.len == 0) { return self.fail(error.IoFailed, "{s}: no tensors found in '{s}'", .{ name, path }); }
const owner = self.allocator.create(value_mod.MapOwner) catch return error.OutOfMemory; // Ownership of the mapping moves to MapOwner; `release` also makes any // later errdefer deinit a no-op, so live tensors can never be unmapped. const map_bytes = mf.release(); owner.* = .{ .map = map_bytes, .refs = views.len };
const out = self.allocator.alloc(Value, views.len) catch return error.OutOfMemory; for (views, 0..) |v, i| { const obj = self.allocator.create(value_mod.TensorObj) catch return error.OutOfMemory; const shape_copy = self.allocator.dupe(u64, v.shape) catch return error.OutOfMemory; const end: usize = @intCast(v.off + v.len); obj.* = .{ .name = v.name, .shape = shape_copy, .dtype = v.dtype, .device = .cpu, .storage = .{ .mapped = .{ .owner = owner, .bytes = map_bytes[@intCast(v.off)..end] } }, }; out[i] = .{ .tensor_val = obj }; } return .{ .array_val = out }; }
/// Implements load_lib(path): opens a shared library via dlopen. fn callLibLoader(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "{s} expects (path: str)", .{name}); } const path = args[0].str_val; const path_copy = self.allocator.dupe(u8, path) catch return error.OutOfMemory; errdefer self.allocator.free(path_copy); // Sonames ("libc.so.6") resolve through the system loader search path. const path_z = self.allocator.dupeZ(u8, path) catch return error.OutOfMemory; defer self.allocator.free(path_z); const handle = dlopen(path_z.ptr, RTLD_NOW) orelse { self.allocator.free(path_copy); return self.fail(error.IoFailed, "{s}: cannot load shared library '{s}'", .{ name, path }); }; const obj = self.allocator.create(value_mod.LibObj) catch return error.OutOfMemory; obj.* = .{ .path = path_copy, .handle = handle }; return .{ .lib_val = obj }; }
fn missingSymbol(self: *Vm, l: *value_mod.LibObj, sym: []const u8) RuntimeError { return self.fail(error.IoFailed, "library '{s}' has no symbol '{s}' with the expected C signature", .{ l.path, sym }); }
/// Methods on load_lib'd libraries: call_int / call_float / deinit. fn libMethod(self: *Vm, method: []const u8, l: *value_mod.LibObj, args: []Value) RuntimeError!Value { if (l.dead and !std.mem.eql(u8, method, "deinit")) { return self.fail(error.TypeMismatch, "use of deinitialized library '{s}'", .{l.path}); } if (std.mem.eql(u8, method, "deinit")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "lib.deinit takes no arguments", .{}); } if (l.handle) |h| _ = dlclose(h); l.handle = null; l.dead = true; self.allocator.free(l.path); return .null_val; } if (std.mem.eql(u8, method, "call_int") or std.mem.eql(u8, method, "call_float")) { const want_float = std.mem.eql(u8, method, "call_float"); if (args.len != 2 or args[0] != .str_val or args[1] != .array_val) { return self.fail(error.TypeMismatch, "{s} expects (symbol: str, args: [num]), e.g. lib.{s}(\"abs\", .{{x}})", .{ method, method }); } const sym_raw = args[0].str_val; // DynLib.lookup requires a NUL-terminated symbol name. const sym = self.allocator.dupeZ(u8, sym_raw) catch return error.OutOfMemory; defer self.allocator.free(sym); const cargs = args[1].array_val; if (cargs.len > 4) { return self.fail(error.TypeMismatch, "{s} supports at most 4 numeric arguments", .{method}); } // Coerce script numbers into C ints/floats. var iv: [4]i64 = .{ 0, 0, 0, 0 }; var fv: [4]f64 = .{ 0, 0, 0, 0 }; for (cargs, 0..) |cv, i| { switch (cv) { .int_val => |n| { iv[i] = n; fv[i] = @floatFromInt(n); }, .float_val => |f| { iv[i] = std.math.lossyCast(i64, @trunc(f)); fv[i] = f; }, else => return self.fail(error.TypeMismatch, "{s} arguments must be numbers", .{method}), } }
const arity = cargs.len; if (!want_float) { const Ret = i64; const result: Ret = switch (arity) { 0 => blk: { const fp = dlsymFn(*const fn () callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(); }, 1 => blk: { const fp = dlsymFn(*const fn (i64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(iv[0]); }, 2 => blk: { const fp = dlsymFn(*const fn (i64, i64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(iv[0], iv[1]); }, 3 => blk: { const fp = dlsymFn(*const fn (i64, i64, i64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(iv[0], iv[1], iv[2]); }, else => blk: { const fp = dlsymFn(*const fn (i64, i64, i64, i64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(iv[0], iv[1], iv[2], iv[3]); }, }; return .{ .int_val = result }; } else { const Ret = f64; const result: Ret = switch (arity) { 0 => blk: { const fp = dlsymFn(*const fn () callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(); }, 1 => blk: { const fp = dlsymFn(*const fn (f64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(fv[0]); }, 2 => blk: { const fp = dlsymFn(*const fn (f64, f64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(fv[0], fv[1]); }, 3 => blk: { const fp = dlsymFn(*const fn (f64, f64, f64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(fv[0], fv[1], fv[2]); }, else => blk: { const fp = dlsymFn(*const fn (f64, f64, f64, f64) callconv(.c) Ret, l.handle.?, sym) orelse return self.missingSymbol(l, sym); break :blk fp(fv[0], fv[1], fv[2], fv[3]); }, }; return .{ .float_val = result }; } } return self.fail(error.TypeMismatch, "unknown lib method '{s}' (known: call_int, call_float, deinit)", .{method}); }
/// Implements llm_load(path): mmap a GGUF model using the rinne.llm.* /// naming convention, extract the integer config kvs, and build a /// zero-copy LlmModel over the mapping. fn callLlmLoader(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "{s} expects (path: str)", .{name}); } const path = args[0].str_val; var mf = mmap_mod.mapFile(self.io.?, path) catch |e| switch (e) { error.FileOpenFailed => return self.fail(error.IoFailed, "{s}: cannot open '{s}'", .{ name, path }), error.EmptyFile => return self.fail(error.IoFailed, "{s}: '{s}' is empty", .{ name, path }), error.MmapFailed => return self.fail(error.IoFailed, "{s}: mmap of '{s}' failed", .{ name, path }), }; errdefer mf.deinit();
const r = gguf_mod.parseWithKv(self.allocator, mf.inner.memory) catch |e| { if (e == error.OutOfMemory) return error.OutOfMemory; return self.aiParseFail(name, path, e); };
// Pull the rinne.llm.* config out of the captured numeric kvs. var cfg: ?value_mod.LlmConfig = null; var got: [7]?u64 = .{ null, null, null, null, null, null, null }; for (r.nums) |n| { const key = n.name; const target: ?usize = if (std.mem.eql(u8, key, "rinne.llm.dim")) 0 else if (std.mem.eql(u8, key, "rinne.llm.hidden_dim")) 1 else if (std.mem.eql(u8, key, "rinne.llm.n_layers")) 2 else if (std.mem.eql(u8, key, "rinne.llm.n_heads")) 3 else if (std.mem.eql(u8, key, "rinne.llm.n_kv_heads")) 4 else if (std.mem.eql(u8, key, "rinne.llm.vocab_size")) 5 else if (std.mem.eql(u8, key, "rinne.llm.seq_len")) 6 else null; if (target) |ti| got[ti] = n.val; } if (got[0] != null and got[1] != null and got[2] != null and got[3] != null and got[4] != null and got[5] != null and got[6] != null) { cfg = .{ .dim = got[0].?, .hidden_dim = got[1].?, .n_layers = got[2].?, .n_heads = got[3].?, .n_kv_heads = got[4].?, .vocab_size = got[5].?, .seq_len = got[6].?, }; } if (cfg == null) { return self.fail(error.IoFailed, "{s}: '{s}' is missing rinne.llm.* metadata (need dim, hidden_dim, n_layers, n_heads, n_kv_heads, vocab_size, seq_len); see docs/libs/llm.md", .{ name, path }); }
const owner = self.allocator.create(value_mod.MapOwner) catch return error.OutOfMemory; // The model borrows the mapping; refs=1 because `load` returns one // object that releases it on deinit. const map_bytes = mf.release(); owner.* = .{ .map = map_bytes, .refs = 1 }; errdefer owner.release();
const m = llm_mod.load(self.allocator, cfg.?, r.nums, r.views, map_bytes, owner) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.InvalidModel => return self.fail(error.IoFailed, "{s}: '{s}' tensors do not match the rinne.llm.* config (all weights must be F32 with the documented shapes); see docs/libs/llm.md", .{ name, path }), }; return .{ .llm_val = m }; }
fn aiParseFail(self: *Vm, name: []const u8, path: []const u8, e: anytype) RuntimeError { const suffix = " (check the file is a complete, valid model file)"; return switch (e) { error.OutOfMemory => error.OutOfMemory, error.BadMagic => self.fail(error.IoFailed, "{s}: '{s}' is not a GGUF file (bad magic)", .{ name, path }), error.UnsupportedVersion => self.fail(error.IoFailed, "{s}: '{s}' uses an unsupported GGUF version (need v2 or v3)", .{ name, path }), error.UnsupportedDtype => self.fail(error.IoFailed, "{s}: '{s}' contains a tensor with an unsupported dtype (supported: F32/F64/I32/I64){s}", .{ name, path, suffix }), else => self.fail(error.IoFailed, "{s}: failed to parse '{s}': {s}{s}", .{ name, path, @errorName(e), suffix }), }; }
/// Runtime dispatch for `obj.method(args)` opcodes. v1 supports tensors. /// Executes a compiled apply_simd kernel over every element of the /// receiver tensor, in place. The kernel chunk runs once per element /// (interpreted scalar fallback — `vector_width` is a hint for the /// native backend, exactly like @vector/@splat/@reduce today); its /// return value is written back to the same slot. Kernels run with a /// frame base above every caller defer, so their own defers retire /// LIFO within each element invocation and never leak outward. /// Runs a hidden `<try.N>` chunk under error recovery. Returns /// [true, result] on success or [false, message] when a runtime error /// occurs inside; the VM state is rolled back to the try boundary. /// OutOfMemory is not recoverable. fn runTry(self: *Vm, kernel_idx: u32) RuntimeError!Value { const saved_stack = self.stack.items.len; const saved_frames = self.frames.items.len; const saved_defers = self.defers.items.len; try self.callFrame(kernel_idx, 0); while (self.frames.items.len > saved_frames) { self.step() catch |e| { if (e == error.OutOfMemory) return e; // Unwind everything the guarded region touched. self.frames.shrinkRetainingCapacity(saved_frames); self.stack.shrinkRetainingCapacity(saved_stack); self.defers.shrinkRetainingCapacity(saved_defers); const msg = self.allocator.dupe(u8, self.err_msg orelse @errorName(e)) catch return error.OutOfMemory; if (self.err_msg) |m| self.allocator.free(m); // Clear so later failures can report fresh messages // (err_msg is first-failure-wins otherwise). self.err_msg = null; const pair = self.allocator.alloc(Value, 2) catch return error.OutOfMemory; pair[0] = .{ .bool_val = false }; pair[1] = .{ .str_val = msg }; return .{ .array_val = pair }; }; } const result = self.stack.pop() orelse .null_val; const pair = self.allocator.alloc(Value, 2) catch return error.OutOfMemory; pair[0] = .{ .bool_val = true }; pair[1] = result; return .{ .array_val = pair }; }
fn runTensorSimd(self: *Vm, kernel_idx: u32, recv: Value) RuntimeError!void { if (recv != .tensor_val) { return self.fail(error.TypeMismatch, "apply_simd can only be called on a tensor", .{}); } const t = recv.tensor_val; if (t.dead) { return self.fail(error.TypeMismatch, "use of deinitialized tensor", .{}); } const total: u64 = t.numel() catch 0; var i: u64 = 0; while (i < total) : (i += 1) { try self.stack.append(self.allocator, t.readElem(@intCast(i))); const saved_depth = self.frames.items.len; try self.callFrame(kernel_idx, 1); while (self.frames.items.len > saved_depth) { try self.step(); } const out = self.stack.pop() orelse .null_val; t.writeElem(@intCast(i), out) catch { return self.fail(error.TypeMismatch, "apply_simd cannot write into memory-mapped tensor '{s}' (loaded from file, read-only)", .{t.name}); }; } }
fn callMethod(self: *Vm, name: []const u8, recv: Value, args: []Value) RuntimeError!Value { switch (recv) { .tensor_val => |t| return self.tensorMethod(name, t, args), .llm_val => |m| return self.llmMethod(name, m, args), .map_val => |mp| return self.mapMethod(name, mp, args), .lib_val => |l| return self.libMethod(name, l, args), .net_val => |n| return self.netMethod(name, n, args), else => return self.fail(error.TypeMismatch, "value has no method '{s}' (methods are supported on tensors and models)", .{name}), } }
/// Methods on llm_load'd models: forward(ids) -> last-token logits, /// deinit() (idempotent). fn llmMethod(self: *Vm, method: []const u8, m: *value_mod.LlmModel, args: []Value) RuntimeError!Value { if (m.dead and !std.mem.eql(u8, method, "deinit")) { return self.fail(error.TypeMismatch, "use of deinitialized model", .{}); } if (std.mem.eql(u8, method, "deinit")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "model.deinit takes no arguments", .{}); } llm_mod.deinit(self.allocator, m); return .null_val; } if (std.mem.eql(u8, method, "forward")) { if (args.len != 1 or args[0] != .array_val) { return self.fail(error.TypeMismatch, "model.forward expects (ids: [int]), e.g. m.forward(.{{0, 1}})", .{}); } const ids_src = args[0].array_val; const ids = self.allocator.alloc(i64, ids_src.len) catch return error.OutOfMemory; defer self.allocator.free(ids); for (ids_src, 0..) |iv, i| { if (iv != .int_val) { return self.fail(error.TypeMismatch, "model.forward ids must be ints", .{}); } ids[i] = iv.int_val; } const logits = llm_mod.forward(self.allocator, m, ids) catch |e| switch (e) { error.OutOfMemory => return error.OutOfMemory, error.InvalidModel => return self.fail(error.TypeMismatch, "model.forward got an empty sequence, a token id out of range, or a sequence longer than seq_len ({d})", .{m.cfg.seq_len}), }; const out = self.allocator.alloc(Value, logits.len) catch return error.OutOfMemory; for (logits, 0..) |lv, i| out[i] = .{ .float_val = lv }; self.allocator.free(logits); return .{ .array_val = out }; } return self.fail(error.TypeMismatch, "unknown model method '{s}' (known: forward, deinit)", .{method}); }
/// Methods on TCP objects. Listeners: accept() -> stream, port() -> int, /// close(). Streams: read(max) -> str, write(str) -> int, close(). fn netMethod(self: *Vm, method: []const u8, n: *value_mod.NetObj, args: []Value) RuntimeError!Value { if (n.dead) { return self.fail(error.TypeMismatch, "use of closed socket", .{}); } if (n.kind == .listener) { if (std.mem.eql(u8, method, "accept")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "listener.accept takes no arguments", .{}); } var srv = n.server.?; const st = srv.accept(self.io.?) catch |e| { return self.fail(error.IoFailed, "listener.accept failed ({s})", .{@errorName(e)}); }; n.server = srv; // accept may mutate server state const obj = self.allocator.create(value_mod.NetObj) catch return error.OutOfMemory; obj.* = .{ .kind = .stream, .stream = st }; return .{ .net_val = obj }; } if (std.mem.eql(u8, method, "port")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "listener.port takes no arguments", .{}); } var srv = n.server.?; return .{ .int_val = @intCast(srv.socket.address.getPort()) }; } if (std.mem.eql(u8, method, "close")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "listener.close takes no arguments", .{}); } var srv = n.server.?; srv.socket.close(self.io.?); n.dead = true; return .null_val; } return self.fail(error.TypeMismatch, "unknown listener method '{s}' (known: accept, port, close)", .{method}); }
// Stream methods. if (std.mem.eql(u8, method, "write")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "stream.write expects (data: str)", .{}); } var st = n.stream.?; var wbuf: [4096]u8 = undefined; var w = st.writer(self.io.?, &wbuf); w.interface.writeAll(args[0].str_val) catch |e| { return self.fail(error.IoFailed, "stream.write failed ({s})", .{@errorName(e)}); }; // Buffered interface: push bytes to the wire before returning, // otherwise peers block on read forever. w.interface.flush() catch |e| { return self.fail(error.IoFailed, "stream.write flush failed ({s})", .{@errorName(e)}); }; return .{ .int_val = @intCast(args[0].str_val.len) }; } if (std.mem.eql(u8, method, "read")) { if (args.len != 1 or args[0] != .int_val or args[0].int_val <= 0 or args[0].int_val > 1024 * 1024) { return self.fail(error.TypeMismatch, "stream.read expects (max_bytes: int 1..1MiB)", .{}); } const max: usize = @intCast(args[0].int_val); var st = n.stream.?; var rbuf: [4096]u8 = undefined; var r = st.reader(self.io.?, &rbuf); const buf = self.allocator.alloc(u8, max) catch return error.OutOfMemory; defer self.allocator.free(buf); const got = r.interface.readSliceShort(buf) catch |e| { return self.fail(error.IoFailed, "stream.read failed ({s})", .{@errorName(e)}); }; const s_copy = self.allocator.dupe(u8, buf[0..got]) catch return error.OutOfMemory; return .{ .str_val = s_copy }; } if (std.mem.eql(u8, method, "close")) { if (args.len != 0) { return self.fail(error.TypeMismatch, "stream.close takes no arguments", .{}); } var st = n.stream.?; st.close(self.io.?); n.dead = true; return .null_val; } if (std.mem.eql(u8, method, "shutdown")) { // Half-close: shutdown() (default .send) signals EOF to the peer // without tearing down our receive side. var how: std.Io.net.ShutdownHow = .send; if (args.len == 1) { if (args[0] != .str_val) { return self.fail(error.TypeMismatch, "stream.shutdown takes no arguments or a string: send, recv, both", .{}); } how = std.meta.stringToEnum(std.Io.net.ShutdownHow, args[0].str_val) orelse return self.fail(error.TypeMismatch, "unknown shutdown mode '{s}' (known: send, recv, both)", .{args[0].str_val}); } else if (args.len > 1) { return self.fail(error.TypeMismatch, "stream.shutdown takes no arguments or a string: send, recv, both", .{}); } var st = n.stream.?; st.shutdown(self.io.?, how) catch |e| { return self.fail(error.IoFailed, "stream.shutdown failed ({s})", .{@errorName(e)}); }; return .null_val; } return self.fail(error.TypeMismatch, "unknown stream method '{s}' (known: read, write, shutdown, close)", .{method}); }
/// Methods on maps: keys(), values(), remove(k), has(k). fn mapMethod(self: *Vm, method: []const u8, mp: *value_mod.MapObj, args: []Value) RuntimeError!Value { if (mp.dead and !std.mem.eql(u8, method, "deinit")) { return self.fail(error.TypeMismatch, "use of dead map", .{}); } const A = self.allocator; if (std.mem.eql(u8, method, "keys")) { if (args.len != 0) return self.fail(error.TypeMismatch, "keys takes no arguments", .{}); const out = A.alloc(Value, mp.map.count()) catch return error.OutOfMemory; var it = mp.map.keyIterator(); var n: usize = 0; while (it.next()) |k| { out[n] = .{ .str_val = k.* }; n += 1; } return .{ .array_val = out }; } if (std.mem.eql(u8, method, "values")) { if (args.len != 0) return self.fail(error.TypeMismatch, "values takes no arguments", .{}); const out = A.alloc(Value, mp.map.count()) catch return error.OutOfMemory; var it = mp.map.valueIterator(); var n: usize = 0; while (it.next()) |v| { out[n] = v.*; n += 1; } return .{ .array_val = out }; } if (std.mem.eql(u8, method, "has")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "has expects a string key", .{}); } return .{ .bool_val = mp.map.contains(args[0].str_val) }; } if (std.mem.eql(u8, method, "remove")) { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "remove expects a string key", .{}); } return .{ .bool_val = mp.map.remove(args[0].str_val) }; } return self.fail(error.TypeMismatch, "unknown map method '{s}' (known: keys, values, has, remove)", .{method}); }
fn tensorMethod(self: *Vm, method: []const u8, t: *value_mod.TensorObj, args: []Value) RuntimeError!Value { if (t.dead and !std.mem.eql(u8, method, "deinit")) { return self.fail(error.TypeMismatch, "use of deinitialized tensor", .{}); } if (std.mem.eql(u8, method, "deinit")) { // Idempotent: double-deinit is a no-op. if (!t.dead) { self.allocator.free(t.shape); switch (t.storage) { .owned => |b| self.allocator.free(b), .mapped => |mv| mv.owner.release(), } t.dead = true; } return .null_val; } if (std.mem.eql(u8, method, "shape")) { if (args.len != 0) return self.fail(error.TypeMismatch, "shape() takes no arguments", .{}); const out = self.allocator.alloc(Value, t.shape.len) catch return error.OutOfMemory; for (t.shape, 0..) |dim, i| out[i] = .{ .int_val = @intCast(dim) }; return .{ .array_val = out }; } if (std.mem.eql(u8, method, "numel")) { if (args.len != 0) return self.fail(error.TypeMismatch, "numel() takes no arguments", .{}); return .{ .int_val = @intCast(t.numel() catch 0) }; } if (std.mem.eql(u8, method, "dtype")) { if (args.len != 0) return self.fail(error.TypeMismatch, "dtype() takes no arguments", .{}); return .{ .str_val = t.dtype.name() }; } if (std.mem.eql(u8, method, "fill")) { if (args.len != 1) return self.fail(error.TypeMismatch, "fill expects exactly one argument", .{}); const n: usize = @intCast(t.numel() catch 0); var i: usize = 0; while (i < n) : (i += 1) t.writeElem(i, args[0]) catch return self.fail(error.TypeMismatch, "cannot fill memory-mapped tensor '{s}' (loaded from file, read-only)", .{t.name}); return .null_val; } if (std.mem.eql(u8, method, "data")) { if (args.len != 0) return self.fail(error.TypeMismatch, "data() takes no arguments", .{}); const n: usize = @intCast(t.numel() catch 0); const out = self.allocator.alloc(Value, n) catch return error.OutOfMemory; var i: usize = 0; while (i < n) : (i += 1) out[i] = t.readElem(i); return .{ .array_val = out }; } return self.fail(error.TypeMismatch, "unknown tensor method '{s}' (known: deinit, shape, numel, dtype, fill, data)", .{method}); } /// RFC4180-ish CSV parser: quoted fields with "" escapes, embedded /// commas/newlines inside quotes, \n / \r\n / \r row endings. fn parseCsv(self: *Vm, text: []const u8) RuntimeError!Value { const A = self.allocator; var rows: std.ArrayList(Value) = .empty; var row: std.ArrayList(Value) = .empty; var field: std.ArrayList(u8) = .empty; defer rows.deinit(A); defer row.deinit(A); defer field.deinit(A);
var err_row: usize = 0; var i: usize = 0; while (i < text.len) { const c = text[i]; if (c == '"') { if (field.items.len != 0) { return self.fail(error.TypeMismatch, "csv_parse: unexpected quote inside unquoted field (line {d})", .{err_row + 1}); } i += 1; // enter quoted field while (true) { if (i >= text.len) { return self.fail(error.TypeMismatch, "csv_parse: unterminated quoted field (line {d})", .{err_row + 1}); } if (text[i] == '"') { if (i + 1 < text.len and text[i + 1] == '"') { field.append(A, '"') catch return error.OutOfMemory; i += 2; continue; } i += 1; break; } field.append(A, text[i]) catch return error.OutOfMemory; i += 1; } continue; } if (c == ',') { row.append(A, .{ .str_val = field.toOwnedSlice(A) catch return error.OutOfMemory }) catch return error.OutOfMemory; i += 1; continue; } if (c == '\n' or c == '\r') { // Row terminator (\r\n counts once). if (c == '\r' and i + 1 < text.len and text[i + 1] == '\n') i += 1; i += 1; row.append(A, .{ .str_val = field.toOwnedSlice(A) catch return error.OutOfMemory }) catch return error.OutOfMemory; rows.append(A, .{ .array_val = row.toOwnedSlice(A) catch return error.OutOfMemory }) catch return error.OutOfMemory; err_row += 1; continue; } field.append(A, c) catch return error.OutOfMemory; i += 1; } // Trailing field/row without newline. if (field.items.len != 0 or row.items.len != 0) { row.append(A, .{ .str_val = field.toOwnedSlice(A) catch return error.OutOfMemory }) catch return error.OutOfMemory; rows.append(A, .{ .array_val = row.toOwnedSlice(A) catch return error.OutOfMemory }) catch return error.OutOfMemory; } const owned = rows.toOwnedSlice(A) catch return error.OutOfMemory; return .{ .array_val = owned }; }
/// Renders one cell: strings as-is, other scalars via display(); values /// containing , " \n or \r are quoted with doubled inner quotes. fn csvCell(self: *Vm, v: Value) RuntimeError![]u8 { const A = self.allocator; const raw: []const u8 = switch (v) { .str_val => |sv| sv, else => v.display(A) catch return error.OutOfMemory, }; const needs_quotes = std.mem.indexOfAny(u8, raw, ",\"\n\r") != null; if (!needs_quotes) return A.dupe(u8, raw) catch return error.OutOfMemory; var out: std.ArrayList(u8) = .empty; out.append(A, '"') catch return error.OutOfMemory; for (raw) |ch| { if (ch == '"') out.append(A, '"') catch return error.OutOfMemory; out.append(A, ch) catch return error.OutOfMemory; } out.append(A, '"') catch return error.OutOfMemory; return out.toOwnedSlice(A) catch return error.OutOfMemory; }
test "csv parse and format roundtrip" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\const rows = std.json.csv_parse("a,b,c\n\"x,1\",\"he said \"\"hi\"\"\"\r\nz"); \\@stdout("{}\n", .{std.strings.len(rows)}); \\@stdout("{}|{}\n", .{rows[1][0], rows[1][1]}); \\@stdout("{}\n", .{rows[2][0]}); \\@stdout("{}\n", .{std.json.csv_format(rows)}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "x,1|he said \"hi\"") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\"x,1\",\"he said \"\"hi\"\"\"") != null);}
/// Converts a std.json.Value tree into script Values (objects become maps). fn jsonToValue(self: *Vm, jv: std.json.Value) RuntimeError!Value { const A = self.allocator; return switch (jv) { .null => .null_val, .bool => |b| .{ .bool_val = b }, .integer => |n| .{ .int_val = n }, .float => |f| .{ .float_val = f }, .number_string => |sv| .{ .str_val = A.dupe(u8, sv) catch return error.OutOfMemory }, .string => |sv| .{ .str_val = A.dupe(u8, sv) catch return error.OutOfMemory }, .array => |arr| blk: { const out = A.alloc(Value, arr.items.len) catch return error.OutOfMemory; for (arr.items, 0..) |item, i| out[i] = try self.jsonToValue(item); break :blk .{ .array_val = out }; }, .object => |obj| blk: { const m = value_mod.MapObj.create(A) catch return error.OutOfMemory; var it = obj.iterator(); while (it.next()) |e| { const key = A.dupe(u8, e.key_ptr.*) catch return error.OutOfMemory; m.map.put(key, try self.jsonToValue(e.value_ptr.*)) catch return error.OutOfMemory; } break :blk .{ .map_val = m }; }, }; }
fn jsonStringify(self: *Vm, v: Value, w: *std.Io.Writer) RuntimeError!void { const A = self.allocator; switch (v) { .null_val => w.writeAll("null") catch return error.WriteFailed, .bool_val => |b| w.writeAll(if (b) "true" else "false") catch return error.WriteFailed, .int_val => |n| w.print("{d}", .{n}) catch return error.WriteFailed, .float_val => |f| w.print("{d}", .{f}) catch return error.WriteFailed, .str_val => |sv| self.jsonWriteString(sv, w), .array_val => |arr| { w.writeByte('[') catch return error.WriteFailed; for (arr, 0..) |item, i| { if (i > 0) w.writeByte(',') catch return error.WriteFailed; try self.jsonStringify(item, w); } w.writeByte(']') catch return error.WriteFailed; }, .map_val => |m| { w.writeByte('{') catch return error.WriteFailed; var it = m.map.iterator(); var first = true; while (it.next()) |e| { if (!first) w.writeByte(',') catch return error.WriteFailed; first = false; self.jsonWriteString(e.key_ptr.*, w); w.writeByte(':') catch return error.WriteFailed; try self.jsonStringify(e.value_ptr.*, w); } w.writeByte('}') catch return error.WriteFailed; }, else => return self.fail(error.TypeMismatch, "json_stringify cannot serialize this value", .{}), } _ = A; }
fn jsonIndent(w: *std.Io.Writer, depth: usize) void { for (0..depth) |_| w.writeAll(" ") catch return; }
fn jsonStringifyPretty(self: *Vm, v: Value, depth: usize, w: *std.Io.Writer) RuntimeError!void { switch (v) { .null_val => w.writeAll("null") catch return error.WriteFailed, .bool_val => |b| w.writeAll(if (b) "true" else "false") catch return error.WriteFailed, .int_val => |n| w.print("{d}", .{n}) catch return error.WriteFailed, .float_val => |f| w.print("{d}", .{f}) catch return error.WriteFailed, .str_val => |sv| self.jsonWriteString(sv, w), .array_val => |arr| { if (arr.len == 0) { w.writeAll("[]") catch return error.WriteFailed; } else { w.writeAll("[\n") catch return error.WriteFailed; for (arr, 0..) |item, i| { jsonIndent(w, depth + 1); try self.jsonStringifyPretty(item, depth + 1, w); if (i + 1 < arr.len) w.writeByte(',') catch return error.WriteFailed; w.writeByte('\n') catch return error.WriteFailed; } jsonIndent(w, depth); w.writeByte(']') catch return error.WriteFailed; } }, .map_val => |m| { if (m.map.count() == 0) { w.writeAll("{}") catch return error.WriteFailed; } else { w.writeByte('{') catch return error.WriteFailed; var it = m.map.iterator(); var first = true; while (it.next()) |e| { if (!first) w.writeByte(',') catch return error.WriteFailed; first = false; w.writeByte('\n') catch return error.WriteFailed; jsonIndent(w, depth + 1); self.jsonWriteString(e.key_ptr.*, w); w.writeAll(": ") catch return error.WriteFailed; try self.jsonStringifyPretty(e.value_ptr.*, depth + 1, w); } w.writeByte('\n') catch return error.WriteFailed; jsonIndent(w, depth); w.writeByte('}') catch return error.WriteFailed; } }, else => return self.fail(error.TypeMismatch, "json_pretty cannot serialize this value", .{}), } }
fn jsonWriteString(self: *Vm, sv: []const u8, w: *std.Io.Writer) void { _ = self; w.writeByte('"') catch return; for (sv) |c| { switch (c) { '"' => w.writeAll("\\\"") catch return, '\\' => w.writeAll("\\\\") catch return, '\n' => w.writeAll("\\n") catch return, '\r' => w.writeAll("\\r") catch return, '\t' => w.writeAll("\\t") catch return, else => { if (c < 0x20) { w.print("\\u{x:0>4}", .{c}) catch return; } else { w.writeByte(c) catch return; } }, } } w.writeByte('"') catch return; }
};
// ---- End-to-end tests (arena-backed; output captured) ----
const TestHarness = struct { arena_state: *std.heap.ArenaAllocator, out_buf: std.Io.Writer.Allocating, err_buf: std.Io.Writer.Allocating, vm: Vm,
/// Must be called on a stable (already-placed) TestHarness value: /// writers embed self-referential pointers. fn init(self: *TestHarness, arena_state: *std.heap.ArenaAllocator) void { const a = arena_state.allocator(); self.* = .{ .arena_state = arena_state, .out_buf = std.Io.Writer.Allocating.init(a), .err_buf = std.Io.Writer.Allocating.init(a), .vm = undefined, }; self.vm = Vm.init(a, &self.out_buf.writer, &self.err_buf.writer); }};
test "arithmetic, string concat and stdout" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\const x: int = 2 + 3 * 4 - 10 % 7; \\@stdout("x={}, float={}\n", .{x, @as(int, 7 / 2)}); \\@print("{}\n", .{"hi" + " " + "rinne"}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings("x=11, float=3\nhi rinne\n", h.out_buf.written());}
test "recursive fibonacci via main" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\fn fib(n: int) int { \\ if n < 2 { return n; } \\ return fib(n - 1) + fib(n - 2); \\} \\ \\pub fn main(args: []str) int { \\ @stdout("{}\n", .{fib(15)}); \\ return 0; \\} , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings("610\n", h.out_buf.written());}
test "while loop with break and continue" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const exit_code = try h.vm.runSource( \\pub fn main() int { \\ var i: int = 0; \\ var total: int = 0; \\ while true { \\ i = i + 1; \\ if i > 100 { break; } \\ if i % 2 == 0 { continue; } \\ total = total + i; \\ } \\ @stdout("total={}\n", .{total}); \\ return total == 2500; \\} , &.{});
// Odd numbers 1..99 sum to 2500. try std.testing.expectEqual(@as(u8, 1), exit_code); try std.testing.expectEqualStrings("total=2500\n", h.out_buf.written());}
test "arrays: literals, indexing, assignment, len" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
_ = try h.vm.runSource( \\fn sum(xs: []int) int { \\ var acc: int = 0; \\ var i: int = 0; \\ while i < std.strings.len(xs) { \\ acc = acc + xs[i]; \\ i = i + 1; \\ } \\ return acc; \\} \\ \\const data: []int = [10, 20, 30]; \\data[1] = 25; \\@stdout("{} {} {}\n", .{sum(data), std.strings.len(data), data[0]}); , &.{});
try std.testing.expectEqualStrings("65 3 10\n", h.out_buf.written());}
test "globals shared across functions" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
_ = try h.vm.runSource( \\var counter: int = 0; \\fn bump(by: int) void { \\ counter = counter + by; \\} \\bump(5); \\bump(37); \\@stdout("{}\n", .{counter}); , &.{});
try std.testing.expectEqualStrings("42\n", h.out_buf.written());}
test "defer runs LIFO at function return" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
_ = try h.vm.runSource( \\fn demo() int { \\ @stdout("A\n", .{}); \\ defer @stdout("cleanup-2\n", .{}); \\ defer @stdout("cleanup-1\n", .{}); \\ @stdout("B\n", .{}); \\ return 1; \\} \\@stdout("{}\n", .{demo()}); , &.{});
try std.testing.expectEqualStrings("A\nB\ncleanup-1\ncleanup-2\n1\n", h.out_buf.written());}
test "env builtin falls back to empty string" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
_ = try h.vm.runSource( \\@stdout("[{}]\n", .{std.env("RINNE_TEST_MISSING_VAR")}); , &.{}); try std.testing.expectEqualStrings("[]\n", h.out_buf.written());}
test "runtime errors propagate" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.DivideByZero, h.vm.runSource("@stdout(\"{}\\n\", .{1 / 0});", &.{}));
var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2); try std.testing.expectError(error.UndefinedVariable, h2.vm.runSource("@stdout(\"{}\\n\", .{missing});", &.{}));}
test "comparisons and logical operators" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
_ = try h.vm.runSource( \\@stdout("{} {} {} {} {} {} {}\n", \\ .{1 < 2, 2 >= 2, "a" < "b", not false, true and false, true or false, !true}); , &.{}); try std.testing.expectEqualStrings("true true true true false true false\n", h.out_buf.written());}
test "format string placeholder mismatches fail at runtime" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
// Too few placeholders. try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\@stdout("x={} y={}\n", .{1}); , &.{}));
var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2);
// Too many placeholders. try std.testing.expectError(error.TypeMismatch, h2.vm.runSource( \\@stdout("x={}\n", .{1, 2}); , &.{}));
var arena_state3 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state3.deinit(); var h3: TestHarness = undefined; h3.init(&arena_state3);
// Literal braces escape correctly. _ = try h3.vm.runSource( \\@stdout("{{literal}} {}\n", .{7}); , &.{}); try std.testing.expectEqualStrings("{literal} 7\n", h3.out_buf.written());
var arena_state4 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state4.deinit(); var h4: TestHarness = undefined; h4.init(&arena_state4);
// Wrong call shape is rejected at compile time. try std.testing.expectError(error.InvalidFormatCall, h4.vm.runSource( \\@stdout("hello\n"); , &.{}));}
test "compile rejects malformed fmt builtin calls" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator();
var p = parser_mod.Parser.init(arena, \\fn f() { @stdout("x", 1); } ); const prog = try p.parseProgram();
var compiler = compiler_mod.Compiler.init(arena); try std.testing.expectError(error.InvalidFormatCall, compiler.compileProgram(&prog));}
test "runtime errors report the failing source line" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
// Division by zero happens on line 4. try std.testing.expectError(error.DivideByZero, h.vm.runSource( \\const a: int = 10; \\ \\pub fn main() int { \\ return a / 0; \\} , &.{})); try std.testing.expect(h.vm.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "at line 4") != null);}
test "compile errors report the offending source line" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator();
var p = parser_mod.Parser.init(arena, \\fn f() void { \\ @stdout("no tuple here"); \\} ); const prog = try p.parseProgram();
var compiler = compiler_mod.Compiler.init(arena); try std.testing.expectError(error.InvalidFormatCall, compiler.compileProgram(&prog)); try std.testing.expect(compiler.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, compiler.err_msg.?, "at line 2") != null);}
test "vector intrinsics: splat, vector, reduce, element-wise math" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
_ = try h.vm.runSource( \\const v = std.vec.splat(4, 2); \\const w = std.vec.vector(4, f32) + v; // zero-filled + broadcast \\@stdout("{} {} {} {}\n", .{ w[0], w[1], w[2], w[3] }); \\ \\const a: []int = [10, 20, 30]; \\const b: []int = [1, 2, 3]; \\const prod = a * b; // element-wise \\@stdout("{}\n", .{std.vec.reduce(add, prod)}); \\@stdout("{} {} {}\n", .{std.vec.reduce(max, a), std.vec.reduce(min, b), prod[1]}); , &.{});
try std.testing.expectEqualStrings("2 2 2 2\n140\n30 1 40\n", h.out_buf.written());}
test "vector length mismatch and empty vector errors" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\const x: []int = [1, 2] + [1, 2, 3]; , &.{}));
var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2); try std.testing.expectError(error.TypeMismatch, h2.vm.runSource( \\@stdout("{}\n", .{std.vec.reduce(add, [])}); , &.{}));}
test "file I/O builtins round-trip" { const tmp_path = "/tmp/opencode/rg/io_roundtrip.txt";
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
// Ensure the target directory exists — write_file does not mkdir parents. // (mkdir -p semantics: succeeds when the path is already a directory.) try std.Io.Dir.cwd().createDirPath(std.testing.io, "/tmp/opencode/rg");
const src = try std.fmt.allocPrint(h.arena_state.allocator(), \\std.io.write_file("{s}", "hello rinne\n"); \\@stdout("exists={{}} contents={{}}", .{{std.io.file_exists("{s}"), std.io.read_file("{s}")}}); , .{ tmp_path, tmp_path, tmp_path }); defer h.arena_state.allocator().free(src);
_ = try h.vm.runSource(src, &.{}); try std.testing.expectEqualStrings("exists=true contents=hello rinne\n", h.out_buf.written());
// Missing file reports IoFailed. try std.testing.expectError(error.IoFailed, h.vm.runSource( \\std.io.read_file("/tmp/opencode/rg/definitely_missing_9x.txt"); , &.{}));}
test "@import std namespace, @as cast and type aliases" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@import = .{ std }; \\ \\pub fn main(args: []str) int { \\ // @as casts: int truncates floats, float widens ints, bool is truthy. \\ std.print("n={} f={} b={} s={}\n", .{@as(int, 3.9), @as(f32, 7), @as(bool, 0), @as(str, 42)}); \\ // Type aliases are documentation sugar; the VM stays dynamic. \\ var v: vec16f = std.vec.splat(16, 2); \\ std.log("len={}\n", .{std.strings.len(v)}); \\ return 0; \\} , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings("n=3 f=7 b=false s=42\n", h.out_buf.written()); try std.testing.expectEqualStrings("len=16\n", h.err_buf.written());}
test "unknown @import module fails at compile time" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const a = arena_state.allocator();
// Compile errors are reported on the Compiler instance (not the VM), // matching the direct-compile assertion pattern used elsewhere. var parser = parser_mod.Parser.init(a, \\@import = .{ nope }; \\ \\pub fn main(args: []str) int { \\ return 0; \\} ); var program = try parser.parseProgram(); defer program.deinit(a);
var compiler = compiler_mod.Compiler.init(a); defer compiler.deinit(); try std.testing.expectError(error.UnknownModule, compiler.compileProgram(&program)); try std.testing.expect(compiler.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, compiler.err_msg.?, "unknown module 'nope' in @import") != null);}
test "@as rejects unknown type names at runtime" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\@stdout("{}\n", .{@as(tensor4d, 1)}); , &.{})); try std.testing.expect(h.vm.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "unknown type 'tensor4d' in @as") != null);}
test "@tensor create, methods, indexing and idempotent deinit" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@import = .{ std }; \\ \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [2, 3], .{ .device = .cpu, .fill = 1.5 }); \\ std.print("s={} n={} d={}\n", .{t.shape(), t.numel(), t.dtype()}); \\ t[0] = 7; \\ t[5] = -2; \\ std.print("t0={} t4={} t5={}\n", .{t[0], t[4], t[5]}); \\ std.print("data={}\n", .{t.data()}); \\ t.fill(9); \\ std.print("filled={}\n", .{t[3]}); \\ t.deinit(); \\ t.deinit(); \\ return 0; \\} , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "s=[2, 3] n=6 d=f32\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "t0=7 t4=1.5 t5=-2\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "data=[7, 1.5, 1.5, 1.5, 1.5, -2]\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "filled=9\n") != null);}
test "@tensor cuda device degrades with clear runtime error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [4], .{ .device = .cuda }); \\ return 0; \\} , &.{})); try std.testing.expect(h.vm.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "'cuda' is not available in this build") != null);}
test "@tensor use after deinit is a runtime error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.IndexOutOfBounds, h.vm.runSource( \\pub fn main(args: []str) int { \\ var t = @tensor(i32, [2]); \\ t[99] = 1; \\ return 0; \\} , &.{}));}
test "@tensor use of dead tensor after deinit fails" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [2]); \\ t.deinit(); \\ t.numel(); \\ return 0; \\} , &.{})); try std.testing.expect(h.vm.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "use of deinitialized tensor") != null);}
test "enum literal outside @tensor options is a compile error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const a = arena_state.allocator();
var parser = parser_mod.Parser.init(a, \\const x = .nope; ); var program = try parser.parseProgram(); defer program.deinit(a);
var compiler = compiler_mod.Compiler.init(a); defer compiler.deinit(); try std.testing.expectError(error.UnknownDirective, compiler.compileProgram(&program)); try std.testing.expect(compiler.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, compiler.err_msg.?, "only valid inside @tensor") != null);}
test "apply_simd ReLU and square kernels run element-wise" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@import = .{ std }; \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [4], .{ .fill = -2 }); \\ t[0] = 1.5; \\ t[3] = 7; \\ t.apply_simd(fn(x: f32) f32 { if x > 0 { return x; } return 0; }); \\ std.print("{}\n", .{t.data()}); \\ \\ var u = @tensor(i64, [3], .{ .fill = -1 }); \\ u[2] = 5; \\ u.apply_simd(fn(x: i64) i64 { return x * x; }); \\ std.print("{}\n", .{u.data()}); \\ t.deinit(); \\ u.deinit(); \\ return 0; \\} , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings("[1.5, 0, 0, 7]\n[1, 1, 25]\n", h.out_buf.written());}
test "apply_simd kernel with vector-width annotation parses and runs" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [5], .{ .fill = 2 }); \\ t.apply_simd(fn(x: @vector(4, f32)) f32 { return x + 1; }); \\ return t.numel(); \\} , &.{});
try std.testing.expectEqual(@as(u8, 5), code);}
test "apply_simd defers stay isolated from caller defers" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@import = .{ std }; \\pub fn main(args: []str) int { \\ defer std.print("caller-defer\n", .{}); \\ var t = @tensor(f32, [2], .{ .fill = 3 }); \\ t.apply_simd(fn(x: f32) f32 { \\ defer std.print("kernel-defer\n", .{}); \\ return x * 2; \\ }); \\ std.print("{}\n", .{t.data()}); \\ t.deinit(); \\ return 0; \\} , &.{});
try std.testing.expectEqual(@as(u8, 0), code); // Each element invocation retires its own kernel defers immediately; // the caller's defer drains last (LIFO at main's frame return). try std.testing.expectEqualStrings( "kernel-defer\nkernel-defer\n[6, 6]\ncaller-defer\n", h.out_buf.written(), );}
test "fn literal outside apply_simd is a compile error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const a = arena_state.allocator();
var parser = parser_mod.Parser.init(a, \\const k = fn(x: f32) f32 { return x; }; ); var program = try parser.parseProgram(); defer program.deinit(a);
var compiler = compiler_mod.Compiler.init(a); defer compiler.deinit(); try std.testing.expectError(error.InvalidSimdKernel, compiler.compileProgram(&program)); try std.testing.expect(compiler.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, compiler.err_msg.?, "only allowed as the kernel of apply_simd") != null);}
test "apply_simd without an inline fn literal is a compile error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const a = arena_state.allocator();
var parser = parser_mod.Parser.init(a, \\fn ker(x: f32) f32 { return x; } \\pub fn main(args: []str) int { \\ var t = @tensor(f32, [2], .{ .fill = 1 }); \\ t.apply_simd(ker); \\ return 0; \\} ); var program = try parser.parseProgram(); defer program.deinit(a);
var compiler = compiler_mod.Compiler.init(a); defer compiler.deinit(); try std.testing.expectError(error.InvalidSimdKernel, compiler.compileProgram(&program)); try std.testing.expect(compiler.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, compiler.err_msg.?, "expects exactly one inline fn literal kernel") != null);}
test "apply_simd on a non-tensor receiver is a runtime error" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const result = h.vm.runSource( \\pub fn main(args: []str) int { \\ const xs = [1, 2, 3]; \\ xs.apply_simd(fn(x: int) int { return x; }); \\ return 0; \\} , &.{});
try std.testing.expectError(error.TypeMismatch, result); try std.testing.expect(h.vm.err_msg != null); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "can only be called on a tensor") != null);}
// ---- ai library loader tests (real fixture files over mmap) ----
fn aiTestAppendU32(list: *std.ArrayList(u8), v: u32) !void { var b: [4]u8 = undefined; std.mem.writeInt(u32, &b, v, .little); try list.appendSlice(std.testing.allocator, &b);}
fn aiTestAppendU64(list: *std.ArrayList(u8), v: u64) !void { var b: [8]u8 = undefined; std.mem.writeInt(u64, &b, v, .little); try list.appendSlice(std.testing.allocator, &b);}
fn aiTestAppendStr(list: *std.ArrayList(u8), s: []const u8) !void { try aiTestAppendU64(list, s.len); try list.appendSlice(std.testing.allocator, s);}
fn aiTestAppendF32(list: *std.ArrayList(u8), v: f32) !void { try list.appendSlice(std.testing.allocator, std.mem.asBytes(&v));}
fn aiTestAppendI32(list: *std.ArrayList(u8), v: i32) !void { var b: [4]u8 = undefined; std.mem.writeInt(i32, &b, v, .little); try list.appendSlice(std.testing.allocator, &b);}
test "load_safetensors e2e: read-only views over a real file" { const st_path = "/tmp/opencode/rg/m4_fix.safetensors";
// Header JSON describes w (f32[2,2]) then b (i32[2]); data follows. const json = \\{"w":{"dtype":"F32","shape":[2,2],"data_offsets":[0,16]},"b":{"dtype":"I32","shape":[2],"data_offsets":[16,24]}} ; var buf: std.ArrayList(u8) = .empty; defer buf.deinit(std.testing.allocator); try aiTestAppendU64(&buf, json.len); try buf.appendSlice(std.testing.allocator, json); try aiTestAppendF32(&buf, 1.0); try aiTestAppendF32(&buf, 2.0); try aiTestAppendF32(&buf, 3.0); try aiTestAppendF32(&buf, 4.0); try aiTestAppendI32(&buf, 7); try aiTestAppendI32(&buf, -3);
try std.Io.Dir.cwd().createDirPath(std.testing.io, "/tmp/opencode/rg"); try std.Io.Dir.cwd().writeFile(std.testing.io, .{ .sub_path = st_path, .data = buf.items });
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
const code = try h.vm.runSource( \\@import = .{ ai }; \\const ts = ai.load_safetensors("/tmp/opencode/rg/m4_fix.safetensors"); \\@stdout("n={} w.shape={} w.dtype={} w.data={}\n", .{std.strings.len(ts), ts[0].shape(), ts[0].dtype(), ts[0].data()}); \\@stdout("b.data={}\n", .{ts[1].data()}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings( "n=2 w.shape=[2, 2] w.dtype=f32 w.data=[1, 2, 3, 4]\nb.data=[7, -3]\n", h.out_buf.written(), );
// Views are read-only: fill must fail without corrupting the mapping. try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\@import = .{ ai }; \\const ts = ai.load_safetensors("/tmp/opencode/rg/m4_fix.safetensors"); \\ts[0].fill(9); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "cannot fill memory-mapped tensor 'w'") != null);
// Truncated/garbage file surfaces a clear IoFailed. try std.Io.Dir.cwd().writeFile(std.testing.io, .{ .sub_path = st_path, .data = "not a safetensors file at all" }); try std.testing.expectError(error.IoFailed, h.vm.runSource( \\@import = .{ ai }; \\ai.load_safetensors("/tmp/opencode/rg/m4_fix.safetensors"); , &.{}));}
test "load_gguf e2e: NE dims reversed to row-major, bad magic rejected" { const gguf_path = "/tmp/opencode/rg/m4_fix.gguf";
// GGUF v3, one kv (general.alignment=32), one F32 tensor "x" with // NE-order dims [3, 2] -> row-major shape [2, 3]. var buf: std.ArrayList(u8) = .empty; defer buf.deinit(std.testing.allocator); try buf.appendSlice(std.testing.allocator, "GGUF"); try aiTestAppendU32(&buf, 3); // version try aiTestAppendU64(&buf, 1); // tensor count try aiTestAppendU64(&buf, 1); // kv count try aiTestAppendStr(&buf, "general.alignment"); try aiTestAppendU32(&buf, 4); // kv type UINT32 try aiTestAppendU32(&buf, 32); // alignment try aiTestAppendStr(&buf, "x"); // tensor name try aiTestAppendU32(&buf, 2); // n_dims try aiTestAppendU64(&buf, 3); // dim0 (NE: innermost) try aiTestAppendU64(&buf, 2); // dim1 try aiTestAppendU32(&buf, 0); // ggml type F32 try aiTestAppendU64(&buf, 0); // relative offset
// Pad to the 32-byte alignment boundary before tensor data. while (buf.items.len % 32 != 0) { try buf.append(std.testing.allocator, 0); } var i: f32 = 1.0; while (i <= 6.0) : (i += 1.0) { try aiTestAppendF32(&buf, i); }
try std.Io.Dir.cwd().createDirPath(std.testing.io, "/tmp/opencode/rg"); try std.Io.Dir.cwd().writeFile(std.testing.io, .{ .sub_path = gguf_path, .data = buf.items });
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
const code = try h.vm.runSource( \\@import = .{ ai }; \\const ts = ai.load_gguf("/tmp/opencode/rg/m4_fix.gguf"); \\@stdout("n={} x.shape={} x.dtype={} x.data={}\n", .{std.strings.len(ts), ts[0].shape(), ts[0].dtype(), ts[0].data()}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings( "n=1 x.shape=[2, 3] x.dtype=f32 x.data=[1, 2, 3, 4, 5, 6]\n", h.out_buf.written(), );
// Wrong magic -> immediate, readable failure. try std.Io.Dir.cwd().writeFile(std.testing.io, .{ .sub_path = gguf_path, .data = "GGUT not quite" }); try std.testing.expectError(error.IoFailed, h.vm.runSource( \\@import = .{ ai }; \\ai.load_gguf("/tmp/opencode/rg/m4_fix.gguf"); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "is not a GGUF file") != null);}
const testing = std.testing;
// ---- @fetch marketplace integration ---------------------------------------
const sample_pkg_src = "fn pkg_add(a: int, b: int) int {\n return a + b;\n}\n";
var fetch_e2e_calls: usize = 0;fn fetchFakeSeam(pm: *pkg_mod.PackageManager, url: []const u8, out: *std.ArrayList(u8)) anyerror!void { fetch_e2e_calls += 1; try testing.expectEqualStrings("https://example.com/lib.rg", url); try out.appendSlice(pm.allocator, sample_pkg_src);}
test "@fetch resolves a package and splices its functions" { var root_buf: [128]u8 = undefined; const root = try std.fmt.bufPrint(&root_buf, "/tmp/opencode/rg/fetch-e2e-{d}", .{std.Io.Timestamp.now(testing.io, .awake).nanoseconds});
var arena_state = std.heap.ArenaAllocator.init(testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = testing.io; h.vm.pkg_opts = .{ .cache_dir_override = root, .fetch_seam = fetchFakeSeam };
const code = try h.vm.runSource( \\@fetch("https://example.com/lib.rg"); \\ \\pub fn main(args: []str) int { \\ @stdout("{}\n", .{pkg_add(2, 3)}); \\ return 0; \\} , &.{});
try testing.expectEqual(@as(u8, 0), code); try testing.expectEqualStrings("5\n", h.out_buf.written()); try testing.expectEqual(@as(usize, 1), fetch_e2e_calls);
// Re-running resolves from cache: the seam is not consulted again. const code2 = try h.vm.runSource( \\@fetch("https://example.com/lib.rg"); \\ \\pub fn main(args: []str) int { \\ return pkg_add(1, 1); \\} , &.{}); try testing.expectEqual(@as(u8, 2), code2); try testing.expectEqual(@as(usize, 1), fetch_e2e_calls);}
test "@fetch rejects name conflicts with existing declarations" { var root_buf: [128]u8 = undefined; const root = try std.fmt.bufPrint(&root_buf, "/tmp/opencode/rg/fetch-dup-{d}", .{std.Io.Timestamp.now(testing.io, .awake).nanoseconds});
var arena_state = std.heap.ArenaAllocator.init(testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = testing.io; h.vm.pkg_opts = .{ .cache_dir_override = root, .fetch_seam = fetchFakeSeam };
const result = h.vm.runSource( \\@fetch("https://example.com/lib.rg"); \\ \\fn pkg_add(a: int, b: int) int { \\ return 0; \\} , &.{});
try testing.expectError(error.PackageFetch, result); try testing.expect(h.vm.err_msg != null); try testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "conflicts") != null);}
test "llm_load e2e: forward prints logits length, use-after-deinit rejected" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
std.Io.Dir.cwd().createDirPath(std.testing.io, "/tmp/opencode/rg") catch {}; const bytes = try llm_mod.buildMicroModelGguf(std.testing.allocator); defer std.testing.allocator.free(bytes); std.Io.Dir.cwd().writeFile(std.testing.io, .{ .sub_path = "/tmp/opencode/rg/m7_fix.gguf", .data = bytes, }) catch return error.SkipZigTest;
const code = try h.vm.runSource( \\@import = .{ llm }; \\const m = llm.llm_load("/tmp/opencode/rg/m7_fix.gguf"); \\const lg = m.forward(.{0, 1}); \\@stdout("n={} first={}\n", .{std.strings.len(lg), lg[2]}); \\m.deinit(); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expect(std.mem.indexOf(u8, h.out_buf.written(), "n=3") != null);
// Use-after-deinit must fail cleanly. var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2); h2.vm.io = std.testing.io;
const r = h2.vm.runSource( \\@import = .{ llm }; \\const m = llm.llm_load("/tmp/opencode/rg/m7_fix.gguf"); \\m.forward(.{1}); \\m.deinit(); \\m.forward(.{0}); , &.{}); try std.testing.expectError(error.TypeMismatch, r); try std.testing.expect(std.mem.indexOf(u8, h2.vm.err_msg.?, "use of deinitialized model") != null);}
test "load_lib FFI: libc abs, libm fabs, missing symbol, use-after-deinit" { if (@import("builtin").os.tag != .linux) return error.SkipZigTest;
// dlopen resolves literal paths here; probe the usual glibc locations. // abs lives in libc; fabs (double math) lives in libm. const libc_candidates = [_][]const u8{ "/lib64/libc.so.6", "/lib/x86_64-linux-gnu/libc.so.6", "/usr/lib/libc.so.6", "/lib/aarch64-linux-gnu/libc.so.6", }; const libm_candidates = [_][]const u8{ "/lib64/libm.so.6", "/lib/x86_64-linux-gnu/libm.so.6", "/usr/lib/libm.so.6", "/lib/aarch64-linux-gnu/libm.so.6", }; const findFirst = struct { fn go(cands: []const []const u8) ?[]const u8 { for (cands) |cp| { std.Io.Dir.cwd().access(std.testing.io, cp, .{}) catch continue; return cp; } return null; } }.go; const libc_path = findFirst(&libc_candidates) orelse return error.SkipZigTest; const libm_path = findFirst(&libm_candidates) orelse return error.SkipZigTest;
var src_buf: [512]u8 = undefined; const happy_src = try std.fmt.bufPrint(&src_buf, \\@import = .{{ std, c }}; \\const c = c.load_lib("{s}"); \\@stdout("{{}}\n", .{{c.call_int("abs", .{{-42}})}}); \\const m = c.load_lib("{s}"); \\@stdout("{{}}\n", .{{m.call_float("fabs", .{{-2.5}})}}); \\c.deinit(); \\m.deinit(); , .{ libc_path, libm_path });
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource(happy_src, &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expectEqualStrings("42\n2.5\n", h.out_buf.written());
// Missing symbol -> IoFailed naming the symbol. var sym_buf: [256]u8 = undefined; const miss_src = try std.fmt.bufPrint(&sym_buf, \\@import = .{{ c }}; \\const c = c.load_lib("{s}"); \\c.call_int("nope_zzz", .{{1}}); , .{libc_path}); var h3: TestHarness = undefined; h3.init(&arena_state); const r3 = h3.vm.runSource(miss_src, &.{}); try std.testing.expectError(error.IoFailed, r3); try std.testing.expect(std.mem.indexOf(u8, h3.vm.err_msg.?, "has no symbol 'nope_zzz'") != null);
// Use-after-deinit rejected. var dead_buf: [256]u8 = undefined; const dead_src = try std.fmt.bufPrint(&dead_buf, \\@import = .{{ c }}; \\const c = c.load_lib("{s}"); \\c.deinit(); \\c.call_int("abs", .{{1}}); , .{libc_path}); var h2: TestHarness = undefined; h2.init(&arena_state); const r2 = h2.vm.runSource(dead_src, &.{}); try std.testing.expectError(error.TypeMismatch, r2); try std.testing.expect(std.mem.indexOf(u8, h2.vm.err_msg.?, "use of deinitialized library") != null);}
test "module gating: namespaced and gated builtins require @import" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
// std is the implicit default namespace: std.print needs no @import. _ = try h.vm.runSource( \\std.print("hi\n", .{}); , &.{});
// A namespaced gated builtin without importing its module fails. var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2); try std.testing.expectError(error.UnknownFunction, h2.vm.runSource( \\llm.llm_load("/tmp/opencode/rg/m7_fix.gguf"); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h2.vm.err_msg.?, "module 'llm' is not imported") != null);
// A bare (un-namespaced) builtin is a hard error pointing at its namespace. var arena_state3 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state3.deinit(); var h3: TestHarness = undefined; h3.init(&arena_state3); try std.testing.expectError(error.UnknownFunction, h3.vm.runSource( \\llm_load("/tmp/opencode/rg/m7_fix.gguf"); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h3.vm.err_msg.?, "must be called through its namespace") != null);}
test "tcp loopback echo" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
const code = try h.vm.runSource( \\@import = .{ net, std }; \\const l = net.tcp_listen(0); \\const p = l.port(); \\std.print("listening on {}\n", .{p}); \\const c = net.tcp_connect("127.0.0.1", p); \\c.write("ping"); \\c.shutdown(); // half-close: server read returns at EOF \\const s = l.accept(); \\const got = s.read(64); \\std.print("server got: {}\n", .{got}); \\s.write("pong"); \\s.shutdown(); \\const back = c.read(64); \\std.print("client got: {}\n", .{back}); \\c.close(); \\s.close(); \\l.close(); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); try std.testing.expect(std.mem.indexOf(u8, h.out_buf.written(), "server got: ping") != null); try std.testing.expect(std.mem.indexOf(u8, h.out_buf.written(), "client got: pong") != null);}
test "crypto builtins: sha256 known vector, base64 roundtrip" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@stdout("{}\n", .{std.sha.sha256("hello")}); \\@stdout("{}\n", .{std.json.base64_encode("rinne")}); \\@stdout("{}\n", .{std.json.base64_decode("cmlubmU=")}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824") != null); try std.testing.expect(std.mem.indexOf(u8, out, "cmlubmU=") != null); try std.testing.expect(std.mem.indexOf(u8, out, "rinne") != null);
// Invalid base64 is a clean runtime error. try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\std.json.base64_decode("!!!"); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "invalid base64") != null);}
test "args() exposes the script argument vector" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@stdout("{}\n", .{std.strings.len(std.args())}); \\@stdout("{}\n", .{std.args()[0]}); , &.{ "alpha", "beta" });
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "2") != null); try std.testing.expect(std.mem.indexOf(u8, out, "alpha") != null);}
test "log_write writes structured lines to stderr" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\std.log_write(2, "hi"); \\std.log_write("CUSTOM", "msg"); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const err = h.err_buf.written(); try std.testing.expect(std.mem.indexOf(u8, err, "[INFO] hi\n") != null); try std.testing.expect(std.mem.indexOf(u8, err, "[CUSTOM] msg\n") != null);}
test "assert and assert_eq pass and fail with diagnostics" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\std.assert(1 < 2); \\std.assert("x" == "x"); \\std.assert_eq(42, 42); \\std.assert_eq([1, 2], [1, 2]); , &.{}); try std.testing.expectEqual(@as(u8, 0), code);
try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\std.assert(1 > 2); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "assertion failed") != null);
// Fresh harness: vm.err_msg is only written once (first failure wins). var arena_state3 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state3.deinit(); var h3: TestHarness = undefined; h3.init(&arena_state3); try std.testing.expectError(error.TypeMismatch, h3.vm.runSource( \\std.assert_eq(1, 2); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h3.vm.err_msg.?, "1 != 2") != null);}
test "string suite builtins" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@stdout("{}\n", .{std.strings.join(std.strings.split("a,b,c", ","), "-")}); \\@stdout("[{}]\n", .{std.strings.trim(" hi\r\n ")}); \\@stdout("{}\n", .{std.strings.replace("aaa", "a", "b")}); \\@stdout("{} {} {}\n", .{std.strings.contains("hello", "ell"), std.strings.starts_with("hello", "he"), std.strings.ends_with("hello", "lo")}); \\@stdout("{} {}\n", .{std.strings.to_lower("ABC"), std.strings.to_upper("abc")}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "a-b-c") != null); try std.testing.expect(std.mem.indexOf(u8, out, "[hi]") != null); try std.testing.expect(std.mem.indexOf(u8, out, "bbb") != null); try std.testing.expect(std.mem.indexOf(u8, out, "true true true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "abc ABC") != null);}
test "maps: literals, indexing, len, methods" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\const m = .{ .name = "rinne", .level = 3 }; \\@stdout("{}\n", .{m["name"]}); \\var n = m; \\n["extra"] = 7; \\@stdout("{} {} {}\n", .{std.strings.len(n), n["extra"], n.has("name")}); \\// maps are references: m and n alias the same object \\@stdout("{}\n", .{std.strings.len(m)}); \\n.remove("level"); \\@stdout("{}\n", .{n.has("level")}); \\@stdout("{}\n", .{m["missing"]}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "rinne") != null); try std.testing.expect(std.mem.indexOf(u8, out, "3 7 true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "3\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "false") != null); try std.testing.expect(std.mem.indexOf(u8, out, "null") != null);}
test "json parse and stringify roundtrip" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\const doc = std.json.json_parse("{\"name\": \"rinne\", \"n\": 42, \"pi\": 3.5, \"ok\": true, \"tags\": [\"a\", \"b\"], \"child\": {\"x\": null}}"); \\@stdout("{}\n", .{doc["name"]}); \\@stdout("{}\n", .{doc["tags"][1]}); \\@stdout("{}\n", .{std.strings.len(doc)}); \\const again = std.json.json_stringify(doc); \\@stdout("{}\n", .{again}); \\const reparsed = std.json.json_parse(again); \\@stdout("{}\n", .{reparsed["child"]["x"]}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "rinne\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "b\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "6") != null); // Map iteration order is unspecified (hash map), so assert fragments // rather than one exact serialized form. try std.testing.expect(std.mem.indexOf(u8, out, "\"name\":\"rinne\"") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\"n\":42") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\"tags\":[\"a\",\"b\"]") != null); try std.testing.expect(std.mem.indexOf(u8, out, "{\"x\":null}") != null); try std.testing.expect(std.mem.indexOf(u8, out, "null\n") != null);
// Invalid JSON is a clean runtime error. try std.testing.expectError(error.TypeMismatch, h.vm.runSource( \\std.json.json_parse("{oops"); , &.{})); try std.testing.expect(std.mem.indexOf(u8, h.vm.err_msg.?, "invalid JSON") != null);}
test "for-in loop: sum, break, continue advances, nesting" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\var s = 0; \\for v | [1, 2, 3] { s = s + v; } \\@stdout("{}\n", .{s}); \\ \\var t = 0; \\for v | [1, 2, 3, 4] { \\ if v == 3 { continue; } \\ if v == 4 { break; } \\ t = t + v; \\} \\@stdout("{}\n", .{t}); \\ \\var pairs = 0; \\for a | [1, 2] { \\ for b | [10, 20] { pairs = pairs + 1; } \\} \\@stdout("{}\n", .{pairs}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "6") != null); // continue at v==3 skips it; break at v==4; total = 1+2 = 3. try std.testing.expect(std.mem.indexOf(u8, out, "3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "4") != null);}
test "try recovers from runtime errors and passes values through" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\fn div(a: int, b: int) int { \\ return a / b; \\} \\const bad = try(div(1, 0)); \\@stdout("{}\n", .{bad[0]}); \\@stdout("{}\n", .{bad[1]}); \\const good = try(div(10, 2)); \\@stdout("{} {}\n", .{good[0], good[1]}); \\var x = 5; \\x = x + 1; \\@stdout("alive {}\n", .{x}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "false") != null); try std.testing.expect(std.mem.indexOf(u8, out, "division by zero") != null); try std.testing.expect(std.mem.indexOf(u8, out, "true 5") != null); try std.testing.expect(std.mem.indexOf(u8, out, "alive 6") != null);
// Nested try: inner failure is data to the outer guard. var arena_state2 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state2.deinit(); var h2: TestHarness = undefined; h2.init(&arena_state2); const code2 = try h2.vm.runSource( \\fn boom() int { \\ return 1 / 0; \\} \\const outer = try(try(boom())); \\@stdout("{}\n", .{outer[0]}); \\@stdout("{}\n", .{outer[1][0]}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code2); const out2 = h2.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out2, "true\nfalse") != null);
// try rejects non-call arguments at compile time. var arena_state3 = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state3.deinit(); var h3: TestHarness = undefined; h3.init(&arena_state3); try std.testing.expectError(error.InvalidTryTarget, h3.vm.runSource( \\try(42); , &.{}));}
test "push and slice utilities" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\var xs = [1]; \\xs = std.collections.push(xs, 2); \\xs = std.collections.push(xs, 3); \\@stdout("{}\n", .{std.strings.len(xs)}); \\@stdout("{}\n", .{std.strings.slice(xs, 1, 3)}); \\@stdout("{}\n", .{std.strings.slice("hello", 1, 4)}); \\@stdout("{}\n", .{std.strings.slice("hello", -3, -1)}); , &.{});
try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "3\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "[2, 3]") != null); try std.testing.expect(std.mem.indexOf(u8, out, "ell") != null); try std.testing.expect(std.mem.indexOf(u8, out, "ll") != null);}
test "panic builtin carries message through try()" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\fn boom() int { \\ std.panic("custom failure"); \\ return 0; \\} \\const r = try(boom()); \\@stdout("{}|{}\n", .{r[0], r[1]}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "false|custom failure") != null);}
test "std.math helpers preserve intness and round like floats" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@stdout("abs-int={}\n", .{std.math.abs(-7)}); \\@stdout("abs-f={}\n", .{std.math.abs(-7.25)}); \\@stdout("floor={} ceil={} round={}\n", .{ \\ std.math.floor(2.7), std.math.ceil(2.1), std.math.round(2.5), \\}); \\@stdout("sqrt={} pow={}\n", .{std.math.sqrt(81), std.math.pow(2, 10)}); \\@stdout("min={} max={} clamp={}\n", .{ \\ std.math.min(3, 9), std.math.max(3, 9), std.math.clamp(15, 1, 10), \\}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "abs-int=7") != null); try std.testing.expect(std.mem.indexOf(u8, out, "abs-f=7.25") != null); try std.testing.expect(std.mem.indexOf(u8, out, "floor=2") != null); try std.testing.expect(std.mem.indexOf(u8, out, "ceil=3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "round=3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "sqrt=9") != null); try std.testing.expect(std.mem.indexOf(u8, out, "pow=1024") != null); try std.testing.expect(std.mem.indexOf(u8, out, "min=3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "max=9") != null); try std.testing.expect(std.mem.indexOf(u8, out, "clamp=10") != null);}
test "std.strings repeat/chars/reverse and std.collections.contains_val" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\@stdout("repeat={}\n", .{std.strings.repeat("ab", 3)}); \\@stdout("chars={}\n", .{std.strings.chars("hi!")}); \\@stdout("reverse={}\n", .{std.strings.reverse("hello")}); \\const xs = [1, 2, 3]; \\@stdout("cv={}\n", .{std.collections.contains_val(xs, 2)}); \\const m = {"a": 1, "b": 2}; \\@stdout("cvm={}\n", .{std.collections.contains_val(m, 2)}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "repeat=ababab") != null); try std.testing.expect(std.mem.indexOf(u8, out, "chars=") != null); try std.testing.expect(std.mem.indexOf(u8, out, "reverse=olleh") != null); try std.testing.expect(std.mem.indexOf(u8, out, "cv=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "cvm=true") != null);}
test "std.json.json_pretty indents nested mappings" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\const obj = {"name": "rinne", "vers": 3}; \\@stdout("{}", .{std.json.json_pretty(obj)}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); // Map iteration order is unspecified, so assert the shape: a multi-line // object with two indented key: value pairs and matching close brace. try std.testing.expect(std.mem.indexOf(u8, out, "{\n \"name\": \"rinne\",\n") != null or std.mem.indexOf(u8, out, "{\n \"vers\": 3,\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\"name\": \"rinne\"") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\"vers\": 3") != null); try std.testing.expect(std.mem.indexOf(u8, out, "\n}") != null);}
test "std.io fs helpers roundtrip through a scratch directory" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state); h.vm.io = std.testing.io;
const dir = std.Io.Dir.cwd(); // Best-effort removal of leftovers from earlier runs: the directory may be // absent (FileNotFound), a file (NeedsDeleteFile/NotDir), or non-empty // (NotEmpty); none of those should fail the test setup. dir.deleteTree(std.testing.io, "/tmp/rinne_vm_test_io") catch {};
const code = try h.vm.runSource( \\const d = "/tmp/rinne_vm_test_io"; \\@stdout("made={}\n", .{std.io.make_dir(d)}); \\@stdout("madeall={}\n", .{std.io.make_dir_all(d + "/sub/deep")}); \\@stdout("exists={}\n", .{std.io.file_exists(d + "/sub/deep")}); \\@stdout("wrote={}\n", .{std.io.write_file(d + "/f.txt", "a")}); \\@stdout("append={}\n", .{std.io.append_file(d + "/f.txt", "b\n")}); \\@stdout("read={}\n", .{std.io.read_file(d + "/f.txt")}); \\@stdout("renamed={}\n", .{std.io.rename_file(d + "/f.txt", d + "/g.txt")}); \\@stdout("removed={}\n", .{std.io.remove_file(d + "/g.txt")}); \\@stdout("listed={}\n", .{std.io.list_dir(d)}); \\@stdout("rmdir={}\n", .{std.io.remove_dir(d + "/sub/deep")}); \\@stdout("rmdir2={}\n", .{std.io.remove_dir(d + "/sub")}); \\@stdout("rmdir3={}\n", .{std.io.remove_dir(d)}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "made=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "madeall=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "exists=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "wrote=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "read=ab\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "renamed=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "removed=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "listed=") != null); try std.testing.expect(std.mem.indexOf(u8, out, "rmdir=true") != null);
dir.deleteTree(std.testing.io, "/tmp/rinne_vm_test_io") catch {};}
test "range for: exclusive, inclusive, break/continue, global scope" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\fn total(n: int) int { \\ var acc: int = 0; \\ for i in 0..n { \\ if std.math.is_odd(i) { continue; } \\ acc = acc + i; \\ } \\ return acc; \\} \\ \\var hits: int = 0; \\for k in 0..=4 { \\ if k == 3 { break; } \\ hits = hits + 1; \\} \\@stdout("even-sum={}\n", .{total(10)}); \\@stdout("inclusive=0..=4\n", .{}); \\@stdout("hits={}\n", .{hits}); \\@stdout("desc=\n", .{}); \\for d in 4..0 { \\ @stdout("{}\n", .{d}); \\} , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "even-sum=20") != null); try std.testing.expect(std.mem.indexOf(u8, out, "inclusive=0..=4") != null); try std.testing.expect(std.mem.indexOf(u8, out, "hits=3") != null); // Empty descending range: nothing printed between the markers. const desc = std.mem.indexOf(u8, out, "desc=\n").?; const after = out[desc + "desc=\n".len ..]; try std.testing.expectEqualStrings("", after);}
test "user functions shadow same-named std builtins" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = try h.vm.runSource( \\fn sum(xs: []int) int { \\ var acc: int = 0; \\ for x | xs { acc = acc + x; } \\ return acc; \\} \\@stdout("sum={}\n", .{sum([2, 4, 6])}); \\@stdout("builtin={}\n", .{std.collections.sum([2, 4, 6])}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "sum=12") != null); try std.testing.expect(std.mem.indexOf(u8, out, "builtin=12") != null);}