From b2ee23150ec23dd707443677c1c2458dff0219e9 Mon Sep 17 00:00:00 2001 From: Enoch-199811 Date: Thu, 27 Aug 2026 16:12:30 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20vsc=20branch=20=E2=80=94=20keep=20vsco?= =?UTF-8?q?de/=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/badges.svg | 28 - assets/social-preview.png | Bin 99310 -> 0 bytes examples/01-hello.bio | 24 - examples/02-requests.bio | 61 -- examples/03-control-flow.bio | 56 - examples/04-streams-fork.bio | 67 -- examples/05-io-substreams.bio | 47 - examples/06-classes-objects.bio | 53 - examples/07-arrays.bio | 54 - examples/08-multi-return.bio | 58 - examples/09-threads.bio | 44 - examples/10-taskm.bio | 47 - examples/11-smart-refs.bio | 80 -- examples/12-computation.bio | 47 - examples/13-need.bio | 70 -- examples/14-binary-lib.bio | 36 - examples/15-annotations.bio | 93 -- examples/16-phonebooth.bio | 97 -- examples/17-llvm.bio | 22 - examples/README.md | 60 -- examples/project/package.toml | 8 - examples/project/src/main.bio | 20 - examples/project/utils/config.bio | 8 - examples/project/utils/greeter.bio | 15 - rust/Cargo.lock | 43 - rust/Cargo.toml | 13 - rust/crates/bbb-cli/Cargo.toml | 16 - rust/crates/bbb-cli/src/main.rs | 861 --------------- rust/crates/bbb-core/Cargo.toml | 8 - rust/crates/bbb-core/src/arena.rs | 210 ---- rust/crates/bbb-core/src/lib.rs | 25 - rust/crates/bbb-core/src/value.rs | 342 ------ rust/crates/bbb-llvm/Cargo.toml | 9 - rust/crates/bbb-llvm/src/lib.rs | 887 --------------- rust/crates/bbb-llvm/tests/objects.rs | 91 -- rust/crates/bbb-syntax/Cargo.toml | 8 - rust/crates/bbb-syntax/src/ast.rs | 132 --- rust/crates/bbb-syntax/src/lexer.rs | 335 ------ rust/crates/bbb-syntax/src/lib.rs | 13 - rust/crates/bbb-syntax/src/parser.rs | 1244 --------------------- rust/crates/bbb-syntax/tests/examples.rs | 77 -- rust/crates/bbb-vm/Cargo.toml | 10 - rust/crates/bbb-vm/src/builtin.rs | 738 ------------- rust/crates/bbb-vm/src/dylib.rs | 174 --- rust/crates/bbb-vm/src/interp.rs | 1256 ---------------------- rust/crates/bbb-vm/src/lib.rs | 46 - rust/crates/bbb-vm/src/project.rs | 57 - rust/crates/bbb-vm/src/registry.rs | 216 ---- rust/crates/bbb-vm/tests/dylib.rs | 43 - rust/crates/bbb-vm/tests/regression.rs | 259 ----- rust/crates/bbb-wasm/Cargo.toml | 12 - rust/crates/bbb-wasm/src/lib.rs | 265 ----- rust/tests/fixtures/dbg_attrs.bio | 13 - rust/tests/fixtures/test_arrays_all.bio | 15 - rust/tests/fixtures/test_err.bio | 1 - rust/tests/fixtures/test_iface.bio | 21 - rust/tests/fixtures/test_iface2.bio | 26 - rust/tests/fixtures/test_iface_bad.bio | 16 - rust/tests/fixtures/test_new.bio | 13 - rust/tests/fixtures/test_new2.bio | 16 - rust/tests/fixtures/test_obj_llvm.bio | 25 - rust/tests/fixtures/test_ref2.bio | 11 - rust/tests/fixtures/test_ref3.bio | 15 - rust/tests/fixtures/test_ref4.bio | 16 - rust/tests/fixtures/test_refparam.bio | 15 - rust/tests/fixtures/test_sort.bio | 11 - rust/tests/fixtures/test_sort2.bio | 11 - 67 files changed, 8710 deletions(-) delete mode 100644 assets/badges.svg delete mode 100644 assets/social-preview.png delete mode 100644 examples/01-hello.bio delete mode 100644 examples/02-requests.bio delete mode 100644 examples/03-control-flow.bio delete mode 100644 examples/04-streams-fork.bio delete mode 100644 examples/05-io-substreams.bio delete mode 100644 examples/06-classes-objects.bio delete mode 100644 examples/07-arrays.bio delete mode 100644 examples/08-multi-return.bio delete mode 100644 examples/09-threads.bio delete mode 100644 examples/10-taskm.bio delete mode 100644 examples/11-smart-refs.bio delete mode 100644 examples/12-computation.bio delete mode 100644 examples/13-need.bio delete mode 100644 examples/14-binary-lib.bio delete mode 100644 examples/15-annotations.bio delete mode 100644 examples/16-phonebooth.bio delete mode 100644 examples/17-llvm.bio delete mode 100644 examples/README.md delete mode 100644 examples/project/package.toml delete mode 100644 examples/project/src/main.bio delete mode 100644 examples/project/utils/config.bio delete mode 100644 examples/project/utils/greeter.bio delete mode 100644 rust/Cargo.lock delete mode 100644 rust/Cargo.toml delete mode 100644 rust/crates/bbb-cli/Cargo.toml delete mode 100644 rust/crates/bbb-cli/src/main.rs delete mode 100644 rust/crates/bbb-core/Cargo.toml delete mode 100644 rust/crates/bbb-core/src/arena.rs delete mode 100644 rust/crates/bbb-core/src/lib.rs delete mode 100644 rust/crates/bbb-core/src/value.rs delete mode 100644 rust/crates/bbb-llvm/Cargo.toml delete mode 100644 rust/crates/bbb-llvm/src/lib.rs delete mode 100644 rust/crates/bbb-llvm/tests/objects.rs delete mode 100644 rust/crates/bbb-syntax/Cargo.toml delete mode 100644 rust/crates/bbb-syntax/src/ast.rs delete mode 100644 rust/crates/bbb-syntax/src/lexer.rs delete mode 100644 rust/crates/bbb-syntax/src/lib.rs delete mode 100644 rust/crates/bbb-syntax/src/parser.rs delete mode 100644 rust/crates/bbb-syntax/tests/examples.rs delete mode 100644 rust/crates/bbb-vm/Cargo.toml delete mode 100644 rust/crates/bbb-vm/src/builtin.rs delete mode 100644 rust/crates/bbb-vm/src/dylib.rs delete mode 100644 rust/crates/bbb-vm/src/interp.rs delete mode 100644 rust/crates/bbb-vm/src/lib.rs delete mode 100644 rust/crates/bbb-vm/src/project.rs delete mode 100644 rust/crates/bbb-vm/src/registry.rs delete mode 100644 rust/crates/bbb-vm/tests/dylib.rs delete mode 100644 rust/crates/bbb-vm/tests/regression.rs delete mode 100644 rust/crates/bbb-wasm/Cargo.toml delete mode 100644 rust/crates/bbb-wasm/src/lib.rs delete mode 100644 rust/tests/fixtures/dbg_attrs.bio delete mode 100644 rust/tests/fixtures/test_arrays_all.bio delete mode 100644 rust/tests/fixtures/test_err.bio delete mode 100644 rust/tests/fixtures/test_iface.bio delete mode 100644 rust/tests/fixtures/test_iface2.bio delete mode 100644 rust/tests/fixtures/test_iface_bad.bio delete mode 100644 rust/tests/fixtures/test_new.bio delete mode 100644 rust/tests/fixtures/test_new2.bio delete mode 100644 rust/tests/fixtures/test_obj_llvm.bio delete mode 100644 rust/tests/fixtures/test_ref2.bio delete mode 100644 rust/tests/fixtures/test_ref3.bio delete mode 100644 rust/tests/fixtures/test_ref4.bio delete mode 100644 rust/tests/fixtures/test_refparam.bio delete mode 100644 rust/tests/fixtures/test_sort.bio delete mode 100644 rust/tests/fixtures/test_sort2.bio diff --git a/assets/badges.svg b/assets/badges.svg deleted file mode 100644 index 1d5e7ef..0000000 --- a/assets/badges.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - version - - v0.4.0 - - license - - MIT - - language - - C - - compiler - - LLVM - - build - - zig - - docs - - 7 langs - - diff --git a/assets/social-preview.png b/assets/social-preview.png deleted file mode 100644 index a7cbb50126d98ffdb75bac18479c750957aa6874..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99310 zcmdSBcU+U%);1h<7>{B(qap(cj3Y(5BE4EhAylPGjY^XmdZ>wxqo^ooLJtrTY0{*I znxF_6DM5MYi?)(fB5l^q26!YZ?6M4=_A0E{efWPtNZ8waggVL+Hm{kC~!$U z@aoM#=i7lU8czN$z#B;MoT7@{IW@WS=Pb@CYn)TpP*#yScTNNNxdpzktARJv+1)kt zzi(iuy&4NNIQy?20^Pk`0s?P)`~LTBPMz(%4onFI{_T=Q$k5_w&@FqHzWw8*H{GAg z!Mx=+GqQLNo``B19UXNDwiy|{eZOhc#SaF&+@(%PoyZGkvM(L1y^ec0>MGHwL)3Ne znog%j7PpO{Q2_^qHH?%kiyfz0_5-8(^OY;uq?C2~f>qehe_sJLP;MAG85{%c`TNSN znq1I7{{8~8)ZhEdzrI*x+z}K9?fv`v#R{oxDe(jM{{9ZS?(o#z;L4?)E%@Z_{nwKZ z@^y>t`RBiGe`|c+SPBgK;qMFg=3nyw3;Wm0;`FYjJMWXW0EW!7vtvt(9gmUT)yehq z&v(vl=mhWo_fGk`;~xF~!^OXc1j>~Lm7Vyqd^Y0QZUw6{?v;6(wy?=FdJ?Q)9iFX7ocgWkr{MpHd9k+|^p7Z(tMTngJ z{|k}49ijz9kR5k$=fc0eyXVlq25G6kOY%)hd^-mnFO_{hYb|%}zrc8pbY*vgU8vA# zQZm>j47z-Er6ywM_uzDN1c`N%T8e#0Q$632)$WJ*LJS- z(23pp_k1T*FE=S=?^2xTofvrdZ<-zdJqTo#vUasMJa4=c{S~`J@Z$T2|3`fOyB6~8 zlA8JVChSD_3BDaB?X&)l3jaLiyduynVkdszNxYM+3);FW|G8CwE;}>|K>t{?_JGydIaN=&hLHu&Y5nr zjPKi9>}>yhmmS$TQ=sYp1&;kc>qOITnzIWt|1VC$Vz-Ier2_xXAa3k5K`Q@lg8qYZ zynY_Q%`U(2KS9)Z=R}8gHvPtxcm8!Zy$1lSbYkbaT1uV#j@jS1kzcdorg(@uH6-R2^@ z{p&8O^1UtExio+=h}&(ocTxPVog8upMkHR}zS-CsbU`A&@ZJ1)(|&Q84Li=Ghd%nV zoBlBZZl_)b;&QvqP;7Dc+Mwb1=zOsUroybr&R*nEH|E!BI8)3NLTjriE>0YY7?SfA z8B=5A=F!(N?T&Lc-}Y_|qE|9n^F}#158~>SjP^eP7X51druUpB-i{SLon#j~0w-gU z9eRQ3w@c@^w58{eTdZSKL?fS&iR>8>!SquRiy0IQ%~!E*bcLa}k+5iwwFwPWJCh4I=rhT)UPj5qh4Fd20D8L_3F*vOR z9m~w^D*Lvvy*F=WJ zfpgocll;%MZi-t~U-+L5$_PX`0k&Q^Pa0!Md zQoCCnVT=s);dBI`oV~au`(x3{a{_mMO-E^d^h-s2`WgZ{HbWi^P0)?kU1kN*)>D_t#g?dWpSNq$D|%ltz3Gv) zN@C$-l}sJ9JOoz-DcEeP*yn0Ec5|t{D+*!}#<6dcTqZa`1ky&-SBkRItIk%# z5}CyjmDY;Rt*3L2hL?7gF_yAJyq{jtd}fS1b+e%v>)H&;TJ=JCS6I7;!3E$Y>#XOZ}?5^C5nu%u@xjh9IK$RUgh$7aT^Y8 zX4&o>fodbqDd*&Fx9@kTbVY(^zgs5`nts@S!<3PTl|zHZ$5W}TT?lVb*QNF z;hCaz1s7HhO9DX?$G2-9*Vmh~<*!Kp6RYEW!G-2$Z$5W*yI;_^Mg41s4lj~;PZDh7`N%CcZS_k}af z+(RO2R_*2EZBbBz6IR_M_rXNexGxCEHEQbb;9I|W@UqcdhmMo8>v?j4oYQ;Wb(VBR ze;WZy;Vj_ozwwsMhbbp4KrBkUh-{iYBGCWV2C)yEpzWR9$gifuXzf7AahCGma4jLd zaIAlpUUD66u+}ZI4vL)qG;$qRMM-0ESeR4jXAXgjF;lKSoY&+NEUbr_^~-}1x)mV# zEYB?dZ`NTGa$WY@-nZeSQCiBg{Iy>=At#5N-k)(3O&tmQXgtM*+mymF&l5Fn)lSo@ zb!#UV^kh_vT3WQ=A6uCcXDWr&dvjZnTN^=aPpl84itgx3$~T3ePFR^-kuM%$F&+pc z4L!@fII>l4O#Vny*`DVs`=_fHj{z8bk_SE=DWm;Zw`eXl33slyj}Qd4A6=?)LRr%7 zoF=qI41dI^eyzwDPC#gpW@-=bC#9yc2h@e3Niu`s720Jsyv@u~7?%$}mzav3<4J>x zq)%TWN+ty!Je=36>f*yii`s(RJ_Elr)3;}P&;M-pmwjO1lf>&6DG`oymsqRq=iIOe zqX1ujYNF(k!KqH?mi%B5m=_PF+my8OEVQUz$DN)SOjAuoF0O4JS?U}7MM}#$q;4gV z3ujU=>+e5cQqf))oNIYwQsf1~2rmR49X0OCT~|PLzU!IoKKPNH$McTty*=%VHrqyH zKN9%J`p|iT>Xo&B74H_^{h$A#&v=x^h)_&HFSv8(=6T;j1FP>@9 zhJlfG%rO;a=04<7!pQ7ag{!euRp`U1lPRa?2J#y7mniJ@rDt}qk&{zf5(=>qGc)vH ztbA%k+9L_@k*}%X zl!GmnVLq(;@zk5yc>qFY58g}1enX|pwy!xTq7TGCR{|h85~x=*GI-NsoqE>=j*TKk zp5~?2+Cq?FJJwIJLuItzLblclOuKanITsKbuMtSCL{0x7q*Mz2_ETAu2^jh4a@PJ< zZo1jLzYQ?qH{RK-jDIbPIuOG^yaYm44GO()UR2KT_J=Qz#YP7IR@!6V>Rm-k!*j{J z>@HQ9L{`6Hf3I%3Qk`Q>C8y-gRH`t}TUTXH=M^)-!BO9}#TMcJ2`4ze z6yYVRk`IB0+lPv-+ZPnbu(-C74IZkpjs}kIVL_$zQEVfn`^C^_E700s3lIzNDe5=P zfaHRWZR|j%kLjXgIWd&@!P0QW^8RQYVT;+4UqN!oJXR#7F>! zw1Dm%<=AIOr+wj=KSY?Vez4MeVYRF>^YTCtP(+v)+GUuVq$T&4)jJXxQWOET89GgP1Z~ zr28n^Bg45bE8MZmTVL@?zd#2jxc-eFy4f)cg$jmMFzp)C36Fm|lPw(Tr0AS;ntTMI z%Lsl5CH`D>lls0$^dz%AC1M48)OE`lN;3Xe&E1w9OlzSyI&M7 zp-o56Jt%4sPm5XBg0&2yHpmsj)wi49jD{|8M$VacEq4UBs4k{I3ybK9%G8v65G&$O zd}o7tpLirJ{40)d&l^`AIevf;Zd!+pBoRQeQ+EjUWka3ON$WpqyynapL z@Ooh$USC6a)O|UoOcI5^z#te4BMCnS)s^^_y}=c6Xf^AAeJFRpRb4l$bzh=3Jw%NSS&KZheXyZ*2c!YWZk+aP{P6luzx{NfB&M6%F~tIu`ER79gj} zNoRJjF7;0#L-)69cFWp{(GI7=vTd+Nt_$Z|Ge(Q_HIAe#HaE8;W9l^`M&Z6)<(JL# z*k*)*^=2RCB8Xxg?eW!XF-EvS*M=n$=0yY}h+BbkN7upHnXL$FAhr2CRCfI~ahf2G zu^q2aKq}1RN4y(qTn3dbU1Bs{iO<3%&~0U3GG&U=W$AOJ#qz$;uB@IJ(=@Chl@&{> z(dRINyebwGpCc+unvjP+AJc{F$O~}CH?x{CiFK^6G7uaL>`wyINo$K7LJ%1O3Awd3 zJ?uzY(!M*6(BbsrY-g66h9nK`>o=d%>XhdCb124`wJraY=@H2e5;;5jVi;T%-`iJ|Hbz))R=9X{hN2J$r*Nc~ov= zaYPu^RD6?F6Pm7nvTDWDbYtj3atR}-V8wo-cih__;q`cL#GOUD$>s0#|MH&RMH zGc1P1__pxI5>bbATtC$}x)!gUMbwHH-CW#$OcDI+XF|+aV3#+nXDUeIU8lzRJL$dA@jB+~E{A*6?4{RwziQJ8x zNGotr{n}RL(KQ2~fw|@!5e;pDQd%c1YF&z?ChfiKLe_Xqeoe@L3bSS_g(P?j>&9cs zF!mh3XJ57Me0iCP%-`=&Hp(_85F$-#+4Fg*OGgh0>P*AzTZ#ubU5PPC=WSfqx=3xL zy@+$v&+n{7LlLdc?LvL(aEena-UNU~xepLl$>=;NEwhc2&pmG>_~X#0x?69 zq8P?iB(#$0y4i3XDJ7qN=lPNF1O@oS@CW`-DX@9AY@pZsi_hIZL0VOdMSHVU(rig^ zZeg~7%DTI`J<5yQ5o}l-bfa{z16_dass`5v3@`QgS|O)P#R$Uj!fq|iM-&Jhcv#XKp*9jXfIC={8>}TcIt+{s6^)=jwW*%iXwq3o7 zxfNw~WGG0dv^!PU&AXTLcbpr?6UoO=L6NZwXLL{Sgu=?Hh{gOwOwMSMaXYp+ELRb&quwyDKtGi>k7-3)9 z`bjsj52BPeT)3QC$u9}+$ouHO9+ua?HcA1tE%U0Tr`jtG zH^q)svX4F)oSM!5_z$~lOUEiM@G&K$M&X|dhYy=p+B!!YTcI_<_%&mmV_b<`ZVhLy zX2^Ni3#EKbET2@ig^Q-37P-_tko2JI)bXyP(3KBCT)1Z0I>wu@93-dKsgeh-@8<`W zTEgVdjhN46!!iFB&n$W#L{xdakfICD{&d1K@T}8FhR{*%J0IJ@B7`it8hPV9Q@Wyw z*Y8{9?I#LhcdzqB^?TZLBi` zCwx)A^T`ApjF>2f`mAB6-7Zb(H=Is5pWBcJ}3_^y-aJ7?k~4Dzf#+T z)GlLopv4AP7S|y*@F-`@Z6-dj`49n>uT!z<=`F4O`N`QVmF~)s*+fpaSwI`YhuT>c zCFICDOz!

tT2s1MJ^bb$o1Kz2c2sVo|;k+a!yBu0M!{n>zelNMb*^!K>_c>Bg34 zuYAx|YT_P5{nke^a!h+`WRoFQP;k@7y#0J{D@q~VKDez3Dw^UP)9uQ=+w6|=LQi)X z#4aah+h@_RD7JQWU{rLia7~y$)gdP$q}Pxz7nNQy9dwwmIoAwp71WL7PD71?&CImbs3&=YVUw`OZ#)(ijuCWJ_8oe=;I$xBW7ay4JhU0pZHROCtIujRs{;B#nARDGGXilO4*0HNP! z9&?J*^^)SaX-ctnMR;!r)Id<>J*(Wz6krQe$YKL~TTuKBAfW5>GeDv@AAYt3dBxN7 z%ttgC5KXNY)6-&}6%lo-ys;1u7K0!u3NP(T^psGS7p-u9n5C3`TH6-xh`-*S3K}q= zGZQ4e#{A-^P)=j&^T;&r%MDb;#UpHBWo5`&APNaiXo~vzOauRjE@$LyxA2*or9p~+YMsp@mOzkAsyoM z*17ZXrIy3mzo>_PHrK7)dgQ#pA8t%?y38|v*iIgP`!Y`h%$Cd)2jnjOcl1c&p`ta2 zb=py;)-;XPOW^b=$tSyxD_sbxISVfJR6hpNRhw=+f+9;iFaT3aDHUT={g%%{0B#vExoM3t_IE0h zYYkI6DR@v<3>4FvID>YZH@8db+`Cz+*v#e&@2DM47cxaHawdvUP$fu~UiX%g+^9vr z4{rMD`9w{<6kn${J5x5MeVTEnRAEE-ohQ$aW(MBTR=M7lMx$NIG?;OE z)x;Bb$21l7ASMdEQKdsjW~iAP{|+h~u_!ZPe3+(5=*fl-%ns8{f|+-T%Q-#PVHUa! z!}boLLj1K-{gcazEma1w**wa4Q7tYZKu6=9Ayt8L80+)C>bNwhvXo8$9P!y zHyrqA`y}2>pR3sH>9fcFE+EL_wQ1Cv%$nRt`eNPs5o|&C8N$Aq!-$qWFo*T&Pj5Qo zHj=Xr?Sbaq;vJVtp@;KeObg08pLWZQC=7ajOs9E=g&^ZpC(4F;uzAe#;EL(j;qvxh z$FZnhG9+NWKG9$c(TT`_jsf-zX>Xo<@ZQ7OlMN5Qq+{8tB1`nmRt3?72(34iF-Tov zTPJRq&>c6WanofeZO|v`Q6BOEM#s0M(9|e+snV{IQe0jAhhftC7~X#>d2S^chvvL> zCJqKeU{i_G^%^tgwINH-nuwo&epkKsRu)MCFpZrWKR=(XMCh?+q)gm=GLI z&{9*+BIXG>mR;;}Th@WWgKr7!zn%@q8rXmG1si#oqbFlrQX!t*_V<05k(H4V?Pyee z%0?)o{GcQB$YLFWUZ^4Ft?E(h=eT2jDiQBNYKL6dG>l&8uz$)q3M(53^6k zE;0Qhd#jN2JkO$%<{m?@#8Y9M_o_ZjU!?g3H}xsZ$|9JbJvA-%+#OO?_w2q<_lv^M z>fNr#kAY;$NwJ~hxx>L>N>Zct8risGeE;PBGHdJJ(d}w`X7KjIB@!eYJ3YeN;aN2| zfAC1I1pt{mQ}1jevxU&%H@>68{q(T$2hJG3a8vwXBskJ|G^awQ!oF^gvB$O+dmIaWTknT`)@Qew`A}k(m@Q>l_ zs@@y&8#xuEq6{ZROS%*$z0OV%My!UVgHRwTVyo z!bWf`CEZJj{G$y=cd#=s`Lw4*u{L#UOjIXDYolYTxGo})y4Xo}3=jA`y{-pWBmJnY zez&cYj_*fQ*O9YsJM+MD*`XZazd52;6ft49O+kq-lw1E$pMXQx!RiV4m{(_pXehBvK+4p#F$y2p+6W`SRt(No$3z zz7et{T~&LfE5N00*$eS8*t>7ue9m%{pjR8Av{+U|uxy0{EJZ@ZOo{78E&WjDT+rRG zVU0(G{jPRZnKEYt)Aco=L)F2hOq6;@a^%HW8 zKdQ!Tb3z)S+i4bnBg*@s0b>hNb*3g9wGOy~ZuQJIIuc49Pps5F5B9&)A;&Gphfx>g zNolB4LBucIs*sfF7^mf}Zq$CsDSiQL&n5~bb_FSBnq%l_gnZ`B`NE47Q1r2IT(FY)b(NowMHeKTVvqlHlu{nq6*|@7tvP0n3pLhHQuEr(5U$kJ99$hq zlh?oXvK>>@VC`Vl6`0;Fn+E~hNJoUKg+gPo9BNZCX> zTGh`rSU*g5ccZ0Ez-->Rdrabk018*t+50YJe@G4;&(#P&F;N z9955eAg_6=p-&rmxY7~dU0N@5z7fL!@@K2{bL$ewaSr7&EAmoMlIx zs&%x&g%)Yio}%O;AYJuW=-S!B$!p1#L>`QSLo-kz;jk5de(ZxoM2ih=F z$%*>eulP_nUL~3SFw{_6Py9@_P|0+znLmw%U1WUyH2qYMd%k}(S^}!D*@29-^5#q& zMTuc%DROuS?k;OG3CeltMSCCIOw5h&N+Qf#Q2v@0keom%Yq#D*$e%Blw%%?^nYlXz{yRm)&I|UP1a-z`nR;yt| zi6eNmV=~gaPnsJMrJxnj`cg>J?&#t8%2BabYVwUJv@tpoGkH23gJSb$DhH&<~-_J-5|3qNzmuC8J}za0~I z{x+ut=R?L#eWRB>|miyHr^ec|p%rw%y5lr#%1{JOrj5RU7B)ryl-!^S*S z*A0&lGiXrj=FKwFm;A_BHcH9mH>gULr zq|@zO^#jD#eq7{I>CrH;;fNZxbjzv4k*ZHYnK5=^Qd;IAB$J4^Nm#kG0EB9rJGXG^~&@N@W$r@F5qYG!2^nV z9fPx)Nel1I*=YIUB5_!vdcQ)%N*9^v&Y6BN8>`$-HkYxd z2rXCpwOwhIh|&pJ=^KuS?-{usVACEaCkm^%P}A1Ms1vhuy*nn|_?uy#jM3daT2+Tu z;Z3UH2--?REy_VKSU9lDgOEhL2GN{-m4F|^`BHfGaBTAQmYvTCJHs9Iy!29n!c?81 zYFx})lGq%( z7$4PU{l;+AlT@2aM;)#5k8$pmvZ{9fQiv+zHRgV_a49HYI`LdARshz*=(qFFzeQV; z9{-d_NHRG3dh3~bP@9WWtm!gh=Kj%iw7PmGEg5Q$kDzWmhUv_gT}3WYv8OEOZkYTw zV+-P5-}CMv##9#8R*Ha(=ZP67%V86tVWK0{iDMXC+Q@ort$esgsaUt=ta{tNqgpEi zG?KW2HiJ4z!M$ihe26gZsJYYH;vJGbL9NM0W<8Q+bktoz)e2HPLOd)4Bt~8gttnhd z#pFYaKwm?Jz(3Y2pS^;L@STn7rfW(hi)(kUPbb7`TK4Cqr{0+td!GEOXyWbHOSXDE z4Hqcd;V2<_qaWo}S8%5jm9Q2HnPu|Y;ZKD6jZ`#zAINflV~}-wWzDs98TJG^Bm&>8dT&M5qAhdi39NT z0Hjt@ORIW)K%ZJMv7IR~1ra345Lqh~AJS(|y~o!SG|uRatlv~b%(fP|JgaS!G(@O# z8wn1^u=w<`w}Nge-TJ+D3P3E7)f1`EeA2U?=U$_~vZ7r8v{U-6oITH}dI zO@7eoe=5NYUETUmvh^Vd5L`HeHS(#VHk*nr$4_TR*gv5aQ#x^)k;&6ba$_PS^2VEf z-7vnIVjJA1ajQ}<*6u7JO)h|)Olf9p5o`Qtk}gJ|)OYE{uyA(OQN-Q*Xah$GrH)!8 ziK+WleI9ovoivgCCeUH-@z(n1e{SX+yypl31i8uctp+`*D&<-4-c1tP|9(fL zLc6j8gu48uyDkm4xI&*!FJ2yHBu^oAF05CxqK$!&H0jo5-f=~lo*b1lWa^8{hFX}j z%Y(rSD*MHFy-UVR%lJR57MFy_YTp@oO^9q6h0qrZ+vS*6G7uIdfHc5dNUxO@s9b$_ z=9+9mT#=mc4ZKfli$mCenKJFU{=t2`Y?_rKd~5Z>8=m7%K;2Ax5BOR+YlL(4!N~?z z7k!=s~`M1D2u)C+d03zA$|lD6uZaqM@WU$d@eE}9K~PHxC*V?P(SQb zkY{(K12lp9IJETzNNonAfzTTKk?_oOebb{Rd%E$8&f7Vx2k(!vS(U#Y0Na8t{PbX1 zkTuv@*2-VpepopXn_Ruoa%ltC!-p30IP&47L?nD3pD3E+2qjHpJCbBZd+b8ji{q&o%un^@X13K1`fC9XAyLTsUH?B zLDPxNB5Jb(e)*E2=Lps=|G24t+WRx?)JdTB109$*#k** zq-t?^K*d;;5oj!DE4YNnY{$r2Ji7O-D1U`}7AVL6mLOqWHpW@+@$Euq zT_IknSw`FUPCPwT0`+%^${!~6pI48dpXsZAm-I}>du_(aq@bZex@WH%5GWEo2C|!G z{6^Qg;B}$BeFoDPWep$5CyjztOr$)4lsKL?_LXkBSnGtGe&pYrw)$3NwALtnIyX+y zhWAI0-0M6Ik9u4`CupWKD1n>+@|@;|X?Ez{ly=P^eF>&-siB~oi3BruO8of+@ueak z*v&>k@bLPOyu)I0xkK@iS(6Pi#mz%R&Wnj@N#j0d@TaUl6eBddB;Re1J#cSE{fJ?n z@f91_4TY_O9&#R$0>*J#`uE`!^!D~amA>A8&yGs7x!-WSC>#}nH{Dow?pG>`>oK;| z#CZ#lFl&x^L==XEewHU3FgYOC%1_5`t)#?eQ|P`(R9CUrfqU!U3|dmv9YJ2s9iVdR z9^@^U(qr=b(h7GdV%<2ZU@fwX%@*J&wtEv)vX*dauJO*lp8CuuQdDa!yhLb z9<=^UZ#mf@M~isPY}5uKVK2l4;`&=v3&$4LLs!eNDdcq<#w5o|)SfY_~0)mY;KH7oAvs)hpx6X$K{JJO1xsV(7f}ZGi31H5${eCw; z<6KflV|9kA>HHYuJ}sh|__cF>jln;wgl&3)bv~`S9JV)b$WC5k)_2dDkM!T(5R*&{ zQelJ?#TK*gP{a~X#B^MldCsq3fhL&$toU(w5L8e{;BS(}ed&Rte9$Z>c&s z_^(=531w+e0`X3`Tv&&_r?{JP>X+LTJ(RHN5Tp8djA65!27JyhG43`f{w?Tc;!vPR zbwaRtY|{J(0#t@uu9LnzWsbAg0H4Rs4pLjVuq{M4;yH9UUe_m4rbc#_miw%}%FWyL*)H-1ePN!3b_uD_j1^ zwuC)hVik^IHA_`ed;^iw4@xa+JU7=fimVO84WNOOw-7b35`mkGmh|%G^5$TF3Ysn{Eb~>2~-5?{yHRa9|wLHa} zMx*U225Tc|oNB(P;&Z+kStwzJ!P^TJLlR_VhfZHl_2pq8lo~H{Zer^L5QIZ38Uud! zF2amiC^%;5AJ(VP5_qjrc+jbbMtBUn7W4CZ5VE|3EZ;~LDH+NYYg7^4Ape9F&yq2W z>Px)M#cJk_S{+sfUz?3P>5ynPZ}x;%b=sR(w;bei_F3UYmTr;_KGmhs$C!|wNEvg@ zltBR@w^01m1OuwOj*m{Jj9;$@`=D+mNS?T!6IT;q&T)r_S>l5s&kd+oWokQAvXi={ z{|N)qVBk8^Fy@O7^HDvjz+r4IBB19U`A2x_^zX7?-fmEc5vmI?%_BPJlQqn9t{w+@ z!oBv4)WpC5?_v88C_eOPM_dA`{VzW3dGq3uON=$z=q_Jk-;KFRHz)GTAf>Fhcw#oX;BuUfGg2X=oTx8%HZv%OT{-AKD?PIc@(|S zAvL9;H0;(NL>d!?)E$u7T4sldc8^Xet$m(YLYf%>(mm~1n?Eef4q?1M z8k`~DO(%+NHoizuoX@&}iAmQ5cU52AR{=JjPwCI}%yI*2YcF~Nr&Si@Gmic#S_x=^ zy5sPyK_Fk1Dck2h1tyd_O;Jcq@jPPagPZBo)mDM|PneU7{cg25&o;T%IMq;ERnv!f zJZevpj5swt5P4e)!iqGaU{30vU}kj{<%Ms5R&jg_L+ zcF1NeJ3wUDMpfrCztAM+EYm}lxP3m+kG_C|Tg~NabPl)2zx(WcdG9SJLFr?7gCKS2 z&_j*UBzud1OSKOYOkGL!{d}VseSe26J*%v5_Hn^oszSh;<9-C=^ilc@*{~}quN%u+ zMOZR>EX4Nseh7jdqtJ)MJ>5K*L5!RW^UrTUo;hlOox(hi` zN>ETmcX^~H;e}XqB`fe+J?o12tIOtsUHF+PHnzDp)IpB@EH9aQff7b8(30ZaT@H*NJoEJYn&3#9%k z)$;)n($vCZ?0i@i;4y>JAb=_Wo5gjY*!12LspdarV~$GcvHE2@sm-44fjSDSsi=og zuJr42pHFw$rvhFTzog-vC>v~=py5V{CNHIp3>jraGspOHJNJ zDmJ29YWWMWlVC~=D$~d#)F!rR1;8)5n%pJdKZ0le1lhLeiiAM-PjI=PbCGqDJ)zo%uCfPgm3RCUU7PP{0lK2)-WMd)T-uPT97X!>+zF z^d{qw9{{(tEuaXz;kWJcn}6jAtAB+IPE~X9x`ko+-XeS6@gX3$lVzk1%>Om~V_T+#MG6`}lVe_c(YwDA zS}``YNggV)t933hL{2aHe-yf*dnfxPRKmS&f9PADeeu#3dscwTWy=z-%*;yo5q9u! z1u9uJZLV*Q<2d()wWn81Dq&SkHcK88he*j(5D6F^5JE1#$n$q*OmW|CfUnwuOskfS zC)qmlOE=hh3O1ZMrHG-Go1?G#5Zd0g@FlbM;!I-2-B;T+B$3*HBIF+Wb`&J3%0L`c zNoDphg+$NlAZ~VDxZt*>*qj!ck?e!!M2i`#W3Tq*oIm{V0Komc9DsPQnz8$*X@I~t zRtkr$Jst*j+Rph27?UV<^tB2@WO^Y}jgz<2VnovoZIn_LL<9=h&K|z) z;GunbwK#B0#Am6$#2|U8Yj7wCB0lJheXeRd@lFgq`|_lBG6VXItKD7L@{>JLjRiO@bs`HUh{URKVdxlN5uKX80VhC%=Di}RRyrC zRo=~!T*}m9^_&RNOY4`xse7Zifx9J&Mg=TBkSEPYV3MWi<4{xnfmV2a3ZUMbQ45ad zg4D@3!;9~FqqsrSU@-Fe=j6MCE6#IWTZSmvv`34;i6^{uEh6rS_f17Yh8&CY=Rfn{ z0F)gFrY2RXxcKZD$quRnajLOJ5uQ)RY9$9t>ilYTdn41_g8}?i*q#NpRFiV|>|X7? z%`JfxGL}XN1V?T>MIr0JZJi5=zWa8L@B}jLb{X4VThQZ8BFujGRY+Q|Po4 z_|v`)PX+9npIE)-@fEOp!p(JtRF2nia-6cUiX{zzbJG(r1_4kIeTd6H0C5Oyd-m6Qd(Tw^X6x$?7QV z*HA|ThpW=Qy*b|5UhMu7|AkImW9?Frp_PVH6O&&D&xm$0dcCYWo3D&RYY(oOqJiw> zG$G0@vg#sU)V2?oqC_d4vhxK6xue}$RY{`9L9IbR3@8y1=LbJt?pXvH80x?_KWm2r zaVY}_ARG&>5!u(VIs5#+X(B>H+hp-eMk_D4$oR^{06=XEHI#xvu#1Sk|IGmG(WXF#honxLBroqKfsBm zb_pH_xeWz0Uvh{sp!QRn*^3QC($?0B7~L)Fr14{qyG0+ZeRP5PvR*(;b`cXzm1#^4 zrpReVajWu+=`)7X;AE;CH+ULJh!WP-!jrlaplpJP@7wwq4BEZ)!K2 zN85q0MYO`mW{ax5x7^Ke2xc%BN)cSUrC7uM+S@D(lN+ zOLJLpBdxW%C5Y_Jo3{oXMH6(FU2+`8=*xvjx@#D>!xTOs$~UM`!Q0=_8T^4|N3FuV znnmRD_$FF~;3|k;pVq}D0}*1W!|o}8XkqNasJps6 z@D}@5aIDIx0y?XGz61 z$o}QvD$h*>`P~wjH13oSN-p9{hdvjue0lI*7T~v@yWf4%PS>fS=Mq&6Tt{$vHF18n zt*1)bcxbxFHBJ%nDF$lU+`w(tQXp#6+Ma_yTk>rId2#I9DPe%+SY7&dm>)P&qwHdA zzm>=1n~yVf*m|2amI|8_<4;(!#(|NSL=SnW22#bMfgSBJUP0dV_5=5(TGfkta=Z<# zKrGxGb3LjqeQhv<7!GNB8Hj(!l0zLkDAdMr63=De`x6Bb1&6WaxNVS+lYkK#v*cihJHP81K+DeH!35a zAW48LH%;?Eb6#@q3b>q=JZ$-d5Eol6gb^68_ z@#pHQ#i7+e?q;MYXe>uQvfWIfT@%(-h9I`qR7xROa56%LwSYy{#hWE~-Uk8IM{9RE zFm-c~q!_5ueUQy*c(q?oi+jD)DfpAEOOn-6U4_F_qIiCgtxNk=m}!yr*R6a}FP`}5 z1kCMf2EW=F_o~W?k2R7&Nli(UC}uJ~6WQbJdoAwtEvX7dO$3ekW!o~$1QnPK34ei4 zJxWB1k>byc_4gm$gpYfG#|-n16%I+}rsY%`4!fav(mCA{W%0E2wVWRssHdX%y!P0C z?b0IZN&l1EujK0d@XyqZR8}BhM-hxVYb!+Yi|LPARvqqgl})eR+6U#5=bj~Nn5qU) zC!9p(zd-HP^;U{qL&}C@x>FO|)LlDj1QgCXjUV_csdw(Dzmj@q&@$Bjhp@MfYw~^D z#|Z%o(6JTV!G#czkuCWo>zwo(Ob%-vvnx;vGo{h$WmtUm-9#^Z?4~){`>JQE5`&8*IC02o{_$m25 zMl|>`jDzl((CK;F=Zo}x^qlZrhLsWuj&mM~Q6#>>($MnQmu~6wl@89-Xh;vzl~=QP zkxo}WJ~bb;sulbD%{=xar2-V3g;x|_;^u?)AAyD66rD^s-)lgq*fC!C_&phxJ+=+# zdXysE!rz2`FIEZP^9B#Jb8i{7Os`!yuKeGNpL*w?<4YCqaniiux3@+|Hyh<}f4b=SuZG6h(^N z=mTvpPP!65CkZPm#arS^yAn5CGPXCaC%hnR4D8_~wEf$}%&}kmg`N_Zmb<$%cm>Nl zcH6>QR-CD0-(cjvp$3Y?TC()^ufwY@JO@wqys#RBVT45Cgk|*i0H@*!}dLV zbu+YI%!rXVrbusf*Yv6b3D3l6Gfa&VJ@(iArwjXiGtV?=qk~a9d55l#MQ*ew8!F|p zcxjjQtiy;>-`(94;Ux@H{}Z1f|NO0PcJ4`1`mF{A>SdcZSfkL)k+z;%h**)Q zmlg@RnHXFMX+ig2&>{2r9;S--WTz+}jxMpaRqG_O`&h)3={xX_(%Hpp_!2q}IraU5 zNB7XpcC$mxin}}St+CUJ3P+QXz3C9|AFX0T1Y@S^xa*E`ZKSW z5|gXMuH^cI^F&%{@>Lt}l*QkJM51-=zROU!+TPUrgutM>Sdv47dVr*7RnVmRY=Yh2tB*qA)mc?8hbXucq71J(Q*I`rqUw@z|`P6>LcTQYTc>A)xZHhdj3?yIQ zQ*&^37k3UHrZPSMJr1C_41{F_LhB46r!1wmlmiEl!K*2WrwRdBDg~%n?RV!f&$;(t zx_y`PR|>_K)b3BSGQabWPszQxPhTx}o#NoBp%#09x&b*4sS4MW{zhtSTZ_cU{sT-V zdI-1Xi0OjQy1X+<5@D4b@!giv+=k8b8z!gGpn}As1r1xP8-xG^Q2Jt+<36{gDBZ>Q z&0G~(2?2N`8Rawa!;fHeZq1Xa^?`i9a91etR_zZDtHN(at{<1v@s%Nmzm}48C#U`D zyP&!NP?c>;C08nLSFv)e7}SGKDlKZO#-3G48U%0RbbIV2RfixgCCG`GITSRQmR$4Q zQT@eOC3zonxO!Si9~54F55gjtAA#M+wwC2!J`nB@7*HZqm6fMU;T`=E+9KgrdL}V3(FKRoCD#-E2vrN6_d< zI$W-St}pQF9T7SVQ2(=&PDV(!qOhr?!EF7uH2$h1YMnqg4&j+cT3qrO3NPOUA) z3N`?ry^HpagL#JvK(QA}MN@=`32fV0H+fM0VfbA%c@0r%orih$^Li2nhzKv0t9%?l zTCX%Jr#r-6rd>_`(Vv0YPB!(U(Nkz=19wKaY-Dox>8ZViBt zX-ye2v`!&^wLRZE<`8^CAf;BRkwRq0iey!;kGFSCXdD*84Kr8#D+@^bl~XEwu`q?ODV&>H3(ejMOt< zBb@ASc_ZmRQjHAcNeXM7?@0j>lm_9Pwgj)A;dQWQzLMz*eRt;`n%OP`eNSs~gl$Ba zH#CY95Zq$?UMt}PC+;WZ{G@!^51=+LvBJD#1Ve`hTSUc`+sLF-%5-K1H9h^jp@a)d z$9?PYuZ;@LW&6Jqxt-!j!1FUjmt{XS5!YWIq!zB=NiIcU)QyOi#Avb5~+2 zY0%gc5$DF)D?%IULwy|Me;sPpD|(b>mu@G|*7tZ6sq6;rP4OlKf>!F6`=*IPJD3%i zyvykzUps3*mwKlZqKUu{f;{^5C)E1V*#>8n!Jxsovk zUV;tk9|nVZm%nifLJlU*Wk9D!PegqF%5axHlcz!St%80$lt#_TPd^(Oh^lA>j=B!$ zVHC-EVY^DTt|8^f4VY$6)(7jvtuePsX8AOcCYK#hpGu4IUT<+_KaDC6;}!>dcglAq zT{hzgc0KCU>_Qg!ZgOZ9rnm0v6S|rYXY{E_ zNgRs7{S&38GEZ`Ichp779Ce(`7C8$+o2RTZ@Mv!+rDu+SP!a$LrhKZy9;!|UQw31g zp>>YiF>5nng|3NXKxGFi-yNN7Pu zM9^V1gYUKVK92jB2~5|^{eHQ(xjGImlrlV}InZONEQ_>sI#gW9WVi2Md0jun0cv|| zsQ`ftwyd80>V!&#GL2E11VjCV;}W;hR78c3DziXITl-55+3CXOBCZwu z)A$~m!ZpoXC#a_jSm<~|$e+qUI#&Pkc+$^PH_X0#f=w}Z3(9(~EdSz82qq?mYtF2_ zRyK?3_^ic1rh5qL3Z)|K$iQ{;tB*vYr2>y;xdADeEvwT)+p;I>Ns#nSOmRFUti}=T z9!xILthaIRD^ova=jk9NU|MS+Q`Yj00-&@|wHAZtjpqG+Y({>)d- z?Z=6|#;Z4UzP^^L7!}`R8v`6{%!IX5645g67!hF1MtuLtwMEmP&1*QyED9NO46~`CdKGbhkoo*Yb6%wuU zofx#XY7BNzwW_o*TAKdNQI>%FqUwvw=|*LgU)epfE&a)FPH0Y{K1**Uz}qVOYETgJ zVsWXhs#5Uko=Zd6ewO3Ma<2Ag@pNh^il*?}Km7Xm)pfePP;T*${_i~Z3&SKay5I^; zMWZbQ=a~|o(W;6eWrQi_5Movufj}K~dpg(TvQwrP=mk9(kTTjH>z#q5d{@D%>_7KG z3p5~UKfb_b+{)?}|DZ=o_$RhwRKdH$@@`^1VZO7?DITAeOIoc))^rPWzB2sEAFohg z+zABelfzI$5ZSA>>cXNECK{01hS8co*~> z+7jq)P;fSh>n}Tkx$o{!z;FDkuEYZJS!+1m3n{+8Pj!%23h%g(BH929=?efO#EYAn zR~OSwCq5B47wMBzID{U}>X4>O5&xS~p{ILU!z|^|H!N{kpPe- z?Dw}QAK;0OznmgbJmC)wpKF~oGxoB>7^Ni9H3`7?=x7#g3_Z5OXT3{NsSy@)4+x`L zZ8RBoPVE)Y zJP{(etm&UM@#a60PuKH3=;AXyflEYr%+W7A>}9Fo>|yIb;%(;%E0dQmZx{ZKe-@S8 zBRh8;3WXfL-Gxn7dW7P>Wr#ME2k(o3OLYs|L7N_8(t(EBa_i~a=JL~PnIL^-^v?1G zv~6Z7azE*Ia`CejC|%z~hyEURL5ZAnH4`u2h2*Fn-Xs-jGvX?rXQqzt(jsX0_BjL_CWAi=re+KD539CH9w2Mf-RnrtFO!VWD5$ss#|9MZr>q8> z=Dgafd;y8_%Q%K0v}p~%+wMDM;~_H?TvDa2k~wbpA!o+^2gmHZHEP)E=NnZVl3OD& zLI}*~r9`x9`@&4LwEiH>+!kY9ajc{HRyLgesu+KMmj0*JCv%n_)BCfH{I_S<1bq7< zVJFn}m6u9nlUirULb?~Ua$e}D|DES2T)skhKoee!t0n0m%EuHhJWbHt8j~hU(kA3!bEqxSf_a0mo1YEl z&Cgy!^sV1GZQq|cmlnU3gZ^dI&_zD=6m7StKOIo+SzCeIjOgtB2YWyG^~3Qq7Ie{{ z=(_i$z3wfOkMAEI{JzKZ{yY6WSE86XIG4TRu4Bvcmr)kZEmg}~Yc0MNGE2&z1+59} zO##13yAt#d8|^BW1{<#id%cTRY_IZ4Jby!_)>bxyZer#j>B=v(?J&?0T4QfCmYzdw z)rO*^h-4tEH{OYRT5rH~74xuyl>XG0Fi(A~_inMgi1aMwyF8YkGS;jzzO&OQZ4Eiy z2<-U!GBQa|+`k0lH6zS_k8b&m+HKn#Bd;1hHT${Rz@;v{rMb-%+0qAqm@$Kn zldV3RGDVj)JKf!9e3eh&Td>an%q-jMhXAZLR3A|{#`~EM4pxScSYvq`HL3|rmH~hl zeajhc(BPo?KVavh1Jd)lI4@+dr8r&sE0Iu}J{W5`{&`)CKQbIa_zvOrMX^V>n-=@U zg<64>2L?G)UFaXG_mFLt;ev@Vc=EF%fh{ zbLSN{Xc|8mqo(BU|GfHO*)qJfSAgESeWB=y2KkLKB|={_xLh^3Cm(wfR7tBY4hGjb zJJ4gxXE8<)&!p`z6mQ7<&A|5`ormpra);FQnU;;$$9H5d@^|r8%~Z-62yy#3aT5p*tB8*Dc`r*ty9$*~U7s z1b2-V8VM0le*P4@Tw6SJcRh-b*f(R?p~?yVW1xGz0K2GuX8>c=a(M#m+_wRjpg%;uN9g?|S~f{1IC z^yF|o%6_((zxLdq68GUvHpI>{tmNjeg<-s8e zeQ;0uS--IT#?_%}h2I?e;C7?!xGG_>O4{b4S6KDo7FrK9&kR%R6TCZ?oFQgeUi}dM z*-ZDO{&_(ucZ-cD)a7~v5V`>)B&tIIVLNa5gd+?2YuAQ@lOVK(M@xxuNhzz6OC5CP z#8mHJ5%0e=J9VsdyI1SzEbRkAEu{!dC;{z_v*|;2yKP_sY{({r(VEDRGLEj15J2ZX z-3*`h78jeAg!Pu+UBIsK8bMt9gz9B-S$0?_4#E}*a<&)AgYvK&W)*Wlh)JSv zz!8^wK6}AMwM~b(dgV7WmYBzL(zR;V-7 z;hNiQ)GFG>%`(AucW?sRkK2ufnNF`3I3)UkTSM1eD<9vP@@QjAo)$HC9(1Zt!=fGQ z9h^N7ZJ6UtA5zE)|D?&oDI>8pwo#y$|> zSqHFu_+Fb}hW%JR!p$0=PMmdYZYm5d-QUoRJ}Zz7gxZdJxoyNYLxt}0rq<9Vv zzf$we^rD93!{VOwi7=ztr0y>NSgui0bvp`aULJ#!4u~NQ%M>RRl$J!FH*A8PQt(51 zbHw>_4`Qs&Ba|$0`lh;g8}AM(&7)-+uo2)}J^oEw88AeGW!erl{fxB~lhsvPIiV+TtIiQEXN-&kmj2vFx>^|O$5b}aTzd#Ls}@di#32h-K`sGPZu znA=AIfng21D&3W|u7hX&y%(k*%D`%Ep_EMaP+@v;#tz1;Kl)L^i8X~pMOUle$6Le> zq~&Ly;Jt1=YhQ4SwYtOO(>sialKzOpj)GgT99nab-N=1_M@CracmpR#NZ@E3I`OKM z;(mvJl9P6y#7z`T63Q`zC4XPWN~F3^)q2Cb+C2$DzwOtQ`we-a60KC9fAo&2Ss8lG z;WUTDepN4Ool9`l%94um=Eovi!0qY79~eA}+f9e|ir||O*dJ|d!&a1z)OI3%}CW5abf8)y9x5>P;3e)@wBEibcj9< z=hyxbyFQYif2ld zCNPnX19#l`tDhDLC#>cAbklPBqI>Xs9~>X79{id38(#5y(~>{4h3G{?*?nB$YOjL3 zz{a`t+oJv8lnB@o*Vq>jqM-1s zqN{$&xqW$c1(=53Wl1WPZ*eBE0kr|CdtDC1gJRrsFtl3^F*I4_?_)#qT}7@nJJ$O< zgIW;p=*W}$DS$F@#Z!|WcROVo@>`PldHFvL7TyvyzKVtbFgqd9;98(oJ#5n4KMw+E z4?PTZ39I(n+o|OQ@9dSruI350(N-dpQlQ~0!+6!l-JUlCqP>@HsH8z8f~ZUm&Y>y_ zD-%%Ex4veW-%=6cS?}OyzM(E{6{{dus<69z z819l}F00)vO3~TJp6&gxh4A8tRdgcMAl92+mLs{J_Z_r6`RN1ccmFQOjXUpvsY?Pj7_Wz<8YY2E?Nnb+LXi%LF>B2xXm*rNAbm-D_sN`$W}? zczvecMrK%2iJ;#)w9uqu_vjz=NuVK#;YcXh59c7?)(ozOSBD1PMUOhpKVmlHX?EHN z6IFhnPWz9|vY=1YXShV^!ZRf}JB!M1(Qy<+V_ec$HUQMm-j!IxH<-4T+zC6v^gwP= ze`zJq2XKVy`~T3eCv&M&Z{kK4NTO+%i(I;dg^B_zDsf*e5=g4y^LyL?Z9R9eAyN({ zA|!b<(hb2_Z#?U7$_1^f6p7#V;(OFoN~dNOP|G^=g-WE!_mF|cem_zL=wdU6Z4UhBPE>Kf-ZCHUtqkcC5&sp4gG-Y~Pm_(;y%nHo>{H0^06=e&Zzc9~k2K13!FJ^8BiIVih?vfWi zzAz)*+a!KEw{ewz+MB-%Pd3~5G`*EPF+|@$;h$rFRc-gs+~ZriC-v$lQhsK9pX9Zg zuYMlUJq9o9jWq8y6eaMeJbx@WxeD{P7Se7u^ z$b7N(_SbUA38L+W{uq)~kx?;RQR%X*^w9CqAQz@M9m#IpUql@m{GwH+(EeoW_7Baiulbwc_-9kBaM(TVB> z$HE`|zm@p6liuRr0k~4rDWETW2u4A=hqdH>$L)y(mv-M@eR zcTX&;2>-u>|KH;O2z@McN=Jmz;J+{Vx9NhQ|3_2(|K0b$TagSr5S)X$l&TEle|5BY z5M39SEMh&Y9y2~cTy5y?fue2xV>s;6S}KTPY_!6 z;43}qdlkY<8j?M9b za9~rLv*y?K22;HznQLQ12QTw@QnBBjJ6Nz*6CF2Quu+ZVr$ay3cf5Kc>%!F+#t$8g zD$OE0Y#15O`pz9pwx%alRKLTRq}I_&gRl1Av(OtZ)&wVnZBAJP zEh&^`meK`D(fQMv1YQ)p2`-t%b@SUi9Yt3#~3d&y^rGf~r zm(+o;4gzCzQAlSzR(fxNY+Cl6`f zGXV9wO>0yAe7bYU#^byC>xUx(A%V)HmoBjK^X}DRj>%BF^5dSwPO{;L)j}00 zNaFg$F{6H&cqSJs(NAQ-8J<9^C#r(+r&XUW?eR>Nwd}U2zx&Xj1hfwS-ZD4yy&1DE z*KRH`bx;GbTgl-FQJ-#G!8g9X|Mg0~E-o?<*+-)dOy2}g{KDpZAADbGUDRbj30Hwm zu`0CquC5$}GK&Wd1#OFyG>Ziz6i;*qRtARulFXqYD4_)~sdJWj&%L&atlSZ&BW^MP zLfiJ$x0yor@P?OvK*!_*bE*fN47RR6>Dsz@?b=J8`MuDzIS#;%fj2l31>R9~nCZb) zkDOm0bL-fd^c>!{EAn7BR1>14g?L&B&2|NFkv$eN>`EO{o~1F@hXh=e@Xc8js=JOl zIfM2Ka67sPK$1pa*uc_rtVK|C`Gvo`lO~YH^z`rP!NzF6%v9us33HM4;MoAddqT#l zGMLp%s8UlG>^Auc9Nj1&-vnNk=;vd{iN5Ol zFecw(OjmL9*&6u~F&QBh?qkDU{!DL$A1GQ31yzWI$e< z7=C**jtJfv$f*sXPXa5f3G9NrN1ziY6i?_TWqgHXNOV>-fKMup95~l9a%-#9+-({q(d>$2tE3b>vWP$Xa@za>70G)R73Rhk-@j_E4pIcg|V6 z1blq(W`X;b-caOd>9 zW8H1YB^W58DA!lv5|z-1y$Q_w@O0k6Z#jtTKABH7+qOO!e(9^j6PjkhsuyZw`y7_> zgO5akKam#wIUL1>;BR@*cPy3r+pncOb-Sjny)wdDIv&ItTF%*ilCK;dt8nH7z^pY+ z9bgRQ;ph+vxZr8l@X50zUc|VeQZ9e?LQvRh?nl&>d(pZ+l)vY4GWSMahjIIv6MvZ& zKT7M+a~lSc&#O)}2v{#({WUtEm_EjE2yPC9D+tkhy>fFTeF(ujQ((!fUUlW`gJs%$ zXv&;a8@jn57bZQxWju6jrgSkaR5sMf?6nh~i{ybmxeM(f2%}56Lo}g#zqETv9G(!U z!rrtx!=o<}#QlPB-x0}RA>yP!>ZJ2K#k)dG9IWFU4X^nVGLxF}=BRg%?~3|OkG46C zy*B_p>^kaAxxJfnnbH2NYNv9;GzBj7I^S3Gs*y$sN}@YB{E2r3)jCkd|5g^znEBDo z!^mw5EPj2e=vhpWmytITr@0iUa*}r=Z){8!xv;$M5K}47bSXZPVK1h1(k;ZM5qgUSP%dirH)d85u{MKjjzApOx9tgugWJ*KxG-K7u0<77ou7ejNl6$$b1v^Ig*y-19*>Z2)*2pgHFk99ecWJkuAqL+6vzOv7rj29WQk$cdu=bT0R_*>l1I> z=2~jHf_*xd95#PPK4`{Vpw)o9^0zsu(&g<%J{#!(DX-6{i$&&xo|P1izK1#c*U#;ONR-;SXXgwSjbxDalbuJc zqEzGEsjE4nKOnJfAI3TTBl3 zY+E5-mk1f$wZ7COSs_&Man(8VdKb8H6f{4Y=6e>u zP1*OxO&M=T8m{E3FG;!Z9=z5jA^7>AucAb7-VU|3)=HvBR7Mf z)R7gsbmetw$Ydi`(ZQMNsb^ifzX(#vttpw+b5)l53P#q0g&vKf2rc8Q&p8#>slso)4D5xHa_U*Ac7uW3+S z+I2UhAP1r0QX4Pw;9@k}&qtjrW8C{#@G-C+d1;a5>aV$^1IQp!(HBFj|H!!JElbuye;^BUQ<%4!j?BYi@>5P~o(fK1j}5X=d3y*dn0 zOb>W8oPUB@U}A}xM%?X9B;zNuz|d~RP$r%d_{(StItrog8M)=sbT+38p?j$q+UyO# zkvv@DirDanvNgnStLg1`ctt-+w&y;6aXV1H|IDG@jKZ3qcB9g2@ga9JHpA3L#0WvM z0`bb9*6m$AZJ3*-F|rj86}}=$sBQ0sBAx12cw!trIDWW&X@b);EJ$+a+o`SAdMC^- z_Oie2dySB7RUuBNW^FUlhsLSb*+CfIL)PWJUxDd++b>Avv`2c%7wA!`Hy5x*#TORVQ z%AH1NuNRFIyw1ys=~7~MAO2%6aILaW|21ba4o@t}Erk9wxm@h?9&z!x&|I)VWU>LE z>5G0^i``sTKl}mbcaErm_}y1mxjE|_aY~tjfvqEP`~!PQ*}B*}&)LQkJQ^$Zt~|xo zH`AzTUiT@=Ppm%wQJUVecAq4g6>$4*ZZK7RUE|ZYO@J4fJn{}6wy^$MHAx!&hCYd+ z_s-e-l_yAskn@{p*w%V~%sZ?-DnF}6zu`u>s*dkKAy3iQ87)!#Bt7uQp!q<}xVV9X zxBE8WY2E3&j(3};rXp>CZ3-cX+e7qCC7OLG3-VQ{V10ydmo1n~E7orn<2hiDDjbn~ z<>vQhR;6iwpS5F{2vUVun)?cnmD&JAUtE3XYN-;Gw0V}E=ze@u=``At_KDonZuplGM87sP+atAPI~4nfn(qWZD9aH1EF@;@o14~L&rRK2X;AE0 zy_Xoi7-0-;fKpgOj^VGP7Nr`GrBH{}#CNgVsg|ZTSqp0?#VfYV8414okCw$=4L;VK zJ@>bZ0#^SY@6&93v|7VwY^d}5!;jBG3@Pe!_okpPhQ8F;-cNQ~-PtnykehN-+D-i8 z8ZW0T|FQa(tp*FwiBC?fw(XQ~UJlvW9M$8E_t-E&@oIySRzg8zC>kppc(_=dKG_)M zhHYrlS_M@3An$Su-;mZ0Yc$MOJP%HlYw7F?{VdnxczuUQz;wKnGq0V$rHLeL$IUij zq{-n}vbqm|C?mKs4RD8nHnVx*Nyjf5dra+dgy&>bhH4h&-2Wmr{N@rzWOzmA?Y8sR zpt)C^h-$$%3|XsMTvTBtjO3dh_|2N1nsikaUxPKii?{mbL)!HX!Vd$7@Xxci;&KrTKpennP)3wWrpD?7vWGFH`4h-Pkb zRj}`KiEsuZ!|v53GZB?zBv%nJOE5=`)mj$+$VGnMyWuWlkCLu2H+u1W%F03@7_Snp z)mZ|T-G2pVqK_Ae2>?U7&g(R!naQLv8g2Jq1T-oezc49)AWRJkhDxkiyD(Af%>E8& zu=Vv5y@j8kRw1{;0=%qxoHQ;w8H}dY(iV(^k$E8ydQ|k{Do;HeTJ}!J3n9PP%JTpc zS9Zac$AH{&uv{t*pDhpSn-V{6F-A4FF2}3Xr|=n-u*~uY$}R6|ALfy`67+iGBAPAi zzE>Sx(1dpm&pwBCo=81B;78d<=j?5k6wS&rXFzNL9(J2avA|1Vy6fLlXTSH*Dcyou znNUXFGwOVgZ$3`T1h9X>T)}sHJONXk#(P5bQSA*DLHAKD%smYNW<`+S4Eoa%!rO_H zsL~J8iO@3US-)elkJ5F6(;aMPjUsYdc$3FSY0c(~rHaO5$wHP{^~247aPgyh(FL(v zoE@hdn%DuynOX)9Rmu}z`sHv|8Trp$zjm|Z^uYMG$*Qq7u8|YMnPw31(CqOnguZc~ z?O1ztMdoO1VOn7DrE@}SwsKbMTk^Lv>+3Y@Anto=Xzn`WVE;NJ!5aI~vf@tW zcEwzG>3e2hURv25uZonIm{0chVz7GEJO6?G!VBYO43SYn831=&N?gQOK75S$ z!yoYGqb+DEAAMZLOaABy^xY)e_>#-J3E@*YvPjoN#uYu|$Jcy?+Ldp~hLrO)`UMbb zmW}O{&YT9A&VeFT8PCsl?m%L#c2j{SwK8?b=u15G>BR64kbX$U>-=^C$&oQhu#h`W zHR@eWVbtkVwh4z971HRF!VIcRh#dm~h7$C%Y4#OF3xh0i(1X_Lca%G}*n$8@&!1IO z{K&4h@l}f~{#fSJQ#3cfX~jCVk*~HyzjMNAUedLTWx#>@=ESb%=vptiiSd8Qw^Ik(2S{1p6#s*xu_q`mFZVYbAhd9 zz^Li`1@DVKz#m8PAvq{xX!;!f-WYdf!zq=;42G2w(<*7ug;mx;FKge^yyYG*Y$Vy9 z4qPvZ7;*)UebDKktdPb)F)l7E()U2^@OYB$jsjoxX#<%L9gDyOl5;}*VBX6+_q318 zo@zTgCUi?;EWybp_+YP@5W>-{+PNd<(2m|@_7DnqZh>WGKxdP3h|0d2;wpv0JN5(K zxMS|=(}y>_Paf(cNIitgobG0e-Ic|c*&SDQq&H+0#!p_`-SfV_-t$XKNYpPTYBFI) zwY25fQ(fy9%;uT)QH4T!cOBTm=2c&WtSQb<4ZBu(0FRuOo7vd(@3)PyBgHF6YO$e{ z2<6v{5TGEsmtAt@BqUf5rF6tyHCPo~W`OK_H6iq5Vq<{0+cjH1Bnp?yVsM~&0LYHB z)7u$mQ*DT7ZEy15e`V7)2kxbq4FwF!zKky`5I-h#VvEcBjL)P?O5X`_A|V&Ieanfz z{TQW>RV~ChJ&64Eh~9A#(bi4lhUNA%vD3(?I+mZwf9yIVUOwVC0^feyW%s5onq~NX z{TQ0zv!LmTfS734+@w$9?3WzwSy&bJArRC*kP-tDCHL`BZHDP4Oyim`SE6got@As1 zL!5De*00Dpt&)V7o4Zw@P|Dvh(_7lfe`C{cuUxyO?JgS_HR#{cbSIv<-k!zFc@y?h z9R}3zQ~WC z0b1)^EDHNR?Slfxsu!(WCeO6oC;T5_q&Fb>nG+dMs|u81Cvgz zpr{0g@vfkR1<>HKI*&Ga_^eCa$*bZD*0$bSD>;>%Fn zM7-sug-umsZhN?&R7h}xV}gm+L^$>2rOqqu9=!}lbOZQ-wb4@xXr0$(@Uf0Iot z>5O{^|1JcNI^VTCH0pfc`AzJcxh?b6eg8e(Q(~fxBB2t7C4W>CnUBm#VZ1Ml$>xg@3CKT{T>~}Eh&h~VM8vJsdxf{4_$P&7Z~c@?KK55< zS%u^xg-M5q-+%9&<%6A1TjA&Y z!K{!@`RJGtB;p^?8Ew<1(yVmgx&HapD6s<0KM-Lx!*~>b01&vvNl!prtli)Jq%VKa z#Hjl!-K?zL?3_O3>Q7l~4ga>*J3LGYB>Cfk%+Yar4IXC2*_^zpZ?eNn9=IV{Lkj%y zFFzKHm@r!oG_qRl(L}?ThG{EHdn0R0P>cy*`h+W?%-Ky>`KOGN4^ihXV5w?g;@WY3 zfinW_r`*T3o=H%0+X9Ph*p+jUl>+C`q4$Xt+|9J4&ilDjz-EiFc&sqwRIVyxLu${L zlr4y9_C|q@y=NLrTu)6*X*lY#xwu>>Jq_PZ4Ty`@}qji6ilqlo>G z6f08A{KxesM6l%ewn3of8-~V;*3qqxVnC}--`7SZ)$tgPatvi^&3GN&afMi&v%Z21 z-&AlYJuNmfI)|cpRn95+ zKCaf~Pb2agQT~BnuL*)A{B-=6W0L=diO-KNJwYW{G)C@TO-hdnyrdz&X0xq+)V5tQ z=#t)vg6fN$P5>sgUgS4bl&W?uNgMs+lz>r|C#_KIXHxL3B=`AM4ftaAgZnDHtj0|J z4Nq^&n{?#EjMz^1zg)SA8baEiaAkExIZcuG4+^sNCT`5J^pXZ+wpw5QG``H`gD(m9 z3RlGKax$vk8%)bgY?4mb!dAX`FqBY4Vok8k5@SLY8z%S>H)BV>y*{C6B!v$>ohx)~ zD7Jzfg|V2THt4)Hcpd%Ind#joYdT|O-DKXJtsL*Tg;B+M9KomKy{X(w>srRf4IfZf zQeTzwDycw8?_RQP;giF&_p*+8g&yi=dot&3JhEgTye~~0`9o&b^ONiFS$E=;l=!#f zSI#d$dfMkh11LY^EX>}$IRs>J$?r`5aNz8*(wt@9uOFyr2v>R#dpa;StE_JR>I7H{ z*e0`1J^r*AuaT#zTKvb3#oR=g1o82>(C*=fYB7REI%r{OztWpB;83_HC9~DE>2~_7 z3~oQ*iQNZ}DlPEY{%uA7{vqZL?ArwIb@;u9_f8y@0)R*3zYy~bxHc7;COJEZg(2$2 zjo3h%yCW*ifNpB|H4NGB53s9nigf1ou(dbUwtImEMO3Ed*~q^_FyG@EJ$*{IKbhbH zT3zcVC#KCWv&HI_{-JcrDgZi+Z9nT6mI_cPoo@w4q6h8_m1L-JH{!yWa$taSp?ROa zG@$ZZU@Unz=Nn-g_2u%zYM;*=2L^ZXeg+&v8>6G&CwkQ`<}#aovGb*|`*UF4%$hXW zt`!f)*q=6}ad+Hj!(gg6o?Aj601a?Ac9YUfdp|%Tu`am*5pVPoQZOm6mfB);6MpK} z#^d`uxk+>IXrWL^rMgg+W!%v9@^&d4OQS!HRiAOciA!AP!tlEad0s|mf7Mi+3$bDb*)dq#MVNN%+mU0m0k5R(lbSi7EMvt7)s+ zI;)WbTnH|a+CsfU?;YOr%6y4K!YMkTYWHu0t&{wn(eA^q%M~qKEHa}sn~kuX#u3|k zNPR!+OV5mh%BM7|F1b_;b`#HB;7u#K@v+dRig%uE zDc1$L^X|ObHLmxk!aU_;n^gj|8|?4#m@+!D8(0f*yB}50iHax-(2npC`}cyk#{^R= zEvQ@DM$tg)H|AHNB{y4HYHcRz&yM|$-t|7@<*j}UYK5$ody^&|O7?obd!tI{OfTPw zSjlLcI~}fQHEn+?s7xxt zpBZN?j7@9u-?4g5((RcdT3|?@acTT72%G49(xU{}phpByj>Kx0-=i^N4S6-k(RK-` zy3n)I2biHSX5#NE;RcQD2&luc2hw@-HaiP%yfGm!e)frehOQfO4q9aL2BSqN*J@_U zzc^exouN`~B_^+X#kIQwcO+r zS^0X(>89DaQ`DE8B01TcJewg1aaEPmudu|5^}UVL>xBHFOfE|~%rWpq$1~GhKFw?Y zA5mW&*JRvwPa~}$-3ETDW4PEs$L$*ge_Qql%!8CqNw#!EgEI%qz<+=&LH}kl`~Lnx;)n z(x9Z4tXsZKWUXXPKF)Pt)r_4K zJ8?ftzd-bPUCm!DS&ZkG)T`v`j-E%3N>DIgA|Ps5mo4D}5~-yXu6d7}<3Glg(Yg15 zImzVCDQH_z&QO%X{jbqh(ycNV_D1`U9^#b|v6zP1Bn7c>-HoUJSDD1~hVQ?Eu>sf; z*UDRCxyTm%Wru}vJpXd82<(ef6th~G=|~tPDeq}A2>1iE8vUwI#l09!@v?IG=$J%& zt>*sh-#XZ`)--R}+m5T88~k)c%*yAD?g(S54N&?N$uHeL7${(9k6Xt!pB{JnIxo;H z{gb|f?V2Ju(y=C1`D=&DpM5-i5`YHiVK2Fg?_9u+E z;i6H%Crr;!DOfR_f{39;Y>!~oR?CLZP+@}nLCSJN$hEw`p?XS&Nvz%YYh9 zt7dapxg&lVKX9ZykVUN7xN~o#)`X1iT*g<;=l;Euj>OSb_SN9DUj*JK(Bm*zVObv^ znn>tk=<7t%ltM7ldn;rrCX84f{-SX$pF%Q7a@&WUc8xt6L&SgK^15#(y)2bJ`~Kyx zy!RF97}FWrs@27yCW99Qcm>J1_Su@ENzoL7hMAH=cuAIW#a+%-Vdp}3=%6F%iCxt_ zF`PWo9}&0-LCnX(A*2VPgMoDaPE$XmuilyEKL?M>(JMi_Fvz<5gFc593Y=KJ|3Rk{3K@u{obNvfg;GM+Xk;D_Jr*ZHnul<>vk*Y*62 z=sxS{+gp>=Up1!Z=cDq=zvqmT!K6!PKMon+%P;oVdr)O|EYpBKuxeG4CK93+APb-D z9Yg)3iw4;msxSf(Ht|^}~(J`ye+36ZJmDt<2 zIlMwPZ{H@XE|J5ijN9zhc;CtAuZx}Q&1`+z8f~Ua?&g@_yBN+&?&Y;GUNfltkpsgv z!JL(mOj$ypJxp8mrDMsRs_b?h-Q3at5PP3J8o+#LHHOvdz|I&$$~$xj&jzX;`>vu8 zqq{k6I+j~7iqHrRfP77Cdy>pz;L|C07t{)Xt*KD^LaIay`ByvshY zw?cB*9VYR>eUZJ4-_&_MxsIGFc`|xlxT1S*oK{vjaKE&sl_ga)E+X6wX6G@ULR}jk z*750N;>C73TWgugp~h)DLkCIQ#pY!~YmO1qjne%;&axGM{8>ia6j7l$9u!YV<9#JD zIE1L)2D^o%=U1`s-a-nbxU7G&2>PU8H8x1(qWTxI2Udo?HHPJx;fl)UTdrH2F`*wf znm#5k$AVh>b$#-UMFKF9lf;~t))U1HHyF1=|-5cobp0hE<-yyk=cB`AJv5b zU1oWHu9Lr3Z~}D9Qv=GR)vh9sC`S-LLvg^r!z+1r>jP-q(aGTQJ~4El|8pqN?jo18 zfMSd>(}aAH*^Q2;rf+w``hh1po?nAKKhn=ho)^kl^ZV7bN@_7L^iz8?Qk zE5u`s)hkf4{#VgLg;Ob*w4f9(wFa1YrNbW~I7bS;H$_o!Q+h;xRwXglol??~$GJC; zzD;Zdg^?uPYFaK$%?*UFHT7MmklAWLI#Pri_AbYWGNu1o6S|GJYE^wQ7yiDv1z}G* z^B{F9tx#l~B)hNsWA%RPQvX%2f8p9KwU)@~RywloP77v6YyV4I`j|o5t9=;oO@A!m0{Cs6>IfmT%oB`j z^I$fKJ2a(D+E2ul4g7Js$oT#)*8i%xz%7j|f-`U0A{v-`JTc7_0pLn|(Ph?XBA5$x zB^4`eiF*!m-pma%&kQ(`9?8zzN=H3$_HG!#AF_NuOtoH@2tz z1nSCt`Y^MyO}AaIUIFSwGPmShBLOus!M;RE2o|T%<_|M*ii85U{I4{2aXvzZQw6AF zOSte!>A)82jq1+$Ysdr?z#5!f9&J6KRn9T**e3)1)4sck|Lnug5vfxE{y5y zkXJ0f;%)YYe>N&!KI{>D>_YqvK^`5Ca5JULomr^;XMNnqt`1gzx7CH!<;9Z-Vq22f zmnZQ2y$Y>vts?L#u0s8L@K+PTvM|K6F23*U#kEiReYsLj?{{=Y?Ov8* z+z^irSqM+=kn0{f*fL)o!OulFmrfsxGJ!0P9rcRT{LC2(HV>mFXhT(#8$QptDl+)H zodb3L(^X3SSmiM=H+_B`0M?R(J$h35*)l#K%``lEeM0mSKO!EYg*@2Z6qaogNEyGQ zZ=M@y#*%~5g>SgkXnd-FwLeuPMwv~;)Ina4zeUi4*=N{Q5)#_$Mr0Ae%#S;Dowpj^$b9W~*-qC{yBGFYCYf?Y%-PvNpRnh&%HTda zp3@VxKRF5s!g*?RBUU4PO_wce2krj$(6_p~O=e*dnS&C2E(v^4! zXf6GnHj9enRJSVP!t70x~e zwOp+TE#BiIP8c{y_Q*wcg^w7&`A;G1RsK%sb6?^U*|mL>yRgpZ9T3AFjwAhBV^pDU zo7sJFAKLx|^yUlL1yiT5UOlMbXT|nrDMn?GsefxW8FU*PeIpmoSxV98p15W*-lN@7 zkB}UW*GB1dN7CjMPOS#5@nJxkg(orOKNDaLm%oSPTnyNZaP!Ilfbw5U7hdtPqAc@( zDACiWmG=T(fi#|^G#Rwv(&U%>@-ry9>lE#VrlH^%$ilR2ueo};-o=^wqa(F6(3S`; zhn6_t)jiD;)XOkaSR*pj3@`;FQLX+oT5pLTg7e0C6I{E79&XP_L4~?stpXNy$JN?? zrLk>lYrlNYk1O;`?oZFY1FEE7^Z`zmN{PIV5Q$(^N$I378;T-HL9f^fwKnUiOk$I^URs%pjpbZMx-43`=Sa#oQyYBUL9 zXO0yIRqqK#F|xrSQ=$)gSljqtG42h6HZOkeBX_i%pG8kmNl8kPPG07jc!kU-7Fpd! zI=$aAt8;cRH}QIyHmO=&XG*-#Q{y!#%#bthx(*)tDCBJ4v{G18x~3tCFkx+qNvPk~ z%swPLhH&ZU2G8@9i$HZ~3>Gw@t#k6XCeLB5+ zhzXqqbvuU$@S7&KPOIWv`61cZ8c6pN-YYhbg}?SFJMuGsE)p{R?;P}cey%F^^<8~i z;lCH2f_vUbm30MK^(>t`e9I7oa`N7T20#r76r+|0rWiN$a zA;3HvH%vd&K30q%;f8H^^J@5zI5}quh|j6O{y>&qhBHXSmdLhc3T}FVY#9AXN9FQH zQSpagUzqIW`Co77GG2c>WC<#vdCBw{4%7$lRmGvE&f4i3sIoqjxLDSOvo!}8y%tTH zY}!&WWs?do$?IsSyLUMNn#J3df>cM$XM&P0PA`~f8%XM`(m{)V&zWvT){RAJ$b%RL z_FN|YLcO6L=O&6T z=2-iimQB_Vhj9L#yGD+V-rx0Hk8SnBLgQW$Mc}w{j=c1Au~iwt=75i(Nx!Lj3Z@>2 zt$1~Hd}ua!4iSj1+L9Ffxa+ZUl+S`o&cB0p31yUyy;XqiZvD;tO*hB@?mh%GuRn)k zMDEb+p#)kaAKrwL%l!U)>JircU*D15g^m^9|CyF>(OQ&K9*uOq2~5-Ybnl9c{bWmOk__P|Vuq093s4Dc;Mn zRNxxdg1@iVBMFk2@>+pcUDEgt({aByGaE=W9di-&k1?X3nbOwcgZ^UyY*%VS(MYkv zkoQ>?W?+m6Vp-~YhZD=75}ZrE#MHQpk*Ov%7a1^>_0()4;48e945dGzAmGJsRrZh7 zSIN{|*T;`KCTdO)APK(@Gp}1GWlKM5Khh^gD1Eg#sVIN=)r`4b@W}e_w#8srbU&xd z!hH9_%)$Vg{d)01XR-S+AxUOS<>8{yB~JXxHv--2{p}-2_uNP)%Yfw2qH0xzMGwPj zMzDy_i|fcxI$Me{ktsQnOoZi9=y6v^KFo4038lp*nBE=12H~bAnmD5%Mc}l}d=JrB zZ7x9Blod?(R_o|3hz+v0p7?x(4gBlRu1g(u537np?7|Hf_^G(eHS8Ik^h;Hxa%|J3 z$c(Q2G4924rVeRIgnCA1ULo1u3fu)hs@~&}7_-^3O5!sp&63ds+Xsv2`{NWx*uG=V zfT!1!PPIsFharL$lINlC%X#O+%7?8>_&)jxlb-NzNy}C}DeS$u(X*sI*E=6974B_x zm+b#QFLFAYIF{$r_P~_+&@N2z#l zDPG^)!6G1}1KXv~2ELg7HteWh5OgxwUC_hLVQqaqx}>TMuq^MkFBqM=`6ACQ)Gsbn zMWGFp%Idbg3z6y31@ZON6uls@Kzck{C-qj@6@^HH+*=uj^%I?&4KsB0DOoG!+}|R2 ztV$pzr6H6Pi>gc6l)oytIe_GPdc!M(M*Pu4-sHcjH+C?FX_r$Mp_{hk?7CIH+!edmnx3u%n;!|v=;l+9 zX;tt0Kss&T5n4UMcx=jCCAFP?JipN_-{E zR4F~;n(;+W+cCaJYzinM%8WYRV*QX9QaUDubAafgOS0Z?<~x$>j6v+Z`34Ux6mUF>H9ZeA_ZE4mDJGfW8G4FHcJQK2__np2_UPQoEhSu0m1|s;RFUZd7nY|rt#rkuDwJP+Vgr*M%uSC8 zwlXv@HOD%R5*6=vL5IE9;;GazS|1gnIfGZkV%g=;Q?{t_(hjqoKLa;3I7jsq4fi8# zKp*@lc7lS0Dl$E5BqHS4mkfEWS#cZpiABNUzAGs3qi66|A_`K2`|=`qMuL8@8Pr*; zOE`^wbCpx0ahe&A;_-L?U>fjhccv?Xp{?U$Hj&tTW2o4rgxw?ZPUR;5EH%F1+vH%` zg=efs-v$l7EE*tsDjn8ZDyNG_3Mw?b!#wu1gwHGmiqxe&7E_pS8(}*w;Vkh|jccmZ z*0X!l>X=*tuh7|b8unNO_guwsKD6utQ|%p2cb6gvN`~8OJnyJSiYXd5PWx4^@Z>%x zFfs-=@>QU85-wMngSaFrOOsxdbc6AwZnYj<5t6@Dkc1+{^sXV051?Yf>cK`A1b8 zu_np8_%^bA$^Ads`J~5Mb0Tw)gy_Da_N{lhUOBw7#{ymYQ!Y>cnT?!I9qQkm-b=4A zbPA1+7rrxK5!ZFx>}l+(jXfZ~eD#$Yu$0^^tD9%(pbY=qkTo|WP6PR!N}POjn;bJv z@gk`m#&UGg<%RjfIC53?Aq*cmUkAv}SqS~&AiVvikXGJh*h8f4_Wb921!na&gS(@4 z@e_4VO`Id057I}n5QL{E;l>j#hQ}6D#obS)4qWP3ZCdBfRIp@B&8N}nGX)BDiEB8L zKF^_sy{*;%6<-)wcS5(c6!1aIu7kA(+T8%3QSs!wJDBcd=KN_l)HZ?Ep7&Gw-wz)$ zp1Hyaj^UDCF+2vUF!-B{XLhqH75?yTOB>K?gBo7~pgQL^kor)>3#e+r{^bsr$7t%` zX6sdl3{xg$^{%cRy@?hL#CF$KyNnmT?Y~jI50Qybu5D#Ie~hgZ zbA|u3D1TO^=MX4>v*n;lS9tzecim%|Vpn!-8tFh8`Fx&UC17Ab$l-q|5LiIIrLO8pu zk*F(3bq>sL*czqgWI8F|#)H)@Ue5wV?$SDn|L*@DXY>l#Y&etjW&tJ4!@y01N5StG7$h>$y$9^QSs$7zKx29mv$fjVBYee^uKxf0(1$-|13cajQbWf;g+q^XiwV>mc<*#d~kk(gI=f@ z2Bh2Pv^@Lym>}e6mwx?$JAs+ol?3HfwM^s2rT078ybqjf)E2GQ7YM%dM$_kIx`n#N z>4oOKpZ=#`>Q@lpTBa?$pF$HSmriX(vRwO-Rcc$lss*aVIL zSkuDSd)Ho1u6%LgEA4XfXFd=&3Om`MCSOlmeHl1;9&Y5@_i-g zBJ#j_wR){;)z%6?Z)wtQTd(qZ`=P!*dj!rWaQa{9!Xdr-0!oS`g7C27M(c{=fd@&p zgK)4tVRu-u!YBo0Y*lyU^N^pcFss(mnMj)r}r z6B@5e>@j$rISiA@+q)tuESx|bcw9*p)K3+`c^?XL4m{s;)x)#>W3Mq~ka*{VTIsHF zsPZA`a_rd7Jk7p8@br9M$ZOM#%EY`6^0#;J+wQj2z)O2?n0o%UKN30dWE;7t&mstD z9tGuVON5=ry0mN2fs$*_zra47Ul|p5{d|ANf6{cD8iNbNxhlW)s1bIz3=q*>)V&X3 z$9_5KpP^qh33}quSd#(_y`%eqi%trYyWEACz}atN!DX)_a$(<2Tyo9@!aP<7|6x+v zB^ymM7Eec5kpsCu_sVKSjH=Gp4ug}2rr`gcBspy+_XMA0-|KDgrDmJld3jnM%qU1c za8zMYSU%A^PFQ&Ooqmx|41aIf+rG!DotAAfJ@xPLoER!>--rKSlhn-O-vvbLs=ci7 z3sEo#dq9uAZJux;{bL?&wnP>K7?=$V!pit>l*v*%@Dg!9F(EOFc6 z9Uhl(P(&?*)=EuFqJVS+=3_KYcW6$)^}o8(`pU>$*DxI=T^(P4Zv_X=`IgGy?x^}5 zsT{j3zb9pa1oEUgblFsTCF?6)@*Pklx#uFNDqZi%e)K%~S2-3tYS2M(MrtZ-ZLR?)#j*o)LLN|}SX7p>%4_AxI6a*ef(C3xEWXPMa@U;rB)x9< z*__CD!mZX^Yxqd_cg|21q1@jw7yXM#5my^e7LY-?*vJ-d-4|aIg%f09-}k(t3u45A*dl2)BEuuF_GO=R zL|k5f*NHdL8o2?(Jl;5g8op08X#dX*`Rr4X5-6nW*DZ~OuYM+KDsEeD z6?|j_zJjcu6hc{VKk1v{)s2Sdec#UtZEo{im6r6YfT9AN;^lmIxhs0sQRo6VY=e{bOZY))zZ*oL&iMc43G)*jCf; zU;E^=X*z}ZaQu~!WggW%Gx%!IL(3y}UDRAN!e(f(5!wY8&lKT zQ8&?;&2Ecd1dT3xQx>UG)*wNJx8=K~7-QZyJw3hLOWcsqp!v^H-@U=eV`fAI-!;4$ zVYgo$hmyNAh?&RFvp7bTK2bc4&&FM9s!QbeGNrky*hK4`uq&L&qcGT&Kvl)>-8jgb z_{Rtg$(PtADe!tOEg39Qt{1gsSu4K9O@SE<&#UPsHEMAlL569#hTYVsaC+=|5w2SN zx&F~`F5GrA>@n~AQ~)_l@##Iz+Ut7uvG?%%aI32RZ)@g=`JcSBb8eiz?(fnW{Hztc zTA-m74mP`wtsdo`K;f~it!nSo3hhrXQ@uJ)Ir^sx&EO%!?p)KkJLNPbN?LfWLNoh@ z0qr-HI|*q1`}#4LgA~nhG%zm7;7ggz*^jfxf3P2*5?NgAj4msbN=$ww%y{zw1G%`7 zLmghjm4ntuJDLf>AEmG6QZi`ZSl{e-z6>%+z&(h(lf6Nlr~Xx%`ZF&L#bH+Wwb1?m z5nMv^Z^wSw>JjZ4`jSVqXnu2;#;tcP3*2D+75xi)p47M?upmK>A7(8Yfis9PER`hoU}o(G=#Km(K~!YgoU)UM1%P|Jhh(g z_k@zvn@plxy5qJc7i-x18Ie)pSK$Rv`o8WA>SxMYiY3tb4X#!3c~nKpTGSdnuf8tg ztuwdO^=&{Vr5k=4rvmrk0$OOr&0sJ9U+CZ_;KE^shPoySoQ##Hg%{%C!p}VgR_3wd znJvcxhx*kw=}PBjtWBSPkeL6F#~0u^mx5NCY$MzO>{JHTRkD0)dg~8{%}McowiFt%imvz9Y0R`ZMn-_!&NV zQ1_|J&Bv9)=BJUd7rN}9aIQlSeZ()Mgs5opBLRCVKW?6+j#@z{)_c!txY#6~atQw> zewF&Fx-mLl{>Xh?CWj}SI4?UPmg`6uf_UetT=;m>az5#XGTxvR(3bRE;+d~THmT9H z33r6Gi&V?tK@O?V!N-fGjUQY`{|j3$Hg3<(yCz(0*lsg$(SBH z$Ru)r2)|6j1 z^2^ezEo4s@^#VGqPZeoI2x9Mo72NZ@X9t(-MX!NO^OhI==9ARn2$(Xgw*N^)4!jd1 z>gNq4yuFvdtNBZ}cUt{HKiHo>k<&rjKYMlkp|8w_)c}12l_j_w_!i7q!YqQn9kg3= zDFy$Q#?KkzZIqhD&M7HjidL%)85d=pUO6$1XfzPB^%-DEP{^7*m z*x6fO7$fsozg2P_EvZTl8{qb7MX5izmtV5{4{>)Io-RkKzHMpopTGc2JW&-hXhoC% zJKrdu(;H_t7Itrm%bZ6i+-S>P)CcTQp$k+9$-Q0}EwW;@lU3DWI|yqrb^AkWG#Rz4 zi-)-{HnJ?v{PT_@O?~>Aj9vQZhkxbN9}7?zqa9*#tzow>E2rjrK7b3Zx*KGVM+fB8 z^_MHYZ(vxII+zfC*`ad-3<;maO3E{386G?}lXjn5Z?PcJ-{zi!R&yD$5Cu~d>;ZM| z>(-TNAiUqV(sMx;9xq#lrN(w*idmZGj<5C6!6#RIp%~GS9!dmR2xKGEAnME;c&w-W zotpw%e8U(QiN1z+qyM#x{OoEdHc&9H7&_JkYJ4Qd^~sMwDNDGlfLljv_!!0 z{H)br_lGe4JHDGK3OBNaia$TAI({UD36@(F?dU-iJ-5nSO0}r((LqGdDN%Ky)O#MH z52oipIDyTRC10rKIG=EYSmlAdcgR{{hs?>hstakrFdRuDNhLUsn-F1pWw=q^bU_0|SJ*#}-811%z>S@15RBCwCNhO+D)MQ{cy4E$e`C_i^J8 zw(c8WoY~QJc1l(7iCdHi+Gg3iPD&9JohvaZ2jc~|>EwO;VZ3Ne@2$6GZi%|Jom+14 zk8A0D?cS4k{u-tYZV4m9dM5{tn(8Ye8G|FJDva`w@E!OvqACIx5d&{JH)O9ip{)u5 z1%|SCKh_r7b}QI@o|(SthQ^r)uMe;X`I+4Y1!j^u;Vx0D?&X>S`(B`}&`*0Mo*0$J z?dFXc=ZLc}_1|u~dI+MVT*j%sG*FEzw>v(^pwq1k=E7gMxy)QWx+2B?G0=8F?(f@L zFR;qA%No!ncu0b{l4cpJk8%l0B$LCp-5e$>%~}wjuC0a^ddm2@s#@L3^+udt1R`%K zgvm`2?2#7xXbscyso31*V^mOZ=jSFO(D(a>U4dPP7lMcSapB?4!_h#(q&N{=rY+Mv zul>?F{=z?aLTipYAG_YsP<7M35_M*?4f|ot7aY*HL2I#Ym8$r4o!1Yqa1k^4KU}*8 z0COH`V_wGF@^ZS%q0(Pe1?M#|99CNjiJHblM74+4Y7!ULR$A)T%HeP>pEh|_^fAUp z5Dp#;Uuo{Ty?uS2XOJ!GLU$Bf3)IR^(Ts@7F#~jkWN0i7y6W4_>{8>nnf_UPF~ejO zBX4N2mi%eZEmFnY?h`Ve;lUaCD)UP^Z>=3n!Ij?(s&RtKeM<96QCzF_SCYY?B@kp` z^O_QOJp`4r>s09D*f$qx5G>j)M1?RZ zUX3ZMCi`z31?~T(44ndXAzgnIaYPfuF`^R8y9dI0kAglamRuY%XxD=Z)(KM^7cOJfJDt#cQoxVYz7;U4w zt!~P?(RVKYD^`vqo3$HPTl-t-;BjnaElO#dmE-!R%_F5X_?-vxdMyxr81EN<`i_C< zZj|s4*v6k$YgRJxs;ykYa(8_X>T#w2Vd4k)9SnpOwAC1}R;ih?=zBm0iZh}6CBmx1 zEJKW10#I&T@)ZWIJ$Au6NpMFGEGFqgYiq>`%Aew3#U!8j+?mc#y_qlhCUgr6VPZnG zx>EGvovXXS^E&5WO$&@}MQys{?a+$^?@JwSM*X>gQ1E#m!!3Eh zWzaAL?B_SGTpLSPbXz7WJl3)>fD&i#>Nu2avk5`a*ZQZ}WSw(BcabZ{%b+rsIn(KZ z+V()?P)L%ZVv+=IuP%>rBaQe{=%Z1&_x);3w*V2%g@xcydewtXkvPi$2fG?!!pbTS5aayRB z?4PT#1+dtaG8;u2e&p$Z6Zi1#{23E{2wU4!*+qEbB`El? zf;^WDXY1n+*P%eEbs&)??kgQ3_$2cFc-1BFEkD_ZvF0Qy7>)-t#XW-AYgza0J>sINK|Jcu|nn+v9b(oJ zgSS5L9p5KHI6rT&V;{kY>{1ehKJt{fw#Qu4?$%t`ul;OErb=mQzuw=C{sxlW$c()7UOb#mp8B5tf+1@q0XUPUx`UcZgKb|So{kU;g6n7~0S`8}J=nR;-jp*>+ zg#V(oxvYuG5}etlxr=v)xOQjfo(l0ut`&7QXD^1`E$)Wxg>sz05;M(1QDO#VeX9$_ z-<%q_u=zNjKJIb!;%QNTv%vJ-2D9JF%{_D6bJ#bWIgePBUB1^5CrpxJg9nM$T|+CJ z*`X2#?IcmV1b^O!5qc%n`=we4>6fpBKsu)KBYBbq*nHg6pGu&tn|IVgbe2I zUte%-(b+X9cNN!8V^`TkY@pjl9^IOkx zFTcpsqlMr~A*Lf;`R$yYj0}FSmOpw@I_HC+6;bSWQf{xi`^7nfJzHF+$fzZjf3>h1 zd=l_AUg|}BZm{CH#-p8-zEDx29Hi=hl1-d zA%x2Nt4VQ9M2Tp%bAM{0q23x^c+whpceC*Kc=~nw8}Y_KA)oQg(*BhQW9f4b4Y}y~ zt%~$Hmq=sN##pW*&^Pyw=!kdzJt8^*1rBZw3#Xl?oj-nReN9#w;n=`^Iue%XC2-8R zX_g`(LOJ``N!~Uix(;5%Il~)4D4MMw(0<~K{h`YVd0J~>@tKRW+#)U}lYcUjwbb-& zeil_=P~i7KAe}|er+f^1Qy%%`WmBHMFg`Ve>tETg*Ks=;QafDO`9SAG7KX=#mSX4ou&8GYMeQl!DhZ$bcTh#pi_YIgH7sZ<)44`r*ukW{^d$xcl zeNZn`eUoSD2K-V4+JZCo03G0d#+@46IAp$dwL%>3Dl>!4-NTN^^2KmFzdqBXxvJf26!K;8j7YvO zNVr-(JC||hU!hMz#RLR@CdUyF3c~=Gy7GbvlOLAAv(I08r3g!`h_(UeK6zxf;c#W+tj?w9o#NguorsO~EN=g%5Icm0fay88N% zzfwBf<%;o5C9t!R(l$KjoQ^NxKP~703cJqfKORVoa85~!Z?zX|+Cm=jpY-X0`>rz; z%;19&#~-J`8GMg6EJL-vwLdzSIL)zFYK|i1eV&p2K=}Rvwfy^4;2Abbz92((xu!<= z;m|xwYq1LciTS@!`HQa|4YPLd7_eW_l5A%eXT@joga~$52VSvggnH`_O+?YbzcYMu zxfglYR~(n?VKB(wgVEG@k3^iIhNSu5h-6uobCH)Fo_HG@H|$xyjs)P&!z7s%qi*y< zp{?L>5KE;OH|d+x*Fbf>gX% z0NY8M3~SKhp4z#8A$YLY(u01Y)glD>bHVfC0GM8rrO=B99;wqQgueDDT_^Iy%syWZ zBbuM{2Ik3Q``I8(;@md2}*7DSI`QPuyvL zSzUclq+>C+2a14M17nM{BC6ZE9N~lDA!Nm{q3VKOnom|u9O}hYARQAS56^2Gi_z`Q zSEZEt3gDhdaDl;dHSlw}xgPg23*W^LW__D9B^J;ZuP+Rc^0 zve-BzqX+C3Y`b&)kH)6>(z`u8E$={MNPZe4We{Y9L1Zp-R`jv^HkM z;Zez^*?nNi+NSsY@5Y!063+sa0Y&Ua+={%ZX=jzrSwDSfcTMer73{9&M)+@UbLb=b zpx0CE?PT7~)Xp`>-9Juw7>OpBKY%r!?YTp{L8jD128@@coo7~$3in)181I~KUpzRZ zD&gyJ<;kXE9rM2_{c9$qA(HRtN}=W-DjbHeVEB0TxBSJBRSYB7c5_WDJGi)ENhBFQ zMx-r7;T01&bXm3a&_sIAU60V}IQ|$B?!frbZ+cU@&cfZts3?8!2yuMKG)sG?vVCP^ z)2B)X0Q&P*>+YDhK@a_(dYG0gx{XOj^tUECxDxf#Yk+G+tnu>L!ipyZs|PmdDyl0N zopEU0_)R(1y{{MAJ2Sl>oQ5_#@zV01GzUt7|?704OAyX8h zggNPYbtCxK;&*=YI;T~)I`VE&u4*oiDBFr%yCHvpb;)E4DxZ`Xqbdrj`h+G2{Zz%d z;4lBRlD8A0=PTc}D6_`w%erYl5Snj`zxZ(x`naSJv+>rB9}?k3l*)A(7iH=s&LtyD z{4L`fPa~u#^n5BA;SiBpe^z8p;Og2|dffMFy3_f-Pmql7a~Mfaq}&l_XhckcjeM=I z)p8F7ea-7`Tnbr!r+oQn@Lg|Ps2h))_D)m3TU%;~PhX=&ilo4t0P59}ppLm#Wl}}h z^f`CAT*D%=Vf*jWaBN7;9SK#&s_?0#ewXf@Uz;2Lkj$teVkeWP%MJ$Gksl@#(UtpnJE7@(Vab)s$_LCfXD5`UzEmntr=aOtA%m9XGF zld_f4c~MK+caNVNzQhpPMxp0j3);1yUyC>0zkG0y@bzS`R=6~Fox^>n0doX4$Cr@TCbhQY&+q!7*y!5D7;z+; zr^VIx&*zu$3ZdpDj4OD#LHZfd^NzAEY^Gh%p7pQy54?tj6-e5K12jJj;rRsKK^-!2 zU#dT$%ObzP-c9qkgM)Hi3U}h)Lv5np@P|2$-(VI)^p(Ev#~XwPLlQ|L9g4rJu|ihE zUhnn=9x}oJ3&GZ(^AuTQt}*3uIk*qc1Dl@weQ{ukrB}KZFEfpdi{o!x>n!YTs+Wqe zB?cMD!bI?b7q%LN}%{wv@2O5{!GY+fY3nhv(_Da=9TJGS#9=G*BnXrld{6x zN|)M37N#r5#p-3M=`8y-5U!a4V6WsB!hUrnBX@<A6KL0cFE4Igam!HgD>2ac^$hiErh@CC4a=Iu-}B96AS$zlrML|AfgJ&H)IqQapE&3@`@H44^XtvFvirWmd zCJR}*e>uxoVnb9@;Ql(D4Zh(gq%Q7Sa?gDcEb?{nzHseY44bbl9~lZekn{HEyhC4q zD}Pi#kDx@2AGrmrQw2@O?mSnl?OYdFqnOTL7tsyXOLs)+A)g?kC$|4ASSc@F#rnH{ zS?P2ym9L}(ZbQmN_Z*P2PLkK6r)aqgVTHb4`ut<%H7zvaI*dk&<*eqP!#0qA^|-ig zB_M{^15}C+t-*oMEg>3H$|B@_u1^*YT`^8Jci;Q8-PH;2XSe!%tFtRlIm$hIW}iYO z$`ziQT|Krqy8A24d_2%Ql-A5oWftj&UF0l<;7Iw6GoSt}njN3CKs!OzYBwm)8Ow-C z$sKGimIIcEmT%IR2o%(@lWX3A?|_|{i_;$=NDcUW25?br>? z3J21`o5MrF0^RlQ!73aqZOvS`A!7dRNG0P5LiMt&u417BcArN{a~tAcajZUhZWgcM zhUc6gL)YX~Sy0g~sDSx#w|!Glw-$b$w{`xv_aHQGw@k5dnl4flgTwLTYy+c`}V z5hV=h1pEujT6sdXRQGo`keIU9A>ZK*#o4S(naHSnjp-U&4ugoLem9k;qF??LEEg;X zqi+#eZaRHOEpv2H*AgzOJs~i&aG+Mb^{WD!_RzBT@!IAp@Gb}Sf+C*+QF+&e=sgy4 z?p?Yy4|c5iq~WOPCvs0%h(8^l4mZU0#rLi!e#e>_#AO4%#pvuWU_%*?8fE!-T=3TP zbyauCNBzeSsW|^AX6x8R$P0zeJl-7|9YQCXMBDgrnwB*XK4>dk>t^e@Si z#Pa3{-B0>YtQlyH%0)30-ST9CMoI}O6DJju|(-UjV3 zvq6%QZV@2=u36ve_RBQW4<8ePO0Qpj;d>uW^jTJZL2M^W`*t$a;c`62bo??JdLF>bAY_ z;4VdrOG_zG++ADT-Dz=mhXgCoQVK0r+`YKFdvVv`?hpcmz{|bQzV~y^KG(CK5AUa3 zVJ$M}SkuQC>;GFd67^5ti4}U&CvGkrCXQ`Lrb(Rf8`#R6MV@BrdOt6`MVF^=9{m>0 zb8GVOsXZ^#R(DFsS|n^Ss({T<6T>h12xc5*pyp{49Y}p8`9l9wx<00iYE(-6Ilhyg zktZ-fMHFR4HN|UX*KbCP53SqBDMYnHTWl>GqZ8eriH<(-FiNLeA zz|*v(WwVw8+2hrfF0N*?S9lZJr~1{e;76<&mnd(kSeO=0^NSro!@ZICQyy9xe19fy zxjy#Re6uUw*(7jfWeJpfIX(U{`!1=VLn+jx;U1-K0i!&Bm0fsIa;iMs3 zS+9Hv$PtzDMvuOg9vMRghl?eUIb<|1#z3)Wzc{2jL#X;}#{<&}?LeT4bSu<$lrIh> zza}=pXJ?=h|F-$rbytpyq$=EzLrHirjxsZuRF8_Xy*3V7gztAo2jNgpBzxf~8lDjb z0R|7nWR$g$%ygVn+H&Q<`=uMSn^whcm-@>zXmuDzHnzXCaBlEt1h=t4RiJn zkwjr6HD%mA?{uqA#P}6bAaOOw+W6nzQ>stuV$jlCzHtp>Z?LZN+i#wEegpq&a zVTk*gh9WVwnD?_-cjjn*^EPzred-sprBR+KxzhL~XY-12sSJd|LH5B~X51LJ47%Z~ z&icK=?rXz_jU^iL7V?#R+>zDp)Cuhx)TOBW003k&}VdT6^ly40lG zelB#7czv~#<((cc?-nV$JqT3RRyKw--tOZjvd>m4JaeHV%J^2eJODg3+Qs$9uHz2T zb2Cp|X7t-jqIq)ayfxu%80)hFan8yp9X=PM0P~^4#>GR+ui?oA0;}${LQVS$K@@m; z_>7G(cFDJ*<|S4P?CIwbKpB)p9t`>be3mw)#%!ymU;}!b-nZe=g89I7rf9!`wf7S_ zuZgtEu5EWAzLDbiy@yHKZ19G(L;?TB_d!*mUrIM1{PX2tuFS%0I*Ep)Ogv%WlEzb* zaBq13_8Z<{ui$jCa&J<~455?-qu`loChe)!b(NdZJ`MH$;(_n?WX%M{KE&+U_sTwXhUwqdnnDrdkO|E0sXu3Qudb*R_hfVIEW;$ zzA)twX>Z*4=7(2C6kbiMJw5AUBQ-Hs1|03?BAqUgI^sF4EMY)IJ^aobUFfSFKZR8f z@7qsnJn6jc9P67CCXO9JcKy6!=RBy>$kVFPG!ipqMcfyw>(zI@J8*56%5EAG(p{-= z)&9qKrbRQghu%)75{6FaO7OIGbbW@Jdh62io<6^fRtwiP$ePojKvETdDdP3pd4HE0 zigm7Br3`6m3%nt(Q8M(Hd9mAW(5r`yy28grvvcFa8WE3rD^WhkNlCmKgGxH`Ic#V^ zn1@);sP*XPS=XRK?CvE=3TW}-{W#Mzz~nhTgrce|e$Sajer(w24__=?-^y3$bZDs! z)ox?@ybOtkl)n_hcq>mU90giQuNDA8!i8_y&!aI7kxt5l!FKC7#Hw0<376`K>qXac zd|nCmgUjb zxbZx8hy;dzcwfL&H~;j;F)eSDns8fJaEBg}+8e*KM;Zk=d1fa@?+P^F);Hx;BEgm5 z!+zx{dm)4VFNC+oI&(eoI?k7inoRn|BkfZ?oZz=TJb16Z z_-B0jpHU#gLIq$?v=86U1WXB^c#d)j6{Tr&q|3$`Pv(! z65tQaA9%0428=&P(rxItq9mMepDjPRHw!l32#HI~hR%kHez#)|TmyR+c0d&dI}%Nk z7!L799{&Z@TVoES!cwyQH#*o;hf8az)>3^$39JOoM+T`4pxMZKct2XGDY<9ftiE9k zz3eEJ^w|>DZk4={{_@evKobC_bcfGP3uc*1x}Oy1{)=*UaZ|($Q1}<9>mL#lk^Fi6 z{Y&}7`CU{shT>v|s6_6}dVNy_RDa%d7J`*74jM2$#3rA2tC)h)HLLCso!N7+Jri=o zl^wE+Gd~uvIK!d7RVriX33rP%0}vUEd^Vgt9o5k~QfN6vCAAS+JE4SeNMSI>3Dn30wA$lFZ z!es0{Ly{paYs1i(9o4y4@8~~D-a@_m`z6h^I)l*d8~u-U&?=_z$%!63uU+NZ(|#+h zBJ1#$u@aj~d^S#CYdVei(5_6QQ-;}`-CXKPPxtIHWCjs9G}rT;tC9{1Coa>Cqo@HG zo(W=qEA!z$;Rv1bisIreW@bK>3)BDiF0=U*UY362nA$*o-F!GYVGO??**hCG&CJ9_ zbGu14@j+fzb$0SFdNOvNg^llQaWUbJo4MBQB=kdIdnXopmdC@*m=w&9$8PH{jfw*1 zE}R;?+dTi(O+j(r{oP|~q^bW`L-*L`ToJdtn(*+8qAAr4!Zc(~_k^RQmA^f4un$>O zxYFpSqIK6QpofX|f}<1_Opx)!8%>9x2xHl@4rhTW%(OHpPVwkvKP zpQU}ho%d>N9a2^Y3})gzAKM^89N(Yc@7KedI-UmpvB91+QP=Zp(MhhVC`cfx* zTn@*qiun{kSObR4{jC^<|KuoiYAjlgBcJs5YXblMiT%U4&A@+<^-j;4DgHM0-|z9E zdLE*Z|AA!q^*QJ5KOyv<1IPb?R~W5?@b2G>{O4@b8gZ?@|8HIRU&cMt1~VoH{$H#@ zj>#YYciY=?Vi5n8cJn_cy60ki#{XNy`@a?YUpMx`KG2{P{0AbT>A$w_|I4~(BB3VW z@64Zn+WM~op5c#F#s7EEq4)Ev!6mQ$4#fZKzy5B#UjO(1VgdZGi)+&NgG8Nw-}-OW z>h+iX4{aR#OuPKIzW?8T{^EI>{x`npzs!0@EdJ-;{SVL3`Tv)(Tqu?Qs>S~__JaE7 zf2!~QqhbF8|B`y@ze!>I`-6^zMn8`fkN=}B^gk>0+~X?tpK(+Fio*T-W&AVJ!2brL z`}fN}XZyLa|JB(aoCSLmnQfxIva!XQ?A0LV zACF-t_~A|j`!D>d146ZJeG@K!fOz^teP6qOY#`f+7-Q8f+~wC+YyX=U_hjEI9cEFc z!V7f+3$I(#x%(wY_KR&LI3Jjl>4w{@tY$Xoti3L9Y6|}E2pIXpD(_f(ExSC6_ z%{$ILwu~x1ZnCl-qacwS0DpK|cXx{!ksju?M`bHC+DrJu{s7d8`8_LWPiO1JA_mCZh9 z^!1h2e%lvmz9B2BF1NC8XmZ(KIxR?|2^r5+rIg-W^E`Vz+b884&hg|EaBwkjUd063 z)()S#h`g@gw#cMR5XD$p2}$~b6H@G z0+JE4qRz9>Aea}w;r&=JHw{&N%j~Xk%+ymxpT>w+LCr^*!>||WuO@4K)4qqf~@x-ItcIZ8_n{;D~>4sGh31OSC6M(JH88YV|M$Yy!n7P+dEL8WIvrt z@RE(2+|o?b1Lh5EUg|kwmidNGLF|nw^b&fk1)58xNNPg@U5VOb(VaeW=ianIR%xHR zdE^sEAkjbRuaQs1jaKR(<;A;x47MHU)=Nt_Bx04wNPQX8Q64?(g0J#d+|M9nVye!E zdqqra1!u}8efEZ|Xxt5?jIJ_D)=Ktf-BRESzH#ta>GKx-x(O~ZGp#dBo{r5+ns{CJ zCM+CH`AhvXVws>qqq<-kr}W)AW{d#t53!FgToVAFzjG&7fHUX|d*9bAn`w-C1pOfF zrTKOc|A1ayU2|M5j!e`y!mL!B-uh0Il4zc_><^&EAu9L{3!UT(Z>Bq5f{eo$=TmiG zp~~<@k6;QITexTwnqW;0|Moiokq`yoI7YyvJ_RYQKgvC2hjloq(R>M+p7`S%SA;&( zibeb@;WxV3_ovOWB5=UlsN!At;P;Q_o4bIILT^L@v4nLVK%)?eMLkAFjy1bY=a&ct zURFra)TL)5vY4PX&Mx14M}9~Y?}PZwm~iD#N#KHRN_n30(rd@*0#*;rn>7lHSBiX* zNcadSMC!@(^n)b$l9m{QYyM|#*%DZ+tMaMe^ZXg_9_8$!h+JAL+B?RLo;u?)+%sy% z1IL;&VQ-2xK3U!8f}E(HTFz5;rVT#=MD{Ua1w_jQAgC?}PiJl&)h6vXYsHl)7TW-u z@&V9KzrHCNr-$h#SVyRAbyCtF5{H4MS;BU#i;YP>+pS;&Ii<|UN66t5MgJ8FlEmUP zi-Z+kfkJ^`0U^FSWaww!V<&PK_I>E|{8$@R2Qrj11I+>}YjLsu%AQ0zUkD*i)HHPU zBZZ~4b*OFTfqmS@-MkNQ=PG&g-FZ+6;Lz5!?^#A;w!`1($+XM9z`K2uV||nlP$veo zvwKTR>>vqK-8%)v97Y_rleoN?V$W~=L54l#K9f70z>drA@j|86mvAL|n6+G%*4x}m z4oEm?lvR;lGs4BLKk$W^MduOA$L~IJ2Tiik|2E_a0FQWbuPxY zt*du%&)#(@^>zU*i3w3gu?Y4|vW8e9>dqTP= z?rAbL_>D*L5qMi@IP~?|p5J;^mrvaFa`=Y)n}2=z)b}-^BaS{$UG2ig=hIVU*wsX< zH>}d2F6a{*z=+!I!?_tJ5gUukyL-%_9e~_$Y3>75-LZIkyPaq$^XSG4ep2ZU9bdF| zGQ9klkoh>i$)`$oOpZXMBzn)ggoQ@x)ZuTNnQx5uINelp$sEtCov_VBf+8V7Q6WFg zTUTq^ohiqzMoi1kk|l%iTGsBTXST$&INz9iss+v1Ji^FJ#P#?ykqK7>3po$(>NXQ9 zuzk>YgHu%YB_6`faCGb{`;m=_Ci;;(%YL!rV(Tlaqow{KJek5iRm!p8diN=G_+FOC zVV%JZj$DY5lOS(xnCU(23Y?c!m(xt-0IA>l{7KF@fij(wcm)i_c7c=Ew*7pT_d;C8 zH?LcORBTzzD$;ytMJ={-^y;_s`4sRnr4m^$@|Tn42C}syrUPp5(zD>cDFX>5WdOx-xb)$#y>C3#u#$H#>byLP9MAcChy*^HtQ1s z8bWrRy7z2f==dJSpVnguIGI%a3?Ss5>UjrQILjPc1;-p|-@TVdb9uRUTMs9V z<)htEw-TVVX=wxZNq4T9hK9%CvJvQG?JZ51-h2;inOm3MX*w;XGXo78Cjc@OsH0|a z$2$gCRtKafi;o2_N(D3z&CLiu89YqaneDt^IH{+xgtEQO@Huvy3~_R@JNxZ2IR1x1 zQ*WNjDedXN=<#elVf&RXPw>2-XN;2zhL^1KA3=VUqVQie?BDk$$4kGqvZK#qxI6Ys zY|b?1%&>G49X&WbcdRG2H5l{IsyARX*e=$HnG}V-U;~_lSK(orZzv1Uxx#sk@_IL~ zPj4={@X3P}4)>YUz8kp;dcV78I?lq8sk^YdeA+;T!XTd1nDFo&{-Z}=py60mB2c=f zbAmBzIo^PM1j#64P(>JonoM`^C3ux3P_t0l+oN$t-DyDLLb&h2cJS2aI{C`IKI8BqbhBVr)86p^evVz2;0{h)BAn z^7Yg)P+rmq2nU1x${qXuYY5#zKYAiK{QYIXMTE)k*ab1q-WX4aSmJ}V@j#l-TS^^{ zs{KHTult#UNiN}z;Vv|_WK##z%?2DsoB|Y4bb@l+MoGATx*?$ZZf8VpWO&!^H|1b6 z{Fi5Bdlddp)5(6Ed`Dq|d|qY&14iRdC(zo(CBgi1Ok@fa?w5cRZ^Gt6+n~@V{_V|< z()P#sO4DtQ6&1v4yRQX5W9XbMoFF0EvFQoCoT~Q8?2j@qCVOAq#8l%>IC55c)}0#Z z)xh+KZDJtL(;I*^ez0bSJ7{t~C_b53Uk=8qyk=LEb#G)Qt|=d6k2S6goI${TBQ4yM zP<=OXWj!nOp49BEtR6bX-lyYQO^Gk0ToTo$OU4iAvDJsrZLnv;L*=%R7U5jeF$h1S zol0H)1eYs0CuZB2<1YDTnpA_I!S>oUh)DJFvEHiTQe3K>XHV~u&S&P5jdM4Xjr;V@ zbIG`9Wq%3hd3-KPz35Hq4Jd;nD%B|w#9e2yy+?V7m{S}$&7s46)uqi`u_r?I)9a0s zY9=BBo??LF4cz_m&l9GJ0S*6qcaGhcvdMnZZh@YO}C^69U3 zxstOvQyS3nbDsMuBjYhQr{dO~s_7%^Me@OyS;8q>C`~`y8otGVvOiTPJUJ@BTIfb97tTk-(F4yQ$v$j_`?@pZA@W_U5fit!4yzcz|FI^_18qRB^!d| zb_B?*fVADqx2bw#EZ-ZiNlGQzl}Rn(-|uaIO?>j4J?oM@SXgj+y;2I=lCA)xajf+xLTw!M5j!CzV2@8B=2|jWkMm~9Qk>c=L5wuGi-ww z*8W~>o2-TJ{-dpaUG}!J~Cxv;|Qp%kEkpwagBbylLCa+9ud;OESz|XKVyrS}ja_ukYR~ z-bbce9$@%k7N9$1L4JMjIck*Hm#u5oQbCBQ`VRDx4f3=~@}%ML9i@=$G+U>$S$e&l zKO01q`8(77&H7ycGm@6VqOnbFVc&xtyzA(9MI1f011o0uJ!kXj;^=yR6V~DeVq`Pr zi{Cdu*QSWiF~Er?Rw4$XJ0kDUvWU5NVxClo=oBbuq!Mjc$^p`TAr@a1WnHxeo(?W( zPNPGGIp<|m#Sp3ni(`5)UM@av`H5a`6ddi~y>eI|hI5;WzYXz&q&#X_a?g;fcQ~2x zMfRx#QmRTQjBdZU_jB2WztVQnc`CS?roSg}L9F1FhCmbIS zdZ+&3Xk~7qz;2JuQ#N>wOLk21ZWgiaN18KrzM(oc70F-}SF0KFmfaIu(+9b_|NAme0L*AKq+eoCF52a%veG?0o~{(U!b9 zJzGaq0^Ik!72|~2Q7Va2OZi>R$m&O#O>b+b#-#hV2RLEID6?fsz3NEJu@mluMGZ?q z$<4r1Vv9P+WX2xp?myRW(o0{_;Dw)e!x%R|bTqc3%Cx6Wvyo8JQ{^5rRAwJRJ- zJS-5LcUOI;F7l<=+Vb0;fVMmR(#d^KDicvWcw$7nHr zu-Bv=ssMvWC4eRR30EO$`S_1LaMe%J!S&<)x+SfF+g4G$;s~#eqF)rg3-cpP5zr|{ z{b`g`x|s#ee6q?ov6^|nMwUkA#12ZWVIPl14?-{pmrVo3_DS;A#tjhraz24VlNeT? zB>cZJ3a@&`ONO$iw&*S*B4|nzw2}t9YaVD%+Ptl0J%8R_9!P6uSoC-CcHM187)qv- ziSw2%T^$U;nor{IrM4K#c6DkHGKGQDqISVOQUl&6X|%$EHSbNBManO;oLUK-(pr8| zeZNDTxc%w6&2+9aSlAWhbOxMhf01^#>jGJ3*|->v@ZTJejLJUYwxtkl8F^YX!}wmQ$11t;UJaxz8vkm2NMZD~z-A3>ZdRbEYEc9m6i(M}CZP_$h-XPdD4_eOGu6n~VD@L*Ol2qkSti z*$#R}TtMQ;Rj{*fr<|Ng4)w40o0tmSr;e=O<(EHN02jN~C!b%JnP3G(%1zw$UAP6h zQ@wHbzWGc-&`b@O$? z$@%@oMG=)%>o~Q*SahFfdjbrDq`#Q|h6~3s%L9d9k&k_*EDf;cea)^OGe7I+DM;D! z1$7xOBHY{HU2!u`NXq9Aex+TuU$2aRV7u*Y&kW7?5Dv)l60&tBcv*kO^KWaW1QAte z(fs(txNZwnu_3sM=EEhrTJ13=kv=LUA zIGDWAjoH&cEYOixV-0D(h=xn^g~%ZKI* zUEfJDEz>kF7DLA$9ckImS*gocw#l0ynZ3$zSDIfi*lOnRO>VxMntN31-sG4f0~Xi# z(|dc`KX*4rsu52YZt=S{W8TsA$WIPrPni-!zX^1uRVTjBf17>Z-G|DUv^M_vI<*+7 zhFo`oeYd%mFy^y2d$j$0S)9I#UoElIXloidCw@xn+HMqiC>>9&RgyxnnfHj(w-W@p z_`bj&LP#nPjb}5E{8%ujcx{oUz8|Z1J74|H#@-RrZdY7Fit2_#p?R=3SH_tsd-qde z?CT1>qWQv$=2-f3o3s3%XbBM6)$g_K`WG$(F05P{*N2ei_;7!NcSTDyDP^{XdggB* z9xVDEUln#{_i~P%QrWELPaAs1gt~RuB?MqTOZeW=zar6K7;?7km4J+R8r&a!1pU#0 z^r1)kzHH(;nAYuRI4k>Dv-AhyjGcM8aXttx+JMuchxO@$*oDF+YztlwUCJISqnDT* ze+Mp03|PL)(oBAse7*GUZ0ML6lD8HM@!dUGA-23UcTkIecXw3{muzcR24OB@*L^S; z^L!%=_KZ&$mEL2$YA#Tga`KDT-5d8UtaP#6gq%QUHW?K-*iQ)K^oKd}1xAk|VEDqkq=jN$hS$b#cL;37PbL_Y6 zjaU{sbK*mFu&q6cVf+MzL=2+yF!wCwseRA%r?$(+|MCER+5zDizJ3~GSN|^$5HZV> z$Z!dB)ry;~Ebl}KuOHn0oc^wNC;JGa5Gv^*6)?B)GVe^zHhz&x2=_2~(S}LOS8dBE ze*~Wj-YXl2uw(^s5~4cuELXQicR|NEFUOs{qQRQ+%qntL@A#>R-*9s< zoTt@E`l76Jx@%eSBAsD!LoHro-;@jc{;ti@K^Ms(AnZkmF+Sf>J#8~)BkIUWTA@T; z;}RB}T(ZI>)V|&TOPt@x^+C?_ZHiW7edGF_*QL=;n}U;9)21qy066KIK>PFQBmAD% z9>F$UGlxsHJM@omZZ3=D&B`A_Um8HuyLBD?!1_h7U`{!L9lM<#!&`hzsY`F-9m(S= zxxG9od|@G|*8?u|$xUW<8ulp_TFa1C*AnN58{Qb=wAdD9+>5VLUlU0iLc{zx4K>&# zFL9yd~40+UCk~h zmyTSIBorx)tKMAOstGIIy+TiCiMl}{Ryc*E$AmcuLW*BQ&n03jB6H?DCCPozx3&GG z8?UWRed!~CGs87b+U%9+cgvlK&!~r6gy-Wov>T*uO=<0iq@g`TV-L9yazO_H&}S))^tBJ$z-dr-n?1Ak>m3iGKp| z$!%s9peBtFu0>F#IJ3M|F#E=%!WoE;ERu@XF(7H1!JP%00S=9VQL=ma{#fA`kq$uR z)6o{Cxm35k0cR-*yN!3!TY|e$?z6oLr7QFw$I()c$CMG{O#4V5hWkorwnu{kWz-P) zqQf*D$9iI|a#w^;!?lbrg~_^H0;f@y&zsk2$S*VI3D7T!#rpVPa*5544tfR0r{$@W z^-bunv^Z@!ZwxKS`V)j~*v;4czzdh2ZCv7o^GTfG;Dm-YOO9pzR=!(aFBsT~p(h&O zQ*@ng%kU6cAUp1r5+5|oSk77(X??Wm{IcsV0sP~b-~o`C!L=uc3JQq57&<pxQ zK~)aRctoNybPB~u)$tNpLazq?X^c%Zk-P(`!Lw{IEeJ>0JIK6=^;9l2V1vXs0H0w^ zxsnvU*bEFg=6YH!f7hO9$&qZxiHf?Xe=yz1dHzSN-m-57iIfO@bzuvXNFToe06_qG z8A&afQ1Dik_Fqr6{n0%I9IBl8%;@;dY2h*LJtWVfIPM$uyiy=X>I#BA0|35TIZ%6; z1hn<>jE4fd1gG77^&7tES(TC|DYiI8H?|>dwyRQ7ygmqM0(n@m{!EXs&`&8vSLk{3 zC4k~3m-Wny3JWc}xVOI~X{7Jbmx=<^+x8>VEFMW5^|n_C$+Yq45ev6nu0xNW{3e&c z4mL@15gG}8^$yNa+|aV56M<|h8~fMMlkDNgL(khmn$RcRuC3NLp%%Xs^BPGk-XERq z6(CvL40*{3CdX&tp}xLNV6XVgXE;jj6KDnk@5*LZbDgK`MW`K{v<^)W67UWDnf!nV zED-#I(1#jhmLw8;oVfed9?4O_NKAB`h{pT9|Nz#^SaK& z0}xh}(l%N?J!Sq8oWdH%P9)))`bo7%7O;DR@z`uY1D4ybt(pJvAxZ+DD<6`;pqPsu3 zRLqS^suS1cTCN_5)I-JKsa_+4Kpc;Zx^nDc*VBr?!Biq+XO zuJrckT*K*=O~a&A4O)w>=M+Z4-K;TBP$m%}EPK0SpM3h-J)MWD;ZYp0o7;Jc4aE^jAOH)Tp~sjf!oEp+-_X z*1i@bSlJ$?C}+vm$PW_PdFGYeL^ms+^^go53NxDiqqjK7kD$~cW?y|E|4pI-i@KbzhNwkuJ4|C4!8k#Ek1F!O~D%yOgt{%EzXujs_ z6jnblx=q7A^HFouo)$F-0iAU^U%U(5&yDGrPeVPB){mbSDXLvHEpJ6@dxLEyATl~|o7>oEd1`vx5FGMvqQ_LqRo+s{HfxzIk2)FPu<48B zr1Fxw98cjfHuq^harJzj(<`}N>p<9eL>+Y^-+3{Y=s2Kxzn&+cH=P2qvenUOmI4mJ z_HRlFuo)<#RYfy)b7V zy;rRERz3b5H&N&+VGGoOM&dET_Xra$`2I*hqe8;$Nv+jh>XHjGa1c#f;GSnam0KO~ zjB$$|Y6PSWM7Ys>@=>|gFYrOuhjQ)i-2?;PCjgmW@SH8O&??XSu4k#qyR-O)4SKy*OM^J6Q59yV+iWdWaHXxT& zp>%RA(Q+$%%o>l3oHvjxxBd?5N^&W^+W9s>6V%|&APY6bKi5x~EV-L-&}r#Tp~e?u zQzC~FUZ-)p=#9L3FQ;U44Oc}%d};x}CTdxh-V^LI*WmzvyXx%&QeW&hMm^lPV6X*C{HbI_kT;OH zJZ?Wp(7XS98I)8JKyJY^O*Q&RonmSS)gQf3^Heyg0n&8#G@PwOKE71KQbtIbR09_; z_AygW>}*ohrDlXfZbrr_AIY1)+&emWq4&=AT2swQEo?43=E6If7VkU*&jkeh!zD`kB&jwI+~IuD+K&?#t3z#4ep4_JJaFTsq(Q@$<070+*HYsl4e(t6o>TB)+{4;G{m^xvU_9?)+-vSwb^= zqCCs^A_8BVp<9#_``8lqY>6tUUbFdIsW+xfX)hMlYUf6z`R~=`SRo~fq(beA7|K7D%Y!*=17GQ}<56*N)Llgl^K=leKR_fk%)D&MM30_f zwbVY+M|>|a+?jWAk29W07{6Jsr$5Sz5oZ5+h_62Ddz7N0BkGKVMZGZ$hEGK*yFi0# z>wOjBdAH#!jOqTYR&#aK>INhFXSCsK0*Kr&FS0D;uh{-yho7BHiG#AA!-~WeLZN)h zab-tHaxdbvByY9X#GjRipv6*eiga|VJ)bDuoGbzpKr}#Kctq<~k3<8#qeS8zZQ(0^ zTK8W-l`%&fzH(!`QRMRqGA@HUeEK}R-iu(hYweA>PwPUY$K4&lI^y1~8Up~4Zmf0n z1b=-WHc!CJrD4E9Hu7v0)mW-T}B) zJPO@_Avj6xP~mD6H}FuhUga7A)5{{O+z~XV!AOFuH z$S+5verAtOvEdy$Mqmjtx}6z^RkAj8XX>DYYv9%FWRFL(lnyLq;J`!eBv0Ps6r$FJ zgcVeQ^ImsnJA2!(2}@@O27@jb0I%h;#((j5*rubm55UEs0@z<|onZNICfa{o2Ln-z z#@ozDfmGyZb(j_PfJUs|t}Rr&6b7E~<)d;75nN{7AoRn@x<5bTa=ydZ*$T6+j!jf4 zgj2w#wHP{^*M^NS77%Ep;3mqXy2;3_KCHPU>t#hE^P(g(AwI4KOQ3IqfbN5OX@{Gj!fxxPO;iE)coX7^Q02n}+3#euiW!S+P%e)24@UJe_3Wh;Agp29 z1dYee9KZtA&%zS%Nn#}Ns?G74&?td1CO9u5_{?h#E?mf_u zJ)L7>OtBnd+sxE6gLhNQ8#8h{Xp?+HWkv$OK-dU0Y#ZgjYk4l+DobEY9 z9udE7zcRrKx7$17xtfA_o}G6hMV?3z4Q*Cjdx&f zw|Q&*V)o@nqfb*3Z3oJUpO1qlws@VjOOWUDNqaYl^+YZn#$f(U6z3$y2(_BLnfqUZ z4&Snn)0k9=5j2=73b$bmWgH?J&s}X5|CE&|m?AX*^Rql2L?1w_xy+nXM_Jh^gO1~p z&9BCi*{5AX>#2?JXHzli=J*CN(oixo?|!TGN`7cHNz-(q`iC!J5GTN)w>FQf1;s6migD|-$iY6?tW5B4rYvl}HZ6r=@pd)ZDb zqtj!g=i$y;_I@wJod1eihY*(Dn6`3~Q?E9WQrhD!aDK(>YAW2W-luvgbOfhPM-3kT zHO#mImHtp$Za=1~_DkQTtNOc`yE@z;)QORtGITf!aojIFL`WQHW#=w%-H!{|K^#0w zD6LfQi)lK0ktM7A_z1h_8iXORVB14*g?>c!U!YRJez;dbpds2bHdBew(#zw&-`vC#}B-!S+~MIg-oGR&(KTwx2|eO4|V( zgwN0=pCNHzW^LT>x=Zg#)?irOsS>N%=EDLejur6pl9Fn4ih;4Zd@Mf@8Uu^3z1y%= zUi?hY30N?2RNm2)kG(XRl7T~m`Y=Bp)@`$ry`M-dpFgTYeIlpJZQJb;Jy3SlLfLxu z)rPl(e5JFHfta+rOX>=qe4Mkdgq`;fx*O6q09CZQ{MJR^aG@f?!an$*NkD%A#d!=3 z6IC=4dLKMiJ5+JszMs(K(5D5&c#AMp$8_^UM*|{!m*`%7nG_F6ymv4mSz}on&{nr2JHVaETv8&re-&2IGS9(B!p16mLb8ig z5LnPu5F;tDHV3w%`6lxC0~F)kl^ac3oxR?u9dBbwg;}y4L6iV<>2Lbso90Du-Mr|7 z_tKusJH@N3T{%@;97^)qBPu<~E$>~cqhU*;l=%MMt(V_2pW2rons|w<_F;5qQJLAV z3+3fmE5nv0eV1yqF&^cTxy}Y-C5afShoboXg`Go_XzVM;IN{XcHT`9s7RN6!dJexe z&HJJc_A`ge6_9RxHdeVClIH7SF^0@J~o#%_7i%y&*rQ*UM-K* zGT2yG4acPPfR$BI?Q(N7mrFSzz#3bERf{-c-n)C%8|#V-F-4+=EE)CI6?})g%0c7^ z|I@GST!=a1K9@E9o6{MJ>x}4Aen3P~vTNlL#3H&1|F;rY-g)yWT6ut%996@bv({=) z9^Co~MiVE>RI&M?ZBRbdmP)ef2#fSTM~DH4Mu2(C%RmCN!7wPb*8;Y(eH~L#6&AHg zJ0V57xqgxfyCN7E0he2*o8VoKZc&a2Zj0&hT}E=zs@~hE+d9{SuKPTAcfRA>L<-5@ zb-nN_Ls%O%?*d74m;lv|)I+0#Mc2I=N;et3v-ekXlo^s(VYwp{n=AJG_JLTxI}_ge zT*KSGyW)(T%q!f@RNKd;%;OBPPtEtrb~e@J6%3+0pAwW=C{aDv92=Py&^Y?aKjI&BB0Aj&MdrS0Q5Egr`-OoeQ$C1Q^%kzpf(!pPjD8Cc3@yo zTq$lw%K^fV;+Ri_{YZOcs+y5bPkB{Vl&zy^qs_-?H0fX^XmU_IxkF%k>myp|tO@Y1 zW6Wo5;h?p6AiE)+|EZ&pJ5b!B(5h~jo8=LE0L#d4kX#rVN(Te`=k~DKh_uaqJG5W# z`ntTqxe=@TtEC~!ALS}Fb66Tkc>7IXB*ZJl>FDqzEVRMPhAk%S7ha~;+%IBuDB`)$ z1j+LR;RL}2e7Z&GWYF;HKX4xr$|}z?*C&Fce6Bk(od)D9NR!H@KisWZD7Nl^T04g4 zm{jC)y0X8U3%7h+ukU)bpp7CGcU~S%%~w^@Rn?}(K`UOeZ@>OkKX`c2#7$uvYj*mG z`Wi%d-|?to)uJ@vs03rM2OJ+X4c(+6W251qZs{oA6$UT!D^Sc6oZn@;{emIK;<jqFSjmPz!8iXiK}gJ-phf@X!8YpTJM+;%O5PZLr*9_Mb_$$^fJJ z&bvcNED}Cf{4{}^x_rc_d(!ULY2Sj)uJ8Zy9;~e}UD9*M!6)PZMRqGD%*;dY5?Z|c%d0v93 zsi?=c%VaLC9o85&e-(%vko^@+b^DlJqBnBn7IUa%^+DPK^h#AIhp~RV?Rb`WtuR~_yhRnGr0Zbz-3gb0j6&PXwyFajF z(|!+4cp`{N^=kw9p93$>zDA2RI%SgwO9+^~zn|a-|H#Yz>xA;qQ1#vp&eipI>h5^f z%1)is9IleKQlRo1xlz@O6jvgf$RaJK9FdHX){eLcfTP|R4#x@qC@%uqu0yGpEBXG1 zygm!dRAY8pmMGoUTs=zxTqD|q5?VQ;Ujmr5&nKrQNGkN*g$Y{&nh+B%VJ}39wGGMe8p!ch~A2 zWEpmDtS?|0t3Z#3M2(;=!4XUrg5Z(`o116zm2yf*yFnpc*EUV_OKJU)#O#w6URzOz zL1w)Ehd*L`^=D{t`az2A^3|QvqZ+roE}~>aY5NIMqfP?9>m$+NI_3|{4F{EFB6QTC z7L}$CySPCa#^*&#ZAYBs=&;Si^@Bfq)9ihmQrEp}xG9ddETxm!mn&|E#6;a)MJI%8 z5z;>lH|{qz=FNg>)2vEKPZ5WVY#FP#e=Vgr2qFtcxEl~RA_&_k3F-!Cfc-#>P_!kE z5P^v?HBbQ|qJ~x5?==IQBc8k)Cwxo?@oGUF*|C#dJ4-q4{6C{cmQ1Qmx8+g95wCTF ziDz=iEbzuflAcJ;kq*I?CR9DdbJ;#T^)6q7!>)3#wVQ@I*!@*}g%1efl37@@`w+${EFIrr z1$H~?P@U4DDF{F-vJvu}&Uy07lgAX{`Xf8jdi>#vCJ(C1j{JLgseCD4rfvhuj1p?2 z$j-M*Ny^H(PZ(GHa0Rg$$6Q6$%8{B5ixzBx#6CHVW<<#7Mm0Cr@~$YtS;F(;iG#FZ zwqca|!fcT59j6nEV&P8_i3$%S(_~PFtaRgs2PbIvR$urb`!N+Mtf7XNcXDad2>1ZP-mmR_i&@caSO9eoj)FNY0y#%P73#ih#hbG=p zZQ5H0Z<(v-wPCWo&Rp=TpzbA=@wYP{W-C(Ue8>8NjIk^9Sw8LC1Gjke+SdVv0v z{xFB{?miMPkqs#iEjgCdH$cC9~VL=&EK|HNeHLSA!we{c`s+-4mt z)>XrfC_EMddh*2|vzJ?Nh&*L%7az($yCY5Jnst8gCk9}PY>N@={Z`k}Xc`(RlU>4A z&Uj>16pp9x-jp9}prn#$UWU}(B28dTomy9fhJ@c$5GT~UXNr6gkM1Rr>X#0i2}JvL z@{NA$$VIKzadW367Orkp(OSN9AKaG^Z!dc1v3Dn-8G=wPYh^r0f&0#9&MlNnEkC{t zDI*0I#rcN=JcEaOz2LP~O>h}?c|!uJMSjFf3q|)sFF;3LZ&KT#g(9Y;ikqTm26RXp zajh8m_#SQ5Bt9zZH&y5D-Wk)x&at*oG%9P}H&dbvHL#_pTZK4*mhOMM-Px1RSZR1t zE!0(ZSops;@gyWk!&Fvp=AJM$$Qfqmth+NH9dct4{~u9r8P>)ZZ4W27yA`L! zEfjYvRw!=8DHJH~5;Qo4BE=nwOAE!_i))cmTm!`+Xpr#ccb|Lz@5h-B^CUCp?6db; zd#x@1+b)ucg+Zpk{AeYQez6XHi~2)dJU(rHE7!#so=Qlv_$ytD(A=ikdiv-&EZ312 zU%}F6JeGhTqwFI2=>fG#m_T(n))+Sq{k^^NGBlB?)IldydLX&GgxmaL$|`M(*^T5A zei&M)wj0)`Tc?1Ss+b4yzi;BMPpbdSz9;*0-&ZFVfMIbPwW=gmj4Lj+k)Tr7K6-im&tv+!V$S3|`}o=#pO1_8bAqgV z9MXd9pfH#k^p+0euhdG2WN~2bY9M!P4JIQZ?%!E*NJLzhX5>ZzMs6Ki;}z2SuZ&ST z#N;=H+oz+yBQETJSAP;?NjDt33a6EBIM@!iIQn8lS0=0-=Webh4(s_A$jg`iwL!|O zGm5=(rM~-?d%sgVFT~|9^!TXNzQ`SOFDhSBa~~MQ;ahDmG7QXnF+vn)^9N5f*z5U; z=C$RrtFcXNjpdU!2tLJ#HhYIHTS)%bjkqE{#sl_#%{@Lnq7l_dS+VmYwQ0zSeE4}A z(O24L$(q~C9i5w$?TTILnF!!~=y6NK)b2GVj7Ymr{89NU&)2gwT8!Z{1?7`T4|9^< zx|W-pE_2+Xfr7v?Z5fN63;6GRW*(Fs*BiC@@qlUEbHUD!4N5dnEK;@6Nu86;Dc`d-^lu`<6XWR?JH{A5I}Q* zX$NASTt%DN=EzV+#&xeVJxF}R^tlEgOz9kpJ`nmfW`Kn{{v5zXLcdR%dY*0x^I7f_W#5KgyciqiupE_ zFs{JDLHlvZ8ZVVUk!=*2H$^42z1d)I!K8)TXRPUi*zFDLJ0Cl3de(wVP8i*AJX}~E z(4)4Zl!v;KG71(e&QS`#kwD4NrJnR$U`M^os!8GPqQM3Zn61r6oomH7HiNtX3ZJeH;o5xSsJ6ZQRQ5$hdUErv_GW}J|ZN7*9^pyLX$b{{1U^k5@u*^7l zTC}2ZF(h`_D~9TP^Znzj$Mv$T|Jy58Q5La~-As&kg%Z6@b(F%9A?U9eWUv?TxE~;! zEg#=$FeE+kp67$3+zyx>PXmeldcVcq2c?};w{Wp!J!CjK$IudYgI>Q#E~tw0_8nV3 zODww51CN5z9{Q|^&$OE8tZOGC!qnhvaWKxzf*!$EnsuP?&jd$CQ_w_HU3Brnt%5}y z+MDkm(tc~nJs_2&uO~1W(*zh-Ik}rJnvO5W;^(5&O+*bA!qxDNNcUIV&L(Nj6ojH; zTKW%%)AabB=Y{+1+*~&%?<&%W&OOQX>NY8fQ}=}ET;77l=`(Q8<~2Rk8yT{B>`khb zX!!DkCtqi6R`}eS7~091Kdf4#2SWxLp7>^o)&)P{csbcUcK&uy3ppZu+e8vK7eBqv zRiU&HAie)>mf|^WjO%+6b%jFk-%ede0eGW_q zAwbg#wW?jjT#qBm;Mvc*DV$t^&$3ZIL zf5)eV*4@UmGRGK*XTK>uxN^bX!yA<;zD`o@i4pBRGIals19%oTpzQrWK|&ARD2Bbn z1wB#7Vc<#~Zi&ph_>n{G!PCJbwFzZ6a)woIJsy2O6wFCV_r$5ylNSvbv(l<JOK;yHmZy`|Z@vDUzni&D4}e7vnEB>4zJ-5#{L;RGN>^H3-i#%h*aPNlL zUHYQvjDt|t{UXzi!EV_uDVnSv2Gj_iuIt?c+Y#sFHP&Z|INW^L zlX9s?dO^MEF*ehnuzj=nhqk02|HiG0i^_h{b2?wumP_J0SU)b>uQ3V9nxuXME)v0M zrUYaWp(_LjL|BtG3#~9LEOYIqbWO?`x_5NB`e(U@CxHHgPOKRaZZ;y2nb z^oC=%FbsZ=x^k?f<5x4ge+n1;zSduM_{t3@)zov9(A+Fgv*Q|r{-xDAQk7Phr>i-< z#<;}nO-={-e*MDGBaW#j#QdrMbBMHAsScF4#}@2{{=+<_n!&2_RwG2RE0=d^|586a zn#_%wv5iR_#Z>8In=zo5ts7={>KdBZ?ot0hu2Pl%KGhuX+U{iw3tdnhm$D5+m7@b! z5-?>#P=jgQf5aHzvj<&`Ouq=mVU|(p_EfCAEzb3 zg8ohv=WgP0eb(K|$&M_`NA9qA*ZqYz5PMwv@$~SH{F}nvSM?CAr?$QNu~17YD-|IE zNOf)ArQhG-$<1Gi-t=9iCrm<;0YILn@|!O;9S6A#G&vky#q6|AKzGtVcj$Iwgu)T zJ7EkNx5&Q$CjkdVVo_8d^9!C;>zft=Y+{C#4wqvnM11tCTEjaA{6vl8w46IL!r5&}2(OPq*$UuibT^>wk7Tuxk{` zxcbk>-ses;$(K6=W$lvka|pSjZXzA;L*MDQpn#Pro0A}+JJX)%Jy<}O(;Deja~3Bb z*S!;fPl+bug$%D}ofGl|jW_{dInj#GJfKk7W=!zigmb-`uA)~k8Ed#6JfPW2YkiRo z+?kv<&SNzc`J3F`&21WAaQNr?xz}pxw6%2I;LY#j{=4l0#GOC0>8pumVn}rKqUw^n zihZ)6!3JvJ`W4s39kKZFq5ID*Yn8leeY>lOwMmiDX;4(rQe;l-h94pbcIuK-db8=- z1$%wVoAh4n4@Z+k$#7Z1#5}|iAGxvQla$*cRd3}Md>t8J5;$_h@-$%I(8gOGD*;?4 zSXa!@VN2-J$XJJ}esI~nQVog^2`-V?+3(*Gyh-z{+DR_?w+iHwA+f=y5~UZt>`p*_ z-a}U?9!m3Ux<|TK7_}b$tr$Twi;qh`AtV2`cJVJs75f57gN^>=V8!=j{pd3MZ}`wN zqw@2TI<=*wp3LIwc63iU5*-*|E#jS`(_*7}>I0|{H!ooP~F41T}Q z6b@>2#?An35E9y0I_u;02wt8Cr%$sePP!-3WH_?f2mbx`pjNu`iv9ZjHJ{V3zm9@Lsp9oci-)>OS{bZkYjgEWEw$W+9aL&|RfDSFh^~b0u>aNwc&^_71^avKcX?Zq)WhZa|Y*k$ZwA>O=S zC`n^xQ?brWg`yvm#$Gu5aCuMbg`%2H(LvMP_v7!lWJ1H${&NI+t0+U|%U_*_CG)UP z(h7leH=emWYNoboL_~mho1}7l|LIlYgF*>n#w?B>;_1Z;=R5Ra*jUQ2dNAc zbT31FedKJ*J23&dC`5*&x1L@YQJ%4p(B&lidi&LwxgT;5TUo9M>E)YmVxYtzd+ zi-K~=v2CWek^>g479lgs!6QUryi)4q2s;pfg(KIJ4WH8AQ)oG=(?? z8kuXPi9ih%{WNN@oL(@4L2Mx8fw$5Kfuo2&YUQ2pf~qj>r(W%ZQ%nbo~u5MI3%P3zw8t zvsJu4;Gb&B(2{`Dka?G_3~6%|hz$PCn8z^c>vg zkLz*me(TNY9po3gUSBmB%o8w3p2A1EsJ5=H6oynIEnH`0F&{r~7P{ zuo!ow_It&-BkrLA=JJc0ecgggYxZLB0gxrDZQ@`qqfw8^pK}xd%}0%0QZA?mcOCvR z?ha1*FQZ=&#H!(u=-Jg~*u?HIZ+0E$9XU?5Gw794;f|^L{2(TZ>f5T<{UqOA^=E~R zHexM8%4tge*W^Rqc0J?Mz~))4N{EWkz|k@%$^Y4$stCHi-Wii2nOX#bGALw@?p9~q;9=Uxnld)s^)&3{8md# z==ta+s>*4*DbY9=)ndBmLSgUB!@Aj~d2f74g$UDS8NyEk2aTuNe|R6OaK{92LNuw| z?%b=n3eG~;C{0x5m2m|Bj863xLOv9dPW$}oC}CEPCEcP?5-msO%Ul<_$~oUH-~XTC z;(w=(waCW|tb{J>u)^2}u6Q)+<7hA<#0h$cte(KvFCaLDxpk zutv#Smiyg(gZ>Ne(Ak#)nP+;a>DizMlUqIkATeqcOd^4D7a;>6P1l zKk35w&#(l4WS!vc0O^KG?dJIT#(wNx7J}Oy5lB5^cc&Hg%{gH0@G}ZWH!KQCJ!5CXiRD&gFmUDM6MKyO@#5lc&F0 zLxZG+P%N3R0iDjA>fRDqXAc@hi_US5su1*fD-^0>YRb%exx2LhnZc~hz-v~TzJ z@2B-=iCaR|(lN7O)R7V@MsU=C;$1ri zUiFuj!cD*K%1@M{pL5Ga34kT5paS!hDCeQYbKtvowwR^{c}sVeAVx*lEO=sgC9n_y zS-@fhH`~T)5bSP;;?<|{OYG+G6wuqUsRWwV#BjZd(gTgXmiy`0)X|+H2k% z-oA*sZI=6R)cEgEEFla}*X zuLzvM$kz*==aj=5dT%`!yLVfEqdy8QO=m$X?8-XFp=ZQE+BPBdHsDmb_eY0y? z^FM#;yp@SDChlf+uZH%|?F_;_51HC{#4$}-$Y@5SlGciPI}?;7i;1p(IJB7pj)g58 zyB$`+iuW<(!Zy=yuSwr#yyuM?k0d-N1S<>C-=#wFATzZNbk*FES`I;GMmSD8jCUk7g7qL%4bdmhzequ#2*s;mfj2XRlD7WPQH5R|X=_YLe-98_Gof1~ z29yW&x%~WoCgAHfr&i7YAW?Yzj9i2$ww7FUy*EQ4bo}@qpbsU*WA5`hWn{hH(^N0s5&9?6{SS{P%H3od5y>$s6QxIW}T_c~&ZtquUUJj5|t!Fq7pDdtuXYY=?G{}{ibw{?cf zMjR!P|E!z%2ycHwyzV)l7*8g(0yZ!}&qfDQ}z7Z`*J)(MB_#I#VH0vZTYL7fK zTtQXKvZ*TE04qL-a*h4a)YMd(*G6qK^U$C)ji%&a_4{(3+8RtC9Pb1}7f$jJcw4&x z{BENThgW$77EWEVVg*H7EMG-u3V*##erG9u340oaF&3?zww=LiqSr^ki=w2fG(ne76&^1 z51T`O%in}b<${+2SrN1Fbao=NtuPWFIgO)ZQus>2Mh_Yhzg{V4Ktj6oQYkU2j`$-z zT>Gl{?%NJvleT;b8(gp}%q@w^y@wVg_o9$w)YatvT2G}hF(<8^4u=}Lh>-F~@$1r! z7*Ht8fs47Cx^#sFL6XLygf6q+P6=6d_>YaBz~ zJ1PLni@`L`<>)8sY5h`mP>AAggjpiZ{i9f)=S8PJKb!;Lg~iCi&7oWipq5#&c;rIA z)y8j>JcbC%_;efJayNeU_WAK3#<%eO=KzITK-%sps4==hSUKg4YRkNuhC`couO?j0 zs2vvSBBO@d$2){b`howCpqyY7rSU{?#hcXprRzPoV|?=RuZI((KP*K{J%l|_=k7z@ zZ&-fpw!Y5ND9C6Y&zTYGqLXfp*BLeJIy9WKUb8*;7<);#Fuc^IEiZIkF_ zS#OHdm0=7u51ZyW8X&0H(sK3frE|0AaolR8JdN*eyRo+zd2f|P_`Kbf?U6qwNk4I# z1fIWI4}breWi58-x9hIiT=1MA>AAwQR+1Y=5=rmB{o>CApBcoD0hQ55Z!hRa?9+l? zGK@&%SA#OqVkS;biT9e6d}wly)GSExQQ}{IW1d8VkAkCdpMr<)8`nAd-HS>o@gpAH z+q`jafpo2th4pTsn$>vc!dy7}uOK6>F|3Ea5Qa)bJ$MyqBBmEQ@c4MAHxRGZ~auj%9o!JSTk9~1zGD$){E4*}AZ7gEZ zxc71zEyuZmm)*~Vd9Lh+ev=uI0Ma(>!pS+ZgJG$j+Ipn(*Z=fD!^`UwWB*nGKKQs?INRGsbr3D=c>olockQ=*TBV8BSN>UB@DpHLJl9!z*~f%x#N@ZFQo4PFI+G_kQ$4>(MF)TY!Tsx175>-u9JE?-pC$pr%_lXGLlyERxZPaLr?6!ae-iNsKL_;-ESXfXKUu*8Vn7Mn z_N9>2n;vBXJ@mWatvyR^02+Vv!2`4cLN2=Vz6Q~W{I{>i2LLf(wkn5YOvX|k4P=)1 zz+T#N^pcNG@Zc|o@3I+}p9h($Fl7LhkNo;JQZyuw2)}fE!Tll< zx(s6Psa0jNAmm0_h9E_a^%^Khs&;$P^F&7D9@A=e! zwZ=nzYP^!qyi+w|vnMy|qAPnNi7l@e>1-oMHx|wiy6j#09!ngIq&zRC*?-1}-3-q) zRN4!S+Df$EYCD+u%3pMEi$$3gR;L;W98nh{9HHa(IbAXv^s6u_jMgiZ6>u|p|IsFR zWmriY3+dsd?y~~CI2#Tu;Kw6b95PT9dv;J{LSmvXLEb?Nq&iBDW&d0OGWA)&Bq5ng zrBn1xO3woC^}X4*fdo!U%U*S2c$2v+U%r2fa@wCrgdJ}oT13M0*MH9M*lmjGH|d?) zzhHyh&`mNwX=4ZI(->LF5ey>-?>=L=s1>fEv%31|S)h@P{&%+@`f4?b!TLcUZU zpw=q`W$L$YC+v7BKeFYnW9ofD8F9+~EpW@sC~qz;1LgH;LaJGlKe_%`)v@3OX;>Uo zdOR)YdmF0wN$ow5u`t>!j{B#3BshGP#BQ~2RD*Z%)@3RN%yLs3*5(%IkL0`nyU);EO}4|e>C*k9L@5@K!y(LT5=J?3xl z)GBSs&P0|e+TvqeuUu@Dn1v@#4*pP`ID%5E=}ga;Jqol0C2RIdBbh2^fthuUvH7#` z&H|5R3bbzjz$c^5{vcZNoJ5e%6EWKy+I=B7=)3SbFP!|`GB(0K$|MdCr>12)Jj{&uDT~*GFeRVIhwJ! zpxZ8ijtr66)AkAQ&#;VeVjb6^qZM9>oLKL%B{3*$6HBgAa3V76wP>wjB72oLXBLgH zfkl-ue~?>Nkc}nlyMz-f;Krh_^c8>2FttTh$O+M0uR-uXSu<@f4YAFkp8dZ!?ADyywdOu9RKMM!%Tu?YD0;-PQkJwG$M(g~~_o%U< zNVPgsa(+vVidQ&1PM)K82{q~^m?{qc7}?JJ)C!e?k|mYCGBXjkLD`@9)5u>|N;vYu z=EdT$g;{V0W<9BfZQT08Xv9R>3n>o`pkcmQf#g0Ilzs%s=Vrv+rc|i?Lyt)&$X^Qq zaSyk6G`xgs08ZXb?u!#!_KtgXd|zK>3%GB~MeW{Ip5;zYjh44kS=~-W)k-ye-}Vvk zQFz98d5vEm}1cofa&;j%dD?ijzpF| z#w_4UU=GtR1nuAGlbOee)RHz^Ls6NOtbtlk!RmOgiRrdL5`mBNK}p1H8M}brt4}@8K;v13f&k>d^EDZZsBr6t20ilCGD#B#+^gOZKJc7!Py7xW3s*u z2o}YvjpH{v@xP^wFQu=3kKcS#ts7J9#v9=?=~MsrqJe*qQgkTiZ&wNJDQ9IMG0`vX zFNHk6xfj&yza)AvD72=r8+PD9NN!0a!U3nZt#E|y+a>>$4z>;>1fvR<#*3^r%H&{; z-1K17cMnKO4IqJhTDK>diflj$L%N~IHU5$O`YIgyjg;9d(d_Fs#`l^#+Py_saPhr- zK1$;j1vp7kSxS8$d-bC(+$V*=*ZGtgLy#HpG4LBrt`x8$nkw3`rT8lUxWZG-lJX}p zzsX_1zBxuw+u)1`^!z_M4&`>j!)~JE>q7DbuVNfRMP@c6e;rg#t|C-}tsx=nWu9w& zhC6shxle^5yJO5FQ`L?cTSM7pQIB7la@l~$`|DfIFVyv0*auoE2(jMYEbiD8-74z!+gQfFInYo z?&qzfarQ+6jkaFQy+qz?-C4|Cyq053&7O8sUPELMmj)c}Xi|R^{&PDbhN7}$rHw5T zy}~22nG48;3gIOQ3iiErCYgDHULW>|u=jD%{F{)QWdeu$``?>DD3OS}h_GKoD_K}! zbYV3kHosu|gIEBg|H+PV_8Tp)(vc1Yz)}N0Xzw(1H|ucPo1jo4-G}c;cXKE9mZsD% z@BzAH^e<3$@eO z^zjj$q|>L$lKWK@ql2=BYBKa`f<_@PW?u2i$fRz&4_~q^DG_C&5iS9ZD_J$!ns>){ ze34@LQE_{k3ko7lukM4tWc7A7=3^_U;D+br;QuprwSx9UE};MXKV_Ti@SGXT)55H( z@(hK&>Q#Y>sjQw`!#}aow*(7iT<B|B0j-nb*DFoP4~H)C0wI+HjY<3y2Po_qEN!^jKCoh z8anH9fi)dyoqd>hkMR5dJVXCZ4HorclOarhc74L*d!BA8sK{>Z2yg3#0b!usGahk5 z=9k+!hQov-*azjD`|X^2Ic(lSx}k$5lEt4yRsz1QQnV`iLUt)W`y3#8ITb(+%t$S^ z*mw(YBai z%ux;gdL@2_!a?<_bmvq79qxPWZ@33k{1BO@h7rokb<*|y4Df7s2^Ib8Ua>|Ww=+Q7 zy5%Ex&5pt!GWs`#3&m>k*3ee@J3w!5D$XqHDSN}UlXBb)!T<*{wq3n+xevMR95|{; z)cPQ3A_g2W&jkydQSi|a3s}&CHr>;@*D)dbvI2hoaChq_HomaM@qNSN&H3>u5EPP5zfSmv#Dofws5Jxjo>=)vii6W%tR8QT=~&*OO<)8yFh{!3=)?m@J*r zuokfVIKkXHhUezd|1INM^eJLRWTL+lKV94}%M(|d;g5p#^ZOp|C(GughibZnjB%ra zs`L@F(51co-C99iFJ4QP*YkoHU84ENq%CyG?v_0Z2)mVPEp}ck&UMs#i`i^DN1ht< za-<7;tmobApB?aMnYvdTT~#3 zmS`HIAzn*>V25_2*#hZIm4sc^nM#UH@=Pf>7ui^_2DRned7@s`lDlmrZv zDdsN0O|dwV3dMvvM~B*GA&6D^u=s&3I~nD7j&*!RaOAhajV^yWY(Kry#9Q7?EMvB` zL%YvM3)FKo^)j~ z9M6wN8;nj{I$NX?hs3+poM+9Nc6V5w26oC832W@+{(&Ax_EJwNlq8a0_0 zm>Wi!J9w=32-pjd7~K%2%%V~;s8Vl_OTSIk&7@o)B6QIaI$>Ujm%9yro;j9(01A0B^%$JC^>?{-JS-&AE2EHf5~s%pB;IzC*bG|Y z31|r6LmJ1H@w1!(*c%3)wie`|oHAB*rpxuYgVK~AOY481$yMr%?y>>d%PQgFdTSLj zA@nx?3sBX;HijrTUWV#x{pVi)KPnk~tRaE$;LW-zE{#9rc;R@)#rP$8s#!_!zfSh0 zFWVI0X}5uKj`>Z1(RGJe*KERjbJk(M;FB&{bMlI}0+5n2-qvY!(N@Saf8`Vc#{*HZ#Oi&Ga&#QvSDfB+Y2iaZjQ>Yez z1e9Tz8KO_f}oWwI@X&C(Stnjq&Ga;Sx%!I_Sg$>1okTd{p=SHp5QB7F6 zIz7YhcVQnxCT)UPAz<1rtlP>l)fx`QFQidRUoe~r)xWN9FRLY)Q}71Z51n*QLS_TE zxVW_Elm5z123#+MVka4h*?8@7#N0_bX?1cj4jRmKnsx;qNNJv8?Pl}9KFO$LmxWkn zflQv^_v|Hw4({Y}D3)*B4%ymx#j#Yt>gQ~{Gq8uf1ukwnnMK+9x69^UfPZ~`8BGP6 z0X>4ZrJMPu9KI)b_G(R)S!Zt!kt>{u65?UP6?LUvi2`?nOu!eq5XWGnX#~aR(<5CTGTDQ6=UHJCi@U8 zrF$bj0{$^|-~7b#+|qkvX`B(Em93v`mb%=2l%L?Ri^`{b`l9Fth%9kBg}tbL~i6u4hpKXy{5>{H}a zAaTfZ&+A(8sL%Z;xP8|&g9Q5p<4=7rlsS9+f;djtu5(1l4PSE=tz*XAF@JXr6l5GM z3zS5n^?`Hm3t4G^@7TV?y|ax=CQl!4i{4G%@&80Nr>e_C@YfY#_FrTO`8aCm(RQOn zG0%MpgitfYg(+cUE#sz!nOMR^mS7~frXZ(86KR%ME50Er#j-r2o4Fsy<>Qdx5%f?u zHpJoL*Z4ll9_GX?tq{wGe$F48l{a-Nx^iteW;j(y_}4=5H-Ra#{>$9}HVH}#pMXY< z)^T@9zs(D+*5a6;*Cv7XD=5YaQpMjzeJowB=m3;8aWi6WF5QLA38RNC$P#w*U!!Ye zCGeyC8(fWD*L}+I??W`mW>p)5xa{hwbY=j~=Ea%Bb*xmiVTvcwvuVX0`bhFP^mw~T*? z8`l<5VMxtRzHa3X5iUoPgF$^}|JwQ&BncDIRcgLBFDP~zBd&7`@0DDveQGh2Rylvm zQQ{mcGoSO|8dk7Z7?=*jv}jC&px(KdD6<*0!_Peb)tVfFjQ^Ng*+j9hh%7G;VeLHE z=N`Z1M%oX51o_BaeAv|X{VKvfY|UHN>Ag4o3} z=n)M92i#ukNa;z_c&*G~z|!}QxryGaR+djT)!7cR%k9*k)9-hsx1XXL!-P2Yf@-^m zs*lFGCdw7u8C2;c*rpAqXW*zh6rGJ0P_P6CwGq9NEUft#XpRl==I*NYDr4jHjVFVR z2*?lDW5^D~{xh0#bjih03dPhQ%&qFgImfy1;Om`tHqJZs3TMPs@wUI#)U`Qt2u`{t z#@qbr*CtK~^}FQW?WU+;OR5j8E{#B9WE$fiI^auCd>r)6O?4~E>OscB1^VvHb+>e% z0^(EWpWe;-Gm?fp&jiZ~jl~Hwu{wXFy(T<4bDLzqXU0r@!B(pI%UQ+vrh5ISL1tx`#iG*+q#8`Zz-V@{_fzze0y%33sfZb z{IQQLhSAAo6^}1ZD98k{DtA-2;xd|OdG-#4i70wc_;`o{95JwhHI z)EI|YOwsl?X8S`^OfE@B1p>u|46BGMheaImDji zl+;Xm22`>n`tI+3UvH{N{ubkI0iXU@2mrH&J>o>mI^9-ekJj11N#EI}_o4|OIW6Og zhcUJ6n#=(<%QFgE7R=W-R|*|Ag%^q&#v|%CQheE_{!9Y=f7S;gaMp0ED07?<4%k3L zSFrQGv~65rNa>T4u9FN=I%H*d%x$qIsYl#ZZA)t;u<&*y&xT|FyG#M%Sw9uW)ein1 zAgn{v$U-eXg_rujd4eSOyC87xH6M z#N+V+r6&(b{GwtU)`C<;_%c*yovqJYd3sazx)kWW<>`Uy4?8>W56Y59CSuFQPtzax zG-4IbcMEEQ{$JzO{(pL1o&TrTz1{SX4gR}?POBldyy6Ww@y)h+LgNc`(xMKxpq=(? zcSw*#{rNs?=Dza8%Xa()D9sv~I#`TcLd^D%q713ST5k(L5LbfTnLUG@W4GUmqBxaR z{CIo;3ZeZ2e}<`p;$!_hVQ)c_fw^qgoo#_W7_UyVKv=aKPq8vGn8h_cl2RduaPx^S zETGasc6F=VB)J;$p+wG#EPcEuPL=vdIe8YLwc00L6H;mXsUf50 zDsO~xo!oj)Z7ulFC97jBMP@X(j?tw99+kk7k(vD?U2i{-H&$o7>0IL-f1}IyizkX&G*o}Di1PAQGp zrXMWEw;fjvabPG;1xt(yqny7Ik#(HUyt2q9471o(x=&1+6MB&EUb0$eg!nTE@nB`4 zvx=n9Id8=dFFg_RqQX0eqkCTU;G{JichuXRyqwHvuSy4h&vnk-P8g)YoBP(ADO2)K zt%5|W!Mv2c5L4hiAzKPzb0-+>UEaH73!V|Q^hss&v-D}+Gu@$ElDuMo6|x(7nAL8{m`p=Hcp3Q5nakC+jy44dtYnhX z|LoSbe=v`^!}$CTF;(=&W()N}zpbo_z&k*wjg}u3+1M0hQcp9*mAKK}fqPzp9BGq8 z@^u{B9V5m%SK2!h*S<@Kz%D;}I~)08GqzfGrjF7RWj~-z{7IUi@Rbjph#mitJ`7TO zLebNsKJ}rd{_gskw~?YEWv8X2vcMwINn>djcO3%oRuNfv6+=zoZcJz1C;GKY0j6VT z7AM!CcbQfr&6gMY9DhkLy|wNMwE(Wf)UyWywXSte9D*y|PNv`;iDO+YUR{T+or|+k zU2=^UtCI*~N$&Os^LKAl4l_l?yOu7`eow11WM7K?L={Cr$Y3h_9WZVW@M#COU8y}d zCnlgeHj1K_p_a9=dP?~We5c~mHKcnl>j5^5K^K7$Qb?h#Wi{Fu4alvpJLffG&Uc!) zezG)awRrgRt6V*QI%CIeSk8xLthAxbQtg7z?Uc9eUn>T)g_;bRROtMh(d92{S}?QE zE$+tl}`zTzh@%PV=c>rPRfXP-3aUr1Ff!>8Ox!&s$0$Yoo!1sBIyAICA;A!tR&aoQwQL)627D4;;#T0X@%Mqh9WM zj#|dFs2J)_B520?fwC#BKXvcy@xhCd1YbrwlVS$*!vr>Y@NI>h46o+p*#l6~N z(nMVGRoQ_(+@?vLhel^FW~_`KG@3AEe;*-38tc!E?eiLELKTXNa4$lLvEGwN{tgE| zX8_!%f#y?`EUf0&(t&QMw&@m#ohW1b^^7h@0BMA-6KKgPbhkgVsU5S{ht5Qt-gJ1> zS3)M@xN{-_T$9wnyRRWPxj!&0p@`{7^G0BKtp=!kidfPP!P`X%@f*f#D#O_{12 zp>^zio7Nn*n7U^7=L}_NF`n|BgNQkfG3Dg=728i_66?Vv+hwi=x;c?dGYfQ;wHpA65?H!qqNkN62y zz^SR_wY^0j?7^yU&(gA!t{L3yPvFhdi?B$W^Z+^!x>qOu@|)+JxBBva?K|$)lLPhV z8auhQVzzJgqq|V4PcZwekb#%qbT-$YDjgRcz6ZZ(Ir8g#<0Y%=d_G8+_)4osv!RpI zTXv57gZr`^pEMsYA3irOL$3BogeLpE*>Xvc3e`uciGSHGST5WhS|Wice3?z9W6OZg zi`GH984!*k!9_vLFeQUDe-MP`HD$C)r>vm_4{Lyvk>6T5{q+chH|PoHQcr#$=K^ zh|ZD@mT@}Xqc@pCiCT!EQd2^4NZ)O&;ZHt&^2Ya*DtC>a;`EXOsDMGl_4BGJ!W$38 zxo)Vi;9g?4IjhQbSFslbhuzGg8YlqoM3yk(6mB$v0^1$sZK19#&)kXaGr!6wa~6s= zAFr%M&YS^rI<{#of}hS#i5xqK_3(pOJA}gCkBiB1^qih$_?!A<5ukI}wt+6Slq_qE zm|Emc>*#ynsB3Xqd<<(TaV!H8u3KF*?M zUhRu+^30(qJ8my7y(w581#VQ3T!-vsBrn$ccLg$YiB}EYu6q*z`q`WXtrPi^sx+ZZ zL8bbI3*p@0eXXXIph7>x3yc9&HK`}eKNh7ED{s4h&ptm+K4`BQrXy}iaA&7SI^txx zX0Wa{I@>om^Yjy7`u|mG7Xl@LC2;xYQX2#ZC8*BeShLarIZsRXT?eC^Mq15!hb-|X z;{E;B?)_FD;U3NQLuJ3s5!Sh^9o&g&3?TLQJyEA#=%sLLUbqlAm^2PULR9Dg>( z(!$5dm!!*iHXqt8nvuQ}Q-l5`>7HgLHiq#}-MZf{{``4Wwe+S=_aZTB-4e<8ui4@< zGtAG=3t`l3sf)pRuitrmP~s-D;DT|~Vcyc`w>F3OHh*jBTI`!MA)Z_y9|6x!n`OsJ zK>Ru{^@oo=(zVr&ewQSV?HS#$iK{r|f6AagU)-;IK4LHB{2JI)8e-1`{g5oj02B;r zHm?_`Z%3MlJrqIsevpyJgA|shjV}+Wzq2dbS9%S9 z&F!R`hpw==l zA?E&Fb6CiIuHOv^m-=hg6fo`cYGkF=H@ytP(JL4ZEPPV9EDc6w{II4*_>V1MI=uju zI+IR=h2dRpMmL&Y^pf7D6|-}iuz)8{tLm<(W(2(}0oBqf;Q#CG%j1$vzrJa+DKlrL zX)#l$QkgQf%zdqKOmQu>%-m|E+{K*~fhMQblmv5E%3M-Y3m4qL%F=Knw_H##*Ax*I z1rdRVd7oeNg7bXleg1l{{|@(c-{)NCe9!ls`v$K2ST@G2Pig!3mZo2O&gh%0g^V%6 zG=6TXqaKtGlGdTVoK_g!(W=z&Sm(~Qv)=X*f_-SJ^+yMabhS(SE37)%~}_>6#fF*-m-i2)VpwE;BGJv?9nbS zbLv>qSl-Sx^P%HG=bE+8?`(LSeOsd%UZ69$g9PoX=t0GWRN1B2?Ks4Xrah9M{0Vnt z;@YXj#xo42yrI;{R{SYzWa{3NbK_lnLxt^;=icR3o`1Dh8aS{lg+JBm-oI@$3&;TO zyrr|r#zoOk)?(Y8KI_){g{(Ofx13%2VUJFJvetnu(>Lw-oyvtZPYJgr-#HHC-iYV7 zsgC}{7$&_|Th7*hX)fB4JWsOQc1gUffo0b_bMX4|7?Vi%k#2| ze!NopVebfY;NCG6>JhF3;HA$A-ym(>I*Sz1FKW08gHET*C$qP=Eau9w`9Hr#47c`L zey{e>KQ$|0hx0sPX@0&Wk9{>Yi1w-aJ4ZucA=p{UJn@U=xJesK@D6byUW&i-{`zEB z!uze?DLR%Gk}XdzzxhR&c z6FCjg@6l5w`@b#$a@4UtckyuzsTR-Fr7EQoE^zMTD>f5rvaI*V|E{^OpRJkz(xdzc zVcCq?adM6t?L74KN1qc917vP(T0TYjPUooSSVYgWqgDs&4(<0zt||#dAO58O-Q9gC zPpaR~FU%6c9ZIEgKiKOj82*9E3oGWCi=L0n*;2mreu$gFcmDeM{Uc&@#xyT(I-<{* zyZ7lHVL8D_22)bj)54Dk!$l+&E06qXZwcQb8zD_`BL45r(6j|_es0tvnHPUn9ATMG?q;K|{a zm6bTG;2pAy%M4etrh8IEn{;^51xBXar4?E3_^Zi;AbL}upZyi80Sv<)K@X=`M)Lr7^L|_*Gt39a^*%a3H1_~J)B1s@sg%5n0J}*$>!899 zE}wTYQ*mA3rR2w}Ds%XwY>vI5GU>Lnr=K?Uv;qbj{S1kt=s=8uGvzT|mXP{3`Q0|f zE-S)IleZ>5T(DtYj<(c_o&8vt8D(>0o<9e3BZG1%Oo#dow~V={7STg4Q1?)HFqti! zMI3T~bD45_3$i>jZqmf#ADGOl>2T7(p;cBk5)-&WWi%77iJ~?+234nN7a>C51V4eJ z3s5>VN=VcarP*sZLU@TZH{0HNJBc*q;W^LL!~?*nyp)16FQfSH~>%UnUdvdB-0PG(1Hf2T7SUAF5`D?*@NaQ8Lb;Z(o-X{x}w@2B1o+amzN&!MKj?|jrfAjsozI!sKBa6X+8Du zW}!gXD`l>zaH)Sa0`2{o7EB1~OlKG=*QzwO_mtzx%4PYN%ze<+hf-GF7AP_>dc?BN zPE*-~r_9pVszB47>%lm?sMW;<2cXhgC$PDJ{t^5+Yg!L0T*hwsmNF*bE;%7x12p9d zn(;Vpn-jWE`chEsR88Ksv8U?(`&AR%cGopjTZUP-*r}<;8v2!Jyf`73DXcoo*-iI% zPkVXrRoHR{4`W#{3>`_Xn|P{_I6E-@2|V@5$85MR5_e;L_F6U^^W|P*?2QFK5EI3Y zCi;R3usSPm2Vu&XYVNp255zj`LT60=53ew~LT~bnIp&nhoIdF;qfw z5%9(0>)tM^a>6(s(9R=u?sFaIg7;??rJ5=4I8MECdM!D8&lQ+*0GSc!&U*`vrx6x$ zZ^JRv`W3Ey`6S8P#Z-I2!OS6tFq_VsX2ZFB$g9z%6pMI!8>vJ{L+#p`un-|fJ(gYv z7L2nHpF&~XI=Lk-)YM^vD=#*Pq1N*=ij-2`jlz5)Hi5rH>_k?))F0mZr;g_>s%);d?t z7PDD(`1;X{)kZ;I4P>OzVE*J$_0)EgLO4pf#3|bEad_Nz4v{B|DIp~>hhy>Zhv347 z$&%;IDv+Accr`r=(6we|y&`2e%ekd@zL=&b7eRl_lA6ytkw(!9=-NkmC* ze^d&hI|>J(Ft98~z2kla_rveHs{Pe|ag832uiQ!6V|Ykro~`;AXNWnoLX$t_sPjtb&gD z9^E<~|2#}9uv~pg#!%ap|Ky@wK%#xHN0|9!(juw2<&uZ+;j}{MftcVXw6a&{AgDWR zF>8gHNoTd2{kYX@`9t-d6ofYEuxqmNb0lFTArqSt5nIC;$Tvq9uGzb@*SkQ)26`rf zFHOsnsFu_Wa^4QkI09V zoUl4bJNcEz*R%?*8_QrO@WR3tr2nG-E)Zpoi@m(W1$T%#P9rFcd!N(kKaKjV8vTNs z^TdI#J7~ll3T@efiGvy!!%^m#L9=Y`!tEf;WU2-QK6wj##=J?F&$gDz*1gGi&Dk?PU;}0qK$RQVx>3cwwI~oXCD)2Pvgy zRdOz`cBue?0j95}CaUO}XN6;{S7N4eAG8&!8k}fszXqz)9Eg5J9+`GPoOIO-_n?*a zcnXYH>CG`kl~4VkdMdiS_xI~5%vGduePqRL{X_xCOKC_`+n^VPNk-U!tX z0yfV5PhDvhKjLlid~`(#SxWDO|`QtD3om4QAtM$(5)p zmm4SIN1i`GP)&B;@aWbJe%q4r$m$aE_mxz`ddpBkH!qC{T!tf=Y8Zi~Osp@)22%wr zD=nozX*a{=hhKo9C(9c~;9dlN915vC6Me0#cie&W2`dXx=*{S5%fBM4x+#-meZ%h% z0rpl`@Yy^r`xL2HmKHiJy&V`3?8ahsu5e&C%52tXpF+YLJwezKp5h3-IB#XCHr|NX zJ(7HLAb=)}b5M%8(v+*LFktPG z{)X-cUlaU{5NBJ^+ekWIg03A<{*l4)3A~xslo9C@>@G6q( zK5epJ{SPNiUa!`N$159yJfLk58iIns&`f8rG^MWvoeARna&ROPrGrMf`^u+~`k zbK!uot}CmjPczCMH06-l-}TyV=~FAr5W5zGTL-o^m#v4Q;ZE3e5P#~4IWI!FPm_&o zz&YT2FTjM?QOas4*XZ%tih7Qw@VVdhaN6DYI>=nkvVt4(H|7@fGdW_&r{qQgzp}ni z5W@bbTotQ+6qB33>P)@}8kdO+e!<9@c&toEcLXcMQg1<240NSg$2s-0#ZMZqu)4t9 zTl+beSJJ`W9s0I-;AJl5P;+b{UqRo_>OqhhoR!E(fnJEW4caPZd;f-d5 zUQCD8zDoRD{fnUS4tX1m%f{EZ=A@@)yA+MCrcV3;88Hx$EHUdYyJPatl{EUI4`ABO z{N~s8%%K@%%R%!+C7Ke5c}$e-+QafRm+4&ida+N zt|*3!CQLtFi~c~p5N=Ge=e|?h_JBD&mExpG(eX3NUHPgKfC=iKYY2%CCG9g)h-5r& zL07dId0oZ_X-%h|&fGVLw$*3-;g*w*ZKnoJarPSvkMruiL|$yGlb50rOhX1k-v;=8 zexc1#Tt#2C4=As-Xi*uV5U!6?WCUZMPRrj=9aM8s2GOsFcQS5TqW7HU=C_bWtDg}L z?*2+#JcIi>S1qe)WKt_kMb?CMPm%`YWe*ToIi!y^cw)d;4^V#xy_24i+^!$7W}$AR zGR~}F))j46WvP<6=P0hEb6#Yg4o9x$*%!a)?wFaZa6`6L*!dJf_x>64W*N8PRd@8F z!D(4#bvH5>&KpZ$mhWxRpuzj}&|V5RC*e?qq`A=kfm%wHY7_QQw0{qY^f9j5G~(+5 z_+rrH1JO?OSDHKLDb?m$lT?sWwur0L9I=+dSY&3-kh9!ERo=8LOk*ZALdyq}ZYSM2 zyF1OJda;p(4ER`nFH*PD`ifpQr7!g2Qm>1mm-hH8FxNtpTI)}q2&yNr78EY2+F9x{ zhZjO{b2BQuvKl=(vnfGgq2goP1FLTqd=N=5U8@S7v)U8%Y&1H?no?1=!G@wI?urMn zb8v2b4t22-T;Y9hS)nEXx7@H+Fv&vm5-N#51i4S-tP&*u?>204!wTx7wb(hriYr&aqU4Qq0f`JALd8)DUzYT8P9ovf_5mIg)afLCrDLN`Q?3@lO@*ucT?<#cmuwzi_auU)BzpFDo_y~N?(E}9fyLJi2 z6iTA(9o>Z65KVu?Av`lvmSbM@x4;B*WE2_4OC`R-!Is%-jKZ8JIlvWt(c1)(6-GR= zHO|AH6p7^3AY)rsr3;0y?&(qDJx;Jgb>m9M(R0gI)dzHD3327tQw*JwAvT1h$@5q& zG;7dvUGA#(Nl{4Yl`ReI_vr^WKbq02dTE98A!fMvh45VZD{~EGlyu2;YIByR{%i+c3B8rIA{iFU<&~?(Ml&$HFm)-RlW=tCfM%NiSt}SNyOm`2NTi zpI}4yyFbBS9Ijw5uh)XxhqFyAM}mD6MPMEqiiI z52v60ddFrzf_3q81t^?(*9MbC8%bV0*x%zERHjt(Mc63~YO@RP3=&S!Oy}q2p!60X z@z{)hE~7F(n-t|Wz%_S69G24!S$%i5Ud4k(0(l(8Bo;|)UXF2J8V!ZgbZU=93phO~ z9d4<4DcOQ!C|Z)a17!V%7Ne+$nP#G!zFKW5@E-b1svD%6ev0(R(S6u~12KNxP;Bca z0`19(wJ6H=RMRxun5nKOWd8B!FuG-C4v(A{@LMODhe_ZIFPlWJeC<^3Hx2D`f_i1o zdBS;LgESq^xf{gHJcgk!jN{=8GP0^_OxIKXkpWth$t~05v^3ET2|TVa*4QmFu_ixbG>v5AQC=t9;3zvIr7heped((i-Q~ zJp>CbPi)f@7|(=$Tx0!^sj~~gdjkOm$hz|KBLgsZ%mPNBi>u1O0=6}!O7wPdc0MW6 zYi+R$s_78bc#~<_GMBlF2pec^29e&}$)sL^nd8>^ZS2c~DYo)jlcR(27!u5MEoWL) zl^aw*to%}3q+1E$+&OF3rZFNDGEFFusxjSynk9Bs3EtgGjIYBRiEejzUELwlabmHl94%R$#MTT|9 z3=WHKG8$84r;;0mtFA*Z(w8=8=Mx{L@j5pE^W zx?z`I8~d%!6j1s!Y&{QE&2iMsl1|81MJzw`&=bIP34s-MymFfuM6EA; zbEc(_>(({uZY|W^dPy@Q{8CFM`lYQ8k+>L{NaG3^g{rPGi(?fj*z7s|fd0s%1-yTe@3X3E_poVN{PUp6m#i-I5|2sekZnfC~F=;qB*@%#3xvVt>M zcdFyi%O8D~-Zvx3?~$-P#1-^py#~E;X%IXusD~!3#dP=9*ZMSNq7{wUHRbaJ(ilEc zo4A@i(dDK%JezK<70qiQE(}+J{N=ErNMhq*Ss6EqK9KYlB&DL>h~+b?nW}^8G;&ES zaouI5e>%X;r-T0+Pp?i%=%Qbao2ZWmsy7}TJn3;=;4=l!A1?wA9d^1wt}`GJqiJf} zn8LMaGhuJ7PgJ`@7|gT%V;AOYjFe=g zfbTH1!PKhV@Ms7@P1Nl{tp5QEh7nx00{{ygHOI*O&44$JxdoU^vTscZubB;^qo%TG zpP6bK#1eWOr)i%9aN909dZ*FOTX}o3ty17%tg><2!iBx zSTNg#atTOBRI_0oK;%s{xGJn}}dh-Fms8{&;EO(GpwXAN(h{!mRo{?w>qKi*IcF9C7!?+5 zD%4r8r1fJu9OM>r3Qm!V<6Ybv*YYNcMpRAL*N>65)YDeRChX9C`(LixO$URDA23Ox z7t3mu$P;?Lnq{s2QC8LuI}G?X-UPVhr94#2{3ZjQx<3L)eUonec-(OhVC&x^?gueJ zbj&W_9`OCQqX27*EkFM)BtHg-3Fnjiv&CiGq$|fa-}-y`E#IUwJN6ds1c)&7 z-{Z`nucT5NNu?jYyZUV^;FrTkZ*BS}J^k~c;ifa+B)~7(N2;8+0{-t|_lM3p;%a_6 zB?{5EVO?mYz7i>J;&F@OB5_N?B&J)5`{9MvMKQ6pz)F3uxFP!`jdXVM0s=&M{m;nH z-6ZDH7aPt^J#taz*0-sE^STm#+ab0bkrNA!h+THlvDZ(`kKM-{OG?VcW9xX#u}^IN zbte%n#6xD2lp%HIZ_U5RODsZLQggnf=DdtGiAd~~I9EI-e?D|}6319rmzYT^m8Jmw zu@%q1;VB}uU;bgVwFJ;w*M4{-5k(1yiF@S7^~#< zeA1O!y13oW4|^TOO`UYi^A|tCE-6FYmkHv6C^sZ?LG*CxnGv;yd@K<% zaSHrZRLFY8FC>QGJf_C zt0j95@inM?%SfK=_Xq%Bb1~?*v(A4Rtm52zQL-xjpT<_4PajG|+oD)J-nt@g{a@C# z$Ks^)KiE#+Q1v7{u>m-eRQqqO;Xf*aWZxy80z2fyYqImkmS#f|-xps__K}TI{eJ*d z0^I*!c}twO0%y-Xy17JRv;WbzYwKzVBT*Dj=Jmf(4M3uDUx#v6OoHKr)W4euz`Du9 z=H~t4%`WMugoL*8@*c7Jk?$zFV87T0Do)zE#G7zg@qY8Th4=W9SHF1QY;>*Q>Yx3sNtK)&Kwi diff --git a/examples/01-hello.bio b/examples/01-hello.bio deleted file mode 100644 index a0b591e..0000000 --- a/examples/01-hello.bio +++ /dev/null @@ -1,24 +0,0 @@ -// 01-hello.bio -// Teaches: the basic skeleton of every BioLang program. -// -// A program starts with `program main;` and defines the main program stream -// `Main`. Its `exec()` method is the entry point that runs when you execute -// the file. -// -// `IO` is the abstract IOStream parent; `CIO` is its console implementation. -// `CIO::println(...)` writes its arguments (space-separated) + newline. - -program main; - -Main { - void exec() { - CIO::println("Hello, BioLang!"); - CIO::println("Every call in BioLang is a request."); - CIO::println("This one was just a request to print a line."); - } -} - -// Expected output: -// Hello, BioLang! -// Every call in BioLang is a request. -// This one was just a request to print a line. diff --git a/examples/02-requests.bio b/examples/02-requests.bio deleted file mode 100644 index f13c28e..0000000 --- a/examples/02-requests.bio +++ /dev/null @@ -1,61 +0,0 @@ -// 02-requests.bio -// Teaches: the request / response model — the heart of BioLang. -// -// Every operation is a *request*. A request is answered with -// res ; (respond) -// or refused with -// ref ; (refuse) -// -// Calling a method returns an `ALL` value that carries both halves: -// get result — the responded value (if not refused) -// cause result — the refusal reason (if refused) -// -// A method that never responds nor refuses is refused with the default -// reason "nothing" (which counts as falsy in `if`). - -program main; - -// A signature stream declares *what* a stream can do. -Stream Calc { - int add(a int, b int); - int div(a int, b int); -} - -// A fork supplies the actual implementation. -Calc MyCalc { - int add(a int, b int) { res a + b; } // respond - int div(a int, b int) { ref "division by zero"; } // refuse -} - -Main { - void exec() { - // A successful request carries its value; get takes it. - ALL r = MyCalc::add(3, 4); - CIO::println("3 + 4 =", get r); - - // A refused request carries its reason; cause takes it. - ALL bad = MyCalc::div(1, 0); - CIO::println("div cause:", cause bad); - - // Methods that were never declared are refused too. - ALL missing = MyCalc::sqrt(9); - CIO::println("missing method cause:", cause missing); - - // No explicit res/ref → default ref(nothing), which is falsy. - ALL d = noReturn(); - CIO::println("default return cause:", cause d); - if (d) { CIO::println("if is true"); } - else { CIO::println("if is false (default ref nothing)"); } - } - - // This method never responds or refuses. - int noReturn() { CIO::println("(noReturn runs)"); } -} - -// Expected output: -// (noReturn runs) -// 3 + 4 = 7 -// div cause: division by zero -// missing method cause: stream MyCalc refuses: no method sqrt -// default return cause: nothing -// if is false (default ref nothing) diff --git a/examples/03-control-flow.bio b/examples/03-control-flow.bio deleted file mode 100644 index df12b0a..0000000 --- a/examples/03-control-flow.bio +++ /dev/null @@ -1,56 +0,0 @@ -// 03-control-flow.bio -// Teaches: control flow — if / else if / else, while, for, break, continue. -// -// Truthiness: the number 0, the empty string "", and a refused request are -// all false; anything else is true. Loops follow the same rule. - -program main; - -Main { - void exec() { - // while: sum 1..10 - ALL sum = 0; - ALL i = 1; - while (i <= 10) { - sum = sum + i; - i = i + 1; - } - CIO::println("1..10 sum (while) =", sum); - - // for: factorial 5! - ALL fac = 1; - for (ALL k = 1; k <= 5; k = k + 1;) { - fac = fac * k; - } - CIO::println("5! (for) =", fac); - - // break skips out, continue skips the rest of this iteration - for (ALL j = 1; j <= 10; j = j + 1;) { - if (j == 4) { continue; } // never print 4 - if (j > 7) { break; } // stop printing after 7 - CIO::print(j, " "); - } - CIO::println(); - - // if / else if / else - ALL n = 0; - if (n > 0) { CIO::println("n is positive"); } - else if (n < 0) { CIO::println("n is negative"); } - else { CIO::println("n is zero"); } - - // for(;;) is an infinite loop; break leaves it - ALL total = 0; - for (;;) { - total = total + 1; - if (total >= 3) { break; } - } - CIO::println("for(;;) counted:", total); - } -} - -// Expected output: -// 1..10 sum (while) = 55 -// 5! (for) = 120 -// 1 2 3 5 6 7 -// n is zero -// for(;;) counted: 3 diff --git a/examples/04-streams-fork.bio b/examples/04-streams-fork.bio deleted file mode 100644 index b83ec75..0000000 --- a/examples/04-streams-fork.bio +++ /dev/null @@ -1,67 +0,0 @@ -// 04-streams-fork.bio -// Teaches: signature streams, forked implementations, fields, bare calls, -// and passing a stream as an argument. -// -// A `Stream` declares a contract (method signatures + optional fields). -// A fork ` { ... }` provides the real implementation. -// A *bare call* `add(a, b)` searches all streams for a method named `add`. - -program main; - -// 1. Signature: `Calc` can add and divide. -Stream Calc { - int add(a int, b int); - int div(a int, b int); -} - -// 2. Fork: `MyCalc` implements `Calc`. -Calc MyCalc { - int add(a int, b int) { res a + b; } - int div(a int, b int) { ref "division by zero"; } -} - -// 3. A fork can carry its own fields (state), read/written via this::. -Stream Counter { - int count; // a field, materialized with a default (0) -} -Counter C { - void reset() { this::count = 0; } // write a field - void bump() { this::count = count + 1; } // bare read of the field - int get() { res count; } - int doubleGet() { res get() + get(); } // bare call inside the stream -} - -// 4. Streams are first-class: pass one as a parameter and call it. -Stream Greeter { - void greet(cio CIO); -} -Greeter G { - void greet(cio CIO) { cio::println("hello from a passed stream!"); } -} - -Main { - void exec() { - // Calling a forked stream by its signature name falls back to the - // forked implementation. - ALL r = Calc::add(2, 3); - CIO::println("Calc::add(2,3) =", get r); - - // Bare calls are resolved globally. - ALL b = add(10, 20); - CIO::println("bare add(10,20) =", get b); - - // Field state with this:: - C::reset(); - C::bump(); C::bump(); C::bump(); - CIO::println("count =", get C::get(), " doubleGet =", get C::doubleGet()); - - // Passing a stream reference (CIO) as an argument. - G::greet(CIO); - } -} - -// Expected output: -// Calc::add(2,3) = 5 -// bare add(10,20) = 30 -// count = 3 doubleGet = 6 -// hello from a passed stream! diff --git a/examples/05-io-substreams.bio b/examples/05-io-substreams.bio deleted file mode 100644 index 5865628..0000000 --- a/examples/05-io-substreams.bio +++ /dev/null @@ -1,47 +0,0 @@ -// 05-io-substreams.bio -// Teaches: the IO family — CIO (console), FIO (file), SIO (string), and IO. -// -// CIO Console : println/print/get/getln/readInt/readNumber/error -// FIO File : open/close/readFile/writeFile/appendFile/exists ... -// SIO String : format/upper/lower/trim/contains/substring/replace ... -// IO Aggregate : dispatches to CIO/FIO/SIO in order -// -// Stream methods come in two kinds: -// text streams: println/print (write text), get/getln (read text) -// byte streams: write (raw bytes), read (raw byte 0-255, -1 on EOF) - -program main; - -Main { - void exec() { - // CIO: console output. - CIO::println("CIO console output"); - CIO::println("IO is abstract; CIO implements"); - - // SIO: an in-memory string buffer acts as a "file". - ALL s = SIO::format("%d + %d = %d", 2, 3, 5); - CIO::println("SIO::format →", get s); - ALL up = SIO::upper("hello"); - CIO::println("SIO::upper →", get up); - SIO::println("line one"); - ALL line = SIO::getln(); // read the buffer back - CIO::println("SIO::getln →", get line); - - // FIO: write and read back a real file. - FIO::writeFile("/tmp/bio_example.txt", "Hello from BioLang!"); - FIO::appendFile("/tmp/bio_example.txt", " Appended line"); - ALL content = FIO::readFile("/tmp/bio_example.txt"); - CIO::println("FIO read back:", get content); - ALL ok = FIO::exists("/tmp/bio_example.txt"); - CIO::println("FIO::exists →", get ok); - } -} - -// Expected output: -// CIO console output -// IO is abstract; CIO implements -// SIO::format → 2 + 3 = 5 -// SIO::upper → HELLO -// SIO::getln → line one -// FIO read back: Hello from BioLang! Appended line -// FIO::exists → 1 diff --git a/examples/06-classes-objects.bio b/examples/06-classes-objects.bio deleted file mode 100644 index 5ece1f7..0000000 --- a/examples/06-classes-objects.bio +++ /dev/null @@ -1,53 +0,0 @@ -// 06-classes-objects.bio -// Teaches: classes and objects. -// -// A `Class` is essentially a stream. `new Class(args...)` forks the class -// stream, auto-calls `__init__(args...)`, and returns an object. Inside -// object methods `this` is the object itself; declared fields are read and -// written via `obj.field`, `obj::field`, or `this::field`. - -program main; - -Class Hero { - // Constructor: called automatically by `new Hero(...)`. - void __init__(name string, hp int) { - Obj::set(this, "name", name); // store arbitrary attributes - Obj::set(this, "hp", hp); - CIO::println("hero appears:", name); - } - - // A method returning a value. - int getHp() { res 100; } - - string getName() { res get Obj::get(this, "name"); } -} - -Main { - void exec() { - // new → fork class stream + run __init__, returning an object. - ALL h = new Hero("TAK", 88); - CIO::println("object:", h); - - // Attributes are directly accessible. - CIO::println("name =", h.name, " hp =", h.hp); - - // Call methods on the object with obj::method(). - ALL hp = Obj::call(h, "getHp"); - CIO::println("Obj::call getHp() =", get hp); - ALL nm = h::getName(); - CIO::println("h::getName() =", get nm); - - // Each instance is independent. - ALL h2 = new Hero("Lily", 66); - CIO::println("h2.hp =", h2.hp, " (h.hp still =", h.hp, ")"); - } -} - -// Expected output: -// hero appears: TAK -// object: -// name = TAK hp = 88 -// Obj::call getHp() = 100 -// h::getName() = TAK -// hero appears: Lily -// h2.hp = 66 (h.hp still = 88 ) diff --git a/examples/07-arrays.bio b/examples/07-arrays.bio deleted file mode 100644 index c3b4536..0000000 --- a/examples/07-arrays.bio +++ /dev/null @@ -1,54 +0,0 @@ -// 07-arrays.bio -// Teaches: arrays and vectors. -// -// Array and Vector are classes *written in Bio code* on top of the Solid -// contiguous stream. `new Array(n)` creates an array of n zeroes; -// `Arrays::vector()` (or `new Vector()`) creates a growable vector. Every -// array registers itself in the `Arrays` collection. - -program main; - -Main { - void exec() { - // Fixed-size array of 3 zeroes. - ALL a = new Array(3); - a::set(0, 10); a::set(1, 20); a::set(2, 30); - CIO::println("array:", a); - - // Arrays can grow too. - a::push(40); - CIO::println("after push:", a, " len:", get a::len()); - CIO::println("join(-):", get a::join("-")); - - // Indexed read and write with a[i]. - CIO::println("a[1] =", a[1]); - a[1] = 99; - CIO::println("after a[1] = 99:", a); - - // Array literal: new type[n] works for base types and classes. - int[] b = new int[4]; - for (ALL i = 0; i < 4; i = i + 1;) { b[i] = i * i; } - CIO::println("new int[4] squares:", b); - - // Vector: grows freely. - ALL v = Arrays::vector(); - v::push(10); v::push(20); v::push(30); - CIO::println("vector:", v, " len:", get v::len()); - - // The Arrays collection tracks all live instances. - CIO::println("Arrays count:", get Arrays::count()); - Arrays::forget(v); - CIO::println("after forget:", get Arrays::count()); - } -} - -// Expected output: -// array: [10, 20, 30] -// after push: [10, 20, 30, 40] len: 4 -// join(-): 10-20-30-40 -// a[1] = 20 -// after a[1] = 99: [10, 99, 30, 40] -// new int[4] squares: [0, 1, 4, 9] -// vector: [10, 20, 30] len: 3 -// Arrays count: 3 (a, b, and v all register in the Arrays collection) -// after forget: 2 diff --git a/examples/08-multi-return.bio b/examples/08-multi-return.bio deleted file mode 100644 index e776a77..0000000 --- a/examples/08-multi-return.bio +++ /dev/null @@ -1,58 +0,0 @@ -// 08-multi-return.bio -// Teaches: multiple return types and multi-value responses. -// -// Methods may declare void/int/float/double/string/char, optionally with a -// [] array suffix, uniformly across signature streams, forks, classes and -// Main. `res a, b, c;` responds with several values at once, which arrive as -// an array. -// -// Note: a raw array from `res a, b, c;` can be printed directly, but indexed -// access and methods need an Array *object* — build one with `new Array(n)`. - -program main; - -Stream Math { - int add(a int, b int); - string greet(name string); - int[] triple(a int); -} -Math M { - int add(a int, b int) { res a + b; } - string greet(name string) { res SIO::format("Hello, %s", name); } - int[] triple(a int) { res a, a * 2, a * 3; } // multi-value → array -} - -Class Hero { - string[] titles() { res "brave", "dragon slayer", "legend"; } -} - -Main { - void exec() { - ALL r = M::add(3, 4); - CIO::println("add(3,4) =", get r); - - ALL g = M::greet("TAK"); - CIO::println("greet =", get g); - - // `res a, b, c;` responds with several values at once; they arrive - // as a raw array, which prints as [ ... ]. - ALL t = get M::triple(10); - CIO::println("triple(10) =", t); - - // For element access and methods, use an Array object: - ALL arr = new Array(3); - arr::set(0, 10); arr::set(1, 20); arr::set(2, 30); - CIO::println("arr =", arr, " arr[1] =", arr[1], " len =", get arr::len()); - - // A method can also return a string[]. - ALL ts = get Hero::titles(); - CIO::println("titles =", ts); - } -} - -// Expected output: -// add(3,4) = 7 -// greet = Hello, TAK -// triple(10) = [10, 20, 30] -// arr = [10, 20, 30] arr[1] = 20 len = 3 -// titles = [brave, dragon slayer, legend] diff --git a/examples/09-threads.bio b/examples/09-threads.bio deleted file mode 100644 index 158259c..0000000 --- a/examples/09-threads.bio +++ /dev/null @@ -1,44 +0,0 @@ -// 09-threads.bio -// Teaches: cooperative threads with Threads. -// -// Threads are cooperative user threads (ucontext). `Threads::spawn("name", -// args...)` runs a *bare method* (found globally by name) in a new thread -// and returns a thread id; `Threads::join(id)` waits for it and returns its -// result. Inside a thread, `Threads::yield()` voluntarily gives up the CPU -// so other threads can run (there is no preemption). - -program main; - -Calc Worker { - void factorial(n int) { - ALL f = 1; - ALL i = 1; - while (i <= n) { f = f * i; i = i + 1; } - res f; - } - void countUp(n int) { - ALL i = 0; - while (i < n) { i = i + 1; Threads::yield(); } - res i; - } -} - -Main { - void exec() { - // Spawn two threads running the bare methods "factorial" and "countUp". - ALL t1 = get Threads::spawn("factorial", 10); - ALL t2 = get Threads::spawn("countUp", 5); - CIO::println("live threads:", get Threads::active()); - - // Join waits for each thread and fetches its res/ref. - ALL r1 = Threads::join(t1); - CIO::println("thread", t1, " 10! =", get r1); - ALL r2 = Threads::join(t2); - CIO::println("thread", t2, " countUp =", get r2); - } -} - -// Expected output: -// live threads: 2 -// thread 1 10! = 3628800 -// thread 2 countUp = 5 diff --git a/examples/10-taskm.bio b/examples/10-taskm.bio deleted file mode 100644 index 8bab7d7..0000000 --- a/examples/10-taskm.bio +++ /dev/null @@ -1,47 +0,0 @@ -// 10-taskm.bio -// Teaches: the Taskm task manager. -// -// Taskm is a scheduling stream that round-robins threads until all are done. -// Taskm::add("method", args...) register a task (reusing Threads) -// Taskm::interval(ms) set the rotation interval (default 0) -// Taskm::run() drive the scheduling loop to completion -// Taskm::stop() stop the loop -// Taskm::active() number of unfinished tasks - -program main; - -Calc Worker { - void jobA(n int) { // sum 1..n, yielding every step - ALL s = 0; - ALL i = 1; - while (i <= n) { s = s + i; i = i + 1; Threads::yield(); } - res s; - } - void jobB(n int) { // 2^n, yielding every step - ALL p = 1; - ALL i = 1; - while (i <= n) { p = p * 2; i = i + 1; Threads::yield(); } - res p; - } -} - -Main { - void exec() { - Taskm::interval(1); // rotate roughly every 1 ms - ALL t1 = get Taskm::add("jobA", 5); - ALL t2 = get Taskm::add("jobB", 4); - CIO::println("tasks:", get Taskm::active()); - - Taskm::run(); // run until both finish - CIO::println("tasks done:", get Taskm::active()); - - CIO::println("jobA sum =", get Threads::join(t1)); - CIO::println("jobB 2^4 =", get Threads::join(t2)); - } -} - -// Expected output: -// tasks: 2 -// tasks done: 0 -// jobA sum = 15 -// jobB 2^4 = 16 diff --git a/examples/11-smart-refs.bio b/examples/11-smart-refs.bio deleted file mode 100644 index 0f6366d..0000000 --- a/examples/11-smart-refs.bio +++ /dev/null @@ -1,80 +0,0 @@ -// 11-smart-refs.bio -// Teaches: smart references — a reference is a typed value: -// -// & = &; -// -// permission: r (read) / w (write) / rw (read-write) / m (move pointer) -// follow : u (program level, Unistream) / f (method level) / a (scope level) -// 7 permission stacks × 4 follows = 28 reference types; the base type is generic -// (int / double / float / string / char / arrays / classes / ...). -// -// Read through `get p`, write through `p = v`, move the pointer with `p++` -// (m permission — like C's a++). - -program main; - -Calc Worker { - void threadJob(n int) { - // a layer = the current thread's own scope, isolated per thread. - thread int local_note = 0; - &w a int wa = &local_note; // writable a-layer ref - Ref::write(wa, n * 2); - &r a int ra = &local_note; // read-only a-layer ref - CIO::println("thread", get Threads::self(), " a-layer =", get Ref::read(ra)); - res get Ref::read(ra); - } -} - -Main { - void exec() { - // u layer = program level, shared by every thread. - int counter = 0; - &w u int wr = &counter; - Ref::write(wr, 10); - &r u int rr = &counter; - CIO::println("u read counter =", get Ref::read(rr)); - - // A read-only reference refuses writes (shown via cause). - CIO::println("read-only write →", cause Ref::write(rr, 99)); - - // Read/write through the reference directly. - &rw u int rw = &counter; - CIO::println("rw read =", get rw); - rw = 5; - CIO::println("counter after rw = 5 →", counter); - - // a-layer isolation: each thread sees its own local_note. - ALL t1 = get Threads::spawn("threadJob", 21); - ALL t2 = get Threads::spawn("threadJob", 22); - CIO::println("t1 =", get Threads::join(t1), " t2 =", get Threads::join(t2)); - - // Move permission m: the reference is a moving pointer (like C's a++). - ALL a = new int[3]; - a[0] = 10; a[1] = 20; a[2] = 30; - &m u int mp = &a[1]; - CIO::println("at [1] =", get mp); - mp++; - CIO::println("after mp++ =", get mp); - mp = 99; - CIO::println("a[2] =", a[2], " mp target =", get mp); - CIO::println("move again →", cause Ref::move(mp)); // out of bounds - - // const declares a read-only program-level constant. - const int SPEED = 9; - CIO::println("const SPEED =", SPEED); - } -} - -// Expected output (the two thread lines may appear in either order): -// u read counter = 10 -// read-only write → Ref refused: reference is read-only, cannot write -// rw read = 10 -// counter after rw = 5 → 5 -// thread 1 a-layer = 42 (thread ids/order may vary) -// thread 2 a-layer = 44 -// t1 = 42 t2 = 44 -// at [1] = 20 -// after mp++ = 30 -// a[2] = 99 mp target = 99 -// move again → Ref refused: reference pointer moved out of bounds -// const SPEED = 9 diff --git a/examples/12-computation.bio b/examples/12-computation.bio deleted file mode 100644 index b2d6acb..0000000 --- a/examples/12-computation.bio +++ /dev/null @@ -1,47 +0,0 @@ -// 12-computation.bio -// Teaches: the Com computation stream and Time timers. -// -// Com handles instant math: abs/min/max/pow/sqrt/floor/ceil/round/sign/ -// sin/cos/tan/log/exp. Time owns several timers per thread: the first timer -// belongs to the thread and cannot be reset; timers from Time::fork may be -// reset. - -program main; - -Main { - void exec() { - // Com computation stream. - CIO::println("Com::abs(0-5) =", get Com::abs(0 - 5), - " Com::sqrt(9) =", get Com::sqrt(9)); - CIO::println("Com::pow(2,10) =", get Com::pow(2, 10)); - CIO::println("Com::min(3,5) =", get Com::min(3, 5), - " Com::max(3,5) =", get Com::max(3, 5)); - CIO::println("Com::sign(0-3) =", get Com::sign(0 - 3)); - CIO::println("Com::floor(2.7) =", get Com::floor(2.7), - " Com::ceil(2.7) =", get Com::ceil(2.7), - " Com::round(2.5) =", get Com::round(2.5)); - CIO::println("Com::log(1) =", get Com::log(1)); - - // Time::start starts the thread's first timer. - Time::start(); - Time::sleep(50); // sleep 50 ms - CIO::println("elapsed after ~50ms =", get Time::elapsed()); - - // Time::fork creates a resettable timer; the first timer refuses reset. - ALL t2 = Time::fork(); - CIO::println("reset first timer →", cause Time::reset()); - Time::reset(get t2); // forked timer resets fine - CIO::println("Time::reset(forked) ok"); - } -} - -// Expected output (elapsed value varies slightly): -// Com::abs(0-5) = 5 Com::sqrt(9) = 3 -// Com::pow(2,10) = 1024 -// Com::min(3,5) = 3 Com::max(3,5) = 5 -// Com::sign(0-3) = -1 -// Com::floor(2.7) = 2 Com::ceil(2.7) = 3 Com::round(2.5) = 3 -// Com::log(1) = 0 -// elapsed after ~50ms = 0.050... -// reset first timer → Time refused: first timer (thread default) cannot be reset; use Time::fork() -// Time::reset(forked) ok diff --git a/examples/13-need.bio b/examples/13-need.bio deleted file mode 100644 index dc0ad67..0000000 --- a/examples/13-need.bio +++ /dev/null @@ -1,70 +0,0 @@ -// 13-need.bio -// Teaches: assumptions with `need`. -// -// `need` declares that a program relies on a value, function, stream, or -// class being available. At run time every need must be satisfied, otherwise -// the WHOLE program refuses to run. This example satisfies all four kinds. -// -// need value PI; ← satisfied by the top-level const PI -// need function greet; ← satisfied by the method greet() in Greeter G -// need stream Writer; ← satisfied by the signature stream Writer -// need Class Hero; ← satisfied by the class Hero - -program main; - -need value PI; -need function greet; -need stream Writer; -need Class Hero; - -// A top-level const → Constantstream (read-only), satisfies `need value PI`. -const int PI = 3; - -// A signature stream satisfies `need stream Writer`. -Stream Writer { - void write(msg string); -} -Writer ConsoleWriter { - void write(msg string) { CIO::println(msg); } -} - -// A stream whose method is named greet satisfies `need function greet`. -Stream Greeter { - void greet(name string); -} -Greeter G { - void greet(name string) { CIO::println("hello,", name); } -} - -// A class satisfies `need Class Hero`. -Class Hero { - int hp; -} - -Main { - void exec() { - // The const is visible as a bare name (via the scope chain). - CIO::println("PI =", PI); - - // Bare call → the method that satisfied `need function greet`. - greet("TAK"); - - // Use the stream that satisfied `need stream Writer`. - ConsoleWriter::write("writing via a needed stream"); - - // Instantiate the class that satisfied `need Class Hero`. - ALL h = new Hero(); - CIO::println("hero created, hp =", h.hp); - } -} - -// Expected output: -// PI = 3 -// hello, TAK -// writing via a needed stream -// hero created, hp = 0 -// -// Try breaking one of the needs (e.g. delete the `Class Hero` block) and the -// whole program refuses to run, printing something like: -// ⛔ assumption unmet: Class Hero -// ⛔ refusing to run: unmet assumptions exist diff --git a/examples/14-binary-lib.bio b/examples/14-binary-lib.bio deleted file mode 100644 index bd735ad..0000000 --- a/examples/14-binary-lib.bio +++ /dev/null @@ -1,36 +0,0 @@ -// 14-binary-lib.bio -// Teaches: calling native C libraries. -// -// `Stream Name & "lib.so" { ... }` dlopens a shared library and exposes its -// exported functions as stream methods. The body is a normal stream body: it -// may also declare Bio methods and fields (implemented methods run as Bio; -// everything else dispatches to the library). Functions follow the C -// convention double(*)(double,...) — numeric arguments only, at most 6. -// -// NOTE: we use "libm.so.6" because on many systems (Fedora, Debian, ...) -// "libm.so" is just a linker script and cannot be dlopen'd directly. - -program main; - -Stream m & "libm.so.6" { - int doubleIt(x int) { res x * 2; } // a normal Bio method in the body -} - -Main { - void exec() { - // Functions exported by the library become stream methods. - ALL s = m::sin(0); - CIO::println("m::sin(0) =", get s); - CIO::println("m::cos(0) =", get m::cos(0)); - CIO::println("m::pow(2,10) =", get m::pow(2, 10)); - - // Bio methods declared in the body run as normal stream methods. - CIO::println("m::doubleIt(21) =", get m::doubleIt(21)); - } -} - -// Expected output: -// m::sin(0) = 0 -// m::cos(0) = 1 -// m::pow(2,10) = 1024 -// m::doubleIt(21) = 42 diff --git a/examples/15-annotations.bio b/examples/15-annotations.bio deleted file mode 100644 index 3b1b8d2..0000000 --- a/examples/15-annotations.bio +++ /dev/null @@ -1,93 +0,0 @@ -// 15-annotations.bio -// Teaches: stream and method annotations. -// -// @unfork refuses any later fork of the stream/class. -// @onlyread refuses user calls to write methods. -// @read/@write override the automatic write detection on methods. - -program main; - -// 1. @unfork on a signature stream: the fork below is refused at startup. -Stream Sealed { - void ping(); -} @unfork -Sealed BadFork {} // startup prints the refusal and skips this fork - -// 2. @unfork on a class: `new` is the class fork path, so it is refused too. -Class Frozen { - int n; -} @unfork - -// 3. @onlyread: users may not call write methods. bump() is detected as a -// write from its AST (it assigns a field), so the call is refused. -Stream ReadOnly { - int count; - void bump(); - int get(); -} -ReadOnly RO { - void bump() { this::count = count + 1; } @write - int get() { res count; } @read -} @onlyread - -// 4. @onlyread on another stream: same rule applies. -Stream ReadOnlyAlias { - int n; - void touch(); - int get(); -} -ReadOnlyAlias RA { - void touch() { this::n = n + 1; } @write - int get() { res n; } @read -} @onlyread - -// 5. @read/@write override automatic detection on @onlyread streams. -Stream Guarded { - int state; - void hiddenWrite(); - int markedWrite(); - int markedRead(); - int safeRead(); -} -Guarded G { - void hiddenWrite() { this::state = 1; res ""; } - int markedWrite() { res 7; } @write - int markedRead() { hiddenWrite(); res state; } @read - int safeRead() { res state; } @read -} @onlyread - -Main { - void exec() { - // The @unfork signature refusal was printed during startup; here the - // class fork path (new) refuses as well. - ALL f = new Frozen(); - CIO::println("new @unfork class →", cause f); - - // @onlyread: AST-based write detection refuses the write method. - CIO::println("onlyread get =", get RO::get()); - CIO::println("onlyread bump →", cause RO::bump()); - - // @onlyread applies the same rule here too. - CIO::println("alias get =", get RA::get()); - CIO::println("alias touch →", cause RA::touch()); - - // @write makes a body-looking-read method a write method. - CIO::println("marked write →", cause G::markedWrite()); - - // @read overrides the AST: hiddenWrite() looks like a write, but the - // outer markedRead() is explicitly read-only (internal writes are ok). - CIO::println("marked read =", get G::markedRead()); - CIO::println("safe read =", get G::safeRead()); - } -} - -// Expected output: -// refused: stream Sealed is @unfork, cannot fork -// new @unfork class → Obj refused: class Frozen is @unfork, cannot fork -// onlyread get = 0 -// onlyread bump → refused: stream RO is @onlyread — bump() is a write method -// alias get = 0 -// alias touch → refused: stream RA is @onlyread — touch() is a write method -// marked write → refused: stream G is @onlyread — markedWrite() is a write method -// marked read = 1 -// safe read = 1 diff --git a/examples/16-phonebooth.bio b/examples/16-phonebooth.bio deleted file mode 100644 index 65e54b4..0000000 --- a/examples/16-phonebooth.bio +++ /dev/null @@ -1,97 +0,0 @@ -// 16-phonebooth.bio -// Teaches: phone-booth methods — @call (one booth per thread) and @ucall -// (one global booth). -// -// A phone-booth method reuses a fixed memory region: the region is cleared -// at the start of every call (zero allocation, zero fragmentation) and kept -// for the next call. A booth fits ONE caller at a time — recursion into a -// booth that is already in use is REFUSED (directly or indirectly). -// -// @call — every thread has its own booth for the method (no interference) -// @ucall — a single booth shared by all threads (single instance) - -program main; - -// 1. @call: each thread gets its own private booth, so concurrent calls do -// not interfere — thread A's locals never see thread B's. -Calc Worker { - void threadJob(n int) { - int sum = 0; - for (int i = 1; i <= n; i = i + 1;) { sum = sum + i; } - res sum; // 1 + 2 + ... + n - } @call -} - -// 2. @ucall: one global booth for the whole program. -Calc Global { - void globalJob(n int) { - int prod = 1; - for (int i = 2; i <= n; i = i + 1;) { prod = prod * i; } - res prod; // n! - } @ucall -} - -// 3. Recursion is refused: the booth is occupied while its method runs. -Calc Rec { - void down(n int) { - if (n <= 0) { res 0; } - else { res get down(n - 1) + 1; } // direct recursion → refused - } @call - - void hop(n int) { res get down2(n); } // bridge for indirect recursion - void down2(n int) { - if (n <= 0) { res 0; } - else { res get hop(n - 1) + 1; } // down2 → hop → down2: indirect - } @call - - void uDown(n int) { - if (n <= 0) { res 0; } - else { res get uDown(n - 1) + 1; } // @ucall recursion → refused too - } @ucall -} - -// 4. Ordinary methods still recurse freely (no booth involved). -Calc Plain { - void fact(n int) { - if (n <= 1) { res 1; } - else { res n * get fact(n - 1); } - } -} - -Main { - void exec() { - // @call booth isolation: two threads run threadJob at the same time, - // each inside its own booth — results are independent. - ALL t1 = get Threads::spawn("threadJob", 100); - ALL t2 = get Threads::spawn("threadJob", 200); - ALL r1 = Threads::join(t1); - ALL r2 = Threads::join(t2); - CIO::println("t1 sum =", get r1, " t2 sum =", get r2); - - // @ucall: the global booth works from anywhere (and is a singleton — - // repeated calls reuse the same region). - CIO::println("global 5! =", get Global::globalJob(5)); - CIO::println("global 6! =", get Global::globalJob(6)); - - // Direct recursion into an occupied @call booth is refused. - CIO::println("direct recursion →", cause Rec::down(3)); - - // Indirect recursion (down2 → hop → down2) is caught too. - CIO::println("indirect recursion →", cause Rec::down2(3)); - - // @ucall recursion is refused as well. - CIO::println("ucall recursion →", cause Rec::uDown(3)); - - // Ordinary methods keep their full recursion — no booth involved. - CIO::println("plain fact(10) =", get Plain::fact(10)); - } -} - -// Expected output: -// t1 sum = 5050 t2 sum = 20100 -// global 5! = 120 -// global 6! = 720 -// direct recursion → refused: phone-booth method down does not support recursion -// indirect recursion → refused: phone-booth method down2 does not support recursion -// ucall recursion → refused: phone-booth method uDown does not support recursion -// plain fact(10) = 3628800 diff --git a/examples/17-llvm.bio b/examples/17-llvm.bio deleted file mode 100644 index fff6682..0000000 --- a/examples/17-llvm.bio +++ /dev/null @@ -1,22 +0,0 @@ -// 17-llvm.bio -// Stage-1 LLVM backend example: loops, a user method, recursion-free calls, -// int locals, and CIO output all compile to native code with `bio llvm`. - -program main; - -Main { - int square(x int) { - res x * x; - } - - void exec() { - int sum = 0; - for (int i = 1; i <= 10; i = i + 1;) { - sum = sum + square(i); - } - CIO::println("sum of squares 1..10 =", sum); - } -} - -// Expected output: -// sum of squares 1..10 = 385 diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index f272832..0000000 --- a/examples/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# BiuBiuBiu examples - -Runnable, heavily-commented programs that walk through BiuBiuBiu feature by feature. -Each file is self-contained — run any of them with: - -```bash -bio examples/01-hello.bio # interpret -bio shell build examples/01-hello.bio -o hello # compile to a standalone executable -``` - -## Reading order - -| # | File | Teaches | -|---|------|---------| -| 01 | [01-hello.bio](01-hello.bio) | The basic skeleton: `program main`, `Main { void exec() }`, `CIO::println` | -| 02 | [02-requests.bio](02-requests.bio) | The request model: `res` / `ref` / `get` / `cause`, `ALL`, prefix unwrapping | -| 03 | [03-control-flow.bio](03-control-flow.bio) | `if` / `else if` / `else`, `while`, `for`, `break` / `continue` | -| 04 | [04-streams-fork.bio](04-streams-fork.bio) | Signature streams, forked implementations, fields, bare calls, streams as arguments | -| 05 | [05-io-substreams.bio](05-io-substreams.bio) | CIO / FIO / SIO / IO: text streams vs byte streams | -| 06 | [06-classes-objects.bio](06-classes-objects.bio) | `Class`, `new`, `__init__`, `this`, object methods, `Obj::set/get` | -| 07 | [07-arrays.bio](07-arrays.bio) | Array/Vector (Bio classes), Solid streams, the `Arrays` collection, array literals | -| 08 | [08-multi-return.bio](08-multi-return.bio) | Multiple return types, `res a, b, c` → array | -| 09 | [09-threads.bio](09-threads.bio) | Cooperative threads: `spawn` / `yield` / `join` / `active` / `self` | -| 10 | [10-taskm.bio](10-taskm.bio) | Task manager: round-robin scheduling of tasks | -| 11 | [11-smart-refs.bio](11-smart-refs.bio) | Typed smart references `&perm follow base`, `get p` / `p = v` / `p++` moving pointer, const/thread variables | -| 12 | [12-computation.bio](12-computation.bio) | `Com` computation stream + `Time` timers | -| 13 | [13-need.bio](13-need.bio) | Assumptions: `need value/function/stream/Class` | -| 14 | [14-binary-lib.bio](14-binary-lib.bio) | Calling native C libraries (`Stream m & "libm.so.6"`) | -| 15 | [15-annotations.bio](15-annotations.bio) | Annotations: `@unfork`, `@onlyread`, `@read`/`@write` | -| [16-phonebooth.bio](16-phonebooth.bio) | Phone-booth methods: `@call` (per-thread booth), `@ucall` (global booth), recursion refused | -| proj | [project/](project/) | A complete project: `package.toml` + `src/` + `utils/` | - -## Suggested learning path - -1. Start with **01** and **02** — they cover the two ideas everything else builds on: - the program skeleton and the request/response model. -2. **03–05** give you control flow, streams, and IO — enough to write real programs. -3. **06–08** introduce classes, objects, and arrays. -4. **09–12** are the more advanced builtin systems: threads, scheduling, - references, math, and time. -5. **13** shows how a program declares what it depends on, and **14** shows how - to call into native C code. -6. Finish with the **[project/](project/)** example to see how a multi-file - project is organized and built: - -```bash -cd examples/project -bio run # interpret the bundled project -bio build # compile it into a standalone ./app executable -``` - -## Conventions - -- Every file starts with a comment block explaining what it teaches. -- Inline comments explain each construct. -- The trailing comment block shows the **expected output**. - -> The exact "refused" messages shown by the interpreter may vary slightly -> between versions; the structural output (numbers, strings, control flow) is -> what matters. diff --git a/examples/project/package.toml b/examples/project/package.toml deleted file mode 100644 index ba704a0..0000000 --- a/examples/project/package.toml +++ /dev/null @@ -1,8 +0,0 @@ -# BioLang demo project manifest. -# Standard fields: name / version / repo (optional) + [dependencies]. -name = "biolang-demo-project" -version = "0.1.0" - -[dependencies] -# libfoo = { version = "1.0.0" } -# libbar = { version = "0.2.0", repo = "https://..." } diff --git a/examples/project/src/main.bio b/examples/project/src/main.bio deleted file mode 100644 index 86d5d91..0000000 --- a/examples/project/src/main.bio +++ /dev/null @@ -1,20 +0,0 @@ -// project/src/main.bio — the entry point of the demo project. -// -// A project bundles its source with `need`-based, on-demand bundling: -// starting from this entry, every `need` is satisfied by providers found in -// src/ + utils/ + .biolang/deps/. Unmet needs abort the build/run. - -program main; - -need value GREETING; // provided by utils/config.bio -need function greet; // provided by utils/greeter.bio - -Main { - void exec() { - // GREETING is a const declared in utils/config.bio. - CIO::println("main entry —", GREETING); - - // greet() is provided by utils/greeter.bio. - greet("project"); - } -} diff --git a/examples/project/utils/config.bio b/examples/project/utils/config.bio deleted file mode 100644 index 913e533..0000000 --- a/examples/project/utils/config.bio +++ /dev/null @@ -1,8 +0,0 @@ -// project/utils/config.bio — provides the value GREETING. -// -// A top-level `const` creates a program-level constant, which satisfies a -// `need value GREETING;` declared in another file. - -program utils; - -const string GREETING = "Hello from utils/"; diff --git a/examples/project/utils/greeter.bio b/examples/project/utils/greeter.bio deleted file mode 100644 index b105543..0000000 --- a/examples/project/utils/greeter.bio +++ /dev/null @@ -1,15 +0,0 @@ -// project/utils/greeter.bio — provides the method greet(). -// -// Any method named `greet` satisfies the `need function greet;` in main.bio. -// It can then be called as a bare call. - -program utils; - -Stream Greeter { - void greet(name string); -} -Greeter G { - void greet(name string) { - CIO::println("hello,", name); - } -} diff --git a/rust/Cargo.lock b/rust/Cargo.lock deleted file mode 100644 index 0f325a9..0000000 --- a/rust/Cargo.lock +++ /dev/null @@ -1,43 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "bbb-cli" -version = "1.0.0" -dependencies = [ - "bbb-core", - "bbb-llvm", - "bbb-syntax", - "bbb-vm", -] - -[[package]] -name = "bbb-core" -version = "1.0.0" - -[[package]] -name = "bbb-llvm" -version = "1.0.0" -dependencies = [ - "bbb-syntax", -] - -[[package]] -name = "bbb-syntax" -version = "1.0.0" - -[[package]] -name = "bbb-vm" -version = "1.0.0" -dependencies = [ - "bbb-core", - "bbb-syntax", -] - -[[package]] -name = "bbb-wasm" -version = "1.0.0" -dependencies = [ - "bbb-syntax", -] diff --git a/rust/Cargo.toml b/rust/Cargo.toml deleted file mode 100644 index 7155f5d..0000000 --- a/rust/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[workspace] -resolver = "2" -members = ["crates/bbb-syntax", "crates/bbb-core", "crates/bbb-vm", "crates/bbb-llvm", "crates/bbb-wasm", "crates/bbb-cli"] - -[workspace.package] -version = "1.0.0" -edition = "2021" -license = "MIT" - -[profile.release] -opt-level = 3 -lto = true -codegen-units = 1 diff --git a/rust/crates/bbb-cli/Cargo.toml b/rust/crates/bbb-cli/Cargo.toml deleted file mode 100644 index 1f64d18..0000000 --- a/rust/crates/bbb-cli/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "bbb-cli" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang Rust 实现 CLI(Rust+LLVM 重写阶段 M1)" - -[[bin]] -name = "bbb" -path = "src/main.rs" - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } -bbb-core = { path = "../bbb-core" } -bbb-vm = { path = "../bbb-vm" } -bbb-llvm = { path = "../bbb-llvm" } diff --git a/rust/crates/bbb-cli/src/main.rs b/rust/crates/bbb-cli/src/main.rs deleted file mode 100644 index 1114ad6..0000000 --- a/rust/crates/bbb-cli/src/main.rs +++ /dev/null @@ -1,861 +0,0 @@ -//! bbb — BiuBiuBiu 语言 CLI(Rust + LLVM 实现)。 -//! -//! 标准 CLI(README 定义,与旧 C 实现一致): -//! bbb 解释运行脚本 -//! bbb shell run 解释运行脚本(显式) -//! bbb shell build [-o out] 编译脚本 → 原生可执行 -//! bbb init 创建项目骨架(package.toml + src/ + utils/) -//! bbb build [dir] [-s|-m] [-o out] 构建项目 -//! bbb run [dir] 运行项目(解释) -//! bbb install [dir] 安装依赖 -//! bbb destroy [dir] 清理构建产物 -//! bbb pack [--entry NAME] -//! bbb unpack [dir] -//! bbb --tokens dump tokens(调试) -//! bbb -e 解释器内存限制(0 = unlimited) -//! bbb -h | --help 帮助 - -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::process::ExitCode; - -use bbb_core::arena::{BumpArena, StrArena}; -use bbb_core::value::Value; -use bbb_syntax::lexer::{self, TokenKind}; -use bbb_syntax::parser::parse_source; -use bbb_vm::interp::Interp; - -const USAGE: &str = "\ -🧬 BiuBiuBiu (bbb) — interpret or compile .bio/.bl programs - -Usage: - bbb run (interpret) a script - bbb shell run run a script (interpret) - bbb shell build [-o out] compile a script → standalone executable - bbb -e set interpreter memory limit (0 = unlimited, default 256M) - bbb --tokens dump tokens (debug) - bbb init create a project skeleton (src/ utils/ package.toml) - bbb build [dir] [-o out] build a project (bundle needs, compile) - bbb build [dir] -s build → standalone executable (default) - bbb build [dir] -m [out.img|out.zip] build → .img/.zip package - bbb run [dir] run a project (bundle needs, interpret) - bbb install [dir] install deps from package.toml - bbb destroy [dir] remove build artifacts - bbb pack [--entry NAME] - package compiled products (raw .img / .zip) - bbb unpack [dir] unpack a .img / .zip package - bbb run built-in demos - bbb -h | --help show this help - - env: BIOLANG_CONFIG → global config file (TOML, may contain repo=) -"; - -fn read_source(path: &str) -> Result { - let mut buf = Vec::new(); - std::fs::File::open(path) - .map_err(|e| format!("cannot open file: {path} ({e})"))? - .read_to_end(&mut buf) - .map_err(|e| format!("read failed: {e}"))?; - String::from_utf8(buf).map_err(|_| "source is not UTF-8".to_string()) -} - -fn parse_or_err(src: &str) -> Result { - let (prog, errs) = parse_source(src); - if !errs.is_empty() { - return Err(errs.iter().map(|e| e.to_string()).collect::>().join("\n")); - } - Ok(prog) -} - -/// 运行单个脚本文件(解释)。 -fn run_script_file(path: &str) -> Result<(), String> { - let src = read_source(path)?; - let prog = parse_or_err(&src)?; - let mut interp = Interp::new(); - let unmet = interp.reg.register(&prog); - let unmet = filter_value_needs(unmet, &prog); - if !unmet.is_empty() { - return Err(unmet - .iter() - .map(|(k, n)| format!("need {k} {n} has no provider")) - .collect::>() - .join("\n")); - } - let out = interp.run(&prog); - print!("{}", out.stdout); - Ok(()) -} - -/// 运行项目目录(解释):合并 src/ + utils/。 -fn run_project_dir(root: &Path) -> Result<(), String> { - let prog = bbb_vm::load_project_sources(&root.to_path_buf())?; - let mut interp = Interp::new(); - let unmet = interp.reg.register(&prog); - let unmet = filter_value_needs(unmet, &prog); - if !unmet.is_empty() { - return Err(unmet - .iter() - .map(|(k, n)| format!("need {k} {n} has no provider")) - .collect::>() - .join("\n")); - } - let out = interp.run(&prog); - print!("{}", out.stdout); - Ok(()) -} - -fn filter_value_needs( - unmet: Vec<(String, String)>, - prog: &bbb_syntax::ast::Program, -) -> Vec<(String, String)> { - unmet - .into_iter() - .filter(|(k, n)| { - !(k == "value" - && prog - .decls - .iter() - .any(|d| matches!(d, bbb_syntax::Decl::Const { name, .. } if name == n))) - }) - .collect() -} - -/// LLVM 编译:AST → IR → clang → 可执行文件(不运行)。 -fn compile_to_executable(prog: &bbb_syntax::ast::Program, out: &str) -> Result<(), String> { - let ir = bbb_llvm::compile(prog).map_err(|e| format!("IR generation failed: {e}"))?; - let dir = std::env::temp_dir().join(format!("bbb-build-{}", std::process::id())); - std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; - let ir_path = dir.join("out.ll"); - std::fs::write(&ir_path, &ir).map_err(|e| e.to_string())?; - // 确保输出目录存在 - if let Some(parent) = Path::new(out).parent() { - if !parent.as_os_str().is_empty() { - std::fs::create_dir_all(parent).map_err(|e| format!("cannot create {}: {e}", parent.display()))?; - } - } - let status = std::process::Command::new("clang") - .arg(&ir_path) - .arg("-o") - .arg(out) - .status() - .map_err(|e| format!("clang invocation failed: {e}"))?; - if !status.success() { - return Err(format!("clang failed (IR kept at {})", ir_path.display())); - } - Ok(()) -} - -// ───────────────────────── 命令实现 ───────────────────────── - -fn cmd_tokens(path: &str) -> Result<(), String> { - let src = read_source(path)?; - let mut toks = Vec::new(); - lexer::tokenize(&src, &mut toks).map_err(|e| format!("lex error: {e}"))?; - for t in &toks { - let kind = match t.kind { - TokenKind::Ident => "ident", - TokenKind::Keyword => "keyword", - TokenKind::Int => "int", - TokenKind::Float => "float", - TokenKind::Str => "string", - TokenKind::Char => "char", - TokenKind::Op => "op", - TokenKind::Eof => "eof", - }; - println!("{:>5}:{:<3} {:<8} {:?}", t.span.line, t.span.col, kind, t.text); - } - Ok(()) -} - -fn cmd_shell_run(file: &str) -> Result<(), String> { - run_script_file(file) -} - -fn cmd_shell_build(file: &str, out: Option<&str>) -> Result<(), String> { - let src = read_source(file)?; - let prog = parse_or_err(&src)?; - // 默认输出:bin/ - let out_path = match out { - Some(o) => o.to_string(), - None => { - let base = Path::new(file) - .file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "a.out".to_string()); - let base = base - .strip_suffix(".bio") - .or_else(|| base.strip_suffix(".bl")) - .unwrap_or(&base) - .to_string(); - format!("bin/{base}") - } - }; - println!("compiling {file} → {out_path}"); - compile_to_executable(&prog, &out_path)?; - println!("✔ compiled: {out_path}"); - Ok(()) -} - -fn cmd_run(target: &str) -> Result<(), String> { - let path = PathBuf::from(target); - if path.is_dir() { - run_project_dir(&path) - } else { - run_script_file(target) - } -} - -fn cmd_init(name: &str) -> Result<(), String> { - let root = PathBuf::from(name); - if root.exists() { - return Err(format!("{name}: already exists")); - } - std::fs::create_dir_all(root.join("src")).map_err(|e| e.to_string())?; - std::fs::create_dir_all(root.join("utils")).map_err(|e| e.to_string())?; - let toml = format!( - "# {name} — BiuBiuBiu project manifest\n\ - # standard fields: name / version / repo (optional) + [dependencies]\n\ - name = \"{name}\"\n\ - version = \"0.1.0\"\n\ - \n\ - [dependencies]\n\ - # libfoo = {{ version = \"1.0.0\" }}\n\ - # libbar = {{ version = \"0.2.0\", repo = \"https://...\" }}\n" - ); - std::fs::write(root.join("package.toml"), toml).map_err(|e| e.to_string())?; - let main = format!( - "program main;\n\ - \n\ - Main {{\n\ - void exec() {{\n\ - CIO::println(\"Hello from {name}!\");\n\ - }}\n\ - }}\n" - ); - std::fs::write(root.join("src").join("main.bio"), main).map_err(|e| e.to_string())?; - println!("✔ project created: {name}"); - println!(" package.toml — manifest (name/version/repo + deps)"); - println!(" src/main.bio — entry"); - println!(" utils/ — libraries (need providers)"); - Ok(()) -} - -/// 项目构建:need bundling(load_project_sources 合并)+ LLVM 编译 → 可执行。 -/// -s standalone(默认);-m 打包 .img/.zip(v1 简化:先做 standalone 产物,包格式后续)。 -fn cmd_build(dir: &str, _mode: &str, out: Option<&str>) -> Result<(), String> { - let root = PathBuf::from(dir); - let prog = bbb_vm::load_project_sources(&root)?; - // 项目名:package.toml 的 name 字段(简单解析),默认目录名 - let pname = parse_package_name(&root).unwrap_or_else(|| { - root.file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "app".to_string()) - }); - let out_path = match out { - Some(o) => o.to_string(), - None => { - let dir_trim = dir.trim_end_matches('/'); - if dir_trim.is_empty() || dir_trim == "." { - format!("bin/{pname}") - } else { - format!("{dir_trim}/bin/{pname}") - } - } - }; - println!("building {dir} → {out_path}"); - compile_to_executable(&prog, &out_path)?; - println!("✔ build ok: {out_path}"); - Ok(()) -} - -/// 简单解析 package.toml 的 name = "..."。 -fn parse_package_name(root: &Path) -> Option { - let toml = std::fs::read_to_string(root.join("package.toml")).ok()?; - for line in toml.lines() { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("name") { - let rest = rest.trim_start(); - if let Some(rest) = rest.strip_prefix('=') { - let rest = rest.trim().trim_matches('"'); - if !rest.is_empty() { - return Some(rest.to_string()); - } - } - } - } - None -} - -fn cmd_install(dir: &str) -> Result<(), String> { - let root = PathBuf::from(dir); - let toml_path = root.join("package.toml"); - let toml = match std::fs::read_to_string(&toml_path) { - Ok(t) => t, - Err(_) => return Err(format!("no package.toml in {dir}")), - }; - // 解析 [dependencies] 下的 name = { version, repo } 或 name = "version" - let mut found = false; - let mut installed = 0; - let mut in_deps = false; - for line in toml.lines() { - let line = line.trim(); - if line.is_empty() || line.starts_with('#') { - continue; - } - if line.starts_with('[') { - in_deps = line.starts_with("[dependencies]"); - continue; - } - if !in_deps { - continue; - } - let Some(eq) = line.find('=') else { continue }; - let dep = line[..eq].trim().to_string(); - let spec = line[eq + 1..].trim(); - if dep.is_empty() { - continue; - } - found = true; - // repo:spec 里的 repo = "...";否则从 BIOLANG_CONFIG 全局配置读 - let repo = extract_repo(spec).or_else(global_repo); - match repo { - Some(r) => { - let dest = root.join(".biolang").join("deps").join(&dep); - let rc = fetch_dep(&r, &dest); - if rc == 0 { - println!("✔ installed {dep}"); - installed += 1; - } else { - eprintln!("⛔ dep {dep}: fetch failed from {r}"); - } - } - None => { - eprintln!("⛔ dep {dep}: no repo (set repo= or BIOLANG_CONFIG global repo)"); - } - } - } - if !found { - println!("ℹ️ no dependencies in {}", toml_path.display()); - } - if installed > 0 { - println!("✔ {installed} dependency(ies) installed → {}/.biolang/deps", root.display()); - } - Ok(()) -} - -fn extract_repo(spec: &str) -> Option { - // 形如 { version = "1.0.0", repo = "https://..." } 或 "1.0.0" - if let Some(inner) = spec.strip_prefix('{') { - let inner = inner.trim_end_matches('}'); - for part in inner.split(',') { - let part = part.trim(); - if let Some(rest) = part.strip_prefix("repo") { - let rest = rest.trim_start(); - if let Some(rest) = rest.strip_prefix('=') { - return Some(rest.trim().trim_matches('"').to_string()); - } - } - } - None - } else { - None - } -} - -fn global_repo() -> Option { - let path = std::env::var("BIOLANG_CONFIG") - .map(PathBuf::from) - .unwrap_or_else(|_| { - let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); - PathBuf::from(home).join(".biolang").join("config.toml") - }); - let text = std::fs::read_to_string(path).ok()?; - for line in text.lines() { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("repo") { - let rest = rest.trim_start(); - if let Some(rest) = rest.strip_prefix('=') { - return Some(rest.trim().trim_matches('"').to_string()); - } - } - } - None -} - -/// 拉取依赖:git 仓库 → git clone;http → curl 下载 package.toml;本地路径 → 复制。 -fn fetch_dep(repo: &str, dest: &Path) -> i32 { - if let Some(parent) = dest.parent() { - let _ = std::fs::create_dir_all(parent); - } - if repo.starts_with("http") && repo.contains(".git") { - let _ = std::fs::remove_dir_all(dest); - std::process::Command::new("git") - .args(["clone", "--depth", "1", repo]) - .arg(dest) - .status() - .map(|s| if s.success() { 0 } else { 1 }) - .unwrap_or(1) - } else if repo.starts_with("http") { - let _ = std::fs::create_dir_all(dest); - let outfile = dest.join("package.toml"); - std::process::Command::new("curl") - .args(["-fsSL", repo, "-o"]) - .arg(&outfile) - .status() - .map(|s| if s.success() { 0 } else { 1 }) - .unwrap_or(1) - } else { - copy_tree(Path::new(repo), dest) - } -} - -fn copy_tree(src: &Path, dest: &Path) -> i32 { - if !src.is_dir() { - return 1; - } - let _ = std::fs::remove_dir_all(dest); - fn rec(src: &Path, dest: &Path) -> std::io::Result<()> { - std::fs::create_dir_all(dest)?; - for entry in std::fs::read_dir(src)? { - let entry = entry?; - let from = entry.path(); - let to = dest.join(entry.file_name()); - if from.is_dir() { - rec(&from, &to)?; - } else { - std::fs::copy(&from, &to)?; - } - } - Ok(()) - } - match rec(src, dest) { - Ok(()) => 0, - Err(_) => 1, - } -} - -fn cmd_destroy(dir: &str) -> Result<(), String> { - let root = PathBuf::from(dir); - let biolang = root.join(".biolang"); - if biolang.exists() { - std::fs::remove_dir_all(&biolang).map_err(|e| e.to_string())?; - } - let app = root.join("app"); - if app.exists() { - let _ = std::fs::remove_dir_all(&app); - } - let bin_cache = root.join("bin").join(".cache"); - if bin_cache.exists() { - let _ = std::fs::remove_dir_all(&bin_cache); - } - println!("✔ destroyed build artifacts: {}/.biolang, {}/app", dir, dir); - Ok(()) -} - -// ── .img / .zip 打包(v1:基于系统 zip/unzip 的 .zip + 原始 .img) ── - -const IMG_MAGIC: &[u8; 7] = b"BIOIMG1"; -const IMG_VERSION: u32 = 2; - -fn cmd_pack(out: &str, entry: Option<&str>, files: &[String]) -> Result<(), String> { - if files.is_empty() { - return Err("pack: no files".to_string()); - } - if out.ends_with(".img") { - img_create(out, entry, files) - } else { - zip_create(out, files) - } -} - -fn zip_create(out: &str, files: &[String]) -> Result<(), String> { - // 用系统 zip(STORE 无压缩);不存在则报错 - let status = std::process::Command::new("zip") - .arg("-0") - .arg("-j") - .arg(out) - .args(files) - .status() - .map_err(|e| format!("zip invocation failed: {e}"))?; - if !status.success() { - return Err(format!("pack failed: {out}")); - } - println!("packed {} file(s) → {out}", files.len()); - Ok(()) -} - -fn img_create(out: &str, entry: Option<&str>, files: &[String]) -> Result<(), String> { - use std::io::Write; - let mut data: Vec = Vec::new(); - // header 占位 - let entry_name = entry.unwrap_or(&files[0]); - data.extend_from_slice(IMG_MAGIC); - data.extend_from_slice(&IMG_VERSION.to_le_bytes()); - data.extend_from_slice(&0u32.to_le_bytes()); // flags - data.extend_from_slice(&(entry_name.len() as u32).to_le_bytes()); - data.extend_from_slice(entry_name.as_bytes()); - data.extend_from_slice(&(files.len() as u32).to_le_bytes()); - // 目录记录区:先写 records(offset 暂填 0),再写 payload - let header_size = 7 + 4 + 4 + 4 + entry_name.len() + 4; // magic(7) + ver + flags + entry_len + name + count - // 每条记录 = name_len(4) + mode(4) + name(nl) + offset(8) + size(8) - let mut offset = header_size as u64; - for f in files { - offset += (4 + 4 + f.len() + 8 + 8) as u64; - } - let mut records: Vec<(String, u32, u64, u64)> = Vec::new(); - for f in files { - let bytes = std::fs::read(f).map_err(|e| format!("cannot read {f}: {e}"))?; - let mode = file_mode(f); - records.push((f.clone(), mode, offset, bytes.len() as u64)); - offset += bytes.len() as u64; - } - for (name, mode, off, size) in &records { - data.extend_from_slice(&(name.len() as u32).to_le_bytes()); - data.extend_from_slice(&mode.to_le_bytes()); - data.extend_from_slice(name.as_bytes()); - data.extend_from_slice(&off.to_le_bytes()); - data.extend_from_slice(&size.to_le_bytes()); - } - for f in files { - let bytes = std::fs::read(f).map_err(|e| format!("cannot read {f}: {e}"))?; - data.extend_from_slice(&bytes); - } - let mut f = std::fs::File::create(out).map_err(|e| e.to_string())?; - f.write_all(&data).map_err(|e| e.to_string())?; - println!("packed {} file(s) → {out}", files.len()); - Ok(()) -} - -fn file_mode(path: &str) -> u32 { - use std::os::unix::fs::PermissionsExt; - std::fs::metadata(path) - .map(|m| m.permissions().mode() & 0o777) - .unwrap_or(0o644) -} - -fn cmd_unpack(pkg: &str, dir: &str) -> Result<(), String> { - if pkg.ends_with(".img") { - img_unpack(pkg, dir) - } else { - std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; - let status = std::process::Command::new("unzip") - .arg("-o") - .arg(pkg) - .arg("-d") - .arg(dir) - .status() - .map_err(|e| format!("unzip invocation failed: {e}"))?; - if !status.success() { - return Err(format!("unpack failed: {pkg}")); - } - println!("unpacked {pkg} → {dir}"); - Ok(()) - } -} - -fn img_unpack(pkg: &str, dir: &str) -> Result<(), String> { - let data = std::fs::read(pkg).map_err(|e| format!("cannot read {pkg}: {e}"))?; - if data.len() < 24 || &data[..7] != IMG_MAGIC { - return Err(format!("{pkg}: not a BIOIMG1 image")); - } - let mut pos = 7usize; - let _version = rd_u32(&data, &mut pos); - let _flags = rd_u32(&data, &mut pos); - let entry_len = rd_u32(&data, &mut pos) as usize; - pos += entry_len; // entry name - let count = rd_u32(&data, &mut pos) as usize; - std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; - for _ in 0..count { - let name_len = rd_u32(&data, &mut pos) as usize; - let mode = rd_u32(&data, &mut pos); - let name = String::from_utf8_lossy(&data[pos..pos + name_len]).to_string(); - pos += name_len; - let off = rd_u64(&data, &mut pos); - let size = rd_u64(&data, &mut pos); - let bytes = &data[off as usize..(off + size) as usize]; - let out_path = Path::new(dir).join(Path::new(&name).file_name().unwrap_or_default()); - std::fs::write(&out_path, bytes).map_err(|e| e.to_string())?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let _ = std::fs::set_permissions(&out_path, std::fs::Permissions::from_mode(mode)); - } - } - println!("unpacked {pkg} → {dir}"); - Ok(()) -} - -fn rd_u32(data: &[u8], pos: &mut usize) -> u32 { - let v = u32::from_le_bytes(data[*pos..*pos + 4].try_into().unwrap()); - *pos += 4; - v -} - -fn rd_u64(data: &[u8], pos: &mut usize) -> u64 { - let v = u64::from_le_bytes(data[*pos..*pos + 8].try_into().unwrap()); - *pos += 8; - v -} - -// ───────────────────────── 内部调试命令(保留) ───────────────────────── - -fn cmd_parse(path: &str) -> Result<(), String> { - let src = read_source(path)?; - let (prog, errs) = parse_source(&src); - if !errs.is_empty() { - for e in &errs { - eprintln!("parse error: {e}"); - } - return Err(format!("{} parse errors", errs.len())); - } - println!("kind: {}", if prog.kind.is_empty() { "" } else { &prog.kind }); - println!("decls: {} | main: {} | methods: {}", - prog.decls.len(), - if prog.main.is_some() { "yes" } else { "no" }, - prog.main.as_ref().map(|m| m.methods.len()).unwrap_or(0)); - for d in &prog.decls { - let name = match d { - bbb_syntax::Decl::Const { name, .. } => format!("const {name}"), - bbb_syntax::Decl::Need { kind, name } => format!("need {kind} {name}"), - bbb_syntax::Decl::StreamSig { name, members, .. } => - format!("stream {name} ({} members)", members.len()), - bbb_syntax::Decl::StreamBin { name, file, .. } => - format!("bin-stream {name} <- {file}"), - bbb_syntax::Decl::Class { name, members, .. } => - format!("class {name} ({} members)", members.len()), - bbb_syntax::Decl::Fork { sig, name, members, .. } => - format!("fork {sig} {name} ({} members)", members.len()), - bbb_syntax::Decl::Interface { name, members, .. } => - format!("interface {name} ({} members)", members.len()), - }; - println!(" {name}"); - } - Ok(()) -} - -fn cmd_arena(n: u32) { - let mut arena = BumpArena::::new(); - let mut last = 0; - for i in 1..=n { - last = arena.alloc(i as u64); - } - assert_eq!(*arena.get(last), n as u64); - println!("BumpArena: {} slots, {} pages, handle {last} valid", n, arena.pages()); - - let mut strs = StrArena::new(); - let mut handle = None; - for i in 0..n { - let s = format!("value-{i}-{}", "x".repeat((i % 97) as usize)); - handle = Some(strs.push(&s)); - } - let h = handle.unwrap(); - assert!(strs.get(h).starts_with("value-")); - println!("StrArena: {} writes, last len {} (cross-page intact)", n, strs.get(h).len()); - - let v = Value::int(42).with_refused(); - println!("Value: size={}B, refused int displays as {v}", std::mem::size_of::()); -} - -// ───────────────────────── 入口 ───────────────────────── - -fn main() -> ExitCode { - let args: Vec = std::env::args().collect(); - let argc = args.len(); - - // 无参数:内置演示(v1:打印 usage 提示) - if argc == 1 { - print!("{USAGE}"); - return ExitCode::SUCCESS; - } - - let a1 = args[1].as_str(); - // 全局选项 - if a1 == "-h" || a1 == "--help" { - print!("{USAGE}"); - return ExitCode::SUCCESS; - } - if a1 == "--version" || a1 == "-V" { - println!("bbb {}", env!("CARGO_PKG_VERSION")); - return ExitCode::SUCCESS; - } - if a1 == "-e" { - // 内存限制:v1 接受参数(0 = unlimited),解释器当前无硬限制 - if argc < 3 { - eprintln!("usage: bbb -e "); - return ExitCode::FAILURE; - } - let _limit = parse_size(&args[2]); - // 剩余参数按普通命令处理(-e 仅设置限制) - let rest: Vec = args[2..].to_vec(); - if rest.is_empty() { - print!("{USAGE}"); - return ExitCode::SUCCESS; - } - return dispatch(&rest[0], &rest[1..]); - } - if a1 == "--tokens" { - if argc < 3 { - eprintln!("usage: bbb --tokens "); - return ExitCode::FAILURE; - } - return finish(cmd_tokens(&args[2])); - } - - dispatch(a1, &args[2..]) -} - -fn parse_size(s: &str) -> u64 { - let (num, mult) = match s.chars().last() { - Some('K' | 'k') => (&s[..s.len() - 1], 1024u64), - Some('M' | 'm') => (&s[..s.len() - 1], 1024u64 * 1024), - Some('G' | 'g') => (&s[..s.len() - 1], 1024u64 * 1024 * 1024), - _ => (s, 1), - }; - num.parse::().unwrap_or(0) * mult -} - -fn dispatch(cmd: &str, rest: &[String]) -> ExitCode { - match cmd { - "shell" => { - match rest.first().map(|s| s.as_str()) { - Some("run") => { - if rest.len() < 2 { - eprintln!("usage: bbb shell run "); - return ExitCode::FAILURE; - } - finish(cmd_shell_run(&rest[1])) - } - Some("build") => { - if rest.len() < 2 { - eprintln!("usage: bbb shell build [-o out]"); - return ExitCode::FAILURE; - } - let file = &rest[1]; - let mut out = None; - let mut i = 2; - while i < rest.len() { - if rest[i] == "-o" && i + 1 < rest.len() { - out = Some(rest[i + 1].clone()); - i += 2; - } else { - eprintln!("usage: bbb shell build [-o out]"); - return ExitCode::FAILURE; - } - } - finish(cmd_shell_build(file, out.as_deref())) - } - _ => { - eprintln!("usage: bbb shell run | bbb shell build [-o out]"); - ExitCode::FAILURE - } - } - } - "init" => { - if rest.is_empty() { - eprintln!("usage: bbb init "); - return ExitCode::FAILURE; - } - finish(cmd_init(&rest[0])) - } - "build" => { - // bbb build [dir] [-s|-m [out]] [-o out] - let mut dir = ".".to_string(); - let mut mode = "s"; - let mut out = None; - let mut i = 0; - while i < rest.len() { - match rest[i].as_str() { - "-s" => mode = "s", - "-m" => { - mode = "m"; - if i + 1 < rest.len() && !rest[i + 1].starts_with('-') { - out = Some(rest[i + 1].clone()); - i += 1; - } - } - "-o" => { - if i + 1 < rest.len() { - out = Some(rest[i + 1].clone()); - i += 1; - } - } - other => dir = other.to_string(), - } - i += 1; - } - finish(cmd_build(&dir, mode, out.as_deref())) - } - "run" => { - let target = rest.first().map(|s| s.as_str()).unwrap_or("."); - finish(cmd_run(target)) - } - "install" => { - let dir = rest.first().map(|s| s.as_str()).unwrap_or("."); - finish(cmd_install(dir)) - } - "destroy" => { - let dir = rest.first().map(|s| s.as_str()).unwrap_or("."); - finish(cmd_destroy(dir)) - } - "pack" => { - if rest.len() < 2 { - eprintln!("usage: bbb pack [--entry NAME] "); - return ExitCode::FAILURE; - } - let out = &rest[0]; - let mut entry = None; - let mut files = Vec::new(); - let mut i = 1; - while i < rest.len() { - if rest[i] == "--entry" && i + 1 < rest.len() { - entry = Some(rest[i + 1].clone()); - i += 2; - } else { - files.push(rest[i].clone()); - i += 1; - } - } - finish(cmd_pack(out, entry.as_deref(), &files)) - } - "unpack" => { - if rest.is_empty() { - eprintln!("usage: bbb unpack [dir]"); - return ExitCode::FAILURE; - } - let dir = rest.get(1).map(|s| s.as_str()).unwrap_or("."); - finish(cmd_unpack(&rest[0], dir)) - } - // 内部调试命令(保留) - "lexer" => { - if rest.is_empty() { - eprintln!("usage: bbb lexer "); - return ExitCode::FAILURE; - } - finish(cmd_tokens(&rest[0])) - } - "parse" => { - if rest.is_empty() { - eprintln!("usage: bbb parse "); - return ExitCode::FAILURE; - } - finish(cmd_parse(&rest[0])) - } - "arena" => { - let n: u32 = rest.first().and_then(|s| s.parse().ok()).unwrap_or(10_000); - cmd_arena(n); - ExitCode::SUCCESS - } - // 默认:`bbb ` 解释运行 - _ => finish(cmd_run(cmd)), - } -} - -fn finish(r: Result<(), String>) -> ExitCode { - match r { - Ok(()) => ExitCode::SUCCESS, - Err(e) => { - eprintln!("{e}"); - ExitCode::FAILURE - } - } -} diff --git a/rust/crates/bbb-core/Cargo.toml b/rust/crates/bbb-core/Cargo.toml deleted file mode 100644 index 44e7029..0000000 --- a/rust/crates/bbb-core/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "bbb-core" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang 运行时核心:arena 内存规划 + Value/请求模型" - -[dependencies] diff --git a/rust/crates/bbb-core/src/arena.rs b/rust/crates/bbb-core/src/arena.rs deleted file mode 100644 index 88c7a77..0000000 --- a/rust/crates/bbb-core/src/arena.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Arena 内存规划(bbb-core)。 -//! -//! # BumpArena — 类型化 bump 分配器 -//! -//! - 分页:每页固定 `PAGE_SLOTS` 槽;页表 `Vec>` 只增不减; -//! - `alloc(v) -> u32`:句柄 = `(page << SHIFT) | slot`,0 保留为 null; -//! - `get(h) -> &T` / `get_mut`:O(1); -//! - 永不释放单个槽(程序级生命周期,与旧 C `aalloc` 语义一致); -//! - 句柄优点:扩容搬迁安全、可序列化、对齐无空洞、u32 省内存。 -//! -//! # StrArena — 字符串字节池 -//! -//! 分页字节缓冲(页 4 KiB 起步,几何增长),`push(&str) -> StrRef{off,len}` -//! 只拷贝一次;之后取用零拷贝。`StrRef` 8 字节,可安全穿越线程边界 -//! (字节池只增,读不竞争——协作式调度下无并发写)。 - -use std::marker::PhantomData; - -/// 字符串引用:(offset, len) 指向 StrArena 字节池。 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StrRef { - pub off: u32, - pub len: u32, -} - -impl StrRef { - pub const NULL: StrRef = StrRef { off: 0, len: 0 }; - pub fn is_null(self) -> bool { - self.off == 0 && self.len == 0 - } -} - -/// 类型化 bump arena。`T: Copy` 约束保证句柄读取无别名问题。 -pub struct BumpArena { - pages: Vec>, - next: u32, // 当前页已用槽数 - _marker: PhantomData, -} - -const PAGE_SLOTS: u32 = 256; // 每页槽数(2^8) -const SHIFT: u32 = 8; -const SLOT_MASK: u32 = PAGE_SLOTS - 1; -const MAX_HANDLE: u32 = u32::MAX >> 1; // 最高位留给 null 标志扩展 - -impl BumpArena { - pub fn new() -> Self { - BumpArena { pages: Vec::new(), next: 0, _marker: PhantomData } - } - - fn ensure_page(&mut self) { - if self.next == PAGE_SLOTS || self.pages.is_empty() { - let page: Box<[T]> = (0..PAGE_SLOTS).map(|_| unsafe { std::mem::zeroed() }).collect(); - self.pages.push(page); - self.next = 1; // 槽 0 保留为 null 哨兵,句柄永不等于 0 - } - } - - /// 分配一个槽,返回 u32 句柄。0 永不返回(保留为 null)。 - #[inline] - pub fn alloc(&mut self, v: T) -> u32 { - self.ensure_page(); - let page = self.pages.len() as u32 - 1; - let slot = self.next; - self.pages[page as usize][slot as usize] = v; - self.next += 1; - (page << SHIFT) | slot - } - - /// 句柄 → 不可变引用。 - #[inline] - pub fn get(&self, h: u32) -> &T { - debug_assert!(h != 0 && h <= MAX_HANDLE); - let page = (h >> SHIFT) as usize; - let slot = (h & SLOT_MASK) as usize; - &self.pages[page][slot] - } - - /// 句柄 → 可变引用(bump 语义下互斥由外部保证)。 - #[inline] - pub fn get_mut(&mut self, h: u32) -> &mut T { - debug_assert!(h != 0 && h <= MAX_HANDLE); - let page = (h >> SHIFT) as usize; - let slot = (h & SLOT_MASK) as usize; - &mut self.pages[page][slot] - } - - pub fn pages(&self) -> usize { - self.pages.len() - } - - pub fn capacity(&self) -> u32 { - self.pages.len() as u32 * PAGE_SLOTS - } -} - -impl Default for BumpArena { - fn default() -> Self { - Self::new() - } -} - -/// 字符串字节池。 -pub struct StrArena { - pages: Vec>, - cur: Vec, -} - -impl StrArena { - pub fn new() -> Self { - StrArena { pages: Vec::new(), cur: Vec::with_capacity(4096) } - } - - /// 写入一个字符串,返回 (offset, len)。数据拷贝一次后永驻。 - #[inline] - pub fn push(&mut self, s: &str) -> StrRef { - let bytes = s.as_bytes(); - if self.cur.len() + bytes.len() > self.cur.capacity() { - // 当前页放不下:封页,开新页(容量几何增长) - if !self.cur.is_empty() { - self.pages.push(std::mem::take(&mut self.cur)); - } - let cap = (4096usize).max(bytes.len().next_power_of_two()); - self.cur = Vec::with_capacity(cap); - } - let off = self.total_len() as u32; - self.cur.extend_from_slice(bytes); - StrRef { off, len: bytes.len() as u32 } - } - - /// 按 StrRef 取回字符串视图(零拷贝)。 - #[inline] - pub fn get<'a>(&'a self, r: StrRef) -> &'a str { - if r.is_null() { - return ""; - } - let start = r.off as usize; - let end = start + r.len as usize; - let mut acc = 0usize; - for page in &self.pages { - let page_len = page.len(); - if start < acc + page_len && end <= acc + page_len { - return std::str::from_utf8(&page[start - acc..end - acc]).unwrap_or(""); - } - acc += page_len; - } - std::str::from_utf8(&self.cur[start - acc..end - acc]).unwrap_or("") - } - - fn total_len(&self) -> usize { - self.pages.iter().map(|p| p.len()).sum::() + self.cur.len() - } -} - -impl Default for StrArena { - fn default() -> Self { - Self::new() - } -} - -/// 通用别名:对象/数组/线程等句柄表都用 BumpArena。 -pub type Arena = BumpArena; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn bump_arena_alloc_get() { - let mut a = BumpArena::::new(); - let h1 = a.alloc(42); - let h2 = a.alloc(7); - assert_ne!(h1, h2); - assert_eq!(*a.get(h1), 42); - assert_eq!(*a.get(h2), 7); - *a.get_mut(h1) = 99; - assert_eq!(*a.get(h1), 99); - } - - #[test] - fn bump_arena_multi_page() { - let mut a = BumpArena::::new(); - let mut last = 0; - for i in 1..1000u32 { - last = a.alloc(i); - } - assert_eq!(*a.get(last), 999); - assert!(a.pages() >= 3); - } - - #[test] - fn str_arena_roundtrip() { - let mut s = StrArena::new(); - let a = s.push("hello"); - let b = s.push("世界"); - let c = s.push("x".repeat(5000).as_str()); - assert_eq!(s.get(a), "hello"); - assert_eq!(s.get(b), "世界"); - assert_eq!(s.get(c).len(), 5000); - assert!(s.get(StrRef::NULL).is_empty()); - } - - #[test] - fn str_arena_cross_page() { - // 跨页边界的长串必须完整可读 - let mut s = StrArena::new(); - let long = "abc".repeat(2000); - let r = s.push(&long); - assert_eq!(s.get(r), long); - } -} diff --git a/rust/crates/bbb-core/src/lib.rs b/rust/crates/bbb-core/src/lib.rs deleted file mode 100644 index d8fe6eb..0000000 --- a/rust/crates/bbb-core/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! bbb-core — BioLang 运行时核心。 -//! -//! 内存规划(手写掌控版,对标旧 C 的 arena 设计并强化): -//! -//! 1. **一切值进 arena,引用用 u32 句柄而非指针** -//! - 句柄 = (page, slot) 打包,arena 扩容/搬迁不需要修指针; -//! - 句柄天然可序列化(.img 打包、跨线程传递);8 字节对齐无空洞; -//! - 与旧 C 的 `aalloc`(裸指针 + 永不释放)相比,句柄方案在保持 -//! "程序级一次性生命周期" 的同时,获得可搬迁 + 可序列化两个能力。 -//! 2. **字符串 = 全局字节池中的 (offset, len)**,写入即 intern, -//! 零拷贝复用;`StrRef` 8 字节。 -//! 3. **Value 16 字节**:u32 tag(含 REFUSED 标志位)+ u64 负载 + 4B pad; -//! 所有标量(int/float/double/bool/char)内联,字符串/对象/数组走句柄。 -//! (v2 候选:NaN-boxing 压到 8 字节,代价是 int 精度受限——旧 LLVM -//! 后端统一 double 语义,解释器保留 i64,故 v1 不采用。) -//! 4. **请求模型 = Value 的 tag 位**:bit31 = refused,负载为 cause 的 -//! 字符串句柄;`res`/`ref` 不产生堆分配,随值传递。 -//! 5. **区域划分**:每个流(Unistream/Remstream/Threadstream...)在 arena -//! 内拥有自己的页区,线程隔离靠区域隔离实现(协作式调度,无锁)。 - -pub mod arena; -pub mod value; - -pub use arena::{Arena, BumpArena, StrArena, StrRef}; -pub use value::{Cause, Outcome, Tag, Value}; diff --git a/rust/crates/bbb-core/src/value.rs b/rust/crates/bbb-core/src/value.rs deleted file mode 100644 index 2773dc0..0000000 --- a/rust/crates/bbb-core/src/value.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! Value / 请求模型(bbb-core)。 -//! -//! # Value — 16 字节标量优先表示 -//! -//! ```text -//! ┌──────────────┬──────────────────┬──────────────┐ -//! │ tag: u32 │ data: u64 │ pad: u32 │ -//! │ bit31 REFUSED│ 负载 │ (对齐) │ -//! └──────────────┴──────────────────┴──────────────┘ -//! ``` -//! -//! - 标量(Int/Num/Bool/Char)全部内联,零堆分配; -//! - Str/Obj/Arr/Ref 走句柄(u32 → arena),8 字节以内; -//! - **REFUSED 位**:`ref "原因"` 产生的请求结果 = 同值 + 标志位, -//! 不额外分配;`get`/`cause` 只是位测试; -//! - `Outcome`:解释器内部用,`Res(Value)` / `Ref(Cause)` 二态, -//! 与语法层 ResStatement/RefStatement 一一对应。 -//! -//! # 类型标签(低 24 位) -//! -//! Nil / Int / Num / Bool / Str / Char / Obj / Arr / Ref - -use crate::arena::StrRef; - -pub const TAG_MASK: u32 = 0x00FF_FFFF; -pub const REFUSED: u32 = 0x8000_0000; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum Tag { - Nil = 0, - Int = 1, - Num = 2, // float/double 统一 double 语义(LLVM 后端同款) - Bool = 3, - Str = 4, - Char = 5, - Obj = 6, - Arr = 7, - Ref = 8, // 智能引用句柄(&perm follow base) -} - -impl Tag { - #[inline] - pub fn from_bits(bits: u32) -> Tag { - match bits & TAG_MASK { - 0 => Tag::Nil, - 1 => Tag::Int, - 2 => Tag::Num, - 3 => Tag::Bool, - 4 => Tag::Str, - 5 => Tag::Char, - 6 => Tag::Obj, - 7 => Tag::Arr, - _ => Tag::Ref, - } - } -} - -/// 16 字节 Value(对齐 8;字段按对齐降序排列保证紧凑)。 -#[derive(Debug, Clone, Copy)] -#[repr(C, align(8))] -pub struct Value { - data: u64, // 负载 - tag: u32, // 低 24 位类型 + bit31 REFUSED - _pad: u32, -} - -impl Value { - pub const NIL: Value = Value { tag: Tag::Nil as u32, data: 0, _pad: 0 }; - - #[inline] - pub fn nil() -> Self { - Self::NIL - } - - #[inline] - pub fn int(v: i64) -> Self { - Value { tag: Tag::Int as u32, data: v as u64, _pad: 0 } - } - - #[inline] - pub fn num(v: f64) -> Self { - Value { tag: Tag::Num as u32, data: v.to_bits(), _pad: 0 } - } - - #[inline] - pub fn boolean(v: bool) -> Self { - Value { tag: Tag::Bool as u32, data: v as u64, _pad: 0 } - } - - #[inline] - pub fn string(r: StrRef) -> Self { - Value { tag: Tag::Str as u32, data: ((r.off as u64) << 32) | r.len as u64, _pad: 0 } - } - - #[inline] - pub fn chr(v: u8) -> Self { - Value { tag: Tag::Char as u32, data: v as u64, _pad: 0 } - } - - #[inline] - pub fn obj(h: u32) -> Self { - Value { tag: Tag::Obj as u32, data: h as u64, _pad: 0 } - } - - #[inline] - pub fn arr(h: u32) -> Self { - Value { tag: Tag::Arr as u32, data: h as u64, _pad: 0 } - } - - #[inline] - pub fn reff(h: u32) -> Self { - Value { tag: Tag::Ref as u32, data: h as u64, _pad: 0 } - } - - #[inline] - pub fn tag(&self) -> Tag { - Tag::from_bits(self.tag) - } - - #[inline] - pub fn refused(&self) -> bool { - self.tag & REFUSED != 0 - } - - /// 标记为拒绝(请求模型:ref)。保留原值,仅置位。 - #[inline] - pub fn with_refused(mut self) -> Self { - self.tag |= REFUSED; - self - } - - /// 拒绝值:REFUSED 位 + 原因字符串句柄(cause)。 - #[inline] - pub fn refused_str(r: StrRef) -> Self { - Value { - tag: Tag::Str as u32 | REFUSED, - data: ((r.off as u64) << 32) | r.len as u64, - _pad: 0, - } - } - - /// 拒绝原因(未拒绝时返回空串句柄)。 - #[inline] - pub fn cause(&self) -> StrRef { - if self.refused() && self.tag() == Tag::Str { - self.as_str() - } else { - StrRef::NULL - } - } - - /// 数值视图:Int → i64 → f64;Num → f64(统一 double 语义)。 - #[inline] - pub fn as_int_or_num(&self) -> f64 { - match self.tag() { - Tag::Int => self.as_int() as f64, - Tag::Num => self.as_num(), - Tag::Bool => self.as_bool() as u8 as f64, - Tag::Char => self.as_char() as f64, - _ => 0.0, - } - } - - #[inline] - pub fn as_int(&self) -> i64 { - debug_assert_eq!(self.tag(), Tag::Int); - self.data as i64 - } - - #[inline] - pub fn as_num(&self) -> f64 { - debug_assert_eq!(self.tag(), Tag::Num); - f64::from_bits(self.data) - } - - #[inline] - pub fn as_bool(&self) -> bool { - debug_assert_eq!(self.tag(), Tag::Bool); - self.data != 0 - } - - #[inline] - pub fn as_str(&self) -> StrRef { - debug_assert_eq!(self.tag(), Tag::Str); - StrRef { off: (self.data >> 32) as u32, len: self.data as u32 } - } - - #[inline] - pub fn as_char(&self) -> u8 { - debug_assert_eq!(self.tag(), Tag::Char); - self.data as u8 - } - - #[inline] - pub fn as_handle(&self) -> u32 { - debug_assert!(matches!(self.tag(), Tag::Obj | Tag::Arr | Tag::Ref)); - self.data as u32 - } - - /// 真值判定(与旧实现一致):0 / "" / 拒绝 = false,其余 true。 - #[inline] - pub fn truthy(&self) -> bool { - if self.refused() { - return false; - } - match self.tag() { - Tag::Nil => false, - Tag::Int => self.as_int() != 0, - Tag::Num => self.as_num() != 0.0, - Tag::Bool => self.as_bool(), - Tag::Str => !self.as_str().is_null(), - Tag::Char => self.as_char() != 0, - _ => true, - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - if self.refused() != other.refused() { - return false; - } - self.tag == other.tag && self.data == other.data - } -} -impl Eq for Value {} - -impl std::fmt::Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.refused() { - return write!(f, ""); - } - match self.tag() { - Tag::Nil => write!(f, "nil"), - Tag::Int => write!(f, "{}", self.as_int()), - Tag::Num => write!(f, "{}", self.as_num()), - Tag::Bool => write!(f, "{}", self.as_bool()), - Tag::Str => write!(f, "", self.data), - Tag::Char => write!(f, "{}", self.as_char() as char), - Tag::Obj => write!(f, "", self.as_handle()), - Tag::Arr => write!(f, "", self.as_handle()), - Tag::Ref => write!(f, "", self.as_handle()), - } - } -} - -/// 请求结果:Res = 响应,Ref = 拒绝(携带 cause 字符串)。 -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Outcome { - Res(Value), - Ref(Cause), -} - -/// 拒绝原因:字符串句柄(arena 内,零拷贝)。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Cause(pub StrRef); - -impl Outcome { - #[inline] - pub fn is_refused(&self) -> bool { - matches!(self, Outcome::Ref(_)) - } - - /// `get`:取实际值(拒绝时返回 Nil)。 - #[inline] - pub fn get(self) -> Value { - match self { - Outcome::Res(v) => v, - Outcome::Ref(_) => Value::nil(), - } - } - - /// `cause`:取拒绝原因(未拒绝时返回空字符串)。 - #[inline] - pub fn cause(self) -> StrRef { - match self { - Outcome::Res(_) => StrRef::NULL, - Outcome::Ref(c) => c.0, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn value_size_is_16() { - assert_eq!(std::mem::size_of::(), 16); - } - - #[test] - fn scalars_inline() { - assert_eq!(Value::int(42).as_int(), 42); - assert_eq!(Value::num(3.5).as_num(), 3.5); - assert!(Value::boolean(true).as_bool()); - assert_eq!(Value::chr(b'x').as_char(), b'x'); - assert_eq!(Value::nil().tag(), Tag::Nil); - } - - #[test] - fn string_roundtrip() { - let r = StrRef { off: 12, len: 5 }; - let v = Value::string(r); - assert_eq!(v.tag(), Tag::Str); - assert_eq!(v.as_str(), r); - } - - #[test] - fn refused_flag() { - let ok = Value::int(7); - let bad = ok.with_refused(); - assert!(bad.refused()); - assert!(!ok.refused()); - assert_ne!(ok, bad); - assert_eq!(bad.truthy(), false); - } - - #[test] - fn truthiness() { - assert!(!Value::nil().truthy()); - assert!(!Value::int(0).truthy()); - assert!(Value::int(1).truthy()); - assert!(!Value::num(0.0).truthy()); - assert!(!Value::boolean(false).truthy()); - assert!(!Value::string(StrRef::NULL).truthy()); - } - - #[test] - fn outcome_get_cause() { - let res = Outcome::Res(Value::int(10)); - assert_eq!(res.get(), Value::int(10)); - assert!(res.cause().is_null()); - - let cause = Cause(StrRef { off: 3, len: 9 }); - let rej = Outcome::Ref(cause); - assert!(rej.is_refused()); - assert_eq!(rej.get(), Value::nil()); - assert_eq!(rej.cause(), cause.0); - } -} diff --git a/rust/crates/bbb-llvm/Cargo.toml b/rust/crates/bbb-llvm/Cargo.toml deleted file mode 100644 index 3eb704d..0000000 --- a/rust/crates/bbb-llvm/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bbb-llvm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang LLVM 后端(M4):AST → LLVM IR 文本 → 系统 clang 编译" - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } diff --git a/rust/crates/bbb-llvm/src/lib.rs b/rust/crates/bbb-llvm/src/lib.rs deleted file mode 100644 index 5ff3f3d..0000000 --- a/rust/crates/bbb-llvm/src/lib.rs +++ /dev/null @@ -1,887 +0,0 @@ -//! bbb-llvm — BioLang 编译器(M4 里程碑)。 -//! -//! 方案(对齐旧 src/llvm.c):**零依赖**发射 LLVM IR 文本,交给系统 clang -//! 编译成原生可执行文件。统一 double 语义在解释器与编译器间保持一致。 -//! -//! M4 v1 支持子集(17-llvm.bio 为准): -//! - int/float/double 变量声明与赋值、算术 + - * / %、比较 == != < > <= >= -//! - if/else、while、for(含 break/continue) -//! - 方法定义与调用(含 Main::exec 外的方法,如 square)、递归 v2 -//! - res 返回值 / ref(打印后退出)、get/cause、ALL 声明 -//! - CIO::println / CIO::print(字符串+数字混合 → printf) -//! - Class + new(对象 = malloc struct,this 指针传参,__init__ 自动调用, -//! this::字段 GEP 读写,对象方法调用)——2026-08-23 扩展 - -use std::collections::HashMap; - -use bbb_syntax::ast::*; - -/// 值类型。 -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Ty { - I64, - F64, - Ptr, // 对象/流指针 -} - -impl Ty { - fn llvm(self) -> &'static str { - match self { - Ty::I64 => "i64", - Ty::F64 => "double", - Ty::Ptr => "ptr", - } - } -} - -/// 编译产物。 -pub struct Module { - pub ir: String, -} - -struct Ctx { - out: String, - tmp: usize, - str_i: usize, - vars: Vec>, // 作用域栈:变量名 → (类型, 寄存器名) - funcs: HashMap)>, // 方法表:名 → (返回类型, 参数) - void_methods: std::collections::HashSet, // void 方法集合(调用时用 call void) - classes: HashMap>, // 类:名 → 字段列表(名, 类型) - var_ty: HashMap, // 变量名 → 类名(对象变量,属性访问用) - current_class: Option, // 当前编译的类名(this:: 属性用) - this_reg: Option, // 当前方法 this 指针寄存器(类方法) - labels: usize, - loop_end: Vec, - loop_continue: Vec, // continue 目标(for=update,while=cond) - current_ret: Ty, - str_list: String, // 字符串常量延迟输出(函数外) -} - -impl Ctx { - fn new() -> Self { - Ctx { - out: String::new(), - tmp: 0, - str_i: 0, - vars: vec![HashMap::new()], - funcs: HashMap::new(), - void_methods: std::collections::HashSet::new(), - classes: HashMap::new(), - var_ty: HashMap::new(), - current_class: None, - this_reg: None, - labels: 0, - loop_end: Vec::new(), - loop_continue: Vec::new(), - current_ret: Ty::I64, - str_list: String::new(), - } - } - - fn emit(&mut self, s: impl AsRef) { - self.out.push_str(s.as_ref()); - self.out.push('\n'); - } - - fn reg(&mut self, hint: &str) -> String { - self.tmp += 1; - format!("%{hint}{}", self.tmp) - } - - fn label(&mut self, hint: &str) -> String { - self.labels += 1; - format!("{hint}{}", self.labels) - } - - fn str_const(&mut self, s: &str) -> String { - // C 风格转义 - let mut esc = String::new(); - for b in s.bytes() { - match b { - b'"' => esc.push_str("\\22"), - b'\\' => esc.push_str("\\5C"), - b'\n' => esc.push_str("\\0A"), - b'\t' => esc.push_str("\\09"), - 0x20..=0x7E => esc.push(b as char), - _ => esc.push_str(&format!("\\{:02X}", b)), - } - } - self.str_i += 1; - let name = format!("@.str{}", self.str_i); - // LLVM 字节数:\XX 转义序列计 1 字节,其余字符 1 字节 - let mut llvm_len = 0usize; - let chars: Vec = esc.chars().collect(); - let mut k = 0; - while k < chars.len() { - if chars[k] == '\\' && k + 2 < chars.len() { - llvm_len += 1; - k += 3; - } else { - llvm_len += 1; - k += 1; - } - } - self.str_list.push_str(&format!("{name} = private unnamed_addr constant [{} x i8] c\"{esc}\\00\", align 1\n", - llvm_len + 1)); - name - } - - fn var_get(&self, name: &str) -> Option<(Ty, String)> { - for scope in self.vars.iter().rev() { - if let Some(v) = scope.get(name) { - return Some(v.clone()); - } - } - None - } - - fn var_set(&mut self, name: &str, ty: Ty, reg: String) { - self.vars.last_mut().unwrap().insert(name.to_string(), (ty, reg)); - } -} - -/// 编译 Program → IR 文本。 -pub fn compile(prog: &Program) -> Result { - let mut ctx = Ctx::new(); - ctx.emit("; BioLang LLVM backend (M4) — generated from AST"); - ctx.emit("declare i32 @printf(ptr, ...)"); - ctx.emit("declare void @exit(i32)"); - ctx.emit("declare ptr @malloc(i64)"); - ctx.emit(""); - - // 收集类字段表(先于类型声明) - collect_classes(prog, &mut ctx); - // 发射 struct 类型声明 - let class_names: Vec = ctx.classes.keys().cloned().collect(); - for cname in &class_names { - let fields = &ctx.classes[cname]; - let mut ty = String::from("type {"); - for (i, (_n, t)) in fields.iter().enumerate() { - if i > 0 { - ty.push_str(", "); - } - ty.push_str(t.llvm()); - } - if fields.is_empty() { - ty.push_str("i8"); - } - ty.push('}'); - ctx.emit(format!("%struct.{cname} = {ty}")); - } - if !ctx.classes.is_empty() { - ctx.emit(""); - } - - // 收集方法签名 - collect_methods(prog, &mut ctx); - - // 编译 Main 流方法(exec 之外的方法作为普通函数) - if let Some(m) = &prog.main { - for method in &m.methods { - if method.name != "exec" { - let fname = format!("@main_{}", method.name); - compile_function(&mut ctx, &fname, method, false)?; - } - } - } - // 编译 fork/class 方法(@<流名>_<方法名>,类方法首参为 this 指针) - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - for mem in members { - if let Member::Method(m2) = mem { - let fname = format!("@{}_{}", name, m2.name); - let is_class = ctx.classes.contains_key(name); - compile_function(&mut ctx, &fname, m2, is_class)?; - } - } - } - } - // Main::exec → @__bio_main - if let Some(m) = &prog.main { - if let Some(exec) = m.methods.iter().find(|x| x.name == "exec") { - let mut e2 = exec.clone(); - e2.name = "__bio_main".into(); - e2.ret = "void".into(); - compile_function(&mut ctx, "@__bio_main", &e2, false)?; - } - } - // 字符串常量(函数外) - if !ctx.str_list.is_empty() { - ctx.out.push_str(&ctx.str_list); - ctx.out.push('\n'); - } - // C main 包装 - ctx.emit("define i32 @main(i32 %argc, ptr %argv) {"); - ctx.emit("entry:"); - ctx.emit(" call void @__bio_main()"); - ctx.emit(" ret i32 0"); - ctx.emit("}"); - Ok(ctx.out) -} - -fn collect_methods(prog: &Program, ctx: &mut Ctx) { - let mut methods: Vec<(String, Ty, Vec<(String, Ty)>)> = Vec::new(); - if let Some(m) = &prog.main { - for m2 in &m.methods { - if m2.name != "exec" { - let (rt, params) = sig_of(m2); - methods.push((format!("main_{}", m2.name), rt, params)); - } - } - } - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - let is_class = ctx.classes.contains_key(name); - for mem in members { - if let Member::Method(m2) = mem { - let (rt, mut params) = sig_of(m2); - if is_class { - // 类方法:首参 this 指针 - params.insert(0, ("this".into(), Ty::Ptr)); - } - methods.push((format!("{}_{}", name, m2.name), rt, params)); - } - } - } - } - for (n, rt, params) in methods { - ctx.funcs.insert(n.clone(), (rt, params)); - let _ = &n; - } - // void 方法登记(从原始方法表再扫一遍) - for d in &prog.decls { - if let (Decl::Class { name, members, .. } | Decl::Fork { name, members, .. }) = d { - for mem in members { - if let Member::Method(m2) = mem { - if m2.ret == "void" { - ctx.void_methods.insert(format!("{}_{}", name, m2.name)); - } - } - } - } - } - if let Some(m) = &prog.main { - for m2 in &m.methods { - if m2.ret == "void" && m2.name != "exec" { - ctx.void_methods.insert(format!("main_{}", m2.name)); - } - } - } -} - -fn decl_name(d: &Decl) -> String { - match d { - Decl::Class { name, .. } => name.clone(), - _ => "s".into(), - } -} - -/// 收集类字段表:类名 → [(字段名, 类型)](new/this:: 用)。 -fn collect_classes(prog: &Program, ctx: &mut Ctx) { - for d in &prog.decls { - if let Decl::Class { name, members, .. } = d { - let mut fields = Vec::new(); - for mem in members { - if let Member::Field { ty, names } = mem { - for n in names { - fields.push((n.clone(), ty_of(ty))); - } - } - } - ctx.classes.insert(name.clone(), fields); - } - } -} - -fn sig_of(m: &Method) -> (Ty, Vec<(String, Ty)>) { - let rt = ty_of(&m.ret); - let params = m - .params - .iter() - .map(|p| (p.name.clone(), ty_of(&p.ty))) - .collect(); - (rt, params) -} - -fn ty_of(t: &str) -> Ty { - match t { - "float" | "double" => Ty::F64, - "int" | "char" | "bool" => Ty::I64, - "void" => Ty::I64, // void 方法:返回 i64 0(调用方忽略) - _ => Ty::Ptr, // 类名/流名/对象 → 指针 - } -} - -fn compile_function(ctx: &mut Ctx, fname: &str, method: &Method, is_class: bool) -> Result<(), String> { - // 类方法:记录当前类名(this:: 属性访问) - let saved_class = ctx.current_class.clone(); - if is_class { - if let Some(cname) = fname.strip_prefix('@').and_then(|f| f.split('_').next()) { - ctx.current_class = Some(cname.to_string()); - } - } - let r = compile_function_inner(ctx, fname, method, is_class); - ctx.current_class = saved_class; - r -} - -fn compile_function_inner(ctx: &mut Ctx, fname: &str, method: &Method, is_class: bool) -> Result<(), String> { - let (rt, params) = sig_of(method); - ctx.current_ret = rt; - let mut sig = format!("define {} {fname}(", rt.llvm()); - let mut body_sig = Vec::new(); - let mut first = true; - // 类方法:首参为 this 指针(隐含) - if is_class { - sig.push_str("ptr %this"); - first = false; - } - for (i, (n, t)) in params.iter().enumerate() { - if !first { - sig.push_str(", "); - } - first = false; - let arg = format!("%{}", n); - sig.push_str(&format!("{} {arg}", t.llvm())); - body_sig.push((n.clone(), *t, arg)); - } - sig.push(')'); - ctx.emit(sig); - ctx.emit("{"); - ctx.emit("entry:"); - // 参数 alloc + store - ctx.vars.push(HashMap::new()); - if is_class { - let alloca = ctx.reg("this_"); - ctx.emit(format!(" {alloca} = alloca ptr")); - ctx.emit(format!(" store ptr %this, ptr {alloca}")); - ctx.this_reg = Some(alloca.clone()); - } - for (n, t, arg) in body_sig { - let alloca = ctx.reg(&format!("{}_", n)); - ctx.emit(format!(" {alloca} = alloca {}", t.llvm())); - ctx.emit(format!(" store {} {arg}, ptr {alloca}", t.llvm())); - ctx.var_set(&n, t, alloca); - } - // 语句 - let (flow, ret_reg) = compile_block(ctx, &method.body)?; - if let Some(r) = ret_reg { - ctx.emit(format!(" ret {} {r}", rt.llvm())); - } else if !flow { - ctx.emit(format!(" ret {} {}", rt.llvm(), if rt == Ty::I64 { "0" } else if rt == Ty::F64 { "0.0" } else { "null" })); - } - ctx.emit("}"); - ctx.emit(""); - ctx.vars.pop(); - ctx.this_reg = None; - Ok(()) -} - -/// 语句块编译结果。 -struct BlockOut { - /// 最后一个 res 的寄存器(Some = 函数已 ret 前) - ret: Option, -} - -/// 编译语句块,返回 (是否以终止指令结束, res 寄存器)。 -/// 块内 break/continue/ret 后不再编译后续语句(不可达丢弃,LLVM 合法)。 -fn compile_block(ctx: &mut Ctx, stmts: &[Stmt]) -> Result<(bool, Option), String> { - let mut ret = None; - for st in stmts { - match st { - Stmt::Ret { kind, values } => { - if *kind == RetKind::Ref { - // ref:拒绝 → 打印消息后退出(消息表达式 v2;先直接 exit) - ctx.emit(" call void @exit(i32 1)"); - ctx.emit(" unreachable"); - return Ok((true, None)); - } else if let Some(v) = values.first() { - let (ty, val) = compile_expr(ctx, v)?; - ctx.current_ret = ty; - return Ok((true, Some(val))); - } - } - Stmt::Expr(e) => { - // 调用语句:CIO::println 等 - compile_call_stmt(ctx, e)?; - } - Stmt::Assign { vtype, target, op, value, .. } => { - if let AssignTarget::Var(name) = target { - let (vty, vval) = compile_expr(ctx, value)?; - let _ = op; - match ctx.var_get(name) { - Some((t, reg)) => { - let cast = coerce(ctx, vval, vty, t); - ctx.emit(format!(" store {} {cast}, ptr {reg}", t.llvm())); - } - None => { - let alloca_t = if vtype.as_deref() == Some("ALL") || vtype.is_none() { vty } else { ty_of(vtype.as_deref().unwrap_or("int")) }; - let alloca = ctx.reg(&format!("{}a_", name)); - ctx.emit(format!(" {alloca} = alloca {}", alloca_t.llvm())); - let cast = coerce(ctx, vval, vty, alloca_t); - ctx.emit(format!(" store {} {cast}, ptr {alloca}", alloca_t.llvm())); - ctx.var_set(name, alloca_t, alloca); - // 对象变量:记录类名(属性访问用) - if alloca_t == Ty::Ptr { - if let Some(vt) = vtype { - if !matches!(vt.as_str(), "ALL") && !vt.is_empty() { - ctx.var_ty.insert(name.clone(), vt.clone()); - } - } - } - } - } - } else if let AssignTarget::Prop { base, name } = target { - // 属性赋值:obj.field = v / this.field = v - let (vty, vval) = compile_expr(ctx, value)?; - let (bt, bv) = compile_expr(ctx, base)?; - if bt != Ty::Ptr { - return Err(format!("property assignment on non-object: {name}")); - } - let bval = if let Expr::Var(vn) = base.as_ref() { - if vn == "this" { - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - p - } - None => bv, - } - } else { - bv - } - } else { - bv - }; - let cls = field_owner(ctx, base); - let (ft, idx) = field_index(ctx, &cls, name)?; - let gp = ctx.reg("gep"); - ctx.emit(format!( - " {gp} = getelementptr %struct.{cls}, ptr {bval}, i32 0, i32 {idx}" - )); - let cast = coerce(ctx, vval, vty, ft); - ctx.emit(format!(" store {} {cast}, ptr {gp}", ft.llvm())); - } - } - Stmt::If { cond, then, els } => { - let (_, cval) = compile_expr(ctx, cond)?; - let then_l = ctx.label("then"); - let else_l = ctx.label("else"); - let end_l = ctx.label("endif"); - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{then_l}, label %{else_l}")); - ctx.emit(format!("{then_l}:")); - let (t1, r1) = compile_block(ctx, then)?; - if !t1 && r1.is_none() { - ctx.emit(format!(" br label %{end_l}")); - } - ctx.emit(format!("{else_l}:")); - if let Some(e) = els { - let (t2, r2) = compile_block(ctx, e)?; - if !t2 && r2.is_none() { - ctx.emit(format!(" br label %{end_l}")); - } - } else { - ctx.emit(format!(" br label %{end_l}")); - } - ctx.emit(format!("{end_l}:")); - } - Stmt::While { cond, body } => { - let cond_l = ctx.label("wcond"); - let body_l = ctx.label("wbody"); - let end_l = ctx.label("wend"); - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{cond_l}:")); - let (_, cval) = compile_expr(ctx, cond)?; - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{body_l}, label %{end_l}")); - ctx.emit(format!("{body_l}:")); - ctx.loop_end.push(end_l.clone()); - ctx.loop_continue.push(cond_l.clone()); - let (flow, r) = compile_block(ctx, body)?; - ctx.loop_end.pop(); - ctx.loop_continue.pop(); - if !flow { - ctx.emit(format!(" br label %{cond_l}")); - } - if let Some(r) = r { - ret = Some(r); - } - ctx.emit(format!("{end_l}:")); - } - Stmt::For { init, cond, update, body } => { - if let Some(i) = init { - compile_block(ctx, std::slice::from_ref(i))?; - } - let cond_l = ctx.label("fcond"); - let body_l = ctx.label("fbody"); - let upd_l = ctx.label("fupd"); - let end_l = ctx.label("fend"); - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{cond_l}:")); - if let Some(c) = cond { - let (_, cval) = compile_expr(ctx, c)?; - let c = ctx.reg("c"); - ctx.emit(format!(" {c} = icmp ne i64 {cval}, 0")); - ctx.emit(format!(" br i1 {c}, label %{body_l}, label %{end_l}")); - } else { - ctx.emit(format!(" br label %{body_l}")); - } - ctx.emit(format!("{body_l}:")); - ctx.loop_end.push(end_l.clone()); - ctx.loop_continue.push(upd_l.clone()); - let (flow, r) = compile_block(ctx, body)?; - ctx.loop_end.pop(); - ctx.loop_continue.pop(); - if let Some(r) = r { - ret = Some(r); - } - if !flow { - ctx.emit(format!(" br label %{upd_l}")); - } - ctx.emit(format!("{upd_l}:")); - if let Some(u) = update { - compile_block(ctx, std::slice::from_ref(u))?; - } - ctx.emit(format!(" br label %{cond_l}")); - ctx.emit(format!("{end_l}:")); - } - Stmt::Break => { - if let Some(e) = ctx.loop_end.last() { - ctx.emit(format!(" br label %{e}")); - return Ok((true, None)); - } - } - Stmt::Continue => { - if let Some(c) = ctx.loop_continue.last() { - ctx.emit(format!(" br label %{c}")); - return Ok((true, None)); - } - } - Stmt::Inc { name, op } => { - if let Some((t, reg)) = ctx.var_get(name) { - let delta = if op == "++" { 1 } else { -1 }; - let t1 = ctx.reg("inc"); - ctx.emit(format!(" {t1} = load {}, ptr {reg}", t.llvm())); - let t2 = ctx.reg("inc"); - ctx.emit(format!(" {t2} = add {} {t1}, {delta}", t.llvm())); - ctx.emit(format!(" store {} {t2}, ptr {reg}", t.llvm())); - } - } - Stmt::RefDecl { .. } => {} - } - } - Ok((false, ret)) -} - -/// 调用语句:CIO::println/print → printf;其他调用(对象方法等)→ 正常 call。 -fn compile_call_stmt(ctx: &mut Ctx, e: &Expr) -> Result<(), String> { - if let Expr::Call { qual, name, args } = e { - if qual.as_deref() == Some("CIO") || qual.as_deref() == Some("IO") { - if name == "println" || name == "print" { - return emit_printf(ctx, args, name == "println"); - } - } - } - // 其他调用:编译(副作用保留) - compile_expr(ctx, e)?; - Ok(()) -} - -fn emit_printf(ctx: &mut Ctx, args: &[Expr], newline: bool) -> Result<(), String> { - // 拼格式串:字符串参数原样(转义 %),数字参数 → %ld / %lf - let mut fmt = String::new(); - let mut call_args: Vec<(Ty, String)> = Vec::new(); - // println 参数空格分隔(print 直接拼接——与解释器一致) - let sep = if newline { " " } else { "" }; - let mut first = true; - for a in args { - if !first { - fmt.push_str(sep); - } - first = false; - match a { - Expr::Str(s) => { - fmt.push_str(&s.replace('%', "%%")); - } - other => { - let (ty, val) = compile_expr(ctx, other)?; - match ty { - Ty::I64 => fmt.push_str("%ld"), - Ty::F64 => fmt.push_str("%lf"), - Ty::Ptr => fmt.push_str("%p"), - } - call_args.push((ty, val)); - } - } - } - if newline { - fmt.push('\n'); // 真换行字节,由 str_const 转义成 \0A - } - let sc = ctx.str_const(&fmt); - let mut call = format!(" call i32 (ptr, ...) @printf(ptr {sc}"); - for (ty, val) in &call_args { - call.push_str(&format!(", {} {}", ty.llvm(), val)); - } - call.push(')'); - ctx.emit(call); - Ok(()) -} - -fn is_void_method(ctx: &Ctx, key: &str) -> bool { - ctx.void_methods.contains(key) -} -fn coerce(ctx: &mut Ctx, val: String, from: Ty, to: Ty) -> String { - if from == to { - return val; - } - let r = ctx.reg("cv"); - match (from, to) { - (Ty::I64, Ty::F64) => ctx.emit(format!(" {r} = sitofp i64 {val} to double")), - (Ty::F64, Ty::I64) => ctx.emit(format!(" {r} = fptosi double {val} to i64")), - _ => return val, - } - r -} - -/// 属性所属类:base 是 this → 当前类;base 是对象变量 → 变量类型对应的类。 -/// 简化:从变量类型名推断(vars 表里对象变量以类名注册)。 -fn field_owner(ctx: &Ctx, base: &Expr) -> String { - if let Expr::Var(vn) = base { - if vn == "this" { - // 当前类:找 this_reg 所在函数——用 classes 里第一个含该字段的类兜底 - // 更准确:compile_function 时记录当前类名 - if let Some(c) = &ctx.current_class { - return c.clone(); - } - } - // 对象变量:vars 存 (Ty, reg),类型信息丢失——用 var_ty 表 - if let Some(t) = ctx.var_ty.get(vn) { - return t.clone(); - } - } - String::new() -} - -fn field_index(ctx: &Ctx, cls: &str, name: &str) -> Result<(Ty, u32), String> { - let fields = ctx.classes.get(cls).ok_or_else(|| { - format!("property {name} on unknown class {cls}") - })?; - for (i, (n, t)) in fields.iter().enumerate() { - if n == name { - return Ok((*t, i as u32)); - } - } - Err(format!("class {cls} has no field {name}")) -} - -fn compile_expr(ctx: &mut Ctx, e: &Expr) -> Result<(Ty, String), String> { - match e { - Expr::Int(v) => Ok((Ty::I64, v.to_string())), - Expr::Float(v) => Ok((Ty::F64, format!("{v:.17}"))), - Expr::Var(name) => { - if name == "this" { - // this 指针 - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thisv"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - return Ok((Ty::Ptr, p)); - } - None => return Err("this used outside a class method".to_string()), - } - } - match ctx.var_get(name) { - Some((t, reg)) => { - let r = ctx.reg("v"); - ctx.emit(format!(" {r} = load {}, ptr {reg}", t.llvm())); - Ok((t, r)) - } - None => Err(format!("undefined variable {name}")), - } - } - Expr::Unwrap { op, l } => { - let (t, v) = compile_expr(ctx, l)?; - let _ = op; - Ok((t, v)) // get/cause 单值语义 - } - Expr::BinOp { op, l, r } => { - let (lt, lv) = compile_expr(ctx, l)?; - let (rt, rv) = compile_expr(ctx, r)?; - let ty = if lt == Ty::F64 || rt == Ty::F64 { Ty::F64 } else { Ty::I64 }; - let lv = coerce(ctx, lv, lt, ty); - let rv = coerce(ctx, rv, rt, ty); - let out = ctx.reg("b"); - match op.as_str() { - "+" | "-" | "*" | "/" | "%" => { - let opc = match (op.as_str(), ty) { - ("+", Ty::I64) => "add", ("+", Ty::F64) => "fadd", - ("-", Ty::I64) => "sub", ("-", Ty::F64) => "fsub", - ("*", Ty::I64) => "mul", ("*", Ty::F64) => "fmul", - ("/", Ty::I64) => "sdiv", ("/", Ty::F64) => "fdiv", - ("%", Ty::I64) => "srem", ("%", Ty::F64) => "frem", - _ => "add", - }; - ctx.emit(format!(" {out} = {opc} {} {lv}, {rv}", ty.llvm())); - Ok((ty, out)) - } - "==" | "!=" | "<" | ">" | "<=" | ">=" => { - let cond_op = match op.as_str() { - "==" => "eq", "!=" => "ne", "<" => "slt", ">" => "sgt", - "<=" => "sle", ">=" => "sge", _ => "eq", - }; - let cmp_op = if ty == Ty::F64 { - match cond_op { - "eq" => "oeq", "ne" => "one", "slt" => "olt", "sgt" => "ogt", - "sle" => "ole", "sge" => "oge", _ => "oeq", - } - } else { - cond_op - }; - if ty == Ty::F64 { - let t1 = ctx.reg("cmp"); - ctx.emit(format!(" {t1} = fcmp {cmp_op} double {lv}, {rv}")); - ctx.emit(format!(" {out} = zext i1 {t1} to i64")); - } else { - let t1 = ctx.reg("cmp"); - ctx.emit(format!(" {t1} = icmp {cmp_op} i64 {lv}, {rv}")); - ctx.emit(format!(" {out} = zext i1 {t1} to i64")); - } - Ok((Ty::I64, out)) - } - _ => Err(format!("unsupported operator {op}")), - } - } - Expr::Call { qual, name, args } => { - // 对象方法调用:qual 是对象变量 → 类名_方法名 + 传 this - let obj_this = if let Some(q) = qual { - ctx.var_ty.get(q).cloned() - } else { - None - }; - let key = if let Some(owner) = &obj_this { - format!("{owner}_{name}") - } else if let Some(q) = qual { - format!("{q}_{name}") - } else if ctx.funcs.contains_key(name) { - name.clone() - } else { - format!("main_{name}") - }; - let fname = format!("@{key}"); - let (rt, _) = ctx - .funcs - .get(&key) - .cloned() - .unwrap_or((Ty::I64, vec![])); - if !ctx.funcs.contains_key(&key) { - // 未知方法:拒绝(printf 消息 + exit)——后端子集边界 - let msg = ctx.str_const(&format!("stream {key} refuses: no method {name}\n")); - ctx.emit(format!(" call i32 (ptr, ...) @printf(ptr {msg})")); - ctx.emit(" call void @exit(i32 1)"); - ctx.emit(" unreachable"); - return Ok((rt, "0".into())); - } - let call_reg = ctx.reg("call"); - let is_void = rt == Ty::I64 && is_void_method(ctx, &key); - let mut call = if is_void { - format!(" call void {fname}(") - } else { - format!(" {call_reg} = call {} {fname}(", rt.llvm()) - }; - let mut first = true; - // 对象方法调用:qual 是对象变量 → 传 this 指针 - if obj_this.is_some() { - if let Some(q) = qual { - if let Some((Ty::Ptr, reg)) = ctx.var_get(q) { - let p = ctx.reg("thisp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - call.push_str(&format!("ptr {p}")); - first = false; - } - } - } - for a in args { - let (at, av) = compile_expr(ctx, a)?; - if !first { - call.push_str(", "); - } - first = false; - call.push_str(&format!("{} {av}", at.llvm())); - } - call.push(')'); - ctx.emit(call); - let _ = qual; - if is_void { - Ok((Ty::I64, "0".into())) - } else { - Ok((rt, call_reg)) - } - } - Expr::New { cls, args } => { - // new Class(args...) → malloc 对象 + 调 __init__(this, args...) - let fields = ctx - .classes - .get(cls) - .cloned() - .unwrap_or_default(); - let size = fields.len().max(1) * 8; - let obj = ctx.reg("obj"); - ctx.emit(format!(" {obj} = call ptr @malloc(i64 {size})")); - // 调 __init__(若存在) - let init_key = format!("{cls}___init__"); - if ctx.funcs.contains_key(&init_key) { - let call_reg = ctx.reg("init"); - let mut call = format!(" call void @{init_key}(ptr {obj}"); - for a in args { - let (at, av) = compile_expr(ctx, a)?; - call.push_str(&format!(", {} {av}", at.llvm())); - } - call.push(')'); - ctx.emit(call); - let _ = call_reg; - } - Ok((Ty::Ptr, obj)) - } - Expr::Prop { base, name } => { - // 对象属性读:obj.name / this.name → GEP load - let (bt, bv) = compile_expr(ctx, base)?; - if bt != Ty::Ptr { - return Err(format!("property access on non-object: {name}")); - } - // 从 this 指针寄存器取值(若 base 是 this 变量) - let bval = if let Expr::Var(vn) = base.as_ref() { - if vn == "this" { - match ctx.this_reg.clone() { - Some(reg) => { - let p = ctx.reg("thp"); - ctx.emit(format!(" {p} = load ptr, ptr {reg}")); - p - } - None => bv, - } - } else { - bv - } - } else { - bv - }; - // 找字段类型 - let cls = field_owner(ctx, base); - let (ft, idx) = field_index(ctx, &cls, name)?; - let gp = ctx.reg("gep"); - ctx.emit(format!( - " {gp} = getelementptr %struct.{cls}, ptr {bval}, i32 0, i32 {idx}" - )); - let v = ctx.reg("fld"); - ctx.emit(format!(" {v} = load {}, ptr {gp}", ft.llvm())); - Ok((ft, v)) - } - _ => Err(format!("LLVM backend does not support this expression yet: {e:?}")), - } -} - -// 占位(保持模块结构) diff --git a/rust/crates/bbb-llvm/tests/objects.rs b/rust/crates/bbb-llvm/tests/objects.rs deleted file mode 100644 index 665f469..0000000 --- a/rust/crates/bbb-llvm/tests/objects.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! bbb-llvm 集成测试:编译 → clang → 运行 → 输出断言。 -//! 需要系统 clang(与 CLI shell build 相同路径)。 - -use std::process::Command; - -use bbb_llvm::compile; -use bbb_syntax::parser::parse_source; - -fn build_and_run(src: &str) -> String { - let (prog, errs) = parse_source(src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - let ir = compile(&prog).expect("IR generation failed"); - - // 目录含进程+时间戳,避免并行测试冲突 - let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.subsec_nanos()) - .unwrap_or(0); - let dir = std::env::temp_dir().join(format!("bbb-llvm-test-{}-{nanos}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let ir_path = dir.join("out.ll"); - let bin_path = dir.join("a.out"); - std::fs::write(&ir_path, &ir).unwrap(); - - let status = Command::new("clang") - .arg(&ir_path) - .arg("-o") - .arg(&bin_path) - .status() - .expect("clang not found"); - assert!(status.success(), "clang failed"); - - let out = Command::new(&bin_path).output().expect("run failed"); - let _ = std::fs::remove_dir_all(&dir); - String::from_utf8_lossy(&out.stdout).to_string() -} - -#[test] -fn llvm_class_new_fields_and_methods() { - let src = r#" -program main; -Class Student { - void __init__(age int, solve double) { - this::age = age; - this::solve = solve; - } - int getAge() { res this::age; } - double getSolve() { res this::solve; } - void bump() { this::age = this::age + 1; } - int age; - double solve; -} -Main { - void exec() { - Student s = new Student(12, 1.2); - CIO::println("age:", get s::getAge()); - CIO::println("solve:", get s::getSolve()); - s::bump(); - CIO::println("after bump:", get s::getAge()); - s::age = 20; - CIO::println("direct:", s::age); - } -} -"#; - let out = build_and_run(src); - assert!(out.contains("age: 12"), "got: {out}"); - assert!(out.contains("solve: 1.200000"), "got: {out}"); - assert!(out.contains("after bump: 13"), "got: {out}"); - assert!(out.contains("direct: 20"), "got: {out}"); -} - -#[test] -fn llvm_class_method_call_with_args() { - let src = r#" -program main; -Class Calc { - int add(a int, b int) { res a + b; } - int mul(a int, b int) { res a * b; } -} -Main { - void exec() { - Calc c = new Calc(); - CIO::println("sum:", get c::add(2, 3)); - CIO::println("prod:", get c::mul(4, 5)); - } -} -"#; - let out = build_and_run(src); - assert!(out.contains("sum: 5"), "got: {out}"); - assert!(out.contains("prod: 20"), "got: {out}"); -} diff --git a/rust/crates/bbb-syntax/Cargo.toml b/rust/crates/bbb-syntax/Cargo.toml deleted file mode 100644 index f70e648..0000000 --- a/rust/crates/bbb-syntax/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "bbb-syntax" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BiuBiuBiu 语法层:手写 AST + 词法器 + 解析器" - -[dependencies] diff --git a/rust/crates/bbb-syntax/src/ast.rs b/rust/crates/bbb-syntax/src/ast.rs deleted file mode 100644 index cdda54c..0000000 --- a/rust/crates/bbb-syntax/src/ast.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! BiuBiuBiu AST(完全原生手写版)。 -//! -//! 完全原生手写。设计贴合旧 C 实现(src/parser.c)的节点形态,并做了枚举化 -//! 与所有权整理: -//! - 表达式/语句/声明全枚举,无 NULL 指针(Option 表达可选); -//! - 字符串用 `String`(解析期零拷贝优化留给后续:arena 字符串池); -//! - 语法面覆盖 examples/01-17:流签名/分叉/类/need/注解/智能引用/ -//! 数组字面量/多返回值/二进制库流。 - -/// 程序 = 声明序列(Main 流单独存放,语义上总在最后执行)。 -#[derive(Debug, Clone, PartialEq)] -pub struct Program { - pub kind: String, // "main" | "utils" - pub decls: Vec, - pub main: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Decl { - /// `const int x = 10;`(顶层 → Constantstream) - Const { name: String, ty: String, init: Expr }, - /// `need value/function/stream/Class X;` - Need { kind: String, name: String }, - /// `Stream Name { members }` — 签名流 - StreamSig { name: String, members: Vec, annos: Vec }, - /// `Stream Name & "lib.so" { members }` — 二进制库流 - StreamBin { name: String, file: String, members: Vec, annos: Vec }, - /// `Class Name implements A, B { members }` — 类(流的分叉),可实现接口 - Class { name: String, members: Vec, annos: Vec, implements: Vec }, - /// `Interface Name { 方法签名 }` — 接口(流的一种:只有签名的方法集合) - Interface { name: String, members: Vec, annos: Vec }, - /// `Sig Name { members }` — 分叉实现 - Fork { sig: String, name: String, members: Vec, annos: Vec }, -} - -/// Main 流:`Main { void exec() {...} ... }`,仅方法。 -#[derive(Debug, Clone, PartialEq)] -pub struct MainDecl { - pub methods: Vec, -} - -/// 流/类成员:字段与方法可任意交错;字段支持逗号分隔 `int x, y;`。 -#[derive(Debug, Clone, PartialEq)] -pub enum Member { - Field { ty: String, names: Vec }, - Method(Method), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Method { - pub ret: String, // void / int / int[] / Hero / T[]... - pub name: String, - pub params: Vec, - pub body: Vec, // 空 = 签名(分号结尾) - pub annos: Vec, // @read/@write/@call/@ucall -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Param { - pub name: String, - pub ty: String, // 基类型(含流名/类名) - pub is_arr: bool, - /// 智能引用参数:`& name type` - pub ref_perm: Option, - pub ref_follow: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Stmt { - If { cond: Expr, then: Vec, els: Option> }, - While { cond: Expr, body: Vec }, - For { init: Option>, cond: Option, update: Option>, body: Vec }, - Break, - Continue, - /// `res expr;` / `res a, b, c;`(多值 → 数组)/ `ref "reason";` - Ret { kind: RetKind, values: Vec }, - /// 变量声明与赋值:`int x = e;` / `ALL x = e;` / `x = e;` / `x += e;` / - /// `const int x = e;` / `thread int x = e;` / `this::attr = e;` / `a[i] = e;` - Assign { - vtype: Option, // Some = 声明(含 "ALL"/"const"/"thread" 变体) - is_const: bool, - is_thread: bool, - target: AssignTarget, - op: String, - value: Expr, - }, - /// `&perm follow base name = &lvalue;` — 智能引用声明 - RefDecl { perm: String, follow: String, base: String, name: String, init: Expr }, - /// `i++;` / `i--;` - Inc { name: String, op: String }, - /// 表达式语句:裸调用 `add(1,2);`、`a[i];` 等 - Expr(Expr), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RetKind { - Res, // res = respond - Ref, // ref = refuse -} - -#[derive(Debug, Clone, PartialEq)] -pub enum AssignTarget { - Var(String), - Index { base: Box, idx: Box }, - Prop { base: Box, name: String }, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Expr { - Int(i64), - Float(f64), - Str(String), - Char(u8), - Bool(bool), - /// 变量引用(含 `this`) - Var(String), - /// `qual::name(args)`(qual=Some)或裸调用 `name(args)`(qual=None) - Call { qual: Option, name: String, args: Vec }, - /// `obj.field` 属性访问(对象属性用 Objstream) - Prop { base: Box, name: String }, - /// `a[i]` 索引 - Index { base: Box, idx: Box }, - BinOp { op: String, l: Box, r: Box }, - /// 前缀解包:`get X` / `cause X` - Unwrap { op: String, l: Box }, - /// `new Class(args...)` → 分叉类流 + 自动 __init__ - New { cls: String, args: Vec }, - /// `new Type[expr]` → 数组字面量 - NewArray { ty: String, size: Box }, - /// `&lvalue` — 取址创建引用值(权限来自声明) - RefOf(Box), -} diff --git a/rust/crates/bbb-syntax/src/lexer.rs b/rust/crates/bbb-syntax/src/lexer.rs deleted file mode 100644 index 28c1b9f..0000000 --- a/rust/crates/bbb-syntax/src/lexer.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! BiuBiuBiu 词法器(手写,Rust 版)。 -//! -//! 规则对标旧 C 实现(src/lexer.c)+ examples/ 实际语法: -//! - 关键字:program / Main / Stream / Class / const / thread / need / -//! res / ref / get / cause / ALL / if / else / while / for / break / -//! continue / new / this / 基础类型 / true / false -//! - 注释:`//` 与 `/* */`;字符串 `"..."`(`\"` 转义);字符 `'x'` -//! - 数字:int / float(`3.14`、`.5`、`1e3`) -//! - 运算符:`::` `==` `!=` `<=` `>=` `&&` `||` `++` `--` `->` + 单字符集 -//! -//! 设计目标(内存规划):Token 零堆分配——`kind: u8` + `len: u32` 引用 -//! 源切片,字符串内容不拷贝;行/列只在出错时按需计算(错误路径才扫描)。 - -/// 词法错误:源位置 + 信息。 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LexError { - pub line: u32, - pub col: u32, - pub msg: &'static str, -} - -impl std::fmt::Display for LexError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}: {}", self.line, self.col, self.msg) - } -} - -/// 源位置(行:列,1 起)。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Span { - pub line: u32, - pub col: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum TokenKind { - Ident, - Keyword, - Int, - Float, - Str, - Char, - Op, - Eof, -} - -/// Token:零拷贝——`text` 是源字符串的切片。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Token<'a> { - pub kind: TokenKind, - pub text: &'a str, - pub span: Span, -} - -const KEYWORDS: &[&str] = &[ - "program", "Main", "Stream", "Class", "Interface", "implements", "const", "thread", "need", - "res", "ref", "get", "cause", "ALL", "if", "else", "while", "for", - "break", "continue", "new", "this", - "void", "int", "float", "double", "string", "char", "bool", - "true", "false", -]; - -const OPS2: &[&str] = &["==", "!=", "<=", ">=", "&&", "||", "::", "++", "--", "->"]; - -fn is_kw(s: &str) -> bool { - KEYWORDS.contains(&s) // 线性查找:29 个关键字,正确性优先 -} - -fn is_ident_start(c: u8) -> bool { - c.is_ascii_alphabetic() || c == b'_' -} - -fn is_ident_char(c: u8) -> bool { - c.is_ascii_alphanumeric() || c == b'_' -} - -fn is_op1(c: u8) -> bool { - matches!(c, b'+' | b'-' | b'*' | b'/' | b'%' | b'<' | b'>' | b'=' | b'!' - | b'&' | b'|' | b'.' | b':' | b',' | b';' | b'(' | b')' - | b'{' | b'}' | b'[' | b']' | b'@') -} - -fn is_digit(c: u8) -> bool { - c.is_ascii_digit() -} - -struct Scan<'a> { - src: &'a [u8], - pos: usize, - line: u32, - col: u32, -} - -impl<'a> Scan<'a> { - fn peek(&self, k: usize) -> Option { - self.src.get(self.pos + k).copied() - } - - fn advance(&mut self) -> Option { - let c = self.src.get(self.pos).copied(); - if let Some(b) = c { - self.pos += 1; - if b == b'\n' { - self.line += 1; - self.col = 1; - } else { - self.col += 1; - } - } - c - } - - fn span(&self) -> Span { - Span { line: self.line, col: self.col } - } -} - -/// 把源码切成 Token 流。`tokens` 预先分配(容量即上限),零堆分配。 -pub fn tokenize<'a>(src: &'a str, tokens: &mut Vec>) -> Result<(), LexError> { - let mut s = Scan { src: src.as_bytes(), pos: 0, line: 1, col: 1 }; - tokens.clear(); - loop { - // 空白 - while matches!(s.peek(0), Some(b' ') | Some(b'\t') | Some(b'\r') | Some(b'\n')) { - s.advance(); - } - // 注释 - if s.peek(0) == Some(b'/') && s.peek(1) == Some(b'/') { - while let Some(c) = s.advance() { - if c == b'\n' { - break; - } - } - continue; - } - if s.peek(0) == Some(b'/') && s.peek(1) == Some(b'*') { - let start = s.span(); - s.advance(); - s.advance(); - loop { - if s.peek(0).is_none() { - return Err(LexError { line: start.line, col: start.col, msg: "unterminated block comment /*" }); - } - if s.peek(0) == Some(b'*') && s.peek(1) == Some(b'/') { - s.advance(); - s.advance(); - break; - } - s.advance(); - } - continue; - } - let start = s.span(); - let c = match s.peek(0) { - None => { - tokens.push(Token { kind: TokenKind::Eof, text: "", span: start }); - return Ok(()); - } - Some(c) => c, - }; - - // 字符串 - if c == b'"' { - s.advance(); - let begin = s.pos; - loop { - match s.peek(0) { - None => return Err(LexError { line: start.line, col: start.col, msg: "unterminated string literal" }), - Some(b'"') => { - let end = s.pos; - s.advance(); - tokens.push(Token { kind: TokenKind::Str, text: &src[begin..end], span: start }); - break; - } - Some(b'\\') => { - s.advance(); - s.advance(); - } - Some(_) => { - s.advance(); - } - } - } - continue; - } - - // 字符 - if c == b'\'' { - s.advance(); - let begin = s.pos; - match s.peek(0) { - None => return Err(LexError { line: start.line, col: start.col, msg: "unterminated character literal" }), - Some(b'\\') => { - s.advance(); - s.advance(); - } - Some(_) => { - s.advance(); - } - } - if s.peek(0) != Some(b'\'') { - return Err(LexError { line: start.line, col: start.col, msg: "character literal must be exactly one character" }); - } - s.advance(); - tokens.push(Token { kind: TokenKind::Char, text: &src[begin..s.pos - 1], span: start }); - continue; - } - - // 数字 - if is_digit(c) || (c == b'.' && s.peek(1).map(is_digit).unwrap_or(false)) { - let begin = s.pos; - let mut is_float = false; - while let Some(d) = s.peek(0) { - if is_digit(d) { - s.advance(); - } else if d == b'.' && !is_float { - is_float = true; - s.advance(); - } else if (d == b'e' || d == b'E') && !is_float { - is_float = true; - s.advance(); - if matches!(s.peek(0), Some(b'+') | Some(b'-')) { - s.advance(); - } - } else { - break; - } - } - let kind = if is_float { TokenKind::Float } else { TokenKind::Int }; - tokens.push(Token { kind, text: &src[begin..s.pos], span: start }); - continue; - } - - // 标识符 / 关键字 - if is_ident_start(c) { - let begin = s.pos; - while s.peek(0).map(is_ident_char).unwrap_or(false) { - s.advance(); - } - let word = &src[begin..s.pos]; - let kind = if is_kw(word) { TokenKind::Keyword } else { TokenKind::Ident }; - tokens.push(Token { kind, text: word, span: start }); - continue; - } - - // 运算符 - let mut matched = false; - for op in OPS2 { - if s.src[s.pos..].starts_with(op.as_bytes()) { - s.advance(); - s.advance(); - tokens.push(Token { kind: TokenKind::Op, text: op, span: start }); - matched = true; - break; - } - } - if matched { - continue; - } - if is_op1(c) { - s.advance(); - tokens.push(Token { kind: TokenKind::Op, text: &src[s.pos - 1..s.pos], span: start }); - continue; - } - - return Err(LexError { - line: start.line, - col: start.col, - msg: "unrecognized character", - }); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn kinds(src: &str) -> Vec<(TokenKind, &str)> { - let mut toks = Vec::new(); - tokenize(src, &mut toks).unwrap(); - toks.iter().map(|t| (t.kind, t.text)).collect() - } - - #[test] - fn hello() { - let toks = kinds(r#"program main; Main { void exec() { CIO::println("hi"); } }"#); - assert_eq!(toks[0], (TokenKind::Keyword, "program")); - assert!(toks.contains(&(TokenKind::Keyword, "Main"))); - assert!(toks.contains(&(TokenKind::Keyword, "void"))); - assert!(toks.contains(&(TokenKind::Str, "hi"))); - assert_eq!(toks.last().unwrap().0, TokenKind::Eof); - } - - #[test] - fn numbers() { - let toks = kinds("1 3.14 .5 1e3"); - assert_eq!(toks[0], (TokenKind::Int, "1")); - assert_eq!(toks[1], (TokenKind::Float, "3.14")); - assert_eq!(toks[2], (TokenKind::Float, ".5")); - assert_eq!(toks[3], (TokenKind::Float, "1e3")); - } - - #[test] - fn ops() { - let toks = kinds("a::b == c && d <= e;"); - let ops: Vec<&str> = toks.iter().filter(|t| t.0 == TokenKind::Op).map(|t| t.1).collect(); - assert_eq!(ops, vec!["::", "==", "&&", "<=", ";"]); - } - - #[test] - fn comment_and_string() { - let src = r#"// line -CIO::println("a\"b"); /* block -comment */ x"#; - let toks = kinds(src); - assert!(toks.contains(&(TokenKind::Str, "a\\\"b"))); - assert!(toks.contains(&(TokenKind::Ident, "x"))); - } - - #[test] - fn unclosed_string_err() { - let mut toks = Vec::new(); - let err = tokenize("CIO::println(\"oops);", &mut toks).unwrap_err(); - assert_eq!(err.msg, "unterminated string literal"); - } - - #[test] - fn line_col() { - let mut toks = Vec::new(); - let err = tokenize("a = 1;\nb = \"x;\n", &mut toks).unwrap_err(); - assert_eq!(err.line, 2); - assert_eq!(err.col, 5); // `b = "` — 引号在第 5 列 - } -} diff --git a/rust/crates/bbb-syntax/src/lib.rs b/rust/crates/bbb-syntax/src/lib.rs deleted file mode 100644 index ad480b3..0000000 --- a/rust/crates/bbb-syntax/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! bbb-syntax — BiuBiuBiu 语法层(完全原生手写)。 -//! -//! - `ast`:手写 AST(唯一事实源) -//! - `parser`:手写解析器(语法面覆盖 examples/01-17) -//! - `lexer`:手写词法器(零拷贝 token) - -pub mod ast; -pub mod lexer; -pub mod parser; - -pub use ast::*; -pub use lexer::{LexError, Span, Token, TokenKind, tokenize}; -pub use parser::{ParseError, Parser, parse_source}; diff --git a/rust/crates/bbb-syntax/src/parser.rs b/rust/crates/bbb-syntax/src/parser.rs deleted file mode 100644 index ee07515..0000000 --- a/rust/crates/bbb-syntax/src/parser.rs +++ /dev/null @@ -1,1244 +0,0 @@ -//! BioLang 解析器(完全原生手写)。 -//! -//! 语法面以旧 C 实现(src/parser.c)与 examples/01-17 为准: -//! - 表达式:优先级 字面量/引用/调用/索引/属性链 → 一元(get/cause/&/new) -//! → 算术(+ - * / %) → 比较(== != < > <= >=) → 逻辑(&& ||) -//! - 语句:if/while/for/break/continue/res/ref/ALL/const/thread/ -//! &引用声明/类型声明/赋值/自增自减/调用/表达式语句 -//! - 声明:program/const/need/Stream(签名|二进制库)/Class/Main/分叉 -//! - 成员:方法(体或签名)+ 字段(逗号分隔)+ type T; 泛型 + 泛型风格 -//! - 参数:`name type`(名字在前),可带 `&perm follow` 引用修饰 -//! -//! 错误处理:收集全部错误(ParseError 带行列),恢复策略为推进 token, -//! 保证不死循环;返回的 Program 在 errors 非空时不可信(调用方检查)。 - -use crate::ast::*; -use crate::lexer::{Token, TokenKind}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseError { - pub line: u32, - pub col: u32, - pub msg: String, -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}: {}", self.line, self.col, self.msg) - } -} - -const TYPE_NAMES: &[&str] = &["int", "float", "double", "string", "char", "bool"]; -const PERMS: &[&str] = &["r", "w", "m", "rw", "rm", "wm", "rwm"]; -const FOLLOWS: &[&str] = &["u", "f", "a", "t"]; -const ASSIGN_OPS: &[&str] = &["=", "+=", "-=", "*=", "/=", "%="]; -const METHOD_ANNOS: &[&str] = &["read", "write", "call", "ucall"]; -const DECL_ANNOS: &[&str] = &["onlyread", "unfork"]; - -fn is_type_name(s: &str) -> bool { - TYPE_NAMES.contains(&s) -} - -pub struct Parser<'a> { - toks: &'a [Token<'a>], - pos: usize, - pub errors: Vec, -} - -impl<'a> Parser<'a> { - pub fn new(toks: &'a [Token<'a>]) -> Self { - Parser { toks, pos: 0, errors: Vec::new() } - } - - // ---- 游标 ---- - - fn peek(&self, k: usize) -> &'a Token<'a> { - let i = (self.pos + k).min(self.toks.len().saturating_sub(1)); - &self.toks[i] - } - - fn next(&mut self) -> &'a Token<'a> { - let t = self.peek(0); - if self.pos < self.toks.len().saturating_sub(1) { - self.pos += 1; - } - t - } - - fn at_eof(&self) -> bool { - self.peek(0).kind == TokenKind::Eof - } - - fn is_op(&self, k: usize, op: &str) -> bool { - let t = self.peek(k); - t.kind == TokenKind::Op && t.text == op - } - - fn at_kw(&self, kw: &str) -> bool { - let t = self.peek(0); - t.kind == TokenKind::Keyword && t.text == kw - } - - fn eat_op(&mut self, op: &str) -> bool { - if self.is_op(0, op) { - self.next(); - true - } else { - false - } - } - - fn error(&mut self, msg: impl Into) { - let t = self.peek(0); - self.errors.push(ParseError { line: t.span.line, col: t.span.col, msg: msg.into() }); - } - - fn expect_op(&mut self, op: &str) -> bool { - if self.eat_op(op) { - true - } else { - self.error(format!("expected '{0}', got '{1}'", op, self.peek(0).text)); - false - } - } - - fn expect_id(&mut self) -> String { - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - self.next(); - t.text.to_string() - } else { - self.error(format!("expected identifier, got '{0}'", t.text)); - String::new() - } - } - - /// 方法名:关键字 `new` 允许(如 Array::new / Obj::new)。 - fn expect_method_name(&mut self) -> String { - if self.at_kw("new") { - self.next(); - return "new".to_string(); - } - self.expect_id() - } - - fn err_if(&mut self, cond: bool, msg: impl Into) { - if cond { - self.error(msg); - } - } - - // ---- 顶层 ---- - - pub fn parse_program(&mut self) -> Program { - let mut kind = String::new(); - let mut decls = Vec::new(); - let mut main = None; - - while !self.at_eof() && self.errors.len() < 50 { - if self.at_kw("program") { - self.next(); - let k = self.expect_id(); - self.expect_op(";"); - kind = k; - continue; - } - if self.at_kw("const") { - self.next(); - let ty = self.expect_id(); // 类型(int/string/...) - let name = self.expect_id(); - let init = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - decls.push(Decl::Const { name, ty, init }); - continue; - } - if self.at_kw("need") { - self.next(); - let k = self.next().text.to_string(); - let name = self.expect_id(); - if self.is_op(0, "{") { - self.skip_block(); - self.eat_op(";"); - } else { - self.expect_op(";"); - } - self.err_if(!matches!(k.as_str(), "value" | "function" | "stream" | "Stream" | "Class"), - format!("need only supports value/function/stream/Class, got '{k}'")); - decls.push(Decl::Need { kind: k, name }); - continue; - } - if self.at_kw("Stream") { - self.next(); - let name = self.expect_id(); - if self.eat_op("&") { - let t = self.peek(0); - let file = match t.kind { - TokenKind::Str | TokenKind::Ident => self.next().text.to_string(), - _ => { self.error("expected binary library file name (string or identifier)"); String::new() } - }; - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::StreamBin { name, file, members, annos }); - } else { - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::StreamSig { name, members, annos }); - } - continue; - } - if self.at_kw("Class") { - self.next(); - let name = self.expect_id(); - let mut implements = Vec::new(); - if self.at_kw("implements") { - self.next(); - implements.push(self.expect_id()); - while self.eat_op(",") { - implements.push(self.expect_id()); - } - } - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Class { name, members, annos, implements }); - continue; - } - if self.at_kw("Interface") { - self.next(); - let name = self.expect_id(); - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Interface { name, members, annos }); - continue; - } - if self.at_kw("Main") { - self.next(); - self.expect_op("{"); - let methods = self.parse_methods_until("}"); - self.expect_op("}"); - main = Some(MainDecl { methods }); - continue; - } - if self.peek(0).kind == TokenKind::Ident { - let sig = self.next().text.to_string(); - let name = self.expect_id(); - let members = self.parse_members(); - let annos = self.parse_decl_annos(); - decls.push(Decl::Fork { sig, name, members, annos }); - continue; - } - self.error(format!("cannot parse top-level declaration '{0}'", self.peek(0).text)); - self.next(); // 推进防死循环 - } - Program { kind, decls, main } - } - - /// 跳过 { ... } 块(need 的细节假设块)。 - fn skip_block(&mut self) { - if !self.eat_op("{") { - return; - } - let mut depth = 1; - while depth > 0 && !self.at_eof() { - if self.eat_op("{") { - depth += 1; - } else if self.eat_op("}") { - depth -= 1; - } else { - self.next(); - } - } - } - - // ---- 成员(流/类公共) ---- - - /// 成员解析:方法与字段任意交错,直到 }。 - /// - `void m(params) {}` / `void m(params);`(签名) - /// - `int m() {...}` / `int x, y;` / `int[] a;` - /// - `type T;` 泛型占位 - /// - `T n;` / `T[] a;` / `T m() {...}` 泛型风格 - fn parse_members(&mut self) -> Vec { - let mut out = Vec::new(); - self.expect_op("{"); - while !self.is_op(0, "}") && !self.at_eof() { - let t = self.peek(0); - let is_prim_kw = t.kind == TokenKind::Keyword && is_type_name(t.text); - if t.kind == TokenKind::Keyword && t.text == "void" { - self.next(); - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: "void".into(), name, params, body, annos })); - continue; - } - // 泛型占位:`type T;`(type 是关键字语义,词法器按 ident 处理) - if t.kind == TokenKind::Ident && t.text == "type" { - self.next(); - self.expect_id(); - self.expect_op(";"); - continue; - } - if t.kind == TokenKind::Ident || is_prim_kw { - let ty0 = self.next().text.to_string(); - let mut ty = ty0.clone(); - let is_arr = self.eat_arr_suffix(&mut ty); - if is_arr { - // T[] a;(字段)或 T[] m() {...}(数组返回方法) - if (self.peek(0).kind == TokenKind::Ident || self.peek(0).kind == TokenKind::Keyword) - && self.is_op(1, "(") { - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: ty, name, params, body, annos })); - } else { - let names = self.parse_field_names(); - out.push(Member::Field { ty, names }); - } - continue; - } - // T m() {...} — 泛型/基类型返回方法(lookahead: 名字 + (;get/cause 等关键字可作方法名) - if (self.peek(0).kind == TokenKind::Ident || self.peek(0).kind == TokenKind::Keyword) - && self.is_op(1, "(") { - let name = self.expect_id(); - let params = self.parse_params(); - let (body, _is_sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Member::Method(Method { ret: ty, name, params, body, annos })); - continue; - } - // 字段:T x, y; 或(基类型)int x; - let names = self.parse_field_names(); - out.push(Member::Field { ty, names }); - continue; - } - self.error(format!("cannot parse stream/class member '{0}'", t.text)); - self.next(); - } - self.expect_op("}"); - out - } - - /// Main 流专用:只有方法。 - fn parse_methods_until(&mut self, end: &str) -> Vec { - let mut out = Vec::new(); - while !self.is_op(0, end) && !self.at_eof() { - let t = self.peek(0); - let (ret, name) = if t.kind == TokenKind::Keyword && t.text == "void" { - self.next(); - ("void".to_string(), self.expect_id()) - } else if (t.kind == TokenKind::Ident || t.kind == TokenKind::Keyword) && is_type_name(t.text) { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - (ty, self.expect_id()) - } else { - self.error(format!("expected method (return type + name), got '{0}'", t.text)); - self.next(); - continue; - }; - let params = self.parse_params(); - let (body, _sig) = self.parse_method_tail(); - let annos = self.parse_method_annos(); - out.push(Method { ret, name, params, body, annos }); - } - out - } - - fn parse_method_tail(&mut self) -> (Vec, bool) { - if self.eat_op("{") { - let stmts = self.parse_stmts_until("}"); - (stmts, false) - } else { - self.expect_op(";"); - (Vec::new(), true) - } - } - - fn eat_arr_suffix(&mut self, ty: &mut String) -> bool { - if self.is_op(0, "[") { - self.next(); - self.expect_op("]"); - ty.push_str("[]"); - true - } else { - false - } - } - - /// 字段名列表:`x, y;`(逗号分隔后跟分号)。 - fn parse_field_names(&mut self) -> Vec { - let mut names = vec![self.expect_id()]; - while self.eat_op(",") { - names.push(self.expect_id()); - } - self.expect_op(";"); - names - } - - /// 参数:`a int` / `a int[]` / `&perm follow a IO` - fn parse_params(&mut self) -> Vec { - let mut out = Vec::new(); - self.expect_op("("); - while !self.is_op(0, ")") && !self.at_eof() { - // 引用参数两种写法都接受(引用是一种类型,参数 = 名字在前 类型在后): - // 标准:name &perm follow type e.g. `cio &r u CIO` - // 兼容:&perm follow name type e.g. `&r f io IO`(旧 C 写法) - let mut ref_perm: Option = None; - let mut ref_follow: Option = None; - let mut name = String::new(); - let mut ty = String::new(); - let mut is_arr = false; - - // 兼容旧写法:&perm follow name type(权限在最前) - if self.is_op(0, "&") { - self.next(); // & - ref_perm = Some(self.expect_id()); - ref_follow = Some(self.expect_id()); - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - name = self.next().text.to_string(); - } else { - self.error(format!("cannot parse parameter '{0}'", t.text)); - self.next(); - if !self.eat_op(",") { - break; - } - continue; - } - // 类型:基类型关键字(int/string/...)或任意标识符(流名/类名/泛型 T) - if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - } - // 无类型参数(旧 C 记为 void,解释器不区分):`push(v)` 合法 - } else { - // 标准写法:name [&perm follow] type(名字在前,引用/类型在后) - let t = self.peek(0); - if t.kind == TokenKind::Ident || (t.kind == TokenKind::Keyword && t.text != "program") { - name = self.next().text.to_string(); - } else { - self.error(format!("cannot parse parameter '{0}'", t.text)); - self.next(); - if !self.eat_op(",") { - break; - } - continue; - } - if self.eat_op("[") { - self.expect_op("]"); - is_arr = true; - } - // 引用类型:name &perm follow type - if self.eat_op("&") { - ref_perm = Some(self.expect_id()); - ref_follow = Some(self.expect_id()); - if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - } - } else if self.peek(0).kind == TokenKind::Ident - || (self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text)) { - ty = self.next().text.to_string(); - if self.eat_op("[") { - self.expect_op("]"); - is_arr = true; - } - } - } - out.push(Param { name, ty, is_arr, ref_perm, ref_follow }); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - out - } - - fn parse_method_annos(&mut self) -> Vec { - let mut out = Vec::new(); - while self.is_op(0, "@") { - self.next(); - let a = self.expect_id(); - self.err_if(!METHOD_ANNOS.contains(&a.as_str()), - format!("unknown method annotation @{a} (only @read/@write/@call/@ucall)")); - out.push(a); - } - out - } - - fn parse_decl_annos(&mut self) -> Vec { - let mut out = Vec::new(); - while self.is_op(0, "@") { - self.next(); - let a = self.expect_id(); - self.err_if(!DECL_ANNOS.contains(&a.as_str()), - format!("unknown stream annotation @{a} (only @onlyread/@unfork)")); - out.push(a); - } - out - } - - // ---- 语句 ---- - - fn parse_stmts_until(&mut self, end: &str) -> Vec { - let mut out = Vec::new(); - while !self.is_op(0, end) && !self.at_eof() { - let before = self.errors.len(); - let s = self.parse_stmt(); - out.push(s); - if self.errors.len() > before { - // 出错时推进到分号或块结束,避免级联 - while !self.is_op(0, ";") && !self.is_op(0, end) && !self.at_eof() { - self.next(); - } - self.eat_op(";"); - } - } - if !self.at_eof() { - self.next(); // 消费 end - } - out - } - - fn parse_stmt(&mut self) -> Stmt { - if self.at_kw("if") { - return self.parse_if(); - } - if self.at_kw("while") { - return self.parse_while(); - } - if self.at_kw("for") { - return self.parse_for(); - } - if self.at_kw("break") { - self.next(); - self.expect_op(";"); - return Stmt::Break; - } - if self.at_kw("continue") { - self.next(); - self.expect_op(";"); - return Stmt::Continue; - } - if self.at_kw("res") || self.at_kw("ref") { - let kind = if self.at_kw("res") { RetKind::Res } else { RetKind::Ref }; - self.next(); - let mut values = Vec::new(); - if !self.is_op(0, ";") { - values.push(self.parse_expr()); - while self.eat_op(",") { - values.push(self.parse_expr()); - } - } - self.expect_op(";"); - return Stmt::Ret { kind, values }; - } - if self.at_kw("const") || self.at_kw("thread") { - let is_const = self.at_kw("const"); - let modif = self.next().text.to_string(); - let _ = &modif; - // const/thread 后可跟 ALL 或类型(const int x = 10; / thread int x = 10;) - let t = self.peek(0); - if t.kind == TokenKind::Keyword && t.text == "ALL" { - self.next(); - let name = self.expect_id(); - let value = self.parse_assign_rhs("="); - return Stmt::Assign { - vtype: Some("ALL".into()), is_const, is_thread: !is_const, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - let mut ty = self.expect_id(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const, is_thread: !is_const, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - if self.at_kw("ALL") { - self.next(); - let name = self.expect_id(); - let value = self.parse_assign_rhs("="); - self.expect_op(";"); - return Stmt::Assign { - vtype: Some("ALL".into()), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // 基类型声明:`int x = e;` / `int[] a = e;` / `string s;`(类型是关键字) - if self.peek(0).kind == TokenKind::Keyword && is_type_name(self.peek(0).text) { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - if self.is_op(0, "&") { - return self.parse_ref_decl(); - } - // this:: 开头的语句(this 是关键字):属性赋值/方法调用 - if self.peek(0).kind == TokenKind::Ident || self.at_kw("this") { - return self.parse_ident_stmt(); - } - self.error(format!("cannot parse statement '{0}'", self.peek(0).text)); - Stmt::Expr(Expr::Int(0)) - } - - fn parse_assign_rhs(&mut self, op: &str) -> Expr { - if !self.eat_op(op) { - self.error(format!("expected '{0}'", op)); - } - self.parse_expr() - } - - fn parse_if(&mut self) -> Stmt { - self.next(); // if - self.expect_op("("); - let cond = self.parse_expr(); - self.expect_op(")"); - self.expect_op("{"); - let then = self.parse_stmts_until("}"); - let els = if self.at_kw("else") { - self.next(); - if self.at_kw("if") { - Some(vec![self.parse_if()]) - } else { - self.expect_op("{"); - Some(self.parse_stmts_until("}")) - } - } else { - None - }; - Stmt::If { cond, then, els } - } - - fn parse_while(&mut self) -> Stmt { - self.next(); // while - self.expect_op("("); - let cond = self.parse_expr(); - self.expect_op(")"); - self.expect_op("{"); - let body = self.parse_stmts_until("}"); - Stmt::While { cond, body } - } - - fn parse_for(&mut self) -> Stmt { - self.next(); // for - self.expect_op("("); - let init = if self.is_op(0, ";") { - self.next(); // 空 init,消费分号 - None - } else { - Some(Box::new(self.parse_stmt())) // 完整语句(含分号) - }; - let cond = if self.is_op(0, ";") { - self.next(); // 空 cond,消费分号 - None - } else { - let c = self.parse_expr(); - self.expect_op(";"); - Some(c) - }; - // update 是完整语句(examples 写法 `k = k + 1;`);`i++` 无分号由 Inc 分支兼容 - let update = if self.is_op(0, ")") { None } else { Some(Box::new(self.parse_stmt())) }; - self.expect_op(")"); - self.expect_op("{"); - let body = self.parse_stmts_until("}"); - Stmt::For { init, cond, update, body } - } - - /// `&perm follow base name = &lvalue;` — 智能引用声明。 - fn parse_ref_decl(&mut self) -> Stmt { - self.next(); // & - let perm = self.expect_id(); - self.err_if(!PERMS.contains(&perm.as_str()), - format!("invalid reference permission '{perm}' (r/w/m stacks: r, w, m, rw, rm, wm, rwm)")); - let follow = self.expect_id(); - self.err_if(!FOLLOWS.contains(&follow.as_str()), - format!("invalid reference follow layer '{follow}' (expected u/f/a/t)")); - let mut base = self.expect_id(); - self.eat_arr_suffix(&mut base); - let name = self.expect_id(); - self.expect_op("="); - let init = self.parse_expr(); - self.err_if(!matches!(init, Expr::RefOf(_)), - "reference declaration requires & initializer (e.g. &rw u int p = &a[0];)"); - self.expect_op(";"); - Stmt::RefDecl { perm, follow, base, name, init } - } - - /// 标识符开头的语句:声明/赋值/自增/索引/属性/调用。 - fn parse_ident_stmt(&mut self) -> Stmt { - let t = self.peek(0); - - // int[] a = expr; / int[] a;(类型名 + []) - if is_type_name(t.text) && self.is_op(1, "[") && self.is_op(2, "]") { - self.next(); self.next(); self.next(); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some("int[]".into()), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // int x = e; / int x;(类型名 + 标识符) - if is_type_name(t.text) && self.peek(1).kind == TokenKind::Ident { - let mut ty = self.next().text.to_string(); - self.eat_arr_suffix(&mut ty); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // 类名/流名类型声明:Box b = new Box(42); / Hero h;(第一个是 Ident,第二个是 Ident) - if t.kind == TokenKind::Ident && self.peek(1).kind == TokenKind::Ident { - let ty = self.next().text.to_string(); - let name = self.expect_id(); - let value = if self.eat_op("=") { self.parse_expr() } else { Expr::Int(0) }; - self.expect_op(";"); - return Stmt::Assign { - vtype: Some(ty), is_const: false, is_thread: false, - target: AssignTarget::Var(name), op: "=".into(), value, - }; - } - // i++; / i--; - if self.is_op(1, "++") || self.is_op(1, "--") { - let name = self.next().text.to_string(); - let op = self.next().text.to_string(); - if !self.is_op(0, ";") { - // for update 子句里无分号 - return Stmt::Inc { name, op }; - } - self.next(); - return Stmt::Inc { name, op }; - } - // a[i] = v; / a[i] += v; / a[i]; 索引 - if self.is_op(1, "[") { - let arr = self.next().text.to_string(); - self.next(); // [ - let idx = self.parse_expr(); - self.expect_op("]"); - let target = AssignTarget::Index { - base: Box::new(Expr::Var(arr.clone())), - idx: Box::new(idx.clone()), - }; - if self.is_op(0, "=") || ASSIGN_OPS.contains(&self.peek(0).text) { - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { vtype: None, is_const: false, is_thread: false, target, op, value }; - } - self.expect_op(";"); - return Stmt::Expr(Expr::Index { base: Box::new(Expr::Var(arr)), idx: Box::new(idx) }); - } - // qual::name(...); / qual::attr = v; / qual::attr += v; - if self.is_op(1, "::") { - let qual = self.next().text.to_string(); - self.next(); // :: - let nm = self.expect_method_name(); - if self.is_op(0, "=") || ASSIGN_OPS.contains(&self.peek(0).text) { - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { - vtype: None, is_const: false, is_thread: false, - target: AssignTarget::Prop { base: Box::new(Expr::Var(qual)), name: nm }, - op, value, - }; - } - self.expect_op("("); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.expect_op(";"); - return Stmt::Expr(Expr::Call { qual: Some(qual), name: nm, args }); - } - // x = e; / x += e; - if self.is_op(1, "=") || ASSIGN_OPS.contains(&self.peek(1).text) { - let name = self.next().text.to_string(); - let op = self.next().text.to_string(); - let value = self.parse_expr(); - self.expect_op(";"); - return Stmt::Assign { - vtype: None, is_const: false, is_thread: false, - target: AssignTarget::Var(name), op, value, - }; - } - // fname(args); 裸调用语句 - if self.is_op(1, "(") { - let name = self.next().text.to_string(); - self.next(); // ( - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.expect_op(";"); - return Stmt::Expr(Expr::Call { qual: None, name, args }); - } - // 其他表达式语句 - let e = self.parse_expr(); - if !self.is_op(0, ";") && !self.is_op(0, ")") { - self.error(format!("expected ';' after statement, got '{0}'", self.peek(0).text)); - } else { - self.eat_op(";"); - } - Stmt::Expr(e) - } - - // ---- 表达式(Pratt 风格优先级) ---- - - pub fn parse_expr(&mut self) -> Expr { - self.parse_binop(0) - } - - fn parse_binop(&mut self, min_bp: u8) -> Expr { - let mut left = self.parse_unary(); - loop { - let t = self.peek(0); - if t.kind != TokenKind::Op { - break; - } - let (bp, op) = match t.text { - "||" => (1, "||"), - "&&" => (2, "&&"), - "==" | "!=" => (3, t.text), - "<" | ">" | "<=" | ">=" => (4, t.text), - "+" | "-" => (5, t.text), - "*" | "/" | "%" => (6, t.text), - _ => break, - }; - if bp < min_bp { - break; - } - self.next(); - let right = self.parse_binop(bp + 1); - left = Expr::BinOp { op: op.to_string(), l: Box::new(left), r: Box::new(right) }; - } - left - } - - fn parse_unary(&mut self) -> Expr { - let t = self.peek(0); - if t.kind == TokenKind::Op && t.text == "&" { - self.next(); - return Expr::RefOf(Box::new(self.parse_unary())); - } - self.parse_primary() - } - - fn parse_primary(&mut self) -> Expr { - let t = self.peek(0); - match t.kind { - TokenKind::Int => { - self.next(); - let v = t.text.parse::().unwrap_or(0); - self.parse_prop_chain(Expr::Int(v)) - } - TokenKind::Float => { - self.next(); - let v = t.text.parse::().unwrap_or(0.0); - self.parse_prop_chain(Expr::Float(v)) - } - TokenKind::Str => { - self.next(); - self.parse_prop_chain(Expr::Str(t.text.to_string())) - } - TokenKind::Char => { - self.next(); - self.parse_prop_chain(Expr::Char(decode_char(t.text))) - } - TokenKind::Keyword if t.text == "true" || t.text == "false" => { - self.next(); - self.parse_prop_chain(Expr::Bool(t.text == "true")) - } - TokenKind::Keyword if t.text == "new" => { - self.next(); - // new Type[expr] → 数组字面量;new Class(args) → Obj::new - let cls = self.expect_id(); - if self.eat_op("[") { - let size = self.parse_expr(); - self.expect_op("]"); - self.parse_prop_chain(Expr::NewArray { ty: cls, size: Box::new(size) }) - } else { - self.expect_op("("); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::New { cls, args }) - } - } - TokenKind::Keyword if t.text == "get" || t.text == "cause" => { - // 前缀解包:get/cause X;`get(...)`/`cause(...)` 是裸调用(方法名 get/cause) - let op = t.text; - if self.is_op(1, "(") { - self.next(); - self.next(); // ( - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: None, name: op.to_string(), args }) - } else { - let is_prefix = !(self.is_op(1, "::") || self.is_op(1, ".") || self.is_op(1, "[")); - if is_prefix { - self.next(); - let l = self.parse_unary(); - self.parse_prop_chain(Expr::Unwrap { op: op.to_string(), l: Box::new(l) }) - } else { - // get 作普通标识符(变量名等) - self.next(); - self.parse_prop_chain(Expr::Var(op.to_string())) - } - } - } - TokenKind::Keyword if t.text == "this" => { - self.next(); - self.ident_primary("this".to_string()) - } - TokenKind::Keyword => { - self.next(); - self.parse_prop_chain(Expr::Var(t.text.to_string())) - } - TokenKind::Ident => { - let name = self.next().text.to_string(); - self.ident_primary(name) - } - TokenKind::Op if t.text == "(" => { - self.next(); - let e = self.parse_expr(); - self.expect_op(")"); - self.parse_prop_chain(e) - } - TokenKind::Op if t.text == "-" => { - // 一元负号(宽松支持;语法面未见,作扩展) - self.next(); - let e = self.parse_unary(); - Expr::BinOp { op: "-".into(), l: Box::new(Expr::Int(0)), r: Box::new(e) } - } - _ => { - self.error(format!("cannot parse expression '{0}'", t.text)); - self.next(); - Expr::Int(0) - } - } - } - - /// 标识符开头的 primary:`name` / `qual::m(...)` / `qual::prop` / - /// `name(...)` 裸调用 / `name[i]` 索引(this 关键字也走这里)。 - fn ident_primary(&mut self, name: String) -> Expr { - if self.eat_op("::") { - let mname = self.expect_method_name(); - if self.is_op(0, "(") { - self.next(); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: Some(name), name: mname, args }) - } else { - // qual::name 属性访问(this::data) - self.parse_prop_chain(Expr::Prop { - base: Box::new(Expr::Var(name)), - name: mname, - }) - } - } else if self.is_op(0, "(") { - // 裸调用 - self.next(); - let mut args = Vec::new(); - while !self.is_op(0, ")") && !self.at_eof() { - args.push(self.parse_expr()); - if !self.eat_op(",") { - break; - } - } - self.expect_op(")"); - self.parse_prop_chain(Expr::Call { qual: None, name, args }) - } else if self.is_op(0, "[") { - // 索引读取 - self.next(); - let idx = self.parse_expr(); - self.expect_op("]"); - self.parse_prop_chain(Expr::Index { base: Box::new(Expr::Var(name)), idx: Box::new(idx) }) - } else { - self.parse_prop_chain(Expr::Var(name)) - } - } - - /// 后缀属性链:`obj.field.field...`(对象属性访问)。 - fn parse_prop_chain(&mut self, mut e: Expr) -> Expr { - while self.is_op(0, ".") { - self.next(); - let t = self.peek(0); - if t.kind != TokenKind::Ident && t.kind != TokenKind::Keyword { - self.error(format!("invalid property name '{0}'", t.text)); - self.next(); - break; - } - let name = self.next().text.to_string(); - e = Expr::Prop { base: Box::new(e), name }; - } - e - } -} - -/// 字符字面量解码:`'x'` / `'\n'` / `'\\'` / `'\''` 等。 -fn decode_char(s: &str) -> u8 { - match s { - "\\n" => b'\n', - "\\t" => b'\t', - "\\r" => b'\r', - "\\0" => 0, - "\\\\" => b'\\', - "\\'" => b'\'', - "\\\"" => b'"', - _ => s.as_bytes().first().copied().unwrap_or(0), - } -} - -/// 便捷入口:源码 → Program。errors 非空时解析失败。 -pub fn parse_source(src: &str) -> (Program, Vec) { - let mut toks = Vec::new(); - match crate::lexer::tokenize(src, &mut toks) { - Ok(()) => {} - Err(e) => { - let prog = Program { kind: String::new(), decls: Vec::new(), main: None }; - return (prog, vec![ParseError { line: e.line, col: e.col, msg: e.msg.to_string() }]); - } - } - let mut p = Parser::new(&toks); - let prog = p.parse_program(); - (prog, p.errors) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn parse_ok(src: &str) -> Program { - let (p, errs) = parse_source(src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - p - } - - #[test] - fn hello() { - let p = parse_ok("program main;\nMain { void exec() { CIO::println(\"hi\"); } }"); - assert_eq!(p.kind, "main"); - let m = p.main.unwrap(); - assert_eq!(m.methods[0].name, "exec"); - assert!(matches!(m.methods[0].body[0], Stmt::Expr(Expr::Call { ref qual, .. }) if qual.as_deref() == Some("CIO"))); - } - - #[test] - fn requests_model() { - let src = r#" -program main; -Stream Calc { int add(a int, b int); } -Calc MyCalc { int add(a int, b int) { res a + b; } } -Main { - void exec() { - ALL r = MyCalc::add(3, 4); - ALL bad = MyCalc::div(1, 0); - CIO::println("x", get r, cause bad); - if (r) { CIO::println("ok"); } else { CIO::println("no"); } - } -}"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 2); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[0], Stmt::Assign { vtype: Some(ref v), .. } if v == "ALL")); - assert!(matches!(m.methods[0].body[2], Stmt::Expr(Expr::Call { ref args, .. }) if args.len() == 3)); - } - - #[test] - fn control_flow() { - let src = r#" -program main; -Main { - void exec() { - ALL i = 1; - while (i <= 10) { i = i + 1; } - for (ALL k = 1; k <= 5; k = k + 1;) { } - for (;;) { break; } - if (i > 5) { } else if (i < 2) { } else { } - } -}"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[2], Stmt::For { .. })); - assert!(matches!(m.methods[0].body[3], Stmt::For { cond: None, .. })); - } - - #[test] - fn smart_refs_and_threads() { - let src = r#" -program main; -Calc Worker { - void threadJob(n int) { - thread int local_note = 0; - &w a int wa = &local_note; - Ref::write(wa, n * 2); - res get Ref::read(ra); - } -} -Main { - void exec() { - int counter = 0; - &rwm t int p = &counter; - ALL t1 = get Threads::spawn("factorial", 10); - } -}"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 1); - } - - #[test] - fn classes_fields_and_arrays() { - let src = r#" -program main; -Class Hero { - void __init__(name string, hp int) { Obj::set(this, "name", name); } - int getHp() { res 100; } -} -Main { - void exec() { - ALL h = new Hero("TAK", 88); - int[] a = Solid::new().res; - a[0] = 1; - ALL x = a[0]; - CIO::println("hp =", h.hp); - } -}"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - let exec = &m.methods[0].body; - assert!(matches!(exec[1], Stmt::Assign { vtype: Some(ref v), .. } if v == "int[]")); - // a[0] = 1; → Assign(target: Index) - assert!(matches!(exec[2], Stmt::Assign { target: AssignTarget::Index { .. }, .. })); - // ALL x = a[0]; → 右侧是 Index - assert!(matches!(exec[3], Stmt::Assign { value: Expr::Index { .. }, .. })); - } - - #[test] - fn need_and_binary_stream() { - let src = r#" -program main; -need value GREETING; -need function greet; -need stream IO; -Stream m & "libm.so.6" { double sin(x double); } -Main { void exec() { } } -"#; - let p = parse_ok(src); - assert_eq!(p.decls.len(), 4); - assert!(matches!(p.decls[3], Decl::StreamBin { ref file, .. } if file == "libm.so.6")); - } - - #[test] - fn annotations_and_phonebooth() { - let src = r#" -program main; -Class Hero { void __init__() { } } @unfork -Main { - void exec() { - } - int fast() { res 1; } @call -}"#; - let p = parse_ok(src); - assert!(matches!(p.decls[0], Decl::Class { ref annos, .. } if annos == &["unfork"])); - let m = p.main.unwrap(); - assert_eq!(m.methods[1].annos, vec!["call"]); - } - - #[test] - fn new_array_literal() { - let src = r#" -program main; -Main { void exec() { ALL a = new int[10]; ALL b = new Hero[3]; } } -"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - assert!(matches!(m.methods[0].body[0], Stmt::Assign { value: Expr::NewArray { ref ty, .. }, .. } if ty == "int")); - assert!(matches!(m.methods[0].body[1], Stmt::Assign { value: Expr::NewArray { ref ty, .. }, .. } if ty == "Hero")); - } - - #[test] - fn multi_value_res() { - let src = r#" -program main; -Main { void exec() { } int pair() { res 1, 2, 3; } } -"#; - let p = parse_ok(src); - let m = p.main.unwrap(); - match &m.methods[1].body[0] { - Stmt::Ret { kind: RetKind::Res, values } => assert_eq!(values.len(), 3), - other => panic!("unexpected {other:?}"), - } - } - - #[test] - fn parse_error_reported() { - let (_p, errs) = parse_source("program main;\nMain { void exec() { x = ; } }"); - assert!(!errs.is_empty()); - assert!(errs[0].line >= 1); - } - - #[test] - fn generic_type_member() { - let src = r#" -program main; -Class Box { - type T; - T n; - T[] a; - T get() { res n; } -} -Main { void exec() { } } -"#; - let p = parse_ok(src); - let cls = &p.decls[0]; - if let Decl::Class { members, .. } = cls { - // type T; 只注册名字不产生成员:T n; / T[] a; / T get() = 3 个 - assert_eq!(members.len(), 3); - assert!(matches!(members[0], Member::Field { ref ty, .. } if ty == "T")); - assert!(matches!(members[1], Member::Field { ref ty, .. } if ty == "T[]")); - assert!(matches!(members[2], Member::Method(Method { ref ret, .. }) if ret == "T")); - } else { - panic!("expected class"); - } - } -} diff --git a/rust/crates/bbb-syntax/tests/examples.rs b/rust/crates/bbb-syntax/tests/examples.rs deleted file mode 100644 index d0d992d..0000000 --- a/rust/crates/bbb-syntax/tests/examples.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! 标准层回归:examples/01-17 + project 全部必须 parse 成功。 -//! -//! 这是"标准层不改变"的硬验证:任何解析器改动导致 examples 解析失败 -//! 即回归失败。examples 目录位于仓库根(相对本 crate 的 ../examples)。 - -use std::path::PathBuf; - -use bbb_syntax::parser::parse_source; - -fn examples_dir() -> PathBuf { - // CARGO_MANIFEST_DIR = rust/crates/bbb-syntax → 上溯 3 级到仓库根 - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("examples") -} - -fn collect_bio_files() -> Vec { - let mut out = Vec::new(); - let ex = examples_dir(); - let mut dirs = vec![ex.clone()]; - dirs.push(ex.join("project").join("src")); - dirs.push(ex.join("project").join("utils")); - for d in dirs { - if let Ok(entries) = std::fs::read_dir(&d) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio").unwrap_or(false) { - out.push(p); - } - } - } - } - out.sort(); - out -} - -#[test] -fn all_examples_parse() { - let files = collect_bio_files(); - assert!(!files.is_empty(), "examples 目录为空?"); - let mut failed = Vec::new(); - for f in &files { - let src = std::fs::read_to_string(f).expect("read"); - let (prog, errs) = parse_source(&src); - if !errs.is_empty() { - failed.push((f.display().to_string(), errs.clone())); - } - // 主程序必须声明了 kind - if prog.kind.is_empty() && !errs.is_empty() { - failed.push((f.display().to_string(), errs)); - } - } - assert!(failed.is_empty(), "解析失败:\n{}", - failed.iter().map(|(f, es)| format!( - "{f}: {}", es.iter().map(|e| e.to_string()).collect::>().join("; "))) - .collect::>().join("\n")); -} - -/// 每个示例必须声明 program main/utils(标准层契约)。 -#[test] -fn examples_declare_program_kind() { - let files = collect_bio_files(); - let mut bad = Vec::new(); - for f in &files { - let src = std::fs::read_to_string(f).expect("read"); - let (prog, errs) = parse_source(&src); - if errs.is_empty() && prog.kind.is_empty() { - bad.push(f.display().to_string()); - } - } - assert!(bad.is_empty(), "缺少 program 声明:{bad:?}"); -} diff --git a/rust/crates/bbb-vm/Cargo.toml b/rust/crates/bbb-vm/Cargo.toml deleted file mode 100644 index 29d7315..0000000 --- a/rust/crates/bbb-vm/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "bbb-vm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BioLang 解释器(M3):流注册表 + eval + 内置流" - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } -bbb-core = { path = "../bbb-core" } diff --git a/rust/crates/bbb-vm/src/builtin.rs b/rust/crates/bbb-vm/src/builtin.rs deleted file mode 100644 index 69cf3bf..0000000 --- a/rust/crates/bbb-vm/src/builtin.rs +++ /dev/null @@ -1,738 +0,0 @@ -//! 内置流(Rust 实现):CIO/SIO/FIO/Com/Time/Obj/Solid/Arrays。 -//! -//! 方法表 `HashMap<(qual, method), fn>`。签名统一: -//! `fn(&mut Interp, &[Value]) -> Outcome`。 -//! 实现原则:与旧 C builtin.c 语义一致(examples 期望输出为准)。 - -use std::collections::HashMap; -use std::fs; -use std::time::Instant; - -use bbb_core::value::{Cause, Outcome, Tag, Value}; - -use crate::interp::Interp; - -pub type BuiltinFn = fn(&mut Interp, &[Value]) -> Outcome; - -fn f(q: &'static str, m: &'static str, f: BuiltinFn) -> ((&'static str, &'static str), BuiltinFn) { - ((q, m), f) -} - -pub fn table() -> HashMap<(&'static str, &'static str), BuiltinFn> { - let mut t = HashMap::new(); - for (k, v) in [ - // ---- CIO 控制台 ---- - f("CIO", "println", cio_println), - f("CIO", "print", cio_print), - f("CIO", "error", cio_error), - f("IO", "println", cio_println), - f("IO", "print", cio_print), - // ---- SIO 字符串缓冲 ---- - f("SIO", "format", sio_format), - f("SIO", "upper", sio_upper), - f("SIO", "lower", sio_lower), - f("SIO", "trim", sio_trim), - f("SIO", "contains", sio_contains), - f("SIO", "substring", sio_substring), - f("SIO", "replace", sio_replace), - f("SIO", "println", sio_println), - f("SIO", "print", sio_print), - f("SIO", "getln", sio_getln), - // ---- FIO 文件 ---- - f("FIO", "writeFile", fio_write_file), - f("FIO", "appendFile", fio_append_file), - f("FIO", "readFile", fio_read_file), - f("FIO", "exists", fio_exists), - // ---- Com 计算 ---- - f("Com", "abs", com_abs), - f("Com", "min", com_min), - f("Com", "max", com_max), - f("Com", "pow", com_pow), - f("Com", "sqrt", com_sqrt), - f("Com", "floor", com_floor), - f("Com", "ceil", com_ceil), - f("Com", "round", com_round), - f("Com", "sign", com_sign), - f("Com", "sin", com_sin), - f("Com", "cos", com_cos), - f("Com", "tan", com_tan), - f("Com", "log", com_log), - f("Com", "exp", com_exp), - // ---- Time 定时器 ---- - f("Time", "start", time_start), - f("Time", "sleep", time_sleep), - f("Time", "elapsed", time_elapsed), - f("Time", "fork", time_fork), - f("Time", "reset", time_reset), - // ---- Obj 对象 ---- - f("Obj", "set", obj_set), - f("Obj", "get", obj_get), - f("Obj", "call", obj_call), - f("Obj", "new", obj_new), - // ---- Solid 连续存储 ---- - f("Solid", "new", solid_new), - f("Solid", "len", solid_len), - f("Solid", "get", solid_get), - f("Solid", "set", solid_set), - f("Solid", "push", solid_push), - f("Solid", "pop", solid_pop), - f("Solid", "join", solid_join), - f("Solid", "clear", solid_clear), - // 裸数组(SolidData)复用 Solid 的数据方法 - f("SolidData", "len", solid_len), - f("SolidData", "get", solid_get), - f("SolidData", "set", solid_set), - f("SolidData", "push", solid_push), - f("SolidData", "pop", solid_pop), - f("SolidData", "join", solid_join), - f("SolidData", "clear", solid_clear), - // ---- Arrays 集合 ---- - f("Arrays", "add", arrays_add), - f("Arrays", "count", arrays_count), - f("Arrays", "all", arrays_all), - f("Arrays", "get", arrays_get), - f("Arrays", "forget", arrays_forget), - f("Arrays", "vector", arrays_vector), - f("Arrays", "sort", arrays_sort), - // ---- Threads 协作线程(顺序 join 式) ---- - f("Threads", "spawn", threads_spawn), - f("Threads", "join", threads_join), - f("Threads", "yield", threads_yield), - f("Threads", "active", threads_active), - f("Threads", "self", threads_self), - // ---- Taskm 任务管理器 ---- - f("Taskm", "add", taskm_add), - f("Taskm", "interval", taskm_interval), - f("Taskm", "run", taskm_run), - f("Taskm", "stop", taskm_stop), - f("Taskm", "active", taskm_active), - // ---- Ref 智能引用 ---- - f("Ref", "read", ref_read), - f("Ref", "write", ref_write), - f("Ref", "move", ref_move), - f("Ref", "target", ref_target), - f("Ref", "perm", ref_perm), - ] { - t.insert(k, v); - } - t -} - -pub fn lookup(q: &str, m: &str) -> Option { - // 静态表每次重建开销可忽略(21 条);或 once_cell——stdlib only,直接静态构造 - use std::sync::OnceLock; - static T: OnceLock> = OnceLock::new(); - let t = T.get_or_init(table); - t.get(&(q, m)).copied() -} - -fn arg_str(interp: &mut Interp, a: &Value) -> String { - interp.fmt_value(a) -} - -fn res(v: Value) -> Outcome { - Outcome::Res(v) -} - -fn refn(interp: &mut Interp, msg: &str) -> Outcome { - Outcome::Ref(Cause(interp.intern(msg))) -} - -// ---- CIO ---- - -fn cio_println(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.stdout.push_str(&line); - interp.stdout.push('\n'); - res(Value::nil()) -} - -fn cio_print(interp: &mut Interp, args: &[Value]) -> Outcome { - // print 直接拼接(无分隔),println 空格分隔——旧 C 行为(03 输出依赖) - let line = args.iter().map(|a| arg_str(interp, a)).collect::(); - interp.stdout.push_str(&line); - res(Value::nil()) -} - -fn cio_error(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.stdout.push_str(&line); - interp.stdout.push('\n'); - res(Value::nil()) -} - -// ---- SIO ---- - -fn sio_format(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(fmt) = args.first() else { return refn(interp, "SIO::format requires a format string") }; - let fmt_s = arg_str(interp, fmt); - let rest = &args[1..]; - let mut out = String::new(); - let mut it = fmt_s.chars().peekable(); - let mut ai = 0; - while let Some(c) = it.next() { - if c != '%' { - out.push(c); - continue; - } - match it.next() { - Some('d') => { - if ai < rest.len() { - out.push_str(&format!("{}", rest[ai].as_int_or_num() as i64)); - } - ai += 1; - } - Some('f') => { - if ai < rest.len() { - out.push_str(&format!("{}", rest[ai].as_int_or_num())); - } - ai += 1; - } - Some('s') => { - if ai < rest.len() { - out.push_str(&arg_str(interp, &rest[ai])); - } - ai += 1; - } - Some('%') => out.push('%'), - Some(other) => { - out.push('%'); - out.push(other); - } - None => out.push('%'), - } - } - res(Value::string(interp.intern(&out))) -} - -fn sio_upper(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.to_uppercase()))) -} - -fn sio_lower(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.to_lowercase()))) -} - -fn sio_trim(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(s.trim()))) -} - -fn sio_contains(interp: &mut Interp, args: &[Value]) -> Outcome { - let hay = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let needle = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::boolean(hay.contains(&needle))) -} - -fn sio_substring(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let start = args.get(1).map(|a| a.as_int_or_num() as usize).unwrap_or(0); - let len = args.get(2).map(|a| a.as_int_or_num() as usize); - let sub: String = s.chars().skip(start).take(len.unwrap_or(s.len())).collect(); - res(Value::string(interp.intern(&sub))) -} - -fn sio_replace(interp: &mut Interp, args: &[Value]) -> Outcome { - let s = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let from = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let to = args.get(2).map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::string(interp.intern(&s.replace(&from, &to)))) -} - -fn sio_println(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::>().join(" "); - interp.sio_buf.push_str(&line); - interp.sio_buf.push('\n'); - res(Value::nil()) -} - -fn sio_print(interp: &mut Interp, args: &[Value]) -> Outcome { - let line = args.iter().map(|a| arg_str(interp, a)).collect::(); - interp.sio_buf.push_str(&line); - res(Value::nil()) -} - -fn sio_getln(interp: &mut Interp, _args: &[Value]) -> Outcome { - if let Some(pos) = interp.sio_buf.find('\n') { - let line: String = interp.sio_buf.drain(..pos + 1).collect(); - let line = line.trim_end_matches('\n').to_string(); - res(Value::string(interp.intern(&line))) - } else { - let all = std::mem::take(&mut interp.sio_buf); - res(Value::string(interp.intern(&all))) - } -} - -// ---- FIO ---- - -fn fio_write_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let content = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::write(&path, content) { - Ok(()) => res(Value::boolean(true)), - Err(e) => refn(interp, &format!("FIO::writeFile failed: {e}")), - } -} - -fn fio_append_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let content = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::OpenOptions::new().append(true).create(true).open(&path) - .and_then(|mut f| std::io::Write::write_all(&mut f, content.as_bytes())) { - Ok(()) => res(Value::boolean(true)), - Err(e) => refn(interp, &format!("FIO::appendFile failed: {e}")), - } -} - -fn fio_read_file(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - match fs::read_to_string(&path) { - Ok(s) => res(Value::string(interp.intern(&s))), - Err(e) => refn(interp, &format!("FIO::readFile failed: {e}")), - } -} - -fn fio_exists(interp: &mut Interp, args: &[Value]) -> Outcome { - let path = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - res(Value::int(std::path::Path::new(&path).exists() as i64)) -} - -// ---- Com ---- - -fn num1(interp: &mut Interp, a: &Value) -> Result { - if matches!(a.tag(), Tag::Int | Tag::Num) { - Ok(a.as_int_or_num()) - } else { - Err(refn(interp, "Com requires a numeric argument")) - } -} - -fn com_abs(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.abs())), - Err(e) => e, - } -} - -fn com_min(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.min(b))) -} - -fn com_max(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.max(b))) -} - -fn com_pow(interp: &mut Interp, args: &[Value]) -> Outcome { - let a = match num1(interp, args.first().unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - let b = match num1(interp, args.get(1).unwrap_or(&Value::nil())) { Ok(v) => v, Err(e) => return e }; - res(Value::num(a.powf(b))) -} - -fn com_sqrt(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.sqrt())), - Err(e) => e, - } -} - -fn com_floor(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.floor())), - Err(e) => e, - } -} - -fn com_ceil(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.ceil())), - Err(e) => e, - } -} - -fn com_round(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.round())), - Err(e) => e, - } -} - -fn com_sign(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::int(v.signum() as i64)), - Err(e) => e, - } -} - -fn com_sin(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.sin())), - Err(e) => e, - } -} - -fn com_cos(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.cos())), - Err(e) => e, - } -} - -fn com_tan(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.tan())), - Err(e) => e, - } -} - -fn com_log(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.ln())), - Err(e) => e, - } -} - -fn com_exp(interp: &mut Interp, args: &[Value]) -> Outcome { - match num1(interp, args.first().unwrap_or(&Value::nil())) { - Ok(v) => res(Value::num(v.exp())), - Err(e) => e, - } -} - -// ---- Time ---- - -fn time_start(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.timers.insert(0, Instant::now()); - res(Value::nil()) -} - -fn time_sleep(_interp: &mut Interp, args: &[Value]) -> Outcome { - let ms = args.first().map(|a| a.as_int_or_num()).unwrap_or(0.0); - std::thread::sleep(std::time::Duration::from_millis(ms as u64)); - res(Value::nil()) -} - -fn time_elapsed(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - match interp.timers.get(&id) { - Some(t) => { - let secs = t.elapsed().as_secs_f64(); - res(Value::num(secs)) - } - None => refn(interp, "Time: timer not started"), - } -} - -fn time_fork(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.timer_seq += 1; - let id = interp.timer_seq; - interp.timers.insert(id, Instant::now()); - res(Value::int(id as i64)) -} - -fn time_reset(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - if id == 0 { - return refn(interp, "Time refused: first timer (thread default) cannot be reset; use Time::fork()"); - } - interp.timers.insert(id, Instant::now()); - res(Value::nil()) -} - -// ---- Obj ---- - -fn obj_set(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::set requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::set first argument is not an object") }; - let h = obj.as_handle(); - let key = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let val = args.get(2).copied().unwrap_or(Value::nil()); - interp.obj_prop_set(h, &key, val); - res(Value::nil()) -} - -fn obj_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::get requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::get first argument is not an object") }; - let h = obj.as_handle(); - let key = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - match interp.obj_prop_get(h, &key) { - Some(v) => res(v), - None => refn(interp, "missing attribute"), - } -} - -fn obj_call(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(obj) = args.first() else { return refn(interp, "Obj::call requires an object") }; - let (Tag::Obj | Tag::Arr) = obj.tag() else { return refn(interp, "Obj::call first argument is not an object") }; - let h = obj.as_handle(); - let name = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let rest = args[2..].to_vec(); - interp.invoke_on_obj(h, &name, rest) -} - -fn obj_new(interp: &mut Interp, args: &[Value]) -> Outcome { - let cls = args.first().map(|a| arg_str(interp, a)).unwrap_or_default(); - let rest = args[1..].to_vec(); - res(interp.new_class(&cls, rest)) -} - -// ---- Solid ---- - -fn solid_new(interp: &mut Interp, _args: &[Value]) -> Outcome { - let h = interp.solid_new(Vec::new()); - res(Value::obj(h)) -} - -fn solid_data_h(interp: &mut Interp, args: &[Value]) -> Result { - let Some(a) = args.first() else { return Err(refn(interp, "Solid method requires a storage handle")) }; - let (Tag::Obj | Tag::Arr) = a.tag() else { return Err(refn(interp, "Solid argument is not a handle")) }; - let h = a.as_handle(); - let cls = interp.obj_class(h); - if cls != "Solid" && cls != "SolidData" { - return Err(refn(interp, "Solid argument is not a Solid instance")); - } - Ok(h) -} - -fn solid_len(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - res(Value::int(interp.solid_data(h).len() as i64)) -} - -fn solid_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let i = match args.get(1).ok_or(0).and_then(|a| Ok(a.as_int_or_num() as i64)) { - Ok(i) => i, - Err(_) => 0, - }; - let data = interp.solid_data(h); - if i < 0 || i as usize >= data.len() { - return refn(interp, "index out of bounds"); - } - res(data[i as usize]) -} - -fn solid_set(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let i = args.get(1).map(|a| a.as_int_or_num() as i64).unwrap_or(0); - let v = args.get(2).copied().unwrap_or(Value::nil()); - let len = interp.solid_data(h).len() as i64; - if i < 0 || i >= len { - return refn(interp, "index out of bounds"); - } - interp.solid_set(h, i as usize, v); - res(Value::nil()) -} - -fn solid_push(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let v = args.get(1).copied().unwrap_or(Value::nil()); - interp.solid_push(h, v); - res(Value::nil()) -} - -fn solid_pop(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let data_h = match interp.solid_data_handle(h) { - Some(dh) => dh, - None => return refn(interp, "no data"), - }; - match interp.objects[data_h as usize].fields.pop() { - Some(v) => res(v), - None => refn(interp, "pop from empty"), - } -} - -fn solid_join(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - let sep = args.get(1).map(|a| arg_str(interp, a)).unwrap_or_default(); - let data = interp.solid_data(h); - let parts: Vec = data.iter().map(|v| interp.fmt_value(v)).collect(); - res(Value::string(interp.intern(&parts.join(&sep)))) -} - -fn solid_clear(interp: &mut Interp, args: &[Value]) -> Outcome { - let h = match solid_data_h(interp, args) { Ok(h) => h, Err(e) => return e }; - if let Some(dh) = interp.solid_data_handle(h) { - interp.objects[dh as usize].fields.clear(); - } - res(Value::nil()) -} - -// ---- Arrays ---- - -fn arrays_add(interp: &mut Interp, args: &[Value]) -> Outcome { - if let Some(a) = args.first() { - if let Tag::Obj | Tag::Arr = a.tag() { - interp.arrays.push(a.as_handle()); - } - } - res(Value::nil()) -} - -fn arrays_count(interp: &mut Interp, _args: &[Value]) -> Outcome { - res(Value::int(interp.arrays.len() as i64)) -} - -fn arrays_all(interp: &mut Interp, _args: &[Value]) -> Outcome { - // 返回整个注册表(所有 Array/Vector 实例组成的数组) - let vals: Vec = interp.arrays.iter().map(|h| Value::obj(*h)).collect(); - let dh = interp.objs_data_handle(vals); - res(Value::arr(dh)) -} - -fn arrays_get(interp: &mut Interp, args: &[Value]) -> Outcome { - let i = args.first().map(|a| a.as_int_or_num() as usize).unwrap_or(0); - match interp.arrays.get(i) { - Some(h) => res(Value::obj(*h)), - None => refn(interp, "Arrays: index out of bounds"), - } -} - -fn arrays_forget(interp: &mut Interp, args: &[Value]) -> Outcome { - if let Some(a) = args.first() { - if let Tag::Obj | Tag::Arr = a.tag() { - let h = a.as_handle(); - interp.arrays.retain(|x| *x != h); - } - } - res(Value::nil()) -} - -fn arrays_vector(interp: &mut Interp, _args: &[Value]) -> Outcome { - let v = interp.new_class("Vector", Vec::new()); - res(v) -} - -fn arrays_sort(interp: &mut Interp, args: &[Value]) -> Outcome { - // Arrays::sort(arr) — 原地排序数组(内部调用数组的 __sort__ 内部方法) - let Some(a) = args.first() else { - return refn(interp, "Arrays::sort requires an array argument"); - }; - let (Tag::Obj | Tag::Arr) = a.tag() else { - return refn(interp, "Arrays::sort argument is not an array object"); - }; - let h = a.as_handle(); - // 通过内部方法 __sort__ 原地排序(数组对象 → Solid 数据) - let out = interp.invoke_on_obj(h, "__sort__", Vec::new()); - if out.is_refused() { - // 裸数组(SolidData)直接排 - if let Some(dh) = interp.solid_data_handle(h) { - interp.sort_data(dh); - return res(Value::nil()); - } - return refn(interp, "Arrays::sort failed: not a sortable array"); - } - res(Value::nil()) -} - -// ---- Threads ---- - -fn threads_spawn(interp: &mut Interp, args: &[Value]) -> Outcome { - let name = args.first().map(|a| interp.fmt_value(a)).unwrap_or_default(); - let rest = args[1..].to_vec(); - interp.thread_seq += 1; - let id = interp.thread_seq; - let def_name = interp.reg.find_bare_method(&name).map(|(d, _)| d.name.clone()); - interp.threads.push(crate::interp::ThreadTask { id, name, def_name, args: rest, done: None }); - Outcome::Res(Value::int(id as i64)) -} - -fn threads_join(interp: &mut Interp, args: &[Value]) -> Outcome { - let id = args.first().map(|a| a.as_int_or_num() as u32).unwrap_or(0); - // 先执行其它未完成任务(逆序——11 期望 thread 2 先打印),再执行目标 - let others: Vec = interp.threads.iter().filter(|t| t.done.is_none() && t.id != id).map(|t| t.id).rev().collect(); - for oid in others { - interp.run_thread(oid); - } - interp.run_thread(id) -} - -fn threads_yield(_interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::nil()) // 顺序执行:no-op -} - -fn threads_active(interp: &mut Interp, _args: &[Value]) -> Outcome { - let n = interp.threads.iter().filter(|t| t.done.is_none()).count(); - Outcome::Res(Value::int(n as i64)) -} - -fn threads_self(interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::int(interp.running_thread.unwrap_or(0) as i64)) -} - -// ---- Taskm ---- - -fn taskm_add(interp: &mut Interp, args: &[Value]) -> Outcome { - threads_spawn(interp, args) -} - -fn taskm_interval(_interp: &mut Interp, _args: &[Value]) -> Outcome { - Outcome::Res(Value::nil()) -} - -fn taskm_run(interp: &mut Interp, _args: &[Value]) -> Outcome { - let ids: Vec = interp.threads.iter().filter(|t| t.done.is_none()).map(|t| t.id).collect(); - for id in ids { - interp.run_thread(id); - } - Outcome::Res(Value::nil()) -} - -fn taskm_stop(interp: &mut Interp, _args: &[Value]) -> Outcome { - interp.threads.clear(); - Outcome::Res(Value::nil()) -} - -fn taskm_active(interp: &mut Interp, _args: &[Value]) -> Outcome { - threads_active(interp, &[]) -} - -// ---- Ref ---- - -fn ref_read(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::read requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::read argument is not a reference"); } - interp.ref_read(a.as_handle()) -} - -fn ref_write(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::write requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::write argument is not a reference") }; - let v = args.get(1).copied().unwrap_or(Value::nil()); - match interp.ref_write(a.as_handle(), v) { - Outcome::Res(_) => Outcome::Res(Value::nil()), - // Ref::write 方法级拒绝消息带 "Ref refused: " 前缀(11 的 cause 输出) - Outcome::Ref(_) => Outcome::Ref(Cause(interp.intern("Ref refused: reference is read-only, cannot write"))), - } -} - -fn ref_move(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::move requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::move argument is not a reference") }; - interp.ref_move(a.as_handle()) -} - -fn ref_target(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::target requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::target argument is not a reference") }; - let r = interp.refs[a.as_handle() as usize].clone(); - match &r.target { - crate::interp::RefTarget::Var { name, .. } => Outcome::Res(Value::string(interp.intern(name))), - crate::interp::RefTarget::ArrElem { index, .. } => Outcome::Res(Value::int(*index)), - crate::interp::RefTarget::ObjProp { name, .. } => Outcome::Res(Value::string(interp.intern(name))), - } -} - -fn ref_perm(interp: &mut Interp, args: &[Value]) -> Outcome { - let Some(a) = args.first() else { return refn(interp, "Ref::perm requires a reference") }; - if a.tag() != Tag::Ref { return refn(interp, "Ref::perm argument is not a reference") }; - let r = interp.refs[a.as_handle() as usize].clone(); - Outcome::Res(Value::string(interp.intern(&r.perm))) -} diff --git a/rust/crates/bbb-vm/src/dylib.rs b/rust/crates/bbb-vm/src/dylib.rs deleted file mode 100644 index 4a8d08e..0000000 --- a/rust/crates/bbb-vm/src/dylib.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! dylib — 跨平台动态库加载(二进制库流底层)。 -//! -//! 平台适配: -//! - Linux / Android:`dlopen` / `dlsym`(.so) -//! - macOS / iOS:`dlopen` / `dlsym`(.dylib;dlopen 可用) -//! - Windows:`LoadLibraryA` / `GetProcAddress`(.dll) -//! -//! 库名归一化:声明 `Stream m & "libm.so"` 时,按当前平台尝试 -//! 候选文件名(libm.so → libm.dylib → libm.dll / m.dll), -//! 并保留原始名兜底。这样同一份 .bio 源码可以跨系统运行。 - -use std::ffi::CString; -use std::os::raw::c_char; - -/// 打开的库句柄(平台特定的不透明指针)。 -#[cfg(unix)] -pub type LibHandle = *mut std::ffi::c_void; -#[cfg(windows)] -pub type LibHandle = *mut std::ffi::c_void; - -/// 平台库名候选:把声明的名字转成当前平台可能的真实文件名。 -/// 策略:原样优先 + 平台等价后缀 + 版本化后缀剥离(libm.so.6 → libm.so)。 -pub fn candidate_names(declared: &str) -> Vec { - let mut out = Vec::new(); - let base = declared.trim(); - if base.is_empty() { - return out; - } - // 1. 原样 - out.push(base.to_string()); - - // 2. 剥离版本后缀:libm.so.6 → libm.so;foo.1.2 → foo(仅 .so/.dylib/.dll 前的版本号) - if let Some((stem, ver)) = split_version(base) { - if !ver.is_empty() && out.iter().all(|x| x != &stem) { - out.push(stem.clone()); - } - } - - // 3. 平台等价后缀 - let (stem, ext) = split_ext(base); - // ext 为纯数字版本号(libm.so.6 的 "6")时视为版本化,不按后缀处理 - let ext_is_ver = !ext.is_empty() && ext.chars().all(|c| c.is_ascii_digit()); - #[cfg(target_os = "windows")] - { - if ext != "dll" && !ext_is_ver { - out.push(format!("{stem}.dll")); - } - if let Some(rest) = stem.strip_prefix("lib") { - if !rest.is_empty() { - out.push(format!("{rest}.dll")); - } - } - // .so.6 → .dll 也试:libm → m.dll - if let Some(rest) = base.split('.').next().and_then(|s| s.strip_prefix("lib")) { - if !rest.is_empty() { - out.push(format!("{rest}.dll")); - } - } - } - #[cfg(target_os = "macos")] - { - if ext != "dylib" && !ext_is_ver { - out.push(format!("{stem}.dylib")); - } - // libfoo.so.6 → libfoo.dylib - if let Some((s, _)) = split_version(base) { - let (s2, _) = split_ext(&s); - if s2 != stem && s2 != "" { - out.push(format!("{s2}.dylib")); - } - } - } - #[cfg(all(unix, not(target_os = "macos")))] - { - if ext != "so" && !ext_is_ver { - out.push(format!("{stem}.so")); - } - // libm.so 可能是链接脚本 → 常见版本化真实库兜底 - if (ext == "so" || ext == "") && !ext_is_ver { - out.push(format!("{stem}.so.6")); - out.push(format!("{stem}.so.1")); - } - } - // 去重 - let mut seen = std::collections::HashSet::new(); - out.retain(|x| seen.insert(x.clone())); - out -} - -/// 剥离版本号:libm.so.6 → ("libm.so", "6");foo.1.2 → ("foo", "1.2")。 -/// 仅当最后一段是数字或 .so.X/.dylib.X 形态。 -fn split_version(name: &str) -> Option<(String, String)> { - let (stem, last) = name.rsplit_once('.')?; - if last.is_empty() || !last.chars().all(|c| c.is_ascii_digit()) { - return None; - } - Some((stem.to_string(), last.to_string())) -} - -fn split_ext(name: &str) -> (String, String) { - match name.rsplit_once('.') { - Some((s, e)) if !s.is_empty() => (s.to_string(), e.to_string()), - _ => (name.to_string(), String::new()), - } -} - -/// 打开动态库,返回句柄;失败返回 None。 -pub fn open(path: &str) -> Option { - let c = CString::new(path).ok()?; - #[cfg(unix)] - { - // RTLD_LAZY = 1 - let h = unsafe { dlopen(c.as_ptr() as *const c_char, 1) }; - if h.is_null() { None } else { Some(h) } - } - #[cfg(windows)] - { - let h = unsafe { LoadLibraryA(c.as_ptr() as *const c_char) }; - if h.is_null() { None } else { Some(h) } - } -} - -/// 按声明名尝试打开库:遍历候选名,第一个成功的返回。 -pub fn open_any(declared: &str) -> Option { - for name in candidate_names(declared) { - if let Some(h) = open(&name) { - return Some(h); - } - } - None -} - -/// 查找符号,返回裸指针;失败返回 None。 -pub fn symbol(h: LibHandle, name: &str) -> Option<*mut std::ffi::c_void> { - let c = CString::new(name).ok()?; - #[cfg(unix)] - { - let p = unsafe { dlsym(h, c.as_ptr() as *const c_char) }; - if p.is_null() { None } else { Some(p) } - } - #[cfg(windows)] - { - let p = unsafe { GetProcAddress(h as *mut _, c.as_ptr() as *const c_char) }; - if p.is_null() { None } else { Some(p.cast()) } - } -} - -/// 关闭动态库。 -pub fn close(h: LibHandle) { - #[cfg(unix)] - unsafe { - dlclose(h); - } - #[cfg(windows)] - unsafe { - FreeLibrary(h as *mut _); - } -} - -// ---- FFI 声明 ---- - -#[cfg(unix)] -extern "C" { - fn dlopen(filename: *const c_char, flags: i32) -> *mut std::ffi::c_void; - fn dlsym(handle: *mut std::ffi::c_void, symbol: *const c_char) -> *mut std::ffi::c_void; - fn dlclose(handle: *mut std::ffi::c_void) -> i32; -} - -#[cfg(windows)] -extern "system" { - fn LoadLibraryA(lpFileName: *const c_char) -> *mut std::ffi::c_void; - fn GetProcAddress(hModule: *mut std::ffi::c_void, lpProcName: *const c_char) -> *mut std::ffi::c_void; - fn FreeLibrary(hLibModule: *mut std::ffi::c_void) -> i32; -} diff --git a/rust/crates/bbb-vm/src/interp.rs b/rust/crates/bbb-vm/src/interp.rs deleted file mode 100644 index 81a97a2..0000000 --- a/rust/crates/bbb-vm/src/interp.rs +++ /dev/null @@ -1,1256 +0,0 @@ -//! 解释器核心(M3):作用域、流调用、表达式求值、语句执行。 - -use std::collections::HashMap; -use std::time::Instant; - -use bbb_core::arena::{StrArena, StrRef}; -use bbb_core::value::{Cause, Outcome, Tag, Value}; -use bbb_syntax::ast::*; -use bbb_syntax::parser::parse_source; - -use crate::builtin; -use crate::registry::{Registry, StreamDef, StreamKind}; -use crate::BUILTIN_CLASS_SRC; - -/// 控制流信号(语句执行结果)。 -pub enum Flow { - Next, - Ret(Outcome), - Break, - Continue, -} - -/// 调用栈帧:方法作用域 + this。 -pub struct Frame { - pub scope: HashMap, - pub this: Option, // 对象/流实例句柄 - pub method: String, // 当前方法名(电话亭递归检测) - pub booth: bool, // @call/@ucall 电话亭方法 -} - -/// 协作线程任务(顺序 join 式:spawn 注册,join 时按逆序执行) -pub struct ThreadTask { - pub id: u32, - pub name: String, // 裸方法名 - pub def_name: Option, // 所属流名 - pub args: Vec, - pub done: Option, -} - -/// 对象数据:类名 + 声明字段 + 动态属性。 -#[derive(Clone)] -pub struct ObjData { - pub def: StrRef, // 类名("Array"/"Vector"/"Solid"/用户类) - pub fields: Vec, - pub attrs: Vec<(StrRef, Value)>, -} - -impl Default for ObjData { - fn default() -> Self { - ObjData { def: StrRef::NULL, fields: Vec::new(), attrs: Vec::new() } - } -} - -/// 引用值(智能引用,&perm follow base)——11 的语义: -/// r=读 w=写 m=移动(纯 m 不可读不可写,只能 p++/Ref::move) -#[derive(Clone)] -pub enum RefTarget { - /// 变量(帧内绑定:frame 索引 + 名字,跨方法调用仍指向原存储位置) - Var { frame: usize, name: String }, - ArrElem { obj: u32, index: i64 }, // 数组元素(m 权限指针移动) - ObjProp { obj: u32, name: String }, -} - -#[derive(Clone)] -pub struct RefVal2 { - pub target: RefTarget, - pub perm: String, - pub follow: String, -} - -/// 运行结果。 -pub struct RunOutcome { - pub stdout: String, - pub unmet_needs: Vec<(String, String)>, -} - -pub struct Interp { - pub strs: StrArena, - pub objects: Vec, - pub refs: Vec, - pub reg: Registry, - pub frames: Vec, - pub stdout: String, - pub sio_buf: String, - pub timers: HashMap, - pub timer_seq: u32, - pub arrays: Vec, // Arrays 集合(对象句柄) - pub consts: Vec<(String, Value)>, - pub stream_instances: HashMap, // fork/class 流单例(持久字段状态) - pub threads: Vec, // 协作线程任务(顺序 join 式) - pub thread_seq: u32, - pub running_thread: Option, // Threads::self() - pub open_libs: Vec<(String, crate::dylib::LibHandle)>, // 已加载库:声明名 → 句柄 - pub pending_ref_perm: Option, - pub pending_ref_follow: Option, -} - -impl Interp { - pub fn new() -> Self { - Interp { - strs: StrArena::new(), - objects: Vec::new(), - refs: Vec::new(), - reg: Registry::new(), - frames: Vec::new(), - stdout: String::new(), - sio_buf: String::new(), - timers: HashMap::new(), - timer_seq: 0, - arrays: Vec::new(), - consts: Vec::new(), - stream_instances: HashMap::new(), - threads: Vec::new(), - thread_seq: 0, - running_thread: None, - open_libs: Vec::new(), - pending_ref_perm: None, - pending_ref_follow: None, - } - } - - pub fn intern(&mut self, s: &str) -> StrRef { - self.strs.push(s) - } - - fn cause(&mut self, s: &str) -> Outcome { - Outcome::Ref(Cause(self.intern(s))) - } - - // ---- 顶层入口 ---- - - /// 解释单个 Program(含 need 校验)。 - pub fn run(&mut self, prog: &Program) -> RunOutcome { - // 注入内置类(Array/Vector,Bio 语言编写) - let builtin_src = BUILTIN_CLASS_SRC.to_string(); - let (builtin_prog, errs) = parse_source(&builtin_src); - if errs.is_empty() { - self.reg.register(&builtin_prog); - } - // 用户声明:@unfork 签名流的 fork 跳过(15:启动打印拒绝) - let mut prog2 = prog.clone(); - let mut unfork_msgs = Vec::new(); - prog2.decls.retain(|d| { - if let Decl::Fork { sig, .. } = d { - let blocked = prog.decls.iter().any(|x| matches!(x, Decl::StreamSig { name, annos, .. } if name == sig && annos.contains(&"unfork".to_string()))); - if blocked { - unfork_msgs.push(format!("refused: stream {sig} is @unfork, cannot fork")); - return false; - } - } - true - }); - let mut unmet = self.reg.register(&prog2); - for m in unfork_msgs { - self.stdout.push_str(&m); - self.stdout.push('\n'); - } - // 顶层常量求值 - for d in &prog2.decls { - if let Decl::Const { name, init, .. } = d { - let v = self.eval_expr(init); - self.consts.push((name.clone(), v)); - } - } - // 执行 Main::exec - let exec = prog2 - .main - .as_ref() - .and_then(|m| m.methods.iter().find(|x| x.name == "exec")) - .cloned(); - if let Some(method) = exec { - self.frames.push(Frame { scope: HashMap::new(), this: None, method: "exec".into(), booth: false }); - let outcome = self.exec_method_body(&method); - self.frames.pop(); - // Main::exec 拒绝:自然结束(nothing)静默;显式/传播拒绝打印 ⛔ 并终止(11 的 mp=99) - if let Outcome::Ref(c) = outcome { - let cause_s = self.strs.get(c.0).to_string(); - if cause_s != "nothing" { - self.stdout.push_str(&format!("⛔ main stream refused: {cause_s}\n")); - } - } - } - // need 校验(多文件/单文件统一) - unmet.retain(|(k, n)| !self.consts.iter().any(|(cn, _)| cn == n && k == "value")); - RunOutcome { stdout: self.stdout.clone(), unmet_needs: unmet } - } - - // ---- 对象 ---- - - pub fn new_object(&mut self, def_name: &str, field_count: usize) -> u32 { - self.new_object_typed(def_name, &[]) - } - - /// 按字段类型初始化默认值:数值 0 / string "" / 其他 nil(13-need 依赖 int hp = 0)。 - pub fn new_object_typed(&mut self, def_name: &str, field_types: &[String]) -> u32 { - let def = self.intern(def_name); - let fields = field_types - .iter() - .map(|ty| match ty.as_str() { - "int" | "float" | "double" => Value::int(0), - "string" => Value::string(self.intern("")), - "char" => Value::chr(0), - "bool" => Value::boolean(false), - _ => Value::nil(), - }) - .collect(); - self.objects.push(ObjData { def, fields, attrs: Vec::new() }); - (self.objects.len() - 1) as u32 - } - - /// 对象字段查找(this 或对象值上的 Prop)。先声明字段后动态属性。 - pub fn obj_prop_get(&mut self, h: u32, name: &str) -> Option { - let def_name = self.objects[h as usize].def; - let defname_str = self.strs.get(def_name).to_string(); - if let Some(d) = self.reg.streams.get(&defname_str) { - if let Some(i) = d.field_names.iter().position(|n| n == name) { - return Some(self.objects[h as usize].fields[i]); - } - } - for (k, v) in self.objects[h as usize].attrs.iter() { - if self.strs.get(*k) == name { - return Some(*v); - } - } - None - } - - pub fn obj_prop_set(&mut self, h: u32, name: &str, v: Value) { - let nref = self.intern(name); - let def_name = self.objects[h as usize].def; - let defname_str = self.strs.get(def_name).to_string(); - if let Some(d) = self.reg.streams.get(&defname_str) { - if let Some(i) = d.field_names.iter().position(|n| n == name) { - self.objects[h as usize].fields[i] = v; - return; - } - } - let o = &mut self.objects[h as usize]; - for (k, slot) in o.attrs.iter_mut() { - if self.strs.get(*k) == name { - *slot = v; - return; - } - } - o.attrs.push((nref, v)); - } - - /// 对象类名。 - pub fn obj_class(&self, h: u32) -> String { - self.strs.get(self.objects[h as usize].def).to_string() - } - - // ---- 变量 ---- - - fn var_get(&mut self, name: &str) -> Option { - for f in self.frames.iter().rev() { - if let Some(v) = f.scope.get(name) { - return Some(*v); - } - } - for (n, v) in self.consts.iter().rev() { - if n == name { - return Some(*v); - } - } - // this 关键字 → 当前实例 - if name == "this" { - return self.current_this().map(Value::obj); - } - // 流字段(fork/class 单例字段)→ 当前 this 实例字段 - if let Some(h) = self.current_this() { - if let Some(v) = self.obj_prop_get(h, name) { - return Some(v); - } - } - // 流名 → 流值(流作为参数/对象传递:CIO、Calc...) - if self.reg.streams.contains_key(name) || builtin_stream_name(name) { - let key = format!("$stream:{name}"); - if let Some(h) = self.stream_instances.get(&key) { - return Some(Value::obj(*h)); - } - let h = self.new_object(name, 0); - self.stream_instances.insert(key, h); - return Some(Value::obj(h)); - } - None - } - - /// 只查 frame 链 + consts(不含流名/this 等解析)。 - fn var_get_raw(&mut self, name: &str) -> Option { - for f in self.frames.iter().rev() { - if let Some(v) = f.scope.get(name) { - return Some(*v); - } - } - for (n, v) in self.consts.iter().rev() { - if n == name { - return Some(*v); - } - } - None - } - - /// 引用读(get p / Ref::read):perm 无 r → 拒绝 "refused: reference is write-only, cannot read" - pub fn ref_read(&mut self, h: u32) -> Outcome { - let r = self.refs[h as usize].clone(); - // 读需要 r 权限(纯 m/纯 w 不可读——11:get mp 拒绝) - if !r.perm.contains('r') { - return Outcome::Ref(Cause(self.intern("refused: reference is write-only, cannot read"))); - } - match &r.target { - RefTarget::Var { frame, name } => { - let v = self - .frames - .get(*frame) - .and_then(|f| f.scope.get(name)) - .copied() - .or_else(|| self.var_get_raw(name)) - .unwrap_or(Value::nil()); - Outcome::Res(v) - } - RefTarget::ArrElem { obj, index } => { - self.invoke_on_obj(*obj, "get", vec![Value::int(*index)]) - } - RefTarget::ObjProp { obj, name } => { - let v = self.obj_prop_get(*obj, name).unwrap_or(Value::nil()); - Outcome::Res(v) - } - } - } - - /// 引用写(p = v / Ref::write):perm 无 w → 拒绝 "refused: reference is read-only, cannot write" - pub fn ref_write(&mut self, h: u32, v: Value) -> Outcome { - let r = self.refs[h as usize].clone(); - if !r.perm.contains('w') { - return Outcome::Ref(Cause(self.intern("refused: reference is read-only, cannot write"))); - } - match &r.target { - RefTarget::Var { frame, name } => { - if let Some(f) = self.frames.get_mut(*frame) { - f.scope.insert(name.clone(), v); - } else { - self.var_set(name, v); - } - Outcome::Res(Value::nil()) - } - RefTarget::ArrElem { obj, index } => { - self.invoke_on_obj(*obj, "set", vec![Value::int(*index), v]); - Outcome::Res(Value::nil()) - } - RefTarget::ObjProp { obj, name } => { - self.obj_prop_set(*obj, name, v); - Outcome::Res(Value::nil()) - } - } - } - - /// 引用移动(Ref::move / p++):perm 无 m → 拒绝;数组越界 → 拒绝 - pub fn ref_move(&mut self, h: u32) -> Outcome { - let r = self.refs[h as usize].clone(); - if !r.perm.contains('m') { - return Outcome::Ref(Cause(self.intern("Ref refused: reference has no move permission"))); - } - if let RefTarget::ArrElem { obj, index } = r.target { - let cls = self.obj_class(obj); - let len = if cls == "Array" || cls == "Vector" { - match self.invoke_on_obj(obj, "len", vec![]) { - Outcome::Res(v) => v.as_int_or_num() as i64, - Outcome::Ref(_) => 0, - } - } else { - 0 - }; - if index + 1 >= len { - return Outcome::Ref(Cause(self.intern("refused: reference moved out of bounds"))); - } - self.refs[h as usize].target = RefTarget::ArrElem { obj, index: index + 1 }; - Outcome::Res(Value::nil()) - } else { - Outcome::Ref(Cause(self.intern("Ref refused: reference is not a moving pointer"))) - } - } - - /// 引用变量赋值语句(rw = 5)→ 引用写;拒绝则传播。 - fn ref_assign(&mut self, _name: &str, old: Value, v: Value) -> Flow { - let h = old.as_handle(); - match self.ref_write(h, v) { - Outcome::Res(_) => Flow::Next, - Outcome::Ref(c) => Flow::Ret(Outcome::Ref(c)), - } - } - - fn var_set(&mut self, name: &str, v: Value) { - for f in self.frames.iter_mut().rev() { - if f.scope.contains_key(name) { - f.scope.insert(name.to_string(), v); - return; - } - } - // 未声明:写入当前帧(宽松;标准:变量须先声明) - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.to_string(), v); - } - } - - // ---- 方法调用 ---- - - /// 执行方法体(当前帧已压栈)。返回 res/ref 或默认 ref(nothing)。 - fn exec_method_body(&mut self, method: &Method) -> Outcome { - for st in &method.body { - match self.exec_stmt(st) { - Flow::Ret(o) => return o, - Flow::Break => return self.cause("break outside loop"), - Flow::Continue => return self.cause("continue outside loop"), - Flow::Next => {} - } - } - self.cause("nothing") - } - - /// 调用方法(def 为所属流,this 为实例句柄)。 - fn call_method( - &mut self, - def: Option, - method: Method, - this: Option, - args: Vec, - ) -> Outcome { - let _ = def; - let mut scope = HashMap::new(); - for (p, a) in method.params.iter().zip(args.iter()) { - scope.insert(p.name.clone(), *a); - } - self.frames.push(Frame { scope, this, method: method.name.clone(), booth: !method.annos.is_empty() && (method.annos.contains(&"call".to_string()) || method.annos.contains(&"ucall".to_string())) }); - // 电话亭:@call/@ucall 方法递归拒绝(16) - let name = method.name.clone(); - if self.frames.iter().filter(|f| f.method == name && f.booth).count() > 1 { - self.frames.pop(); - return self.cause(&format!("refused: phone-booth method {name} does not support recursion")); - } - let r = self.exec_method_body(&method); - self.frames.pop(); - r - } - - /// 调用表达式:Outcome → Value(refused 位 + cause)。 - fn call_to_value(&mut self, qual: Option<&str>, name: &str, args: Vec) -> Value { - match self.invoke(qual, name, args) { - Outcome::Res(v) => v, - Outcome::Ref(c) => Value::refused_str(c.0), - } - } - - /// 统一调用入口:内置流 → 对象方法 → 流方法 → 裸方法 → 拒绝。 - fn invoke(&mut self, qual: Option<&str>, name: &str, args: Vec) -> Outcome { - if let Some(q) = qual { - // 内置流 - if builtin::lookup(q, name).is_some() { - let f = builtin::lookup(q, name).unwrap(); - return f(self, &args); - } - // this::method - if q == "this" { - if let Some(h) = self.current_this() { - return self.invoke_on_obj(h, name, args); - } - return self.cause("no this context"); - } - // 流名 - if let Some(def) = self.reg.resolve_qual(q).cloned() { - // StreamBin:库方法优先 dl(签名成员 = 声明导出符号,无 Bio 体) - if let Some(lib) = def.bin_file.clone() { - // 带 body 的 Bio 方法仍走 Bio 执行 - let has_body = def.methods.get(name).map(|m| !m.body.is_empty()).unwrap_or(false); - if !has_body { - return self.dl_call(&lib, name, args); - } - } - if let Some(m) = def.methods.get(name).cloned() { - // @onlyread:@write 注解方法拒绝(15) - if def.annos.contains(&"onlyread".to_string()) - && m.annos.contains(&"write".to_string()) { - return self.cause(&format!("refused: stream {} is @onlyread — {name}() is a write method", def.name)); - } - let singleton = self.stream_instance(&def); - return self.call_method(Some(def), m, singleton, args); - } - // StreamBin:Bio 方法体之外回退 dlsym(14) - if let Some(lib) = def.bin_file.clone() { - return self.dl_call(&lib, name, args); - } - } - // 变量(对象值 / 流值) - if let Some(v) = self.var_get(q) { - if let Tag::Obj | Tag::Arr = v.tag() { - let h = v.as_handle(); - let cls = self.obj_class(h); - // 内置流实例(cio CIO 传参) - if let Some(bf) = builtin::lookup(&cls, name) { - if cls == "Solid" || cls == "SolidData" { - // Solid 方法需要 self 作第一个参数(旧 C:all[0] = self) - let mut a2 = Vec::with_capacity(args.len() + 1); - a2.push(v); - a2.extend_from_slice(&args); - return bf(self, &a2); - } - return bf(self, &args); - } - if self.reg.class(&cls).is_some() || cls == "Solid" { - return self.invoke_on_obj(h, name, args); - } - } - } - return self.cause(&format!("stream {q} refuses: no method {name}")); - } else { - // 裸调用:先查当前流上下文(04:流内 bare call),再全局方法名 - if let Some(h) = self.current_this() { - let cls = self.obj_class(h); - // 内部方法 __sort__:对 this 的 Solid 数据原地排序(sort() 委托) - if name == "__sort__" { - if let Some(dh) = self.solid_data_handle(h) { - self.sort_data(dh); - return Outcome::Res(Value::nil()); - } - return self.cause("no data to sort"); - } - if let Some(def) = self.reg.streams.get(&cls).cloned() { - if let Some(m) = def.methods.get(name).cloned() { - if !m.body.is_empty() { - return self.call_method(Some(def), m, Some(h), args); - } - } - } - } - let found = self.reg.find_bare_method(name).map(|(d, m)| (d.clone(), m.clone())); - if let Some((def, m)) = found { - let singleton = self.stream_instance(&def); - return self.call_method(Some(def), m, singleton, args); - } - return self.cause(&format!("no method {name}")); - } - } - - fn current_this(&self) -> Option { - self.frames.iter().rev().find_map(|f| f.this) - } - - /// 对象方法调用(a::set(0,10) / h::getHp())。 - pub fn invoke_on_obj(&mut self, h: u32, name: &str, args: Vec) -> Outcome { - let cls = self.obj_class(h); - if let Some(def) = self.reg.class(&cls).cloned() { - if let Some(m) = def.methods.get(name).cloned() { - return self.call_method(Some(def), m, Some(h), args); - } - } - self.cause(&format!("object {cls} refuses: no method {name}")) - } - - /// 执行一个线程任务(裸方法),结果存 task.done。 - pub fn run_thread(&mut self, id: u32) -> Outcome { - if let Some(t) = self.threads.iter().find(|t| t.id == id) { - if let Some(d) = t.done { - return d; - } - } - let Some(idx) = self.threads.iter().position(|t| t.id == id) else { - return self.cause("no such thread"); - }; - let name = self.threads[idx].name.clone(); - let def_name = self.threads[idx].def_name.clone(); - let args = self.threads[idx].args.clone(); - let found = if let Some(dn) = &def_name { - self.reg.streams.get(dn).cloned() - .and_then(|d| d.methods.get(&name).cloned().map(|m| (d, m))) - .or_else(|| self.reg.find_bare_method(&name).map(|(d, m)| (d.clone(), m.clone()))) - } else { - self.reg.find_bare_method(&name).map(|(d, m)| (d.clone(), m.clone())) - }; - let old_running = self.running_thread; - self.running_thread = Some(id); - let out = if let Some((def, m)) = found { - let singleton = self.stream_instance(&def); - self.call_method(Some(def), m, singleton, args) - } else { - self.cause(&format!("no method {name}")) - }; - self.running_thread = old_running; - if let Some(t) = self.threads.iter_mut().find(|t| t.id == id) { - t.done = Some(out); - } - out - } - - /// 流实例:fork/class 有**持久单例**(流级字段状态跨调用保持);signature/main 无。 - fn stream_instance(&mut self, def: &StreamDef) -> Option { - match def.kind { - StreamKind::Fork | StreamKind::Class => { - let key = def.name.clone(); - if let Some(h) = self.stream_instances.get(&key) { - return Some(*h); - } - let h = self.new_object_typed(&def.name, &def.field_types); - self.stream_instances.insert(key, h); - Some(h) - } - _ => None, - } - } - - // ---- 表达式 ---- - - pub fn eval_expr(&mut self, e: &Expr) -> Value { - match e { - Expr::Int(v) => Value::int(*v), - Expr::Float(v) => Value::num(*v), - Expr::Str(s) => Value::string(self.intern(s)), - Expr::Char(c) => Value::chr(*c), - Expr::Bool(b) => Value::boolean(*b), - Expr::Var(name) => self - .var_get(name) - .unwrap_or_else(|| Value::nil()), - Expr::Call { qual, name, args } => { - let vals: Vec = args.iter().map(|a| self.eval_expr(a)).collect(); - self.call_to_value(qual.as_deref(), name, vals) - } - Expr::Prop { base, name } => { - if name == "res" { - // Solid::new().res — 取响应值本身 - return self.eval_expr(base); - } - let bv = self.eval_expr(base); - match bv.tag() { - Tag::Obj | Tag::Arr => { - let h = bv.as_handle(); - self.obj_prop_get(h, name).unwrap_or(Value::nil()) - } - Tag::Ref => Value::nil(), - _ => Value::nil(), - } - } - Expr::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx); - self.index_get(bv, i) - } - Expr::BinOp { op, l, r } => { - let lv = self.eval_expr(l); - let rv = self.eval_expr(r); - self.binop(op, lv, rv) - } - Expr::Unwrap { op, l } => { - let v = self.eval_expr(l); - if op == "get" { - // 引用值 → 引用读(11:get rw / get mp) - if v.tag() == Tag::Ref { - return match self.ref_read(v.as_handle()) { - Outcome::Res(val) => val, - Outcome::Ref(c) => Value::refused_str(c.0), - }; - } - // 拒绝传播(11:get mp 打印 refused: refused: ...) - v - } else { - // cause - if v.refused() { - Value::string(v.cause()) - } else { - Value::string(self.intern("")) - } - } - } - Expr::New { cls, args } => { - let vals: Vec = args.iter().map(|a| self.eval_expr(a)).collect(); - self.new_class(cls, vals) - } - Expr::NewArray { ty: _ty, size } => { - let n = self.eval_expr(size); - let n = n.as_int_or_num() as usize; - self.new_class("Array", vec![Value::int(n as i64)]) - } - Expr::RefOf(target) => self.make_ref(target), - } - } - - /// dlopen 调用导出符号(double fn(double...) -> double,14 用)。 - pub fn dl_call(&mut self, lib: &str, sym: &str, args: Vec) -> Outcome { - // 已加载缓存:声明名 → 句柄(多库独立,不串) - let cached = self.open_libs.iter().find(|(name, _)| name == lib).map(|(_, h)| *h); - let handle = match cached { - Some(h) => h, - None => { - match crate::dylib::open_any(lib) { - Some(h) => { - self.open_libs.push((lib.to_string(), h)); - h - } - None => { - return self.cause(&format!("cannot open library {lib}")); - } - } - } - }; - let Some(fptr) = crate::dylib::symbol(handle, sym) else { - return self.cause(&format!("stream {lib} refuses: no symbol {sym}")); - }; - let nums: Vec = args.iter().map(|a| a.as_int_or_num()).collect(); - unsafe { - let result: f64 = match nums.len() { - 0 => std::mem::transmute::<*mut core::ffi::c_void, fn() -> f64>(fptr)(), - 1 => std::mem::transmute::<*mut core::ffi::c_void, fn(f64) -> f64>(fptr)(nums[0]), - _ => std::mem::transmute::<*mut core::ffi::c_void, fn(f64, f64) -> f64>(fptr)(nums[0], nums[1]), - }; - Outcome::Res(Value::num(result)) - } - } - - pub fn new_class(&mut self, cls: &str, args: Vec) -> Value { - if let Some(def) = self.reg.class(cls).cloned() { - // @unfork 类拒绝(15) - if def.annos.contains(&"unfork".to_string()) { - let c = self.intern(&format!("Obj refused: class {cls} is @unfork, cannot fork")); - return Value::refused_str(c); - } - let h = self.new_object_typed(cls, &def.field_types); - if let Some(m) = def.methods.get("__init__").cloned() { - self.call_method(Some(def), m, Some(h), args); - } - Value::obj(h) - } else { - // 内置类(Array/Vector 已注入 registry;未知类拒绝) - Value::nil() - } - } - - fn make_ref(&mut self, target: &Expr) -> Value { - let perm = self.pending_ref_perm.take().unwrap_or_else(|| "rw".into()); - let follow = self.pending_ref_follow.take().unwrap_or_else(|| "u".into()); - let t = match target { - Expr::Var(name) => RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: name.clone() }, - Expr::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx).as_int_or_num() as i64; - if let Tag::Obj | Tag::Arr = bv.tag() { - RefTarget::ArrElem { obj: bv.as_handle(), index: i } - } else { - RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() } - } - } - Expr::Prop { base, name } => { - let bv = self.eval_expr(base); - if let Tag::Obj | Tag::Arr = bv.tag() { - RefTarget::ObjProp { obj: bv.as_handle(), name: name.clone() } - } else { - RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() } - } - } - _ => RefTarget::Var { frame: self.frames.len().saturating_sub(1), name: "?".into() }, - }; - self.refs.push(RefVal2 { target: t, perm, follow }); - Value::reff((self.refs.len() - 1) as u32) - } - - fn index_get(&mut self, base: Value, idx: Value) -> Value { - let i = idx.as_int_or_num() as i64; - match base.tag() { - Tag::Obj | Tag::Arr => { - let h = base.as_handle(); - let cls = self.obj_class(h); - if cls == "Solid" || cls == "SolidData" { - // 裸数组 / Solid 数据:直接下标读 - let data = self.solid_data(h); - if i < 0 || i as usize >= data.len() { - return Value::nil(); - } - data[i as usize] - } else { - // Array/Vector:调对象 get 方法 - self.invoke_on_obj(h, "get", vec![Value::int(i)]).get() - } - } - _ => Value::nil(), - } - } - - fn binop(&mut self, op: &str, l: Value, r: Value) -> Value { - // 拒绝传播保留 cause(16:get down(...) 拒绝后 + 1 仍带原因) - if l.refused() { - return l; - } - if r.refused() { - return r; - } - match op { - "+" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 + r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() + r.as_int_or_num()), - }, - "-" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 - r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() - r.as_int_or_num()), - }, - "*" => match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 * r.as_int_or_num() as i64), - _ => Value::num(l.as_int_or_num() * r.as_int_or_num()), - }, - "/" => { - let d = r.as_int_or_num(); - if d == 0.0 { - return Value::nil().with_refused(); - } - match (l.tag(), r.tag()) { - (Tag::Int, Tag::Int) => Value::int(l.as_int_or_num() as i64 / d as i64), - _ => Value::num(l.as_int_or_num() / d), - } - } - "%" => { - let d = r.as_int_or_num() as i64; - if d == 0 { - return Value::nil().with_refused(); - } - Value::int(l.as_int_or_num() as i64 % d) - } - "==" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Equal), - "!=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Equal), - "<" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Less), - ">" => Value::boolean(self.val_cmp(&l, &r) == std::cmp::Ordering::Greater), - "<=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Greater), - ">=" => Value::boolean(self.val_cmp(&l, &r) != std::cmp::Ordering::Less), - _ => Value::nil(), - } - } - - fn val_cmp(&self, l: &Value, r: &Value) -> std::cmp::Ordering { - if !matches!((l.tag(), r.tag()), (Tag::Int | Tag::Num, Tag::Int | Tag::Num)) { - return std::cmp::Ordering::Equal; - } - l.as_int_or_num().partial_cmp(&r.as_int_or_num()).unwrap_or(std::cmp::Ordering::Equal) - } - - // ---- 语句 ---- - - pub fn exec_stmt(&mut self, s: &Stmt) -> Flow { - match s { - Stmt::If { cond, then, els } => { - let c = self.eval_expr(cond); - if c.truthy() { - self.exec_block(then) - } else if let Some(e) = els { - self.exec_block(e) - } else { - Flow::Next - } - } - Stmt::While { cond, body } => { - loop { - let c = self.eval_expr(cond); - if !c.truthy() { - return Flow::Next; - } - match self.exec_block(body) { - Flow::Break => return Flow::Next, - Flow::Continue => continue, - Flow::Ret(o) => return Flow::Ret(o), - Flow::Next => {} - } - } - } - Stmt::For { init, cond, update, body } => { - if let Some(i) = init { - if let Flow::Ret(o) = self.exec_stmt(i) { - return Flow::Ret(o); - } - } - loop { - if let Some(c) = cond { - if !self.eval_expr(c).truthy() { - return Flow::Next; - } - } - match self.exec_block(body) { - Flow::Break => return Flow::Next, - Flow::Continue => { - if let Some(u) = update { - if let Flow::Ret(o) = self.exec_stmt(u) { - return Flow::Ret(o); - } - } - continue; - } - Flow::Ret(o) => return Flow::Ret(o), - Flow::Next => {} - } - if let Some(u) = update { - if let Flow::Ret(o) = self.exec_stmt(u) { - return Flow::Ret(o); - } - } - } - } - Stmt::Break => Flow::Break, - Stmt::Continue => Flow::Continue, - Stmt::Ret { kind, values } => match kind { - RetKind::Res => { - let mut vals: Vec = values.iter().map(|v| self.eval_expr(v)).collect(); - match vals.len() { - 0 => Flow::Ret(Outcome::Res(Value::nil())), - 1 => Flow::Ret(Outcome::Res(vals.remove(0))), - _ => { - // 多值 → 数组(Solid) - let h = self.solid_new(vals); - Flow::Ret(Outcome::Res(Value::obj(h))) - } - } - } - RetKind::Ref => { - let reason = values - .first() - .map(|v| self.eval_expr(v)) - .map(|v| match v.tag() { - Tag::Str => v.as_str(), - _ => { - let s = self.fmt_value(&v); - self.intern(&s) - } - }) - .unwrap_or_else(|| self.intern("nothing")); - Flow::Ret(Outcome::Ref(Cause(reason))) - } - }, - Stmt::Assign { vtype, is_const, is_thread, target, op, value } => { - let _ = (is_const, is_thread); - let v = self.eval_expr(value); - // 拒绝传播:非 ALL 声明赋值右侧拒绝 → 方法拒绝(11:mp = 99 → ⛔) - // nothing(void 方法自然结束)不传播 - if v.refused() && vtype.as_deref() != Some("ALL") - && self.strs.get(v.cause()) != "nothing" { - return Flow::Ret(Outcome::Ref(Cause(v.cause()))); - } - // 赋值目标是引用变量 → 引用写(rw = 5) - if let AssignTarget::Var(name) = target { - if op == "=" && vtype.is_none() { - if let Some(old) = self.var_get_raw(name) { - if old.tag() == Tag::Ref { - return self.ref_assign(name, old, v); - } - } - } - } - match target { - AssignTarget::Var(name) => { - let v = if op == "=" { - v - } else { - let old = self.var_get(name).unwrap_or(Value::nil()); - self.binop(&op[..1], old, v) - }; - if vtype.is_some() { - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.clone(), v); - } - } else { - self.var_set(name, v); - } - } - AssignTarget::Prop { base, name } => { - let bv = self.eval_expr(base); - if let Tag::Obj | Tag::Arr = bv.tag() { - let h = bv.as_handle(); - let v = if op == "=" { - v - } else { - let old = self.obj_prop_get(h, name).unwrap_or(Value::nil()); - self.binop(&op[..1], old, v) - }; - self.obj_prop_set(h, name, v); - } - } - AssignTarget::Index { base, idx } => { - let bv = self.eval_expr(base); - let i = self.eval_expr(idx); - self.index_set(bv, i, v); - } - } - Flow::Next - } - Stmt::RefDecl { perm, follow, base, name, init } => { - let _ = base; - self.pending_ref_perm = Some(perm.clone()); - self.pending_ref_follow = Some(follow.clone()); - let rv = self.eval_expr(init); - if let Some(f) = self.frames.last_mut() { - f.scope.insert(name.clone(), rv); - } - Flow::Next - } - Stmt::Inc { name, op } => { - let old = self.var_get(name).unwrap_or(Value::nil()); - let delta = if op == "++" { 1 } else { -1 }; - if old.tag() == Tag::Ref { - // m 权限指针移动(11:mp++) - let h = old.as_handle(); - if !self.refs[h as usize].perm.contains('m') { - return Flow::Ret(Outcome::Ref(Cause(self.intern("reference has no move permission")))); - } - if let RefTarget::ArrElem { obj, index } = self.refs[h as usize].target.clone() { - self.refs[h as usize].target = RefTarget::ArrElem { obj, index: index + delta }; - } - Flow::Next - } else { - let nv = match old.tag() { - Tag::Int => Value::int(old.as_int_or_num() as i64 + delta), - Tag::Num => Value::num(old.as_int_or_num() + delta as f64), - _ => old, - }; - self.var_set(name, nv); - Flow::Next - } - } - Stmt::Expr(e) => { - let v = self.eval_expr(e); - // 调用语句拒绝 → 传播(nothing = void 自然结束,不传播) - if v.refused() && matches!(e, Expr::Call { .. }) - && self.strs.get(v.cause()) != "nothing" { - return Flow::Ret(Outcome::Ref(Cause(v.cause()))); - } - Flow::Next - } - } - } - - fn exec_block(&mut self, stmts: &[Stmt]) -> Flow { - for s in stmts { - match self.exec_stmt(s) { - Flow::Next => {} - other => return other, - } - } - Flow::Next - } - - fn index_set(&mut self, base: Value, idx: Value, v: Value) { - let i = idx.as_int_or_num() as i64; - match base.tag() { - Tag::Obj | Tag::Arr => { - let h = base.as_handle(); - let cls = self.obj_class(h); - if cls == "Solid" { - if let Some(d) = self.objects.get_mut(h as usize) { - if let Some(slot) = d.attrs.iter().position(|(k, _)| self.strs.get(*k) == "$data") { - // Solid 数据存 attrs 的 "$data" 键(builtin 约定) - let _ = slot; - } - let _ = (d, i, v); - } - } else { - self.invoke_on_obj(h, "set", vec![Value::int(i), v]); - } - } - _ => {} - } - } - - /// 创建 Solid 实例(多值返回/内置)。 - pub fn solid_new(&mut self, data: Vec) -> u32 { - let def = self.intern("Solid"); - self.objects.push(ObjData { def, fields: vec![], attrs: Vec::new() }); - let h = (self.objects.len() - 1) as u32; - // 数据存 attrs "$data" - let key = self.intern("$data"); - let dh = self.objs_data_handle(data); - self.objects[h as usize].attrs.push((key, Value::arr(dh))); - h - } - - pub(crate) fn objs_data_handle(&mut self, data: Vec) -> u32 { - // 数据 Vec 存独立 "Data" 对象 - let def = self.intern("SolidData"); - self.objects.push(ObjData { def, fields: data, attrs: Vec::new() }); - (self.objects.len() - 1) as u32 - } - - // ---- 值格式化(打印/字符串化) ---- - - pub fn fmt_value(&mut self, v: &Value) -> String { - if v.refused() { - return format!("refused: {}", self.strs.get(v.cause())); - } - match v.tag() { - Tag::Nil => "nil".to_string(), - Tag::Int => v.as_int_or_num().to_string(), - Tag::Num => { - let f = v.as_int_or_num(); - let s = format!("{f}"); - s - } - Tag::Bool => if v.as_bool() { "true" } else { "false" }.to_string(), - Tag::Str => self.strs.get(v.as_str()).to_string(), - Tag::Char => (v.as_char() as char).to_string(), - Tag::Obj | Tag::Arr => { - let h = v.as_handle(); - self.fmt_object(h) - } - Tag::Ref => "".to_string(), - } - } - - fn fmt_object(&mut self, h: u32) -> String { - let cls = self.obj_class(h); - if cls == "Solid" || cls == "SolidData" { - let data = self.solid_data(h).to_vec(); - return format!("[{}]", data.iter().map(|x| self.fmt_value(x)).collect::>().join(", ")); - } - if cls == "Array" || cls == "Vector" { - // data 属性(this::data = Solid::new().res)或声明字段 - let o = &self.objects[h as usize]; - let mut data_h = o.fields.first().map(|v| v.as_handle()); - if data_h.is_none() { - for (k, v) in &o.attrs { - if self.strs.get(*k) == "data" { - if let Tag::Obj | Tag::Arr = v.tag() { - data_h = Some(v.as_handle()); - } - } - } - } - if let Some(dh) = data_h { - let data = self.solid_data(dh).to_vec(); - return format!("[{}]", data.iter().map(|x| self.fmt_value(x)).collect::>().join(", ")); - } - } - // 一般对象: - let o = self.objects[h as usize].clone(); - let mut parts = Vec::new(); - for (k, val) in &o.attrs { - let kn = self.strs.get(*k).to_string(); - if kn == "$data" { - continue; - } - parts.push(format!("{kn}: {}", self.fmt_value(val))); - } - format!("", cls, parts.join(", ")) - } - - /// Solid/SolidData 的数据读取(借用处理:复制出来)。 - pub fn solid_data(&self, h: u32) -> Vec { - let o = &self.objects[h as usize]; - let cls = self.strs.get(o.def).to_string(); - if cls == "Solid" { - // attrs "$data" → Arr 句柄 → SolidData - for (k, v) in &o.attrs { - if self.strs.get(*k) == "$data" { - if let Tag::Arr = v.tag() { - return self.objects[v.as_handle() as usize].fields.clone(); - } - } - } - Vec::new() - } else { - o.fields.clone() - } - } - - /// 修改 Solid 数据。 - pub fn solid_set(&mut self, h: u32, i: usize, v: Value) { - let data_h = self.solid_data_handle(h); - if let Some(dh) = data_h { - self.objects[dh as usize].fields[i] = v; - } - } - - pub fn solid_data_handle(&self, h: u32) -> Option { - let o = &self.objects[h as usize]; - let cls = self.strs.get(o.def).to_string(); - if cls == "SolidData" { - return Some(h); - } - // Array/Vector 类:data 字段(this::data = Solid::new().res)→ 继续解 Solid 的 $data - if let Some(d) = self.reg.streams.get(&cls) { - if let Some(i) = d.field_names.iter().position(|n| n == "data") { - if let Tag::Obj | Tag::Arr = o.fields[i].tag() { - let solid_h = o.fields[i].as_handle(); - if let Some(dh) = self.solid_data_handle(solid_h) { - return Some(dh); - } - } - } - } - for (k, v) in &o.attrs { - let kn = self.strs.get(*k); - if (kn == "$data" || kn == "data") { - if let Tag::Obj | Tag::Arr = v.tag() { - let solid_h = v.as_handle(); - if let Some(dh) = self.solid_data_handle(solid_h) { - return Some(dh); - } - } - } - } - None - } - - pub fn solid_push(&mut self, h: u32, v: Value) { - let data_h = self.solid_data_handle(h); - if let Some(dh) = data_h { - self.objects[dh as usize].fields.push(v); - } - } - - /// 原地排序 Solid 数据(__sort__ 内部方法):数字升序 → 字符串字典序 → 其余保持稳定序。 - pub fn sort_data(&mut self, dh: u32) { - use std::cmp::Ordering; - // 先复制出来排序,避免借用冲突 - let mut vals = self.objects[dh as usize].fields.clone(); - vals.sort_by(|a, b| { - let ka = self.sort_key(a); - let kb = self.sort_key(b); - match (ka, kb) { - (Some(x), Some(y)) => x.partial_cmp(&y).unwrap_or(Ordering::Equal), - (Some(_), None) => Ordering::Less, - (None, Some(_)) => Ordering::Greater, - (None, None) => Ordering::Equal, - } - }); - self.objects[dh as usize].fields = vals; - } - - fn sort_key(&self, v: &Value) -> Option { - match v.tag() { - Tag::Int | Tag::Num => Some(v.as_int_or_num()), - Tag::Str => { - // 字符串字典序:用字符串池内容编码成可比较键(前缀优先) - let s = self.strs.get(v.as_str()); - // 用前 4 字符的字节值编码(大端),保证字典序近似;短串自然靠前 - let b = s.as_bytes(); - let mut key = 0.0f64; - for (i, c) in b.iter().take(4).enumerate() { - key += (*c as f64) * 256f64.powi(3 - i as i32); - } - // 长度作为微小尾数(保证短串 < 长串同前缀) - Some(key + (s.len() as f64) * 1e-9) - } - _ => None, - } - } -} - -impl Default for Interp { - fn default() -> Self { - Self::new() - } -} - -/// 内置流名(流可作为值传递)。 -pub fn builtin_stream_name(name: &str) -> bool { - matches!(name, "CIO" | "SIO" | "FIO" | "IO" | "Com" | "Time" | "Obj" | "Solid" | "Arrays" | "Ref" | "Threads" | "Taskm") -} diff --git a/rust/crates/bbb-vm/src/lib.rs b/rust/crates/bbb-vm/src/lib.rs deleted file mode 100644 index 167f738..0000000 --- a/rust/crates/bbb-vm/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! bbb-vm — BioLang 解释器(M3)。 -//! -//! 设计(对齐旧 C interp.c + examples 语义): -//! - 流注册表:签名流/分叉/类/Main 统一登记,方法名解析(qual::name → -//! 流方法 → 签名回退分叉 → 对象方法;裸调用 → 全局方法名扫描); -//! - 值语义:Value 16B(bbb-core),调用结果带 REFUSED 位(cause 为 -//! 字符串句柄),`get`/`cause` 是位测试; -//! - 对象:arena 句柄(ObjData = 类定义 + 字段 + 动态属性), -//! Array/Vector 是注入的 Bio 类源码(底层 Solid 流,Rust 实现); -//! - 控制流:Flow 枚举(Next/Ret/Break/Continue)驱动语句块。 - -pub mod builtin; -pub mod dylib; -pub mod interp; -pub mod project; -pub mod registry; - -pub use interp::{Interp, RunOutcome}; -pub use project::load_project_sources; -pub use registry::Registry; - -/// 预置的 Array/Vector 类源码(Bio 语言编写,与旧 C 注入的类一致)。 -pub const BUILTIN_CLASS_SRC: &str = r#" -Class Array { - void __init__(n int) { - this::data = Solid::new().res; - ALL i = 0; - while (i < n) { Solid::push(this::data, 0); i = i + 1; } - Arrays::add(this); - } - int len() { res Solid::len(this::data); } - void set(i int, v) { Solid::set(this::data, i, v); } - int get(i int) { res Solid::get(this::data, i); } - void push(v) { Solid::push(this::data, v); } - string join(sep string) { res Solid::join(this::data, sep); } - void sort() { __sort__(); } -} -Class Vector { - void __init__() { this::data = Solid::new().res; Arrays::add(this); } - int len() { res Solid::len(this::data); } - void set(i int, v) { Solid::set(this::data, i, v); } - int get(i int) { res Solid::get(this::data, i); } - void push(v) { Solid::push(this::data, v); } - void sort() { __sort__(); } -} -"#; diff --git a/rust/crates/bbb-vm/src/project.rs b/rust/crates/bbb-vm/src/project.rs deleted file mode 100644 index 13174e5..0000000 --- a/rust/crates/bbb-vm/src/project.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! 项目加载:目录模式(package.toml + src/ + utils/ 合并解析)。 - -use std::path::PathBuf; - -use bbb_syntax::ast::{Decl, Program}; -use bbb_syntax::parser::parse_source; - -/// 加载项目所有 .bio 文件并合并成一个 Program。 -/// need 跨文件配对由 registry.register 统一校验。 -pub fn load_project_sources(root: &PathBuf) -> Result { - let mut files: Vec = Vec::new(); - for base in ["src", "utils"] { - let d = root.join(base); - if !d.is_dir() { - continue; - } - if let Ok(entries) = std::fs::read_dir(&d) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio" || x == "bl").unwrap_or(false) { - files.push(p); - } - } - } - } - files.sort(); - if files.is_empty() { - return Err(format!("{}: no .bio files under src/ or utils/", root.display())); - } - - let mut decls: Vec = Vec::new(); - let mut main = None; - let mut kind = String::new(); - for f in &files { - let src = std::fs::read_to_string(f) - .map_err(|e| format!("{}: read failed: {e}", f.display()))?; - let (prog, errs) = parse_source(&src); - if !errs.is_empty() { - return Err(format!( - "{}: {}", - f.display(), - errs.iter().map(|e| e.to_string()).collect::>().join("; ") - )); - } - if !prog.kind.is_empty() { - kind = prog.kind.clone(); - } - if prog.main.is_some() { - if main.is_some() { - return Err(format!("{}: multiple Main stream definitions", f.display())); - } - main = prog.main; - } - decls.extend(prog.decls); - } - Ok(Program { kind, decls, main }) -} diff --git a/rust/crates/bbb-vm/src/registry.rs b/rust/crates/bbb-vm/src/registry.rs deleted file mode 100644 index 36865cf..0000000 --- a/rust/crates/bbb-vm/src/registry.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! 流注册表:把 Program 的声明构建成可调用的流/方法表。 - -use std::collections::HashMap; - -use bbb_syntax::ast::{Decl, Member, Method, Program}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StreamKind { - Signature, // Stream X { ... } 仅签名 - Fork, // Sig X { ... } 实现 - Class, // Class X { ... } - Binary, // Stream X & "lib.so"(本轮仅注册,调用拒绝) - Main, // Main 流 -} - -#[derive(Debug, Clone)] -pub struct StreamDef { - pub name: String, - pub kind: StreamKind, - pub sig: Option, // fork 的签名流名 - pub bin_file: Option, // StreamBin 的库文件 - pub methods: HashMap, - pub field_names: Vec, - pub field_types: Vec, // 与 field_names 一一对应(默认值初始化用) - pub annos: Vec, // @onlyread/@unfork(流注解) -} - -impl StreamDef { - pub fn is_class(&self) -> bool { - self.kind == StreamKind::Class - } -} - -#[derive(Debug, Default)] -pub struct Registry { - pub streams: HashMap, - pub order: Vec, // 声明顺序(对象打印/调试用) -} - -impl Registry { - pub fn new() -> Self { - Registry::default() - } - - /// 把 Program 的声明注册进表。返回未满足的 need(name, kind)。 - pub fn register(&mut self, prog: &Program) -> Vec<(String, String)> { - let mut unmet = Vec::new(); - let mut needs = Vec::new(); - for d in &prog.decls { - match d { - Decl::Need { kind, name } => needs.push((kind.clone(), name.clone())), - Decl::StreamSig { name, members, annos, .. } => { - let def = build_stream(name.clone(), StreamKind::Signature, None, members, annos); - self.insert(def); - } - Decl::StreamBin { name, file, members, .. } => { - let def = build_stream(name.clone(), StreamKind::Binary, None, members, &[]); - let def = StreamDef { bin_file: Some(file.clone()), ..def }; - self.insert(def); - } - Decl::Class { name, members, annos, implements } => { - let def = build_stream(name.clone(), StreamKind::Class, None, members, annos); - // 接口实现检查:类必须提供接口的全部方法签名 - for iname in implements { - let Some(idef) = self.streams.get(iname).cloned() else { - unmet.push(("Interface".into(), iname.clone())); - continue; - }; - for (mn, mm) in &idef.methods { - if !def.methods.contains_key(mn) { - unmet.push(( - format!("Interface {iname} method {mn}"), - format!("class {name} does not implement it"), - )); - } - } - } - self.insert(def); - } - Decl::Interface { name, members, annos, .. } => { - // 接口 = 签名流(只有签名方法;成员 body 必须为空) - let def = build_stream(name.clone(), StreamKind::Signature, None, members, annos); - self.insert(def); - } - Decl::Fork { sig, name, members, annos, .. } => { - let mut def = build_stream(name.clone(), StreamKind::Fork, Some(sig.clone()), members, annos); - // 字段/签名方法继承自签名流(15:int count 声明在 Stream ReadOnly) - if let Some(sig_def) = self.streams.get(sig).cloned() { - let mut names = sig_def.field_names.clone(); - let mut types = sig_def.field_types.clone(); - for (i, n) in names.iter().enumerate() { - if !def.field_names.contains(n) { - def.field_names.push(n.clone()); - def.field_types.push(types[i].clone()); - } - } - for (mn, mm) in &sig_def.methods { - def.methods.entry(mn.clone()).or_insert_with(|| mm.clone()); - } - } - self.insert(def); - } - Decl::Const { .. } => {} - } - } - if let Some(m) = &prog.main { - let mut methods = HashMap::new(); - for meth in &m.methods { - methods.insert(meth.name.clone(), meth.clone()); - } - let def = StreamDef { - name: "Main".into(), - kind: StreamKind::Main, - sig: None, - methods, - field_names: Vec::new(), - field_types: Vec::new(), - annos: Vec::new(), - bin_file: None, - }; - self.insert(def); - } - // need 校验 - for (kind, name) in needs { - let ok = match kind.as_str() { - "value" => prog.decls.iter().any(|d| matches!(d, Decl::Const { name: n, .. } if n == &name)), - "function" => self.find_bare_method(&name).is_some(), - "stream" | "Stream" => self.streams.contains_key(&name), - "Class" => self.streams.get(&name).map(|s| s.is_class()).unwrap_or(false), - _ => false, - }; - if !ok { - unmet.push((kind, name)); - } - } - unmet - } - - fn insert(&mut self, def: StreamDef) { - self.order.push(def.name.clone()); - self.streams.insert(def.name.clone(), def); - } - - /// 签名流调用回退:qual 是签名流时找其分叉实现。 - pub fn resolve_qual(&self, qual: &str) -> Option<&StreamDef> { - let d = self.streams.get(qual)?; - if d.kind == StreamKind::Signature { - for other in self.streams.values() { - if other.sig.as_deref() == Some(qual) { - return Some(other); - } - } - } - Some(d) - } - - /// 裸调用:全局按方法名扫描(Main 优先,然后声明顺序)。 - pub fn find_bare_method(&self, name: &str) -> Option<(&StreamDef, &Method)> { - if let Some(main) = self.streams.get("Main") { - if let Some(m) = main.methods.get(name) { - return Some((main, m)); - } - } - for key in &self.order { - if key == "Main" { - continue; - } - if let Some(d) = self.streams.get(key) { - if let Some(m) = d.methods.get(name) { - // 签名方法(无体)不是实现,跳过;找分叉的实现 - if m.body.is_empty() { - continue; - } - return Some((d, m)); - } - } - } - None - } - - /// 类定义(new 用)。 - pub fn class(&self, name: &str) -> Option<&StreamDef> { - let d = self.streams.get(name)?; - if d.is_class() { - Some(d) - } else { - None - } - } -} - -fn build_stream( - name: String, - kind: StreamKind, - sig: Option, - members: &[Member], - annos: &[String], -) -> StreamDef { - let mut methods = HashMap::new(); - let mut field_names = Vec::new(); - let mut field_types = Vec::new(); - for m in members { - match m { - Member::Method(meth) => { - methods.insert(meth.name.clone(), meth.clone()); - } - Member::Field { ty, names } => { - for n in names { - field_names.push(n.clone()); - field_types.push(ty.clone()); - } - } - } - } - StreamDef { name, kind, sig, bin_file: None, methods, field_names, field_types, annos: annos.to_vec() } -} diff --git a/rust/crates/bbb-vm/tests/dylib.rs b/rust/crates/bbb-vm/tests/dylib.rs deleted file mode 100644 index 5a6afba..0000000 --- a/rust/crates/bbb-vm/tests/dylib.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! dylib 模块测试:候选名生成 + 实际加载/符号查找(Linux 环境)。 - -use bbb_vm::dylib; - -#[test] -fn candidate_names_keeps_original() { - let c = dylib::candidate_names("libm.so"); - assert_eq!(c[0], "libm.so"); - assert!(c.contains(&"libm.so".to_string())); -} - -#[test] -fn candidate_names_linux_so() { - // Linux:libm.so → 版本化兜底 libm.so.6 / libm.so.1 - let c = dylib::candidate_names("libm.so"); - assert!(c.contains(&"libm.so.6".to_string()), "{c:?}"); -} - -#[test] -fn candidate_names_versioned_no_garbage() { - // libm.so.6 不应生成 libm.so.so 之类的垃圾 - let c = dylib::candidate_names("libm.so.6"); - assert!(!c.iter().any(|x| x.contains(".so.so")), "{c:?}"); - assert!(c.contains(&"libm.so".to_string()), "{c:?}"); -} - -#[test] -fn candidate_names_bare_lib() { - let c = dylib::candidate_names("libm"); - assert!(c.contains(&"libm.so".to_string()), "{c:?}"); - assert!(c.contains(&"libm.so.6".to_string()), "{c:?}"); -} - -#[cfg(unix)] -#[test] -fn open_and_symbol_libm() { - // 平台真实库:声明 libm.so(链接脚本),应通过候选命中 libm.so.6 - let h = dylib::open_any("libm.so").expect("open libm"); - let s = dylib::symbol(h, "sin").expect("symbol sin"); - let f: fn(f64) -> f64 = unsafe { std::mem::transmute(s) }; - assert_eq!(f(0.0), 0.0); - assert!((f(std::f64::consts::FRAC_PI_2) - 1.0).abs() < 1e-9); -} diff --git a/rust/crates/bbb-vm/tests/regression.rs b/rust/crates/bbb-vm/tests/regression.rs deleted file mode 100644 index 5372041..0000000 --- a/rust/crates/bbb-vm/tests/regression.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! M3 解释器回归:examples 01-08 + 12 + 13 输出断言。 -//! 完整期望值以旧 C 解释器(bin/bio)实测输出为准(标准层)。 - -use std::path::PathBuf; - -use bbb_syntax::parser::parse_source; -use bbb_vm::interp::Interp; - -fn examples_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap().parent().unwrap() - .join("examples") -} - -fn run_file(name: &str) -> String { - let path = examples_dir().join(name); - let src = std::fs::read_to_string(&path).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "{name} parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "{name} unmet needs: {:?}", out.unmet_needs); - out.stdout -} - -#[test] -fn ex01_hello() { - let out = run_file("01-hello.bio"); - assert_eq!(out, "Hello, BioLang!\nEvery call in BioLang is a request.\nThis one was just a request to print a line.\n"); -} - -#[test] -fn ex02_requests() { - let out = run_file("02-requests.bio"); - assert!(out.contains("3 + 4 = 7")); - assert!(out.contains("div cause: division by zero")); - assert!(out.contains("missing method cause: stream MyCalc refuses: no method sqrt")); - assert!(out.contains("default return cause: nothing")); - assert!(out.contains("if is false (default ref nothing)")); -} - -#[test] -fn ex03_control_flow() { - let out = run_file("03-control-flow.bio"); - assert!(out.contains("1..10 sum (while) = 55")); - assert!(out.contains("5! (for) = 120")); - assert!(out.contains("1 2 3 5 6 7")); - assert!(out.contains("for(;;) counted: 3")); -} - -#[test] -fn ex04_streams_fork() { - let out = run_file("04-streams-fork.bio"); - assert!(out.contains("Calc::add(2,3) = 5")); - assert!(out.contains("bare add(10,20) = 30")); - assert!(out.contains("count = 3 doubleGet = 6")); - assert!(out.contains("hello from a passed stream!")); -} - -#[test] -fn ex05_io() { - let out = run_file("05-io-substreams.bio"); - assert!(out.contains("SIO::format → 2 + 3 = 5")); - assert!(out.contains("SIO::upper → HELLO")); - assert!(out.contains("SIO::getln → line one")); - assert!(out.contains("FIO read back: Hello from BioLang! Appended line")); -} - -#[test] -fn ex06_classes() { - let out = run_file("06-classes-objects.bio"); - assert!(out.contains("object: ")); - assert!(out.contains("name = TAK hp = 88")); - assert!(out.contains("Obj::call getHp() = 100")); - assert!(out.contains("h::getName() = TAK")); -} - -#[test] -fn ex07_arrays() { - let out = run_file("07-arrays.bio"); - assert!(out.contains("array: [10, 20, 30]")); - assert!(out.contains("after push: [10, 20, 30, 40] len: 4")); - assert!(out.contains("join(-): 10-20-30-40")); - assert!(out.contains("a[1] = 20")); - assert!(out.contains("after a[1] = 99: [10, 99, 30, 40]")); - assert!(out.contains("new int[4] squares: [0, 1, 4, 9]")); - assert!(out.contains("vector: [10, 20, 30] len: 3")); - assert!(out.contains("Arrays count: 3")); - assert!(out.contains("after forget: 2")); -} - -#[test] -fn ex08_multi_return() { - let out = run_file("08-multi-return.bio"); - assert!(out.contains("triple(10) = [10, 20, 30]")); - assert!(out.contains("arr = [10, 20, 30] arr[1] = 20 len = 3")); -} - -#[test] -fn ex12_computation() { - let out = run_file("12-computation.bio"); - assert!(out.contains("Com::abs(0-5) = 5 Com::sqrt(9) = 3")); - assert!(out.contains("Com::pow(2,10) = 1024")); - assert!(out.contains("Time::reset(forked) ok")); -} - -#[test] -fn ex13_need() { - let out = run_file("13-need.bio"); - assert!(out.contains("PI = 3")); - assert!(out.contains("hello, TAK")); - assert!(out.contains("writing via a needed stream")); - assert!(out.contains("hero created, hp = 0")); -} - -#[test] -fn project_multi_file() { - let root = examples_dir().join("project"); - let prog = bbb_vm::load_project_sources(&root).unwrap(); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "unmet: {:?}", out.unmet_needs); - assert_eq!(out.stdout, "main entry — Hello from utils/\nhello, project\n"); -} - -#[test] -fn need_unmet_is_error() { - let src = r#" -program main; -need value MISSING; -Main { void exec() { CIO::println("x"); } } -"#; - let (prog, errs) = parse_source(src); - assert!(errs.is_empty()); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert_eq!(out.unmet_needs, vec![("value".to_string(), "MISSING".to_string())]); -} - -#[test] -fn ex09_threads() { - let out = run_file("09-threads.bio"); - assert!(out.contains("live threads: 2")); - assert!(out.contains("thread 1 10! = 3628800")); - assert!(out.contains("thread 2 countUp = 5")); -} - -#[test] -fn ex10_taskm() { - let out = run_file("10-taskm.bio"); - assert!(out.contains("tasks: 2")); - assert!(out.contains("tasks done: 0")); - assert!(out.contains("jobA sum = 15")); - assert!(out.contains("jobB 2^4 = 16")); -} - -#[test] -fn ex11_smart_refs() { - let out = run_file("11-smart-refs.bio"); - assert!(out.contains("u read counter = 10")); - assert!(out.contains("read-only write → Ref refused: reference is read-only, cannot write")); - assert!(out.contains("rw read = 10")); - assert!(out.contains("counter after rw = 5 → 5")); - assert!(out.contains("thread 2 a-layer = 44")); - assert!(out.contains("thread 1 a-layer = 42")); - assert!(out.contains("t1 = 42 t2 = 44")); - assert!(out.contains("at [1] = refused: refused: reference is write-only, cannot read")); - assert!(out.contains("⛔ main stream refused: refused: reference is read-only, cannot write")); -} - -#[test] -fn ex14_binary_lib() { - let out = run_file("14-binary-lib.bio"); - assert!(out.contains("m::sin(0) = 0")); - assert!(out.contains("m::cos(0) = 1")); - assert!(out.contains("m::pow(2,10) = 1024")); - assert!(out.contains("m::doubleIt(21) = 42")); -} - -#[test] -fn ex15_annotations() { - let out = run_file("15-annotations.bio"); - assert!(out.contains("refused: stream Sealed is @unfork, cannot fork")); - assert!(out.contains("new @unfork class → Obj refused: class Frozen is @unfork, cannot fork")); - assert!(out.contains("onlyread get = 0")); - assert!(out.contains("onlyread bump → refused: stream RO is @onlyread — bump() is a write method")); - assert!(out.contains("alias get = 0")); - assert!(out.contains("alias touch → refused: stream RA is @onlyread — touch() is a write method")); - assert!(out.contains("marked write → refused: stream G is @onlyread — markedWrite() is a write method")); - assert!(out.contains("marked read = 1")); - assert!(out.contains("safe read = 1")); -} - -#[test] -fn ex16_phonebooth() { - let out = run_file("16-phonebooth.bio"); - assert!(out.contains("t1 sum = 5050 t2 sum = 20100")); - assert!(out.contains("global 5! = 120")); - assert!(out.contains("global 6! = 720")); - assert!(out.contains("direct recursion → refused: phone-booth method down does not support recursion")); - assert!(out.contains("indirect recursion → refused: phone-booth method down2 does not support recursion")); - assert!(out.contains("ucall recursion → refused: phone-booth method uDown does not support recursion")); - assert!(out.contains("plain fact(10) = 3628800")); -} - -fn fixtures_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap() - .join("tests").join("fixtures") -} - -fn run_fixture(name: &str) -> String { - let path = fixtures_dir().join(name); - let src = std::fs::read_to_string(&path).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "{name} parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!(out.unmet_needs.is_empty(), "{name} unmet needs: {:?}", out.unmet_needs); - out.stdout -} - -#[test] -fn new_classname_decl() { - // 类名 = new 类():宏展开语法(parser 层) - let out = run_fixture("test_new.bio"); - assert!(out.contains("b.val = 42"), "got: {out}"); -} - -#[test] -fn arrays_sort_inplace() { - // arr::sort() → 内部 __sort__() 原地排序 - let out = run_fixture("test_sort.bio"); - assert!(out.contains("after: [10, 20, 30, 40, 50]"), "got: {out}"); - // Arrays::sort(arr) 流方法同效 - let out2 = run_fixture("test_sort2.bio"); - assert!(out2.contains("after: [10, 20, 30, 50]"), "got: {out2}"); -} - -#[test] -fn interface_basic_and_polymorphism() { - let out = run_fixture("test_iface2.bio"); - assert!(out.contains("circle area: 12.56"), "got: {out}"); - assert!(out.contains("square area: 9"), "got: {out}"); -} - -#[test] -fn interface_missing_method_rejected() { - let src = std::fs::read_to_string(fixtures_dir().join("test_iface_bad.bio")).unwrap(); - let (prog, errs) = parse_source(&src); - assert!(errs.is_empty(), "parse errors: {errs:?}"); - let mut interp = Interp::new(); - let out = interp.run(&prog); - assert!( - out.unmet_needs.iter().any(|(k, _)| k.contains("draw")), - "expected missing draw method, got: {:?}", - out.unmet_needs - ); -} diff --git a/rust/crates/bbb-wasm/Cargo.toml b/rust/crates/bbb-wasm/Cargo.toml deleted file mode 100644 index 6667e54..0000000 --- a/rust/crates/bbb-wasm/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "bbb-wasm" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "BiuBiuBiu 插件 wasm 核心:Rust 实现的格式化器/词法器(VSCode 插件用)" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -bbb-syntax = { path = "../bbb-syntax" } diff --git a/rust/crates/bbb-wasm/src/lib.rs b/rust/crates/bbb-wasm/src/lib.rs deleted file mode 100644 index b2e4e0d..0000000 --- a/rust/crates/bbb-wasm/src/lib.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! bbb-wasm — BiuBiuBiu 格式化器/词法器,编译为 wasm32 供 VSCode 插件调用。 -//! -//! 导出(手写 C ABI,零 wasm-bindgen 依赖): -//! - `format(ptr, len, out_cap) -> usize`:格式化源码,写入输出缓冲,返回长度 -//! - `format_len(ptr, len) -> usize`:预计算格式化后长度(JS 分配缓冲) -//! -//! formatter 规则(对齐旧 formatter.js 0.16.0 行为,17 examples 幂等): -//! - 4 空格缩进(块级);`{` 换行、`}` 缩进减一换行 -//! - 运算符两侧空格;`::`/`.`/`,`/`;`/括号规则明确 -//! - 关键字与 `(` 之间空格(if/while/for);函数调用名与 `(` 无空格 -//! - 字符串/字符 token 重新包裹引号(内容原样);注释原样 -//! - 连续空行压缩为单空行;行尾无空格 - -use std::slice; - -/// 格式化输入(len 字节 UTF-8),写入 out(out_cap 字节),返回写入字节数。 -#[no_mangle] -pub extern "C" fn format(src_ptr: *const u8, src_len: usize, out_ptr: *mut u8, out_cap: usize) -> usize { - if src_ptr.is_null() || src_len == 0 || out_ptr.is_null() || out_cap == 0 { - return 0; - } - let src = unsafe { slice::from_raw_parts(src_ptr, src_len) }; - let Some(text) = std::str::from_utf8(src).ok() else { return 0 }; - let out = fmt(text); - let bytes = out.as_bytes(); - if bytes.len() > out_cap { - return 0; - } - unsafe { - std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_ptr, bytes.len()); - } - bytes.len() -} - -/// 预计算格式化后长度。 -#[no_mangle] -pub extern "C" fn format_len(src_ptr: *const u8, src_len: usize) -> usize { - if src_ptr.is_null() || src_len == 0 { - return 0; - } - let src = unsafe { slice::from_raw_parts(src_ptr, src_len) }; - let Some(text) = std::str::from_utf8(src).ok() else { return 0 }; - fmt(text).len() -} - -use bbb_syntax::lexer::{Token, TokenKind}; - -const CONTROL_KW: &[&str] = &["if", "while", "for", "else"]; -const SPACE_OP: &[&str] = &[ - "+", "-", "*", "/", "%", "=", "==", "!=", "<", ">", "<=", ">=", - "&&", "||", "+=", "-=", "*=", "/=", "%=", -]; - -fn is_ident_like(t: &Token) -> bool { - matches!(t.kind, TokenKind::Ident | TokenKind::Keyword) -} - -/// Rust 格式化器主体。 -pub fn fmt(src: &str) -> String { - let mut toks = Vec::new(); - if bbb_syntax::lexer::tokenize(src, &mut toks).is_err() { - return src.to_string(); // 词法错误:原样返回 - } - let mut out = String::new(); - let mut indent: usize = 0; - let mut prev: Option = None; - let mut line_start = true; - let mut paren_depth: usize = 0; - - for t in toks.iter() { - if t.kind == TokenKind::Eof { - break; - } - // 行首缩进(; 和 , 不缩进;} 用 indent-1) - if line_start { - if t.text != ";" && t.text != "," { - let n = if t.text == "}" { indent.saturating_sub(1) } else { indent }; - for _ in 0..n * 4 { - out.push(' '); - } - } - line_start = false; - } - - let text = t.text; - let is_block_open = text == "{"; - let is_block_close = text == "}"; - let is_semi = text == ";"; - let is_comma = text == ","; - let is_paren_open = text == "("; - let is_paren_close = text == ")"; - if is_paren_open { - paren_depth += 1; - } else if is_paren_close && paren_depth > 0 { - paren_depth -= 1; - } - let is_colon2 = text == "::"; - let is_dot = text == "."; - let is_comment = text.starts_with("//") || text.starts_with("/*"); - - // 前置空格决策 - let space_before = if let Some(p) = &prev { - if line_start { - false - } else if p.text == "{" || p.text == ";" || p.text == "," || p.text == "(" || is_colon2 || is_dot { - false - } else if is_block_close { - // } 前无空格(但 `} else {` 由 else 关键字前补空格处理) - false - } else if is_semi || is_comma || is_paren_close { - false - } else if is_paren_open { - // 控制关键字后空格;调用名后无 - is_ident_like(p) && CONTROL_KW.contains(&p.text) - } else if is_comment { - true - } else if is_block_open { - // { 前空格:Main {、while (...) {、} else { - true - } else if is_ident_like(t) && is_ident_like(p) { - // 标识符/关键字相邻 → 空格(program main、void exec) - true - } else if is_ident_like(t) || is_ident_like(p) { - // 标识符与运算符/括号之间:看运算符 - SPACE_OP.contains(&text) || SPACE_OP.contains(&p.text) || text == "=" || p.text == "=" - } else { - // 运算符之间 - SPACE_OP.contains(&text) || SPACE_OP.contains(&p.text) - } - } else { - false - }; - if space_before && !out.ends_with(' ') && !out.ends_with('\n') { - out.push(' '); - } - // ) 前无空格(分号后 push 的空格在此清理) - if is_paren_close && out.ends_with(' ') { - out.pop(); - } - - // 输出 token 文本(字符串/字符重新加引号) - match t.kind { - TokenKind::Str => { - out.push('"'); - out.push_str(text); - out.push('"'); - } - TokenKind::Char => { - out.push('\''); - out.push_str(text); - out.push('\''); - } - _ => out.push_str(text), - } - - // 后置处理 - if is_block_open { - out.push('\n'); - indent += 1; - line_start = true; - } else if is_block_close { - if indent > 0 { - indent -= 1; - } - out.push('\n'); - line_start = true; - } else if is_semi { - if paren_depth == 0 { - out.push('\n'); - line_start = true; - } else { - out.push(' '); // for 头内分号不换行 - } - } else if is_comma { - out.push(' '); - } else if is_comment && text.starts_with("//") { - out.push('\n'); - line_start = true; - } - prev = Some(*t); - } - - // 清理:空行压缩 + 行尾空格 - let mut cleaned = String::new(); - let mut nl = 0usize; - for c in out.chars() { - if c == '\n' { - nl += 1; - if nl <= 2 { - cleaned.push('\n'); - } - } else { - nl = 0; - cleaned.push(c); - } - } - let lines: Vec<&str> = cleaned.split('\n').collect(); - let trimmed: Vec = lines.iter().map(|l| l.trim_end().to_string()).collect(); - let mut result = trimmed.join("\n"); - while result.ends_with('\n') { - result.pop(); - } - result.push('\n'); - result -} - -#[cfg(test)] -mod tests { - use super::*; - - fn fmt2(s: &str) -> String { - let a = fmt(s); - let b = fmt(&a); - assert_eq!(a, b, "格式化不幂等:\n---1---\n{a}\n---2---\n{b}"); - a - } - - #[test] - fn hello() { - let src = r#"program main; -Main { void exec() { CIO::println("Hello"); } }"#; - let out = fmt2(src); - assert!( - out.contains("program main;\nMain {\n void exec() {\n CIO::println(\"Hello\");\n }\n}"), - "{out}" - ); - } - - #[test] - fn control_flow() { - let src = "program main;\nMain {\nvoid exec() {\nALL i=1;\nwhile(i<=10){i=i+1;}\nfor(ALL k=1;k<=5;k=k+1;){}\n}\n}"; - let out = fmt2(src); - assert!(out.contains("ALL i = 1;"), "{out}"); - assert!(out.contains("while (i <= 10) {"), "{out}"); - assert!(out.contains("for (ALL k = 1; k <= 5; k = k + 1;) {"), "{out}"); - assert!(out.contains(" void exec() {"), "{out}"); - } - - #[test] - fn strings_untouched() { - let src = r#"CIO::println("a + b keep spaces");"#; - let out = fmt2(src); - assert!(out.contains("\"a + b keep spaces\""), "{out}"); - } - - #[test] - fn idempotent_on_examples() { - let ex = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent().unwrap().parent().unwrap().parent().unwrap().join("examples"); - let mut n = 0; - if let Ok(entries) = std::fs::read_dir(&ex) { - for e in entries.flatten() { - let p = e.path(); - if p.extension().map(|x| x == "bio").unwrap_or(false) { - let src = std::fs::read_to_string(&p).unwrap(); - let a = fmt(&src); - let b = fmt(&a); - assert_eq!(a, b, "not idempotent: {}", p.display()); - n += 1; - } - } - } - assert!(n >= 10, "examples 太少: {n}"); - } -} diff --git a/rust/tests/fixtures/dbg_attrs.bio b/rust/tests/fixtures/dbg_attrs.bio deleted file mode 100644 index 9459014..0000000 --- a/rust/tests/fixtures/dbg_attrs.bio +++ /dev/null @@ -1,13 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(3); - a::set(0, 30); a::set(1, 10); a::set(2, 20); - // 直接看 a 的属性 - CIO::println("len:", get a::len()); - CIO::println("data type check"); - // 用 Arrays::all 拿注册表看 - CIO::println("count:", get Arrays::count()); - } -} diff --git a/rust/tests/fixtures/test_arrays_all.bio b/rust/tests/fixtures/test_arrays_all.bio deleted file mode 100644 index 8e0e1de..0000000 --- a/rust/tests/fixtures/test_arrays_all.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Main { - void exec() { - ALL a = new Array(2); - a::set(0, 10); a::set(1, 20); - ALL v = Arrays::vector(); - v::push(5); - CIO::println("count:", get Arrays::count()); - ALL all_arr = get Arrays::all(); - CIO::println("all:", all_arr); - CIO::println("all len:", get all_arr::len()); - CIO::println("all[0]:", all_arr[0]); - } -} diff --git a/rust/tests/fixtures/test_err.bio b/rust/tests/fixtures/test_err.bio deleted file mode 100644 index 6cd906d..0000000 --- a/rust/tests/fixtures/test_err.bio +++ /dev/null @@ -1 +0,0 @@ -program main; Main { void exec() { int x = ; } } diff --git a/rust/tests/fixtures/test_iface.bio b/rust/tests/fixtures/test_iface.bio deleted file mode 100644 index 569ddce..0000000 --- a/rust/tests/fixtures/test_iface.bio +++ /dev/null @@ -1,21 +0,0 @@ -program main; - -Interface Shape { - double area(); - void draw(); -} - -Class Circle implements Shape { - double area() { res 3.14 * this::r * this::r; } - void draw() { CIO::println("drawing circle"); } - double r; -} - -Main { - void exec() { - Circle c = new Circle(); - c::r = 2.0; - CIO::println("area:", get c::area()); - c::draw(); - } -} diff --git a/rust/tests/fixtures/test_iface2.bio b/rust/tests/fixtures/test_iface2.bio deleted file mode 100644 index 200b6c1..0000000 --- a/rust/tests/fixtures/test_iface2.bio +++ /dev/null @@ -1,26 +0,0 @@ -program main; - -Interface Shape { - double area(); -} - -Class Circle implements Shape { - double area() { res 3.14 * this::r * this::r; } - double r; -} - -Class Square implements Shape { - double area() { res this::side * this::side; } - double side; -} - -Main { - void exec() { - Shape s = new Circle(); - s::r = 2.0; - CIO::println("circle area:", get s::area()); - Shape t = new Square(); - t::side = 3.0; - CIO::println("square area:", get t::area()); - } -} diff --git a/rust/tests/fixtures/test_iface_bad.bio b/rust/tests/fixtures/test_iface_bad.bio deleted file mode 100644 index 0619fd2..0000000 --- a/rust/tests/fixtures/test_iface_bad.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Interface Shape { - double area(); - void draw(); -} - -Class Circle implements Shape { - double area() { res 0.0; } -} - -Main { - void exec() { - Circle c = new Circle(); - } -} diff --git a/rust/tests/fixtures/test_new.bio b/rust/tests/fixtures/test_new.bio deleted file mode 100644 index 12c50ab..0000000 --- a/rust/tests/fixtures/test_new.bio +++ /dev/null @@ -1,13 +0,0 @@ -program main; - -Class Box { - void __init__(v int) { this::val = v; } - int val; -} - -Main { - void exec() { - Box b = new Box(42); - CIO::println("b.val =", get b::val); - } -} diff --git a/rust/tests/fixtures/test_new2.bio b/rust/tests/fixtures/test_new2.bio deleted file mode 100644 index d71944a..0000000 --- a/rust/tests/fixtures/test_new2.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Class Box { - void __init__(v int) { this::val = v; } - int val; -} - -Main { - void exec() { - Box b = new Box(99); - CIO::println("b.val =", get b::val); - Array a = new Array(3); - a::set(0, 7); - CIO::println("a[0] =", a[0], "len:", get a::len()); - } -} diff --git a/rust/tests/fixtures/test_obj_llvm.bio b/rust/tests/fixtures/test_obj_llvm.bio deleted file mode 100644 index 46a58b6..0000000 --- a/rust/tests/fixtures/test_obj_llvm.bio +++ /dev/null @@ -1,25 +0,0 @@ -program main; - -Class Student { - void __init__(age int, solve double) { - this::age = age; - this::solve = solve; - } - int getAge() { res this::age; } - double getSolve() { res this::solve; } - void bump() { this::age = this::age + 1; } - int age; - double solve; -} - -Main { - void exec() { - Student s = new Student(12, 1.2); - CIO::println("age:", get s::getAge()); - CIO::println("solve:", get s::getSolve()); - s::bump(); - CIO::println("after bump:", get s::getAge()); - s::age = 20; - CIO::println("direct:", s::age); - } -} diff --git a/rust/tests/fixtures/test_ref2.bio b/rust/tests/fixtures/test_ref2.bio deleted file mode 100644 index 5c305fd..0000000 --- a/rust/tests/fixtures/test_ref2.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - int x = 42; - &r u int p = &x; - CIO::println("p =", get p); - p = 99; - CIO::println("x =", x); - } -} diff --git a/rust/tests/fixtures/test_ref3.bio b/rust/tests/fixtures/test_ref3.bio deleted file mode 100644 index 74d9dd4..0000000 --- a/rust/tests/fixtures/test_ref3.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Class Box { - void take(p &r u int) { - CIO::println("got:", get p); - } -} - -Main { - void exec() { - int x = 42; - Box b = new Box(); - b::take(&x); - } -} diff --git a/rust/tests/fixtures/test_ref4.bio b/rust/tests/fixtures/test_ref4.bio deleted file mode 100644 index 8193383..0000000 --- a/rust/tests/fixtures/test_ref4.bio +++ /dev/null @@ -1,16 +0,0 @@ -program main; - -Class Box { - void bump(p &w u int) { - p = get p + 1; - } -} - -Main { - void exec() { - int x = 41; - Box b = new Box(); - b::bump(&x); - CIO::println("x =", x); - } -} diff --git a/rust/tests/fixtures/test_refparam.bio b/rust/tests/fixtures/test_refparam.bio deleted file mode 100644 index d154aea..0000000 --- a/rust/tests/fixtures/test_refparam.bio +++ /dev/null @@ -1,15 +0,0 @@ -program main; - -Class Box { - void take(&r u int p) { - CIO::println("got:", get p); - } -} - -Main { - void exec() { - int x = 42; - Box b = new Box(); - b::take(&x); - } -} diff --git a/rust/tests/fixtures/test_sort.bio b/rust/tests/fixtures/test_sort.bio deleted file mode 100644 index 9094839..0000000 --- a/rust/tests/fixtures/test_sort.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(5); - a::set(0, 30); a::set(1, 10); a::set(2, 50); a::set(3, 20); a::set(4, 40); - CIO::println("before:", a); - a::sort(); - CIO::println("after: ", a); - } -} diff --git a/rust/tests/fixtures/test_sort2.bio b/rust/tests/fixtures/test_sort2.bio deleted file mode 100644 index 202543c..0000000 --- a/rust/tests/fixtures/test_sort2.bio +++ /dev/null @@ -1,11 +0,0 @@ -program main; - -Main { - void exec() { - Array a = new Array(4); - a::set(0, 30); a::set(1, 10); a::set(2, 50); a::set(3, 20); - CIO::println("before:", a); - Arrays::sort(a); - CIO::println("after: ", a); - } -}