Something went wrong. Try again.
A general purpose .. programming language written in zig ijadux2.tngl.sh/Rinnescript
zig language
Something went wrong. Try again.
244 kB · 5296 lines
Zig
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297const std = @import("std");const linux_os = std.os.linux;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, saved_tty: ?std.posix.termios = 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,
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); } } 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") or std.mem.eql(u8, name, "exec") or std.mem.eql(u8, name, "pipe") or std.mem.eql(u8, name, "wait") or std.mem.eql(u8, name, "reap") or std.mem.eql(u8, name, "kill") or std.mem.eql(u8, name, "sig") or std.mem.eql(u8, name, "setpgid") or std.mem.eql(u8, name, "fg") or std.mem.eql(u8, name, "raw") or std.mem.eql(u8, name, "size") or std.mem.eql(u8, name, "open") or std.mem.eql(u8, name, "close") or std.mem.eql(u8, name, "read") or std.mem.eql(u8, name, "write") or std.mem.eql(u8, name, "cwd") or std.mem.eql(u8, name, "glob") or std.mem.eql(u8, name, "which"))) { return self.fail(error.IoFailed, "@{s} requires an Io context (unavailable here)", .{name}); }
// ---- runic: POSIX/sys toolkit (Linux v1, raw syscalls) —- the shell // in `.plan/plan3.md` is written entirely against this namespace. if (std.mem.eql(u8, name, "exec")) return self.runicExec(args); if (std.mem.eql(u8, name, "pipe")) return self.runicPipe(args); if (std.mem.eql(u8, name, "wait") or std.mem.eql(u8, name, "reap")) return self.runicWait(name, args); if (std.mem.eql(u8, name, "kill")) return self.runicKill(args); if (std.mem.eql(u8, name, "sig")) return self.runicSig(args); if (std.mem.eql(u8, name, "setpgid")) return self.runicSetpgid(args); if (std.mem.eql(u8, name, "fg")) return self.runicFg(args); if (std.mem.eql(u8, name, "raw")) return self.runicRaw(args); if (std.mem.eql(u8, name, "size")) return self.runicSize(args); if (std.mem.eql(u8, name, "open")) return self.runicOpen(args); if (std.mem.eql(u8, name, "close")) return self.runicClose(args); if (std.mem.eql(u8, name, "read")) return self.runicRead(args); if (std.mem.eql(u8, name, "write")) return self.runicWrite(args); if (std.mem.eql(u8, name, "cwd")) return self.runicCwd(args); if (std.mem.eql(u8, name, "glob")) return self.runicGlob(args); if (std.mem.eql(u8, name, "which")) return self.runicWhich(args);
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.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}); }
// ------------------------------------------------------------------ // runic: POSIX/sys toolkit. Linux v1 — raw syscalls through // std.os.linux (Zig 0.16 std.posix no longer exposes fork/execvpe/ // waitpid etc.). fds are plain ints owned by the caller; the VM never // closes them. Failures read "runic.<verb>: <reason>". // ------------------------------------------------------------------
const runic_sigs = [_]struct { n: []const u8, v: u32 }{ .{ .n = "HUP", .v = 1 }, .{ .n = "INT", .v = 2 }, .{ .n = "QUIT", .v = 3 }, .{ .n = "ABRT", .v = 6 }, .{ .n = "KILL", .v = 9 }, .{ .n = "PIPE", .v = 13 }, .{ .n = "ALRM", .v = 14 }, .{ .n = "TERM", .v = 15 }, .{ .n = "STOP", .v = 17 }, .{ .n = "TSTP", .v = 18 }, .{ .n = "CONT", .v = 19 }, .{ .n = "CHLD", .v = 20 }, .{ .n = "TTIN", .v = 21 }, .{ .n = "TTOU", .v = 22 }, .{ .n = "USR1", .v = 30 }, .{ .n = "USR2", .v = 31 }, };
fn runicSigNum(name: []const u8) ?u32 { for (Vm.runic_sigs) |row| { if (std.mem.eql(u8, row.n, name)) return row.v; } return null; }
fn runicMapPut(self: *Vm, m: *value_mod.MapObj, key: []const u8, val: Value) RuntimeError!void { const k = self.allocator.dupe(u8, key) catch return error.OutOfMemory; m.map.put(k, val) catch return error.OutOfMemory; }
fn runicStatus(self: *Vm, status: u32, stdout: ?[]const u8, stderr: ?[]const u8) RuntimeError!Value { const m = value_mod.MapObj.create(self.allocator) catch return error.OutOfMemory; const code: i64 = if (linux_os.W.IFEXITED(status)) linux_os.W.EXITSTATUS(status) else 0; const sig: i64 = if (linux_os.W.IFSIGNALED(status)) @intFromEnum(linux_os.W.TERMSIG(status)) else 0; try self.runicMapPut(m, "code", .{ .int_val = code }); try self.runicMapPut(m, "signal", .{ .int_val = sig }); if (stdout) |s| try self.runicMapPut(m, "stdout", .{ .str_val = s }); if (stderr) |s| try self.runicMapPut(m, "stderr", .{ .str_val = s }); return .{ .map_val = m }; }
fn runicErr(self: *Vm, verb: []const u8, msg: []const u8, rc: usize) RuntimeError!Value { const reason = @tagName(linux_os.errno(rc)); return self.fail(error.IoFailed, "runic.{s}: {s} ({s})", .{ verb, msg, reason }); }
/// Walks PATH (or a litteral/relative path containing a '/') and returns /// the first executable, or null. The result is a fresh allocation. fn runicFindInPath(self: *Vm, name: []const u8) ?[:0]u8 { if (std.mem.indexOfScalar(u8, name, '/') != null) { const z = self.allocator.dupeZ(u8, name) catch return null; if (linux_os.errno(linux_os.access(z, 1)) == .SUCCESS) return z; self.allocator.free(z); return null; } const path_env = if (self.environ) |m| m.get("PATH") else null; const path_str = path_env orelse "/usr/local/bin:/usr/bin:/bin"; var it = std.mem.splitScalar(u8, path_str, ':'); while (it.next()) |dir| { if (dir.len == 0) continue; const cand = std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ dir, name }) catch return null; const z = self.allocator.dupeZ(u8, cand) catch return null; self.allocator.free(cand); if (linux_os.errno(linux_os.access(z, 1)) == .SUCCESS) return z; self.allocator.free(z); } return null; }
/// Minimal POSIX-style tokenizer for the string form of runic.exec. No /// expansion and no redirection here — that lives in the RinneScript /// shell. Shell words are copied into fresh, owned slices. fn runicTokenize(self: *Vm, line: []const u8, out: *std.ArrayList([]const u8)) RuntimeError!void { var i: usize = 0; var quote: ?u8 = null; while (i < line.len) { while (i < line.len and (line[i] == ' ' or line[i] == '\t' or line[i] == '\n')) i += 1; if (i >= line.len) break; var tok: std.ArrayList(u8) = .empty; quote = null; while (i < line.len) { const ch = line[i]; if (quote == null) { if (ch == ' ' or ch == '\t' or ch == '\n') break; if (ch == '\'') { quote = '\''; i += 1; continue; } if (ch == '"') { quote = '"'; i += 1; continue; } if (ch == '\\' and i + 1 < line.len) { i += 1; try tok.append(self.allocator, line[i]); i += 1; continue; } try tok.append(self.allocator, ch); i += 1; } else if (quote.? == '\'' and ch == '\'') { quote = null; i += 1; } else if (quote.? == '"' and ch == '"') { quote = null; i += 1; } else if (quote.? == '"' and ch == '\\' and i + 1 < line.len) { i += 1; try tok.append(self.allocator, line[i]); i += 1; } else { try tok.append(self.allocator, ch); i += 1; } } const owned = tok.toOwnedSlice(self.allocator) catch return error.OutOfMemory; try out.append(self.allocator, owned); } }
fn runicReadAll(self: *Vm, fd: i32) RuntimeError![]const u8 { const chunk = 4096; var buf: std.ArrayList(u8) = .empty; errdefer buf.deinit(self.allocator); var buf2: [chunk]u8 = undefined; while (true) { const rc = std.posix.read(fd, &buf2) catch |e| { buf.deinit(self.allocator); return self.fail(error.IoFailed, "runic.read: {s}", .{@errorName(e)}); }; if (rc == 0) break; try buf.appendSlice(self.allocator, buf2[0..rc]); } return buf.toOwnedSlice(self.allocator) catch return error.OutOfMemory; }
fn runicExec(self: *Vm, args: []Value) RuntimeError!Value { if (args.len < 1 or args.len > 2) { return self.fail(error.TypeMismatch, "runic.exec expects (command: str | args: [str], opts: .{{...}})", .{}); } var words: std.ArrayList([]const u8) = .empty; defer words.deinit(self.allocator); if (args[0] == .str_val) { try self.runicTokenize(args[0].str_val, &words); } else if (args[0] == .array_val) { for (args[0].array_val) |v| { if (v != .str_val) return self.fail(error.TypeMismatch, "runic.exec argv must be strings", .{}); try words.append(self.allocator, v.str_val); } } else { return self.fail(error.TypeMismatch, "runic.exec expects (command: str | args: [str], opts: .{{...}})", .{}); } if (words.items.len == 0) return self.fail(error.TypeMismatch, "runic.exec: empty command", .{});
var capture = false; var bg = false; var search = true; var cwd: ?[]const u8 = null; var fdin: ?i32 = null; var fdout: ?i32 = null; var fderr: ?i32 = null; var pgid: i32 = 0; var env_override: ?*value_mod.MapObj = null; if (args.len == 2) { if (args[1] != .map_val) return self.fail(error.TypeMismatch, "runic.exec opts must be a map", .{}); const m = args[1].map_val.map; if (m.get("capture")) |v| { if (v != .bool_val) return self.fail(error.TypeMismatch, "runic.exec .capture must be bool", .{}); capture = v.bool_val; } if (m.get("bg")) |v| { if (v != .bool_val) return self.fail(error.TypeMismatch, "runic.exec .bg must be bool", .{}); bg = v.bool_val; } if (m.get("search")) |v| { if (v != .bool_val) return self.fail(error.TypeMismatch, "runic.exec .search must be bool", .{}); search = v.bool_val; } if (m.get("cwd")) |v| { if (v != .str_val) return self.fail(error.TypeMismatch, "runic.exec .cwd must be str", .{}); cwd = v.str_val; } if (m.get("in")) |v| { if (v != .int_val) return self.fail(error.TypeMismatch, "runic.exec .in must be int (fd)", .{}); fdin = @intCast(v.int_val); } if (m.get("out")) |v| { if (v != .int_val) return self.fail(error.TypeMismatch, "runic.exec .out must be int (fd)", .{}); fdout = @intCast(v.int_val); } if (m.get("err")) |v| { if (v != .int_val) return self.fail(error.TypeMismatch, "runic.exec .err must be int (fd)", .{}); fderr = @intCast(v.int_val); } if (m.get("pgid")) |v| { if (v != .int_val) return self.fail(error.TypeMismatch, "runic.exec .pgid must be int", .{}); pgid = @intCast(v.int_val); } if (m.get("env")) |v| { if (v != .map_val) return self.fail(error.TypeMismatch, "runic.exec .env must be a map", .{}); env_override = v.map_val; } } if (capture and (fdin != null or fdout != null or fderr != null)) { return self.fail(error.TypeMismatch, "runic.exec: .capture and explicit fds are mutually exclusive", .{}); }
const exe = words.items[0]; const exe_z = if (search) blk: { const found = self.runicFindInPath(exe) orelse { return self.fail(error.IoFailed, "runic.exec: command not found: {s}", .{exe}); }; break :blk found; } else self.allocator.dupeZ(u8, exe) catch return error.OutOfMemory;
// argv/enp/cwd are prepared in the parent so the child performs only // raw syscalls (no allocator once forked). const argv_buf = self.allocator.alloc(?[*:0]const u8, words.items.len + 1) catch return error.OutOfMemory; defer self.allocator.free(argv_buf); for (words.items, 0..) |w, i| { argv_buf[i] = self.allocator.dupeZ(u8, w) catch return error.OutOfMemory; } argv_buf[words.items.len] = null; const argv_p: [*:null]const ?[*:0]const u8 = @ptrCast(argv_buf.ptr);
var envp_buf: std.ArrayList(?[*:0]const u8) = .empty; defer envp_buf.deinit(self.allocator); if (env_override) |ov| { var it = ov.map.iterator(); while (it.next()) |kv| { if (kv.value_ptr.* != .str_val) continue; const entry = std.fmt.allocPrint(self.allocator, "{s}={s}", .{ kv.key_ptr.*, kv.value_ptr.*.str_val }) catch return error.OutOfMemory; envp_buf.append(self.allocator, self.allocator.dupeZ(u8, entry) catch return error.OutOfMemory) catch return error.OutOfMemory; self.allocator.free(entry); } } else if (self.environ) |mp| { var it = mp.iterator(); while (it.next()) |kv| { const entry = std.fmt.allocPrint(self.allocator, "{s}={s}", .{ kv.key_ptr.*, kv.value_ptr.* }) catch return error.OutOfMemory; envp_buf.append(self.allocator, self.allocator.dupeZ(u8, entry) catch return error.OutOfMemory) catch return error.OutOfMemory; self.allocator.free(entry); } } envp_buf.append(self.allocator, null) catch return error.OutOfMemory; const envp_p: [*:null]const ?[*:0]const u8 = @ptrCast(envp_buf.items.ptr);
const cwd_z = if (cwd) |c| self.allocator.dupeZ(u8, c) catch return error.OutOfMemory else null;
var child_fds: std.ArrayList([2]i32) = .empty; defer child_fds.deinit(self.allocator); var cap_out: ?[2]i32 = null; var cap_err: ?[2]i32 = null; if (capture) { var fds: [2]i32 = undefined; const r1 = linux_os.pipe2(&fds, .{}); if (linux_os.errno(r1) != .SUCCESS) return self.runicErr("exec", "pipe failed", r1); cap_out = fds; var fds2: [2]i32 = undefined; const r2 = linux_os.pipe2(&fds2, .{}); if (linux_os.errno(r2) != .SUCCESS) return self.runicErr("exec", "pipe failed", r2); cap_err = fds2; try child_fds.append(self.allocator, .{ cap_out.?[1], 1 }); try child_fds.append(self.allocator, .{ cap_err.?[1], 2 }); } else { if (fdin) |f| try child_fds.append(self.allocator, .{ f, 0 }); if (fdout) |f| try child_fds.append(self.allocator, .{ f, 1 }); if (fderr) |f| try child_fds.append(self.allocator, .{ f, 2 }); }
const fork_rc = linux_os.fork(); if (@as(isize, @bitCast(fork_rc)) < 0) return self.runicErr("exec", "fork failed", fork_rc);
if (@as(isize, @bitCast(fork_rc)) == 0) { // Child: syscalls only, no error paths (all failures exit with // a coarse status code since we cannot allocate a message). var code: u8 = 0; if (cwd_z) |p| { if (@as(isize, @bitCast(linux_os.chdir(p))) < 0) code = 125; } if (code == 0) { for (child_fds.items) |pair| { if (pair[0] == pair[1]) continue; if (@as(isize, @bitCast(linux_os.dup2(pair[0], pair[1]))) < 0) { code = 124; break; } } } if (code == 0) { if (@as(isize, @bitCast(linux_os.setpgid(0, pgid))) < 0) code = 123; } if (code == 0) { _ = linux_os.execve(exe_z, argv_p, envp_p); code = 127; } _ = linux_os.syscall1(.exit_group, code); }
// Parent. const pid: i32 = @intCast(fork_rc); if (capture) { _ = linux_os.close(cap_out.?[1]); _ = linux_os.close(cap_err.?[1]); } var out_buf: ?[]const u8 = null; var err_buf: ?[]const u8 = null; if (capture) { out_buf = self.runicReadAll(cap_out.?[0]) catch null; err_buf = self.runicReadAll(cap_err.?[0]) catch null; _ = linux_os.close(cap_out.?[0]); _ = linux_os.close(cap_err.?[0]); } if (bg) return .{ .int_val = pid }; var status: u32 = 0; const wrc = linux_os.wait4(pid, &status, 0, null); if (@as(isize, @bitCast(wrc)) < 0) return self.runicErr("exec", "wait failed", wrc); return self.runicStatus(status, out_buf, err_buf); }
fn runicPipe(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 0) return self.fail(error.TypeMismatch, "runic.pipe takes no arguments", .{}); var fds: [2]i32 = undefined; const rc = linux_os.pipe2(&fds, .{}); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("pipe", "pipe failed", rc); const m = value_mod.MapObj.create(self.allocator) catch return error.OutOfMemory; try self.runicMapPut(m, "read_fd", .{ .int_val = fds[0] }); try self.runicMapPut(m, "write_fd", .{ .int_val = fds[1] }); return .{ .map_val = m }; }
fn runicWait(self: *Vm, name: []const u8, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .int_val or args[0].int_val <= 0) { return self.fail(error.TypeMismatch, "runic.{s} expects (pid: int > 0)", .{name}); } const pid: i32 = @intCast(args[0].int_val); var status: u32 = 0; const flags: u32 = if (std.mem.eql(u8, name, "reap")) linux_os.W.NOHANG else 0; const rc = linux_os.wait4(pid, &status, flags, null); if (@as(isize, @bitCast(rc)) < 0) { if (std.mem.eql(u8, name, "reap") and linux_os.errno(rc) == .CHILD) { return .{ .null_val = {} }; } return self.runicErr(name, "wait failed", rc); } if (rc == 0) return .{ .null_val = {} }; return self.runicStatus(status, null, null); }
fn runicKill(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .int_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "runic.kill expects (pid: int, signal: str)", .{}); } const num = Vm.runicSigNum(args[1].str_val) orelse { return self.fail(error.TypeMismatch, "runic.kill: unknown signal '{s}'", .{args[1].str_val}); }; const pid: i32 = @intCast(args[0].int_val); const sig: std.posix.SIG = @enumFromInt(@as(u32, num)); const rc = linux_os.kill(pid, sig); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("kill", "kill failed", rc); return .{ .bool_val = true }; }
fn runicSig(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "runic.sig expects (name: str, action: .ignore | .default)", .{}); } const act: ?std.posix.Sigaction.handler_fn = if (std.mem.eql(u8, args[1].str_val, "ignore")) std.c.SIG.IGN else if (std.mem.eql(u8, args[1].str_val, "default")) std.c.SIG.DFL else return self.fail(error.TypeMismatch, "runic.sig: unknown action '{s}' (use .ignore or .default)", .{args[1].str_val}); const num = Vm.runicSigNum(args[0].str_val) orelse { return self.fail(error.TypeMismatch, "runic.sig: unknown signal '{s}'", .{args[0].str_val}); }; const sig: std.posix.SIG = @enumFromInt(@as(u32, num)); var sa: std.posix.Sigaction = .{ .handler = .{ .handler = act }, .mask = std.posix.sigemptyset(), .flags = 0, }; std.posix.sigaction(sig, &sa, null); return .{ .bool_val = true }; }
fn runicSetpgid(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .int_val or args[1] != .int_val) { return self.fail(error.TypeMismatch, "runic.setpgid expects (pid: int, pgid: int)", .{}); } const pid: i32 = @intCast(args[0].int_val); const grp: i32 = @intCast(args[1].int_val); const rc = linux_os.setpgid(pid, grp); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("setpgid", "setpgid failed", rc); return .{ .bool_val = true }; }
fn runicFg(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .int_val or args[1] != .int_val) { return self.fail(error.TypeMismatch, "runic.fg expects (fd: int, pgid: int)", .{}); } const fd: i32 = @intCast(args[0].int_val); var grp: std.posix.pid_t = @intCast(args[1].int_val); const rc = linux_os.tcsetpgrp(fd, &grp); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("fg", "tcsetpgrp failed", rc); return .{ .bool_val = true }; }
fn runicRaw(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .bool_val) { return self.fail(error.TypeMismatch, "runic.raw expects (on: bool)", .{}); } const fd: std.posix.fd_t = 0; if (args[0].bool_val) { const orig = std.posix.tcgetattr(fd) catch |e| { return self.fail(error.IoFailed, "runic.raw: {s}", .{@errorName(e)}); }; var t = orig; t.lflag.ICANON = false; t.lflag.ECHO = false; t.lflag.ISIG = false; t.lflag.IEXTEN = false; t.iflag.IXON = false; t.iflag.ICRNL = false; t.iflag.BRKINT = false; t.iflag.INPCK = false; t.iflag.ISTRIP = false; t.oflag.OPOST = false; t.cc[6] = 1; t.cc[5] = 0; std.posix.tcsetattr(fd, .NOW, t) catch |e| { return self.fail(error.IoFailed, "runic.raw: {s}", .{@errorName(e)}); }; self.saved_tty = orig; } else { if (self.saved_tty) |t| { std.posix.tcsetattr(fd, .NOW, t) catch |e| { return self.fail(error.IoFailed, "runic.raw: {s}", .{@errorName(e)}); }; self.saved_tty = null; } } return .{ .bool_val = true }; }
fn runicSize(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 0) return self.fail(error.TypeMismatch, "runic.size takes no arguments", .{}); var ws: std.posix.winsize = .{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 }; _ = linux_os.ioctl(0, linux_os.T.IOCGWINSZ, @intFromPtr(&ws)); const m = value_mod.MapObj.create(self.allocator) catch return error.OutOfMemory; try self.runicMapPut(m, "rows", .{ .int_val = ws.row }); try self.runicMapPut(m, "cols", .{ .int_val = ws.col }); return .{ .map_val = m }; }
fn runicOpen(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .str_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "runic.open expects (path: str, mode: str)", .{}); } const mode = args[1].str_val; var flags: std.posix.O = .{ .ACCMODE = .RDWR, .CREAT = false, .TRUNC = false, .APPEND = false, }; if (std.mem.eql(u8, mode, "r")) { flags = .{ .ACCMODE = .RDONLY }; } else if (std.mem.eql(u8, mode, "w")) { flags = .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }; } else if (std.mem.eql(u8, mode, "a")) { flags = .{ .ACCMODE = .WRONLY, .CREAT = true, .APPEND = true }; } else if (std.mem.eql(u8, mode, "r+")) { flags = .{ .ACCMODE = .RDWR }; } else if (std.mem.eql(u8, mode, "w+")) { flags = .{ .ACCMODE = .RDWR, .CREAT = true, .TRUNC = true }; } else if (std.mem.eql(u8, mode, "a+")) { flags = .{ .ACCMODE = .RDWR, .CREAT = true, .APPEND = true }; } else { return self.fail(error.TypeMismatch, "runic.open: unknown mode '{s}' (use r, w, a, r+, w+, a+)", .{mode}); } const fd = std.posix.openat(@as(std.posix.fd_t, @intCast(linux_os.AT.FDCWD)), args[0].str_val, flags, 0o644) catch |e| { return self.fail(error.IoFailed, "runic.open('{s}', '{s}'): {s}", .{ args[0].str_val, mode, @errorName(e) }); }; return .{ .int_val = fd }; }
fn runicClose(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .int_val) { return self.fail(error.TypeMismatch, "runic.close expects (fd: int)", .{}); } const fd: i32 = @intCast(args[0].int_val); const rc = linux_os.close(fd); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("close", "close failed", rc); return .{ .bool_val = true }; }
fn runicRead(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .int_val or args[1] != .int_val or args[1].int_val <= 0) { return self.fail(error.TypeMismatch, "runic.read expects (fd: int, max_bytes: int > 0)", .{}); } const fd: i32 = @intCast(args[0].int_val); const n: usize = @intCast(args[1].int_val); const buf = self.allocator.alloc(u8, n) catch return error.OutOfMemory; defer self.allocator.free(buf); const rc = std.posix.read(fd, buf) catch |e| { return self.fail(error.IoFailed, "runic.read: {s}", .{@errorName(e)}); }; if (rc == 0) return .{ .null_val = {} }; const owned = self.allocator.dupe(u8, buf[0..rc]) catch return error.OutOfMemory; return .{ .str_val = owned }; }
fn runicWrite(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 2 or args[0] != .int_val or args[1] != .str_val) { return self.fail(error.TypeMismatch, "runic.write expects (fd: int, data: str)", .{}); } const fd: i32 = @intCast(args[0].int_val); const data = args[1].str_val; var off: usize = 0; var total: usize = 0; while (off < data.len) { const rc = linux_os.write(fd, data[off..].ptr, data.len - off); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("write", "write failed", rc); total += rc; off += rc; } return .{ .int_val = @intCast(total) }; }
fn runicCwd(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "runic.cwd expects (path: str)", .{}); } const z = self.allocator.dupeZ(u8, args[0].str_val) catch return error.OutOfMemory; defer self.allocator.free(z); const rc = linux_os.chdir(z); if (linux_os.errno(rc) != .SUCCESS) return self.runicErr("cwd", "chdir failed", rc); return .{ .bool_val = true }; }
/// Recursive * / ? / [..] matcher used by runic.glob. Sub-second on the /// tiny entry sets the shell sees; the shell pattern language is bigger, /// so { } and ** are not special here. fn runicMatch(pat: []const u8, s: []const u8) bool { if (pat.len == 0) return s.len == 0; if (pat[0] == '*') { var i: usize = 0; while (true) : (i += 1) { if (Vm.runicMatch(pat[1..], s[i..])) return true; if (i >= s.len) break; } return false; } if (pat[0] == '?') return s.len > 0 and Vm.runicMatch(pat[1..], s[1..]); if (pat[0] == '[') { var j: usize = 1; var neg = false; if (j < pat.len and pat[j] == '!') { neg = true; j += 1; } var matched_any = false; var closed = false; while (j < pat.len and pat[j] != ']') { const lo = pat[j]; var hi = lo; if (j + 2 < pat.len and pat[j + 1] == '-' and pat[j + 2] != ']') { hi = pat[j + 2]; j += 3; } else { j += 1; } if (s.len > 0 and s[0] >= lo and s[0] <= hi) matched_any = true; } if (j < pat.len and pat[j] == ']') closed = true; if (!closed or s.len == 0) return false; const hit = if (neg) !matched_any else matched_any; return hit and Vm.runicMatch(pat[j + 1 ..], s[1..]); } return s.len > 0 and pat[0] == s[0] and Vm.runicMatch(pat[1..], s[1..]); }
fn runicGlob(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "runic.glob expects (pattern: str, e.g. \"*.rg\")", .{}); } const pattern = args[0].str_val; var matches: std.ArrayList(Value) = .empty; defer matches.deinit(self.allocator); const dir = std.Io.Dir.cwd().openDir(self.io.?, ".", .{ .iterate = true }) catch |e| { return self.fail(error.IoFailed, "runic.glob: {s}", .{@errorName(e)}); }; defer dir.close(self.io.?); var it = dir.iterate(); while (it.next(self.io.?)) |maybe_entry| { const child = maybe_entry orelse break; if (Vm.runicMatch(pattern, child.name)) { const dup = self.allocator.dupe(u8, child.name) catch return error.OutOfMemory; try matches.append(self.allocator, .{ .str_val = dup }); } } else |e| { return self.fail(error.IoFailed, "runic.glob: iteration failed: {s}", .{@errorName(e)}); } return .{ .array_val = matches.toOwnedSlice(self.allocator) catch return error.OutOfMemory }; }
fn runicWhich(self: *Vm, args: []Value) RuntimeError!Value { if (args.len != 1 or args[0] != .str_val) { return self.fail(error.TypeMismatch, "runic.which expects (name: str)", .{}); } const found = self.runicFindInPath(args[0].str_val) orelse return .{ .null_val = {} }; return .{ .str_val = found }; }
/// 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 client = net.tcp_connect("127.0.0.1", p); \\client.write("ping"); \\client.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 = client.read(64); \\std.print("client got: {}\n", .{back}); \\client.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);}
test "runic: bare builtin name is rejected without a namespace" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); var h: TestHarness = undefined; h.init(&arena_state);
const code = h.vm.runSource("exec(\"true\");\n", &.{}) catch |e| { try std.testing.expect(e == error.UnknownFunction); return; }; try std.testing.expect(code != 0);}
test "runic: exec string form, capture, exit code, env override" { 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 = .{ std, runic }; \\const st = runic.exec("echo 'a b'", .{ .capture = true }); \\@stdout("code={} signal={}\n", .{st["code"], st["signal"]}); \\@stdout("out={}\n", .{st["stdout"]}); \\const st2 = runic.exec(["sh", "-c", "echo --$FOO--; exit 4"], .{ .capture = true, .env = .{ .FOO = "x" } }); \\@stdout("code2={} out2={} err2={}\n", .{st2["code"], st2["stdout"], st2["stderr"]}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "code=0 signal=0") != null); try std.testing.expect(std.mem.indexOf(u8, out, "out=a b\n") != null); try std.testing.expect(std.mem.indexOf(u8, out, "code2=4") != null); try std.testing.expect(std.mem.indexOf(u8, out, "out2=--x--\n") != null);}
test "runic: pipe, background exec, reap, wait, kill" { 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 = .{ std, runic }; \\const p = runic.pipe(); \\@stdout("rf={} wf={}\n", .{p["read_fd"], p["write_fd"]}); \\const pid = runic.exec(["sleep", "100"], .{ .bg = true }); \\const nr = runic.reap(pid); \\@stdout("reap={}\n", .{nr == null}); \\const k = runic.kill(pid, "KILL"); \\const w = runic.wait(pid); \\@stdout("killed={} code={} sig={}\n", .{k, w["code"], w["signal"]}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "rf=") != null); try std.testing.expect(std.mem.indexOf(u8, out, "reap=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "killed=true code=0 sig=9") != null);}
test "runic: open/write/read/close roundtrip, which, glob, cwd" { 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 = .{ std, runic }; \\const path = "/tmp/rinne_runic_roundtrip.txt"; \\const fd = runic.open(path, "w"); \\const wrote = runic.write(fd, "rinne!"); \\runic.close(fd); \\const fr = runic.open(path, "r"); \\const got = runic.read(fr, 6); \\runic.close(fr); \\const rem = runic.exec(["rm", "-f", path]); \\const wp = runic.which("true"); \\const g = runic.glob("*"); \\const cdir = runic.cwd("."); \\@stdout("wrote={} got={} rem={} wp={} n={} cdir={}\n", .{wrote, got, rem["code"], wp != null, std.strings.len(g), cdir}); , &.{}); try std.testing.expectEqual(@as(u8, 0), code); const out = h.out_buf.written(); try std.testing.expect(std.mem.indexOf(u8, out, "wrote=6 got=rinne! rem=0 wp=true") != null); try std.testing.expect(std.mem.indexOf(u8, out, "cdir=true") != null);}