From 4e5f1d8faac313e74328640227d8308dba7ecee5 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 10 Feb 2026 10:32:54 -0600 Subject: [PATCH] fix labeled loops, do-while, shorthand property syntax, and added more tests --- internal/engine.mach | Bin 22470 -> 22790 bytes parse.cm | 7 +- parse.mach | Bin 52835 -> 52939 bytes source/mach.c | 171 +++++++++++++++++++ vm_suite.ce | 382 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 559 insertions(+), 1 deletion(-) diff --git a/internal/engine.mach b/internal/engine.mach index e740337fdeb5f513ba81a37ceff3ae70a8eb3d28..a65ba3e1d901cb5993f4c671237ae0602055e28b 100644 GIT binary patch delta 1228 zcmY*Y&u<$=82!Hay=x~B*eLWvrW?U@a)+)-+cS#eQ$sLg};2j_ur~> zCA?j4)v81!41V^9?+tGE!>9Fk4u!&Vd@2{W2mAdCu__^|IZkpXh*5EdiL{D8M2rqX z0FtQfnA)kJZc5xtQc0{=lhvhJdo(Na{Hv?^5ku#jLsT)snX#(JU1-8-85c;f3Iq&n+&{XOsdJ zVD1>x*Oy;boP>{?p69|h&8zEZMT7Cqs2EO$0vD!hMKLUd#cFRbE)GVM(yPfQqkidT z(cc{p_rqV!^}{z%bL>s@D_4rzXwfvxUCpeO1~)@(B}iMQ4NI(zr47Y0a%5DwZYtKf zf(_tsR>%F0;?-O&QqDE-1~$Qaob4*Xd8`wBsLMeKCKSKWi`tDNuM^{SD!PjK47g`| zm2xlg;HtTDXk4X^=4#}$gm{RJ^Eij0moMU!AZ|8_!*3fhX;7 zBYJ!mTJF+ZSkB(PG*3U#3Xq+`i&#foq@T$GD?sZAYxpk_?quJx4!zbj7hR^o-h3Fh zUQymE4nMT6&NpdF&tdw1>t=NZL7{SHql57W;lldD#yFK;ni?&>kH)#i*Ww>cQY^1eLg|>{rVr3QuC3#hc_B?#c((&?|`VkU+jJue!J1w zJG#_vF*z93A$3s;$`Q5F70srr5n05z2lu=r1%aXofnExYil>y=woaYGPFe+Nzr057quvpB66|;QSnF6YKkCe z+eLzcXcy!nX*TUz1pNcGt&MGK(;rahzGkdV4?ei}oO{ms&Uemx^p-xpq5Z2l3hZL8 zqyhl;aqe2P$_L>A-G;o_2#^9s2+)ck1p6cq8HP`&_@KP!3-}ThBZ$~$JYN;H@nSu| z?-aDBG39IH{8|{qoPZ3WD1~u(Aw9&BbGw{IO|}F4{{|2f=mbt@VRmtZ%?sHw*J=e+}PUO+1M7` zG`8<;)NQRW)tuprYh5$VSSm#9&1<5HBGP=1Wd@NH(A?Iv0cmY8ElD~X>z17Jl5{L( z@{VJ*^Oq%!y4aY!L{`4{R$XO|FDOq}B+-aQ6Mr54p@VJdSo)kWKv_}RJ=UykBvV{d zU_t{GDJZ5CxOkT<&M~LCco!E>cL=p@j!>G<1t>=zzm74F7wExD*jL4+@CaVRV+=A% z|H7UsJtbv7m(Ed-jiO0=C7R0&qlH7X_M*$_lWx?BtQ$L9cbFR!g(E&zZ4-3`%iKU& z_>5c!t;O*t-Q?NtGoj7QUTWsuGfB0gkQDQxY8LsAXkcw95slNv_D7evs^Hdq^Tu8f8O+ytBzb`;&ntTAguPI9(mJ1CeD6+Jo5~^|gM@?q+Dj?p}Ck zhv%OKqu8ee_QU))yB7x8t@`S1ZUk7rTi;x5tgYBD7l+RFvZ0@c6Z6lM^sC5J6mJX{ ybxkl4kx!;in$I<@2_++Sqy_&R5>?(&yR=a5`axN=VV^Gy=@36DY`tGNAN&TSk#rmY diff --git a/parse.cm b/parse.cm index 6dc0f54a..77262d1f 100644 --- a/parse.cm +++ b/parse.cm @@ -426,7 +426,12 @@ var parse = function(tokens, src, filename, tokenizer) { function_nr = function_nr + 1 ast_node_end(fn) pair.right = fn - } else if (!(is_ident && (tok.kind == "," || tok.kind == "}"))) { + } else if (is_ident && (tok.kind == "," || tok.kind == "}")) { + right = ast_node("name", pair.left) + right.name = pair.left.name + ast_node_end(right) + pair.right = right + } else { parse_error(tok, "expected ':' after property name") } push(list, pair) diff --git a/parse.mach b/parse.mach index 03761786629096d061511ba5d0e98cb6e77a8f14..d5dce8b25d314bd59e057066317277fb1e76800a 100644 GIT binary patch delta 17318 zcmZYG2Y6J)+Q9MIE+qw0Hk)jgR3IUZP(m+C5szYo_A%CGc_Rd(m2E!>v}{^Ym{3VusorFu7NL!(ci0u>3oE}bUFFf{`?6HA zFHhC@qN~UGfYutI-Y2ORp%txcA5ftdrWM5r zHMBMAL&j+l+KBo%5LKTA3e<1x-hc5&AYYXP3sv7>rur6}Lpkcpkk9s2C`H*Uf^S0E zNykC~bu5%=`<`82?kK&~_@6rQEW9 zVUYMe)Y#Ti@n3xSgE{f3Xv-XrT7D>ix>AysX( zn0zCi^`s<3XpI=Lwu#PSizkFk+jAt8(dz@sx4p_FyIuAW8?O@^ zdx`fl@m?;U@dn2<-flRzFJZqYONlodunIZATY#r3=`AkBTb^dfbtxkjl}T^2`nOr( z+nyFEw!Oo#cR2PA$KLUjH*kQ%o1`jE;Ug~V-8 z-M~lOSRb(iA9?c7!giP)FphY#)e+7amIR+r%_mH#71A1Qi90s%8Og=xOeBu7TUvzH zC?XaW3AMDf>PzmQFS&WeSL8V6N$nvIw&R>U&dcjK+irZvvF|wc9mm88Pr!DP`^8rO zMz?$X)+u(EQA}L*{{)#x%cIpod`fX^#B-wwMHwl?DHN8%M<|g-VQKta z7wKLroriSQj344!j8~7X&p)HNNRKB9t1X*0TXiF!=)p13n-7k{{MW?(Ie)lYdj02pn)umk<#FVxYdC%lcg8goX{b%pN+Lu4KgMkq{brFz%%=bCEWl0fJ`WN%Wc{>( zsU9oN+k^1{+x0I7KFF#c;`l;t#6`r5X&uGH#cB!3#Zuk@9;OwKF!m9OTE^-eMZ}^) z{hI>CqwJD+%$sqEJR}sW&oZsp#B|1H zehz41S|dtqyIgIdrY+oNTPQ+2$6#YCYu(DAt-Q$?&$F501^S5>6Sgt&Hr6a&V%!e$ z3i(g2a7w&)N@o3rA+5aD0ctkhWqk)(=X+ewL$vR6?n91!#Ph=q6!W}TeaxHE5z-!^ zEaMaUJ7VfHo`23*MW~p)pIAX*`HL=!)K8|8yqaCr)kJuPVJW^F~ z?cB^wbB2BU$V0s6=&e>;_C?hCnyYq9)t>#(a%hbf#G*}72l{s8Rxs+g!bT^qT_?_U z;<_1~$={hh5?wf-5LDfnuzS5V=H;N(n>x-U@0q>`N|VlFtmwn}zUaqw>`!|(a}MD6 zKn%iQ@`@oWeF*1z_PHWTwMOD(JtUyem2)D~fb{UggMlxd> z$ENXenZ~4II?w;cSdGt8?0^=d)vPPoN^v!vwJ@y_CDyKCGA&AL6cbmYebNjr(+qZR z28D?mxSTgIR?DR|@`=kV9;$3Nax>jXg>Eys>BP<42jUhA6MrYEmPM=G%EZEAYmHgt z6SG;I7N#|##Fjin)ooO$No*7oSEGH>9ZYZstGk1hi90Do;~xzuMBGImF^5UDFs(M1 z{$d_E?xwK2EgtUS`n%oBF>xQ)PLtS(5o`0=F)c=GloGc^$D{>JxPZbIu=V0Wc2qpX zu@3UEkm|%DRwNcv-C|x!#u8q~OPEY7<*j3B{neA^1+0JbYJHTAdz9K9<*mbbjBa8% z=T~4QS7a6a9%s(~@caqRJ&C8VI^k(*6wk16&oKQnlp)sBUu@u<#wR^WhNU)=cq5%Q z^5QU_(LWJiN}q*SU3G zXHv13=ld9}@lQGIfEJ_G_Oq4ZE&6IgL;=6=v^jp zdym0d7Oi%OYk7z*K19*R2OJZJxiQ7Z^c6=aMtnjZEsIwBl>Xu~pOwag_BjQJqpU~^ z(;87??MwQLuh^llnD8sM*!Y?qF}|Tlag2$MaX*OT#K*aDkFyBz?b%HDEl!X~oTNhW z0~3oMNg#fr^H1EU#wlLSr`R=dnuMq8U%29}EbBL3W&dTzf2TI_2hacH`Cp_~{%qy( zXDY8hUHRZg06~9q74m1PGdSOfwlU9}_?xMwJWrv28hz3^*36%ynsYpZW0?s1Q9_xJE#_ZE6jtD zORN?8QES#TQ%%p4aaKyMQE9DIjxwPu!2@>uvrq+`ZH8JuAOL4 z0ipvFXkl8dBa?Jwl8)?BM}HUeOR8f>L}ymqh2pzXxRy(+b>n zyHl~zg8@cQl8IhSE_&OtX|vUtj1^~bxwHta)`ta%zTAXHKVs3}pPyh!P>N12J&8vq zoy`DaAcYJf=^%Dw5JeC2_rV|)OBzb?Ln&MgBOYe4O~Y7$8165& zjbJregw}`=Yap+C$TYxK4U0k40(+UxYS}Sa;Me!AD+hJTZ}%^F(%RB4foQav75;R9s39Eo>2l6QZ=*6p~CK$rKWZ zsT3(LqjGUMZ!Xg?ow4Ey`d;BLOZpp=h%1@gxQcO(2yqNnIS=LPYLba-=p?SCLM=?I z&ER^8>$p5xgjTzr!o>}|0f`&QW!%JgM?P^8Dr_??CYnk0;${YkzY~jF87M4vK+NKt zF`MI#eBvTh+3sMh_y>K&Ke=rGWcS3K?8safYaUPL(PAR3? zZ7iaXSj^Z7Ji$!{mBc-v1wArH`;RVi`#ub6HNjoQ}&W%veExv63Ri zDkc|?Q{;c}1pUR6O!6d!8c)$jtR||5^MGJx9S;CPiL`#%8ZRvDxM{Y7N#|##M&m3Y$C@d#*5ADjM!r1 zgEqVR9C=@Gd6D=+FLOxdA?P-^>(1mbg&iKATJqudYT3vztz z@(uAf^!dhLm~@Q($NUx_fLWOqrZr;3qTHpHSo@aUG`{0fh!Z3fCvE(0nnHeH;!`fC ziBB`fX?DmaZ@oV=K>R`>TA0>|5sPw{T4L>23Ne182=P0)#2>ay+D!E)6MF&J zTv^^g5rO6lgp}|n1ZV?%hfM?-(8#4RabprR<_lRy6Z(s$e6dRl(;7v@S_)r-OR!XG zpph+|fufm>-#s&Fb0*1h$tKPwVK(0sGs5&2IZUdBX^kRcEyAQmE^~@#fa@P+aK0^* zme2D{Qs`1dY?KlkEg2(PaZHrEw4oR+ht?=07FCXpfzCV;{MI+2g32nWwu0}wRZzX7 zir5yPx=9 z!!ZH1c3dc@tYPG#K)f-h!6VL$}x8Z($rn-%p7{nVJb%(;;u<^IX{>0S~_Zo&q|ud z4$ccSMz$6o@ZhtZ?_<9C%y2&o5euku0dE#Yoa^uavpx_gZz$gBp||@kBIhDSSWEaca>W3{H?Xgy{AuC9u71N43+e*%@BF*FY4^tRVu(3~a z?n$=C?J4>_#dKN=+7@axxmUA2Vhu08HGwusPY0ThP-{tStS9k$ZU;vTVo_(S=eDS4 zoyG==5YIABY-U$Ca}|fQc%D+8=NdoHa*P)^n-DKt8jJgvjBLA|emkhZ*hv;GpEh6Z z;<irMbdaTd6VLqoQag?IJV5`1l0bk*3F74Mmcl(CIzhVE6 z(H`R!c%1R#TaJnE*gEmOtp%-if?UQAPLIDGk z>R*4Xlh-N@W-ABZY6}*tmekb>t*O5Z<>#wW3lameUmycNe6bh17BM=I&!Qd+-h_gBFh!Mxs0FDo2lLz4(rW#B;gkwXwR>QbChO_x27&nr0=d$smm~s?%;wU!U zIG>Wwr-bu^Erq2JLz*LeeR@>xQkcv9A1QTcnfr!%UwU07usCz^0~n>+dT3Z_mEf2=h%Fy z&<`NWePGoA`&SZXx` z9OY^aldNGyYxvQ%hFw_0k1V&RdF_g4C}=Hx*HVz%Iwn}h8>m>%4mtQ$6+d~_bAPQT z=Xzeg>w}g&3`-bKJ6_fE?p03#VgtoF_{tz}s2hTHwv9|=Y@(P=y#9^N?8IiqZ{`hK zY~j7*If{OcqTIGx4BX20ZDqAvgZwE9o~Hm~8|Sx?*m#LV+ex&YL~c9iw}U)8$g_hS zVkdcwSAu-r}8e48%(l~E#JqMyX|MS``M}e zT=V_hOX5w&8E=zEyvs4;AkW{E>(7Jj5Gy;xN{#m^MtneG@gWn7!}L4M`}tv3V|>i9 zkJ%OD2>rz;c-A$`fjY9JD~0%^PZ_3_pLrCPExD&fvLd6v{|y9LiLU`8&79 zq1H)Fn9NZ~T&S9c`27!m2iG)I0IL@d{gYA|;3y>SoRr4EH2&x=Efhlu+9Y(Q)tZMw zsySavY#z$DWl*HZrg$UFbCE;wIhH&iLiG`P(UwXHn!?6nPd!iasRnLm_=aEzucgHPDwy`mu<9 zEW)ini|9|0{n@Jiq4u`3na3E&JOe{~Yi1zTi9r-1hS13vO5&juF_a`jLv`rgz%V8s zPM_gS?lywSM=<$_P!VcvBgt!=$K>a+Gv`@Md>%$|U=*t!#bn}q3K17D$QaAkjpgEx z4V9u3&TQa9in)+Ujf=Pw#l=kIs3F$I(cc=!$#En!CU9^9$0l%0Oro$!6gG(>#blmO z=J{khE>xG&e+oM=g&lC4$_`AWn5m&+w6k5tVvK3znHI{mcqp_@XSL!=Iyp*-wW}C# z6%${@?ue`DD`wE&xQ^%IMvfUbg_^3HD8y|h#m!_lXNIf@4-s_>gM`IEV-^EvG0<%` z17|aEHezULv78-R&VRDucDi0gDehK!k zvsUbB>K3wW;k|bY7jjFeV*}4o-SeSz^#X%mWUpUhdE0ruoy=}KIKLwlwe6%z z2mk!YcJ1V~xsz+MlOI>wNZOH>Jg8lvw4_&=;58hol89N}(vU{MuCi&-Xd^-22Sy-|O|h=X;;$obUOb^E~&?ZQR+j$a8F= z=g|t^AAx%6kC)s3QO`Q#3aW1I6m^d~S>5YyXq)M7ptK}f5wgwVxRyklq-N7!OQ97x zw)^O(CDDqks6tv%ZoA*z&{8}gIVwymTG}2Uk=Be>WY`{LjFwEBtme8?)oOQ8Z6|(< zFbbqOtp*W z@A_DYEl4Z!qsnPT=cqokebjFHYC&4jJgS0Lbcw2>tx|jF4~vIjR6ea}9o2|fdCpurO|4q1NGJEKrrTe`u@PU9|D<}3AF!y~8jhq%gWE^1!vavh6IE@gX^k+kwt>E)))i2-7K3W>3Q4sf ztq~&DHoBTBEktYN6PKf{Z4*gfbJ$Ey<8{UxTbWpFqii}PGaL7Vq+KaF6P~3xpE1#cR8r>I_Ko>s6DO}CElwe zm*>3nTxBu;W%K{*N<@Z35wU0$vzNWu%L4bhvX!-$t1tF(a32Tvad4ljxQ_i?N#g+h z4$$ua{SGire873*BUdvOS4>>2KBn-vT;dXx3(ExR6KAR5Sd>Op(pIXkxMjZLRu#uca@^qr z=T5kikP-7O$G+v*w;U6vTt3@r?iWMJf04Gm(rSivx!!A?WmAoFT*V)m>L=QtT}{+4 zY~b(2abe=H`h#@hPx_o^FSH=7_803Ef0OHPZqdJ4n(+_I7RsFEkxyLq|G3;u zl$J@Wx%s5v8i_tPwip6SDY&o=As{AykV=6!WMH)q< zQL#wpoXDVHEljIr(ocjaI7GoA3O2Ib*-C`@pq5RcB8NPdJmfH0Tt2ZVkI8i>DkF~~ z;zGo^D7EFYGA)zV$RXC6GnZ(=u4(BOL3&h}Rx5NjR)u`1Dx_l3l2x^2Vo^i_QOpi# zVOp((m6x#c5*A>TGG4S|k)n)TqTI$0qjHkACZVVxsTQWyDoI*N(n^vVZAd8El2EiG znP_iIrA<{G$R|2+OkBu^H(~y3C_&AIy+R)AA~v`?T`y*1L{Dy;UQFAYG?%a!eH{9_ z14>kJtcv@(is_Ah?4?tG`t>J|xRm~vISk~S802nj@eo8PW-y7IhR|^cNyJc+40E`G zb7DBhhLdMFcZ4y5JWeC&H$P+m~(OsZf1lKZw_!pn!#UutzBDt8%RlAPM6PKy3=lJ#971vXw zaRcMU6mDH{BROuQpO{L4Q(4qh`WrWKnZ@-t(?Q(AfLrV~SxsZWZ4UqDoVcB1x0C30 z5*c?esnc}&O(&0-LI1m0Kupc{svMW~FmD9LBW%v2bbE|7KF;w7x7|GA`LuEQ#QEw8 zQi%n;)}N#mPcil>N_d*J#N`o-QuPewh=uHlSmaI`B@a>gYB6;#WQ!^cBlVxZG}&)w3KID=21#J2E$tU30j9FPHT^i+G-kWW2yaUu1!6u$FV{cpeuf z4y%`VscI>-+IklIGV_TI%wyE>R<8wVjS#VIjH;!eTJEM=iV&|b*x1M-H!^4=?<2-0 zc2T@aKk-`BW+vXuV#Vu>+e+T8HRlIh;rNH!^<6dQ0)Y?uN^gsN9}ZS^2)%>0&D&Ps&>UoltQQy;8)#uj*_4ze{(ZG|WL__+CMjrmZw2>Vos{~Gn zCQQ;KvU=#(R=TIY(o$%R5V0t;Wza`Up*8rd;t|CT6~v-*)FrgWFydh<)8j)TQiWS( z)+7vD=(4gs2`Y!Gv@}{R*V9mmJhm$?NGytM`J9Uj5rUzEjZ$Kx6S2)w1q_G_ z6C35kS_}HbhOF#t`Bms*r2vWbTxD64?JmsuCJTZ<*y*Df=T@d77y*rYvV) zv@}{Hn^=^@w5D$bw}es2l{VUN4cl<84cE_TOa8X(m1xKLD8K5+gdJ;6jM(Y7E~Jhw zgF;?Rf6FZ=VX*FvSTPd!i zvlgT^Ld4qjOs0isjeO#Aw2hgfxk zR%fv?aSw%P{A!*;#J%(pGnrHi(rUBlFJ_bDJ_@_f;^BU-ztaO86AyCjG>MHcu{MVt z)55ey0dWP|$INBIxfC{+trw56qvCOnwUdVk)ronmNX)0Y`Mi{jCwLt{!DM0qZygJ2 z4o#Tjv!3DAx{!@qNNo#w>o6A4O)Td85-jD4ETi9Y=6sgt&v9-AR$^7u^VBF_VB=n3 z`WGletfjwriE|ns^e7pYT2JEjbXw1g!+4ojtg(S)qQ=Irb~#r|T+142?O2}k3r335 z_=O@@Uu>kG*u;79Dz6bOgH~AbkQ>#CR(p-Brlr$rn_1Cjl5Xa$#(15=jV+uPTUpRn zCfmw_v|?JLHSvY$j%u~l;yq_;)Hd$5ZLDG&lZZEXF5a}I(egV;l53f?Mjo*!i+PI$ zh_@-iX(zdMGRIDG2}>T{;ov*mI`1&4*v0eR4A%Gu6Lvrg(`tLzO7UO%YC&2fL>yB4 zx!(JkOv|7(vWSaNX32wkpBw0XCUN?J!CDHfc93g%kS#t)(Z)v{6Nk7l#bNr2BNQV( zC6AUut9?d)@wvxp!h`k&1&E`pNDI;$A!6+-`io=i&@m=F#ugi2vm?eg6e*4~(Q)nv zaf0{+H|_}*Ax`#T!jm{fB5|4u#rI4sejtH3L+3NxsK!}d&1cy)agK!NY6@;RkYfG9 ztL%U5_^;F^e&hM?JpY5#f8snf{KfOX@eh=@sd9OnDYrL8dAxkirSd#vE&cX5PjzN!~^ZmOQ9rZ+)bIU%?@T=OUHCL6T`9TB8-Qmga4u zv`ku~HL;e?#99um(T=#C%JA}w{tT~?L9B(m2}(<+HNwPUl|?}!o8uyfRppQ)hvY`C zH=slw#TohZ70ta3qk^ir#lYq^KHIXo0tOXOv1mc!qB=r6*NRC}Ol*{}TP5sZ3CBvj zd1w(eiB{YZ^&G9JwOJCh@+PS=uAL~S0MVKWv>>fk!6X$-Qo$}&crQTjm`Zj;v}MKZ zD84;~YZZiwkY3w5h5KV?|dkmzGYe zbz=eIB5p#XJF)2D&5E)lC_qPtZp1@kE@ptyi$Z#nv^P7_o1%MrdtqQ5eK_BT^L;qq z$2$~bW2z{=io!)d;(iv})Q=U2{@#4srL0Cvr!~UF+5pCgfm|jrh(5+(5*tJ5Gn7Jx zlGnJLOD%>mZWxQP!bkxX1qCvgK6YC&3U3fD{A$mP+}X|<^oE^gutNZd>=;}*upWfAA0 z)HcmxqG?nwZe@`8H?g>#f#ME!KuqVHF@xiAS;ToLvssK4|Dlh#o6B}LyC?2pM`k%# zvw1R`PP2LUGVWu5xS!&*AgvK1)*j$8J-|c{P@H&>T*e%BMm$6gEl6vGh;5ddOOm-H znM(rkFmE8@5eAD#S@mOhoWUYO-w4;*m`5KmpUI6UxP)=(#9A0A}j3x9JODR$;V{);aBA>-`^cO3bWCev9E9oOvk<(ahQR(WK zbmA}yY|oQGyg)MXB3r3RY~&DYYuL#(^j|}osrKk_X#n28h?0Pz%x; zA!2O{6K-L`Elei1(q9YGYTI~;YU#8_9!%qv_^L}O!C^w4uk|bX{d_(*VeZKML#vG@=IKj%aAgvK5 z7R3&g#M()A+4$CLWvXwvPNzsFPTTk$Hidl8#AhAO5uam{bL^1uBmKos6ru%bjWDq& zcBmxQex{J0EqTB%6d`^knfT3?Oq;BJXX5h?e-Z!1B!6+k8h_JY{1c^o0lo*6Y;*Y< zE3bo(*vD6eo|e(f7qAo$Dv3$8Agz%{tR*w4k-~%`$T*Q^OQz*>K9htTvWSfwVk4I^BF`7u z{%A7q0WBz?1>Xvb%Ow_N4xNYvzXgseq>w_2E9Bc{g%lfCMy$1@k0|o-5926dE-j4~ z&3GuKLn(<$NrcFN$L4mpfG_E36|`Ds`gNvXXZm$!!niVGTjaaP3KMfxH_GqEk*G-C z;~n@iMNhs!X!N3xUVJal=uL6G`KnxR@*0;gm(hp0Mc=3@>Nfh3NA%}hf4;0JE+yX} zzJ_NE9_~w2gQJFUIfihlhxoE%hBCRhoJ(^#ms1QQ7FW=JgwKaWq}fK2+*b2YgnvyO z$9&^Rsg=@}s`1n>CQ#4SK0Ynbcx3{jOt5<9fc4Y23hf7;j*$ z#uWB(imz!`9x`L5a&Rh1rgEW-nalK^IsToudj)^mtB}i zkC{vpS3=x5W)|mX@h!%<*2EXb%x1S{`x+osi@f~A#!e41*BoYeh((CG)Hs*dzwt18 z{s^-^;w!EzvUI@*z2=c~9y#Zcb3UV=;A%YKD~wsdG4Ui>#8YG!PdnAT`s8qzwS>)B zLK-cfR&=l}<=isTEXT7nh2dgnNAtLe9-r`!6S+WsKlpE!@d@HgA{566|CUugM*b-4I4 zu-o5QdHhK+Ui$g`{Gux+i?|pSQ5Vo^em~!BBd6aVM)Q_Dltguafk?c=*$R#dAyQr?TT8f`< zz4at{ia!r6Q6ANWR!d_~j7$bN$-j5Hks$v}UNiLBG`E%GPgQaG#Q7?hI`fdvdYYqv z8Vmi2F)i8pBBqJUAd5CGnMu{>ymeH|{ zj%6&q%-<`fHIuYv(_8b!aHE1_6^t_~*(1@0K1Ms98yzT88zeztOh=M*@&}NLEZYSX z5|=^TDyB1qb|zV8RvZ=iep9&S(8_P>Tiw~RxG-^8^AY;+~orqDkwhuA15Ho6gOH_|_jkL@=yKr5#;x)Ezr84$-;+PDN-1+8`y zed2P6jdEh68?kmX{i7^`JcmkR?G`%4BRt<%^waVyVn<@v34oTYAK7sVZn zH>T5PI(??oN6g^)44%*M^K07~9JrGMcXHrPJI+#fkwDx{65}5F+(Vyx=p*js^*oc8 z;!NHcon~>@&*J4ai@SW5zsNS5JjVUx6>~T?$C3wI4y{p6tUbiJxB_BrF2&5Hn7I@p z9wwo9jQ+;s6d0kv2n9OLqriC-IFACYc~mIoGtgK-BJniGjAux;kYo!<=Cp{H+aliS z7IU{RA@LFtFCnp5%6MZr`NRs287uusmRiMtxMH=MNmjF>)qXx|u?wsDIp*{{uU+v1 z1-(e$7b(bT4HK;4ZB(pfhvN8U6+e#Fa(}HQ=UQIAYyFly42-I#tyb%J_gY5*;w6fU z<2!@At-j>%U|Y{b#s-Sn!0X?rVJB)BU&GtBsO7!m6^ed^qMSBb4BW`}ZDh3@{rs&7 zHc^1Fne&@TY`jjQEhO4PBB!nN+e)6T&c0Zr|c;hkN;{JJyW7b;?5^wX) zCU#J@*vYjLyI7_1E|ctL%XhQoPJ3AG9(HOE*L)B6l6a4C#$NJ>_c>-9;Q0q~{duq* zWMv0gsqrDjh>u7tK4xNZh<=B7KR?83jKdr|%&r(m=r2AcpZJWzKci@^kXHNL-epda6pYSu!e_`DJ*ppwG=67P_4@&rx_5Vq^4wgF4?w_Z|^X&e4c3%9& zK;s`033q@m7DjpaTQT7cB*pjw$^5b>7cFD_fpis@OPs3$0e-Q=-m8VEow)#PVK3F=!U8%e) zm3O6T(T%*_sH0n;1v=qkVVOW(#3bEWM0Xb9)PqIzpvWF;|^kSl36xWO5 zL~jZaed%LVk++H>s>o3l=z#7~mJa$cd4D?fXL6@Ynfy{Fzci4CO4|Su8v~hqAd?Sd zVljy4gIM(-CKH1xL|o1oV;DO(jB7tEP@slcfsO<{>bQbpu3%DQICr8L!9;PbiM5gR zAIZ6qBs4~IY&6G4b4-k-u(1?2mLf$p&#QTERohQ;)m01_#}15R2b{*U1LG-Xd>|if zY!g_GaSe&CVWMluBPOz1F^N8L1;pB9`cG!!$?Og+bse3>6b2YK@?6}^G2@m%BXtXf zI8CFtX>8{-iWavqM%=+TV>;ueGtOxS<7O~!M!@2GdknmbLE>%(8uzdx_p&4Ra^7hs zgJ+U(CU^5pUb|uzrBOc_KF^5&pVbyb3wRnj0;*rR2yAt?=m&Yh3E}gglrKm)w zsQ$Ftf#M0)wSc}0SeMO;22WD(lf3lBQxqE) zB5sLFbVARlfwZF9_B2I3O#$K=ii-;qw?YToLgq0RvoY3UCS1&l#S*GpN_9&q%4r$9 zzKo>HcqJ~Q5V4#@#&e8&j#rhjg7>nOtac^2omSCr75D2Zc4-xFC+MwKQ@QaX_sfgi z?8X`@T*I+73=(U3>lW)+rFee>`&s$ON- zYi#oCtZNI;w~*3lE9bWcLbh#`7so#^vQOK1EpFptY~#n1HiUMFB@gP2K$DoanBZ+D zc$*2FcCfA;6tIJh*g-O}lX1o_#=Xn9cNyojn{m4tx0`Xhxd>tp<21gnwwJ#i-^)9J zv5)GF{Um*#r0SO&u*nD5y#uUHd_Yp;L$0Lwm^(v!LIL6s`3{j!3#&sxZh=EM KOlL!WSM+}}C+ds< diff --git a/source/mach.c b/source/mach.c index e71336e2..606c1afc 100644 --- a/source/mach.c +++ b/source/mach.c @@ -93,6 +93,16 @@ typedef struct MachCompState { int loop_break; /* instruction index to patch, or -1 */ int loop_continue; /* instruction index to patch, or -1 */ + /* Named label stack for labeled break/continue */ +#define MACH_MAX_LABELS 8 + struct { + const char *name; + int break_chain; + int continue_chain; + } labels[MACH_MAX_LABELS]; + int label_count; + int pending_label; /* label index associated with next loop, or -1 */ + /* Parent for nested function compilation */ struct MachCompState *parent; @@ -1160,6 +1170,7 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { child.scopes = cs->scopes; child.filename = cs->filename; child.freereg = 1; /* slot 0 = this */ + child.pending_label = -1; /* Read function_nr from AST node */ cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive(node, "function_nr"); @@ -1513,6 +1524,9 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); + int my_label = cs->pending_label; + cs->pending_label = -1; + int old_break = cs->loop_break; int old_continue = cs->loop_continue; cs->loop_break = -1; @@ -1550,6 +1564,17 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { cs->code[cp] = MACH_sJ(MACH_JMP, off); cp = prev; } + /* Also patch labeled continue chain */ + if (my_label >= 0) { + cp = cs->labels[my_label].continue_chain; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = loop_top - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + cs->labels[my_label].continue_chain = -1; + } } /* Jump back to loop top */ @@ -1573,6 +1598,87 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { return; } + /* Do-while loop */ + if (strcmp(kind, "do") == 0) { + cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); + + int my_label = cs->pending_label; + cs->pending_label = -1; + + int old_break = cs->loop_break; + int old_continue = cs->loop_continue; + cs->loop_break = -1; + cs->loop_continue = -1; + + int body_top = mach_current_pc(cs); + + /* Compile body first (always executes at least once) */ + { + cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); + if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); + cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL; + if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } else if (body) { + mach_compile_stmt(cs, body); + } + } + + /* Patch continue chain to condition check */ + int cond_pc = mach_current_pc(cs); + { + int cp = cs->loop_continue; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = cond_pc - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + /* Also patch labeled continue chain */ + if (my_label >= 0) { + cp = cs->labels[my_label].continue_chain; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = cond_pc - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + cs->labels[my_label].continue_chain = -1; + } + } + + /* Condition check — jump back to body if true */ + int save = cs->freereg; + int cr = mach_compile_expr(cs, cond, -1); + int jmpfalse_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); + mach_free_reg_to(cs, save); + + /* Jump back to body top */ + int offset = body_top - (mach_current_pc(cs) + 1); + mach_emit(cs, MACH_sJ(MACH_JMP, offset)); + + /* Patch jmpfalse to after loop */ + offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + + /* Patch break chain */ + int bp = cs->loop_break; + while (bp >= 0) { + int prev = MACH_GET_sJ(cs->code[bp]); + offset = mach_current_pc(cs) - (bp + 1); + cs->code[bp] = MACH_sJ(MACH_JMP, offset); + bp = prev; + } + cs->loop_break = old_break; + cs->loop_continue = old_continue; + return; + } + /* For loop */ if (strcmp(kind, "for") == 0) { cJSON *init = cJSON_GetObjectItemCaseSensitive(stmt, "init"); @@ -1581,6 +1687,9 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); + int my_label = cs->pending_label; + cs->pending_label = -1; + int old_break = cs->loop_break; int old_continue = cs->loop_continue; cs->loop_break = -1; @@ -1624,6 +1733,17 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { cs->code[cp] = MACH_sJ(MACH_JMP, off); cp = prev; } + /* Also patch labeled continue chain */ + if (my_label >= 0) { + cp = cs->labels[my_label].continue_chain; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = continue_target - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + cs->labels[my_label].continue_chain = -1; + } } /* Update — assignment expressions must be compiled as statements */ @@ -1656,8 +1776,46 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { return; } + /* Label statement */ + if (strcmp(kind, "label") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name")); + cJSON *inner = cJSON_GetObjectItemCaseSensitive(stmt, "statement"); + if (inner && cs->label_count < MACH_MAX_LABELS) { + int li = cs->label_count++; + cs->labels[li].name = name; + cs->labels[li].break_chain = -1; + cs->labels[li].continue_chain = -1; + cs->pending_label = li; + mach_compile_stmt(cs, inner); + /* Patch labeled break chain to after the statement */ + int bp = cs->labels[li].break_chain; + while (bp >= 0) { + int prev = MACH_GET_sJ(cs->code[bp]); + int offset = mach_current_pc(cs) - (bp + 1); + cs->code[bp] = MACH_sJ(MACH_JMP, offset); + bp = prev; + } + cs->label_count--; + } else if (inner) { + mach_compile_stmt(cs, inner); + } + return; + } + /* Break */ if (strcmp(kind, "break") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name")); + if (name) { + /* Labeled break — find the label */ + for (int i = cs->label_count - 1; i >= 0; i--) { + if (cs->labels[i].name && strcmp(cs->labels[i].name, name) == 0) { + int pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, cs->labels[i].break_chain)); + cs->labels[i].break_chain = pc; + return; + } + } + } int pc = mach_current_pc(cs); mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_break)); cs->loop_break = pc; @@ -1666,6 +1824,18 @@ static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { /* Continue */ if (strcmp(kind, "continue") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name")); + if (name) { + /* Labeled continue — find the label */ + for (int i = cs->label_count - 1; i >= 0; i--) { + if (cs->labels[i].name && strcmp(cs->labels[i].name, name) == 0) { + int pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, cs->labels[i].continue_chain)); + cs->labels[i].continue_chain = pc; + return; + } + } + } int pc = mach_current_pc(cs); mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_continue)); cs->loop_continue = pc; @@ -1797,6 +1967,7 @@ MachCode *JS_CompileMachTree(cJSON *ast) { MachCompState cs = {0}; cs.freereg = 1; /* slot 0 = this */ cs.maxreg = 1; + cs.pending_label = -1; MachCode *code = mach_compile_program(&cs, ast); diff --git a/vm_suite.ce b/vm_suite.ce index 9ddc0d26..06c68570 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -3832,6 +3832,388 @@ run("default param closure over outer", function() { assert_eq(fn(), 200, "closure sees updated outer") }) +// ============================================================================ +// DO-WHILE LOOPS +// ============================================================================ + +run("do-while basic", function() { + var i = 0 + do { + i = i + 1 + } while (i < 3) + assert_eq(i, 3, "do-while counted to 3") +}) + +run("do-while executes body once when false", function() { + var count = 0 + do { + count = count + 1 + } while (false) + assert_eq(count, 1, "body ran once") +}) + +run("do-while break", function() { + var i = 0 + do { + if (i == 2) break + i = i + 1 + } while (i < 10) + assert_eq(i, 2, "break at 2") +}) + +run("do-while continue", function() { + var sum = 0 + var j = 0 + do { + j = j + 1 + if (j == 3) continue + sum = sum + j + } while (j < 5) + assert_eq(sum, 12, "skip 3: 1+2+4+5") +}) + +run("do-while nested", function() { + var result = 0 + var i = 0 + var j = 0 + do { + j = 0 + do { + result = result + 1 + j = j + 1 + } while (j < 2) + i = i + 1 + } while (i < 3) + assert_eq(result, 6, "3 outer * 2 inner") +}) + +// ============================================================================ +// LABELED BREAK AND CONTINUE +// ============================================================================ + +run("labeled break exits outer for loop", function() { + var result = 0 + var j = 0 + var k = 0 + outer: for (j = 0; j < 3; j = j + 1) { + for (k = 0; k < 3; k = k + 1) { + if (k == 1) break outer + result = result + 1 + } + } + assert_eq(result, 1, "only one iteration before break outer") + assert_eq(j, 0, "outer loop did not advance") +}) + +run("labeled continue skips to outer iteration", function() { + var result = 0 + var a = 0 + var b = 0 + outer: for (a = 0; a < 3; a = a + 1) { + for (b = 0; b < 3; b = b + 1) { + if (b == 1) continue outer + result = result + 1 + } + } + assert_eq(result, 3, "3 outer iters, each runs b=0 only") +}) + +run("labeled break from while", function() { + var x = 0 + top: while (true) { + x = x + 1 + if (x == 5) break top + } + assert_eq(x, 5, "broke out at 5") +}) + +run("labeled break triple nested", function() { + var r = 0 + var c = 0 + var d = 0 + var e = 0 + outer: for (c = 0; c < 3; c = c + 1) { + for (d = 0; d < 3; d = d + 1) { + for (e = 0; e < 3; e = e + 1) { + if (e == 1) break outer + r = r + 1 + } + } + } + assert_eq(r, 1, "broke out from 3 levels") +}) + +run("labeled continue with do-while", function() { + var total = 0 + var i = 0 + var j = 0 + outer: for (i = 0; i < 3; i = i + 1) { + j = 0 + do { + if (j == 1) continue outer + total = total + 1 + j = j + 1 + } while (j < 3) + } + assert_eq(total, 3, "3 outer iters, each does j=0 only") +}) + +// ============================================================================ +// SHORTHAND PROPERTY SYNTAX +// ============================================================================ + +run("shorthand property basic", function() { + var name = "Alice" + var age = 30 + var obj = {name, age} + assert_eq(obj.name, "Alice", "name shorthand") + assert_eq(obj.age, 30, "age shorthand") +}) + +run("shorthand property mixed with regular", function() { + var x = 10 + var obj = {x, y: 20} + assert_eq(obj.x, 10, "shorthand x") + assert_eq(obj.y, 20, "regular y") +}) + +run("shorthand property with function value", function() { + var greet = function() { return "hi" } + var obj = {greet} + assert_eq(obj.greet(), "hi", "shorthand fn") +}) + +run("shorthand property single", function() { + var val = 42 + var obj = {val} + assert_eq(obj.val, 42, "single shorthand") +}) + +run("shorthand property with null", function() { + var x = null + var obj = {x} + assert_eq(obj.x, null, "null shorthand") +}) + +// ============================================================================ +// SHARED CLOSURES +// ============================================================================ + +run("closures share captured variable", function() { + var x = 0 + var inc = function() { x = x + 1 } + var get = function() { return x } + inc() + assert_eq(get(), 1, "after one inc") + inc() + assert_eq(get(), 2, "after two incs") +}) + +run("closure factory returns shared state", function() { + var make = function() { + var count = 0 + return { + inc: function() { count = count + 1 }, + get: function() { return count } + } + } + var c = make() + c.inc() + c.inc() + c.inc() + assert_eq(c.get(), 3, "factory counter") +}) + +run("closure set and get", function() { + var make = function() { + var val = null + return { + set: function(v) { val = v }, + get: function() { return val } + } + } + var o = make() + o.set("hello") + assert_eq(o.get(), "hello", "set then get") + o.set(42) + assert_eq(o.get(), 42, "overwrite") +}) + +// ============================================================================ +// STRING COMPARISON OPERATORS +// ============================================================================ + +run("string less than", function() { + assert_eq("apple" < "banana", true, "apple < banana") + assert_eq("banana" < "apple", false, "banana < apple") + assert_eq("abc" < "abd", true, "abc < abd") +}) + +run("string greater than", function() { + assert_eq("banana" > "apple", true, "banana > apple") + assert_eq("apple" > "banana", false, "apple > banana") +}) + +run("string less than or equal", function() { + assert_eq("abc" <= "abc", true, "equal strings") + assert_eq("abc" <= "abd", true, "abc <= abd") + assert_eq("abd" <= "abc", false, "abd <= abc") +}) + +run("string greater than or equal", function() { + assert_eq("abc" >= "abc", true, "equal strings") + assert_eq("abd" >= "abc", true, "abd >= abc") +}) + +// ============================================================================ +// CROSS-TYPE STRICT EQUALITY +// ============================================================================ + +run("strict equality different types", function() { + assert_eq(5 == "5", false, "number != string") + assert_eq(true == 1, false, "bool != number") + assert_eq(false == 0, false, "false != zero") + assert_eq(null == false, false, "null != false") + assert_eq("" == false, false, "empty string != false") + assert_eq(0 == null, false, "zero != null") +}) + +// ============================================================================ +// COMPOUND ASSIGNMENT OPERATORS (&&= and ||=) +// ============================================================================ + +run("logical and assign", function() { + var x = true + x &&= false + assert_eq(x, false, "true &&= false") + var y = false + y &&= true + assert_eq(y, false, "false &&= true") +}) + +run("logical or assign", function() { + var x = false + x ||= true + assert_eq(x, true, "false ||= true") + var y = true + y ||= false + assert_eq(y, true, "true ||= false") +}) + +// ============================================================================ +// EVERY/SOME ON EMPTY ARRAYS +// ============================================================================ + +run("every on empty array", function() { + var result = every([], function(x) { return false }) + assert_eq(result, true, "vacuous truth") +}) + +run("some on empty array", function() { + var result = some([], function(x) { return true }) + assert_eq(result, false, "no elements match") +}) + +// ============================================================================ +// CHAINED METHOD CALLS +// ============================================================================ + +run("chained method calls", function() { + var make_chain = function() { + var obj = { + val: 0, + add(n) { + obj.val = obj.val + n + return obj + } + } + return obj + } + var c = make_chain() + var result = c.add(1).add(2).add(3).val + assert_eq(result, 6, "1+2+3 chained") +}) + +// ============================================================================ +// TEMPLATE LITERALS WITH EXPRESSIONS +// ============================================================================ + +run("template literal arithmetic", function() { + var a = 3 + var b = 4 + assert_eq(`${a + b}`, "7", "addition in template") +}) + +run("template literal nested calls", function() { + var double = function(x) { return x * 2 } + assert_eq(`result: ${double(5)}`, "result: 10", "fn call in template") +}) + +// ============================================================================ +// DEF CONSTANTS +// ============================================================================ + +run("def prevents reassignment", function() { + var caught = false + def x = 42 + assert_eq(x, 42, "def value") + // reassignment should disrupt +}) + +// ============================================================================ +// RECURSIVE CLOSURES +// ============================================================================ + +run("recursive closure fibonacci", function() { + var fib = function(n) { + if (n <= 1) return n + return fib(n - 1) + fib(n - 2) + } + assert_eq(fib(0), 0, "fib(0)") + assert_eq(fib(1), 1, "fib(1)") + assert_eq(fib(6), 8, "fib(6)") + assert_eq(fib(10), 55, "fib(10)") +}) + +// ============================================================================ +// KEYWORD PROPERTY NAMES +// ============================================================================ + +run("keyword property names", function() { + var obj = {if: 1, while: 2, return: 3} + assert_eq(obj.if, 1, "if prop") + assert_eq(obj.while, 2, "while prop") + assert_eq(obj.return, 3, "return prop") +}) + +// ============================================================================ +// NESTED RETURN FROM IF/ELSE +// ============================================================================ + +run("nested return in if-else chain", function() { + var classify = function(n) { + if (n < 0) { + return "negative" + } else if (n == 0) { + return "zero" + } else { + return "positive" + } + } + assert_eq(classify(-5), "negative", "negative") + assert_eq(classify(0), "zero", "zero") + assert_eq(classify(10), "positive", "positive") +}) + +// ============================================================================ +// IIFE (IMMEDIATELY INVOKED FUNCTION EXPRESSION) +// ============================================================================ + +run("IIFE with arguments", function() { + var result = (function(a, b) { return a + b })(10, 20) + assert_eq(result, 30, "IIFE sum") +}) + // ============================================================================ // SUMMARY // ============================================================================