From f630830b3ca641534e4f23456b8b7176660c45f1 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 12 Aug 2022 02:26:40 +0200 Subject: [PATCH] new testing infrastructure and basic test suite --- tests/makefile | 8 + tests/suite/Testcase_000_000 | 7 + tests/suite/Testcase_001_000 | 10 + tests/suite/Testcase_001_001 | 10 + tests/suite/Testcase_001_002 | 10 + tests/suite/Testcase_001_003 | 10 + tests/suite/Testcase_001_004 | 10 + tests/suite/Testcase_001_005 | 10 + tests/suite/Testcase_001_006 | 10 + tests/suite/Testcase_001_007 | 10 + tests/suite/Testcase_002 | 12 + tests/suite/Testcase_003 | 12 + tests/suite/Testcase_004 | 12 + tests/suite/Testcase_005 | 12 + tests/suite/Testcase_006 | 12 + tests/suite/Testcase_007 | 12 + tests/suite/Testcase_008 | 12 + tests/suite/Testcase_009 | 12 + tests/suite/Testcase_010 | 12 + tests/suite/Testcase_011 | 12 + tests/suite/Testcase_012 | 12 + tests/suite/Testcase_013 | 12 + tests/tester | Bin 0 -> 84864 bytes tests/tester.c | 555 +++++++++++++++++++++++++++++++++++ 24 files changed, 794 insertions(+) create mode 100644 tests/makefile create mode 100644 tests/suite/Testcase_000_000 create mode 100644 tests/suite/Testcase_001_000 create mode 100644 tests/suite/Testcase_001_001 create mode 100644 tests/suite/Testcase_001_002 create mode 100644 tests/suite/Testcase_001_003 create mode 100644 tests/suite/Testcase_001_004 create mode 100644 tests/suite/Testcase_001_005 create mode 100644 tests/suite/Testcase_001_006 create mode 100644 tests/suite/Testcase_001_007 create mode 100644 tests/suite/Testcase_002 create mode 100644 tests/suite/Testcase_003 create mode 100644 tests/suite/Testcase_004 create mode 100644 tests/suite/Testcase_005 create mode 100644 tests/suite/Testcase_006 create mode 100644 tests/suite/Testcase_007 create mode 100644 tests/suite/Testcase_008 create mode 100644 tests/suite/Testcase_009 create mode 100644 tests/suite/Testcase_010 create mode 100644 tests/suite/Testcase_011 create mode 100644 tests/suite/Testcase_012 create mode 100644 tests/suite/Testcase_013 create mode 100755 tests/tester create mode 100644 tests/tester.c diff --git a/tests/makefile b/tests/makefile new file mode 100644 index 0000000..b70ed44 --- /dev/null +++ b/tests/makefile @@ -0,0 +1,8 @@ + +all: tester + +libnoja.a: + make -B -C .. tests/libnoja.a BUILD_MODE=RELEASE OUTDIR=tests + +tester: tester.c libnoja.a + gcc $^ -o $@ -Wall -Wextra -lm -g \ No newline at end of file diff --git a/tests/suite/Testcase_000_000 b/tests/suite/Testcase_000_000 new file mode 100644 index 0000000..6df821e --- /dev/null +++ b/tests/suite/Testcase_000_000 @@ -0,0 +1,7 @@ + +#source + + # (Empty source) + +#bytecode + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_000 b/tests/suite/Testcase_001_000 new file mode 100644 index 0000000..d23ecea --- /dev/null +++ b/tests/suite/Testcase_001_000 @@ -0,0 +1,10 @@ + +#source + + 1; + +#bytecode + + PUSHINT 1; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_001 b/tests/suite/Testcase_001_001 new file mode 100644 index 0000000..f4a65f7 --- /dev/null +++ b/tests/suite/Testcase_001_001 @@ -0,0 +1,10 @@ + +#source + + 6.7; + +#bytecode + + PUSHFLT 6.7; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_002 b/tests/suite/Testcase_001_002 new file mode 100644 index 0000000..35dcbf5 --- /dev/null +++ b/tests/suite/Testcase_001_002 @@ -0,0 +1,10 @@ + +#source + + 'Hello, world!'; + +#bytecode + + PUSHSTR "Hello, world!"; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_003 b/tests/suite/Testcase_001_003 new file mode 100644 index 0000000..5fbeb88 --- /dev/null +++ b/tests/suite/Testcase_001_003 @@ -0,0 +1,10 @@ + +#source + + true; + +#bytecode + + PUSHTRU; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_004 b/tests/suite/Testcase_001_004 new file mode 100644 index 0000000..be9d43c --- /dev/null +++ b/tests/suite/Testcase_001_004 @@ -0,0 +1,10 @@ + +#source + + false; + +#bytecode + + PUSHFLS; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_005 b/tests/suite/Testcase_001_005 new file mode 100644 index 0000000..7f60ef1 --- /dev/null +++ b/tests/suite/Testcase_001_005 @@ -0,0 +1,10 @@ + +#source + + none; + +#bytecode + + PUSHNNE; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_006 b/tests/suite/Testcase_001_006 new file mode 100644 index 0000000..0b40032 --- /dev/null +++ b/tests/suite/Testcase_001_006 @@ -0,0 +1,10 @@ + +#source + + ({}); + +#bytecode + + PUSHMAP 0; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_001_007 b/tests/suite/Testcase_001_007 new file mode 100644 index 0000000..772a1e0 --- /dev/null +++ b/tests/suite/Testcase_001_007 @@ -0,0 +1,10 @@ + +#source + + []; + +#bytecode + + PUSHLST 0; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_002 b/tests/suite/Testcase_002 new file mode 100644 index 0000000..4dfcc87 --- /dev/null +++ b/tests/suite/Testcase_002 @@ -0,0 +1,12 @@ + +#source + + 1 + 7; + +#bytecode + + PUSHINT 1; + PUSHINT 7; + ADD; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_003 b/tests/suite/Testcase_003 new file mode 100644 index 0000000..bc9c72f --- /dev/null +++ b/tests/suite/Testcase_003 @@ -0,0 +1,12 @@ + +#source + + 1 - 7; + +#bytecode + + PUSHINT 1; + PUSHINT 7; + SUB; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_004 b/tests/suite/Testcase_004 new file mode 100644 index 0000000..0a5843a --- /dev/null +++ b/tests/suite/Testcase_004 @@ -0,0 +1,12 @@ + +#source + + 1 * 7; + +#bytecode + + PUSHINT 1; + PUSHINT 7; + MUL; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_005 b/tests/suite/Testcase_005 new file mode 100644 index 0000000..caebb5d --- /dev/null +++ b/tests/suite/Testcase_005 @@ -0,0 +1,12 @@ + +#source + + 1 / 7; + +#bytecode + + PUSHINT 1; + PUSHINT 7; + DIV; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_006 b/tests/suite/Testcase_006 new file mode 100644 index 0000000..b42af91 --- /dev/null +++ b/tests/suite/Testcase_006 @@ -0,0 +1,12 @@ + +#source + + 1.0 + 7; + +#bytecode + + PUSHFLT 1.0; + PUSHINT 7; + ADD; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_007 b/tests/suite/Testcase_007 new file mode 100644 index 0000000..551e049 --- /dev/null +++ b/tests/suite/Testcase_007 @@ -0,0 +1,12 @@ + +#source + + 1.0 - 7; + +#bytecode + + PUSHFLT 1.0; + PUSHINT 7; + SUB; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_008 b/tests/suite/Testcase_008 new file mode 100644 index 0000000..454b990 --- /dev/null +++ b/tests/suite/Testcase_008 @@ -0,0 +1,12 @@ + +#source + + 1.0 * 7; + +#bytecode + + PUSHFLT 1.0; + PUSHINT 7; + MUL; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_009 b/tests/suite/Testcase_009 new file mode 100644 index 0000000..817b3e8 --- /dev/null +++ b/tests/suite/Testcase_009 @@ -0,0 +1,12 @@ + +#source + + 1.0 / 7; + +#bytecode + + PUSHFLT 1.0; + PUSHINT 7; + DIV; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_010 b/tests/suite/Testcase_010 new file mode 100644 index 0000000..f3c7d3c --- /dev/null +++ b/tests/suite/Testcase_010 @@ -0,0 +1,12 @@ + +#source + + 1 + 7.0; + +#bytecode + + PUSHINT 1; + PUSHFLT 7.0; + ADD; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_011 b/tests/suite/Testcase_011 new file mode 100644 index 0000000..28608f7 --- /dev/null +++ b/tests/suite/Testcase_011 @@ -0,0 +1,12 @@ + +#source + + 1 - 7.0; + +#bytecode + + PUSHINT 1; + PUSHFLT 7.0; + SUB; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_012 b/tests/suite/Testcase_012 new file mode 100644 index 0000000..a8e7f98 --- /dev/null +++ b/tests/suite/Testcase_012 @@ -0,0 +1,12 @@ + +#source + + 1 * 7.0; + +#bytecode + + PUSHINT 1; + PUSHFLT 7.0; + MUL; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/suite/Testcase_013 b/tests/suite/Testcase_013 new file mode 100644 index 0000000..25543eb --- /dev/null +++ b/tests/suite/Testcase_013 @@ -0,0 +1,12 @@ + +#source + + 1 / 7.0; + +#bytecode + + PUSHINT 1; + PUSHFLT 7.0; + DIV; + POP 1; + RETURN 0; \ No newline at end of file diff --git a/tests/tester b/tests/tester new file mode 100755 index 0000000000000000000000000000000000000000..6877e34b1ee5458604b77b913b042fae15eac6de GIT binary patch literal 84864 zcmeFadwf*Y)jmE0364lSqXvx%I%?EJE+&Eq5;Ow|oZti^5H12j2#KH}Ns}2a$|X26 z!f_m%TD-iyytUP~R@=9}eHA0E7%nED)#9~y2fUrrsNfAFR`Y$Hwa=NEjMBHi_rKrg zLs~g!uf6tKYp=cb+H2p=tSb#n?BjO3oWH)VD_l%@&sC6OCu4jPr`R>xmE|gSUEn(3 z)erd@_|L&hf9~yrO#C@dk4n1w;>mJ3eEFW|AO&_gPlr37EO)fOdrww5&eP>pX_m|6 ztB?QS&nN#To@0Kb(#|urobYS6@zb83OBLLCrk0~_3XZ7!g8q~MY5RGInfRN1oO(Lt zs4iyh`$oa{WvOz`^N>oqoTsBt%E~ag=R&>&zKeO${c{=>AMmbw$s^I_fuifmfX*R#3wZk4U3YW`y+Uj*(T(@-5 zsF4@fEgn|4v_7pERY?)u+YPPMA~P?Q%={c)#LdQkR6ENLSayZ?kvU^0e>LUp<>l}HWmErO-}5+eSPzyV4*v+_f6gD(bsqjB zj_8LkZf_sLa`9jO;M)d``84KqBS3opnMm4QrdLMeQQ&!?|9$u$9R>eSM}bpJAO1`K zJM$>??+4=d@!fJ1_|K05XZQO)I$syU!usI=HH_X2GHbJbMpbaS4R8zH>$?B>g>*($GHass4{;&-%PJ=&U!)K?#+iZAa8vHLdd}A8C-G<+r z2H$DJ+tT2>Z1|ou_+M?fc@(&7K=1nQvGKiW@Ikge%SnSz-KFd&FAd&e;}@jCU$W^J zr@>d+_<9=rRU3a{8hoD(UzrB~z=pS`!4KN-d(+^JHs1%*;9uJKZE5fv8^1jb-ecqM zNrPwG_je4Su!_ADsrj(1z=2@Fh0iiZuAe zHvYmi_+lG>NgDhz8-HaQe4&lsk_Nxh#&1o7&$IFGNrO+c@gGQo&#>_yPJ>Uk@!Qkj zvu*rcY4BM`CtG3cP;`{_GU^2`TV}DewU)@Fgj547`cI#uWI838>4p zG6k-sz*|z_&REXujVbVxQt(?-;HmNBJt^>0Qt9zDF%vqPx$TzUZDp7Y4A^Q?V6_&fhKrn#hG?UD2gOmiv3 zYM1oWOmk_$dRWrindXv$b+4qiFwLa|t5wo}Vwy`PR*R$`WSUC~R->eU#Wa@^tc8;P z3DaCcuqq^d2h&_Su!<#pE7M#uunHu7Gt*ouuzZrfk!dawSYAmtG0mj`%O&aSnbw#- z^eq7AFJhWY0oFcAU&Az)0IWTdp2jq%{#LuBCo|0{zxA-B$1}~TzICspFK3!le5+N` zmoUw#z11S=iabD8E8-&!c?GnnSo-l~xF$xL%fZxu_rKhvDbTLqHt!!)Px zmJjLD!o$|aF>KoiKrR>Uq41+Jz@K8L_w(A} z4nE+oehj;bXTHG4nC{>}n9^Dx(Ugww*{7Y|Lb^NbC-8qK_?P~QkNj_SI)3ELktjwE)S;v#KcykJ1t-E)8PhAcj zw|uDCBSk>7v|A6iAE#}0>DtzI-Hre6(H?VYkF}rA!v$!#<52xW$A#*M3bdm0LREGt zHcCs&%q0mS>nZaZewJ01jnXC?-YA5?KSra<0jY{pxl*)(qxxZij z<01c*z2rY0#g8Wc36%eMEqW^`lN&jm7g}dCmO3m7HQGt3!>U9Lr4GSF4W$k>i5l8< zaB3)ZXh_tMIxSk9sG;ZIu3@Ya%?%|Puh6Wso-}796YMRDqy_3V7%?R zQQ4*&e**0JiBvkRe1X_v-Gab)!Q!{%$To+o6Btp)?1FL}>_h z9I9ywAb$@i9o+&3*lc zUg$+Pp2z`h-FVcC$6`H}aef}_Xq0vrLe8kl>@=fM0;5Zan}n_d)My8sBZVG*g4wQ6 z9{g3!&xyZn%PS-n&zye`lO1@QCvTnRyWMb?(cviI?ucH@hu#_GfPn%i5#?B<$K!

``G4b6tZLi3tiU<>ZzpNXDf_83kDXfqzt9xZ54N7=b%6JJ|TtrUu^1cKpbUWJXv9zs+79KE_HEAspsrcze$w3 zR!Sv#{qsFllfslzyX;bpiBdc@2cv4I?C3Ao-GpRJRBFJn#+Rr%iNfO}-3iQX(BmI$ zV%3Px(0RNT8+<#u+^|H64!Ta4c`>=->1RtCC|De_h92i5V`9KQ@Bx}F>Mq8S$>=fw#5DGOxyvpvy`jBjK ze8EifqR)GJ5FCrJcIo&D6dM2=P0m)naj>8Hgcd^}_67#f%SECv(2iLgv;@B-LrQeVQT~Wxn0XfF@I#bl*y+ zp-)YLJ`E@&Q3CaD>MKDw(bO%fsW(Ezw5CoWHkibmCbE60L{O2nlvTA>SnP#{;25E8 zs-E8R7EoSzLYMV;Z$mguaS^RL79d>4V~|l^?Q~R0nFjQ;#-Z~A(n)6g0vSlE*6B3! z)WAV^inic`gLN{$QK5^bp!TW+S>z7b=YG!L%Am5X*IX!-Av|&~p2v zaXZa#Q=7EYyg*SD$-sFMW_~Gh?OmPZhuqyrDz)7#wwRD>-*Vy&e03imkJ|l5XvVXG zHsjx8rn)hDH&vj9F&fz4PZkDJ0?Az}>8Nc^Z}yXsgaMNTnnK&$p6)=bgQ-@9`3)S% zM#R49R)w*c5iuCvjS|q?9wx(2@Zkzw1TUgVP3{!5c-C%Q8Ay#>+=nn0y^o*;2CS?CN*%jX$j9PB8X*ulC?_(J^2F~H zh|R&hr^AJ4&SB^7h-5MwUPVA|>(9#e_mWDu{S@ePA75kY0WJGKCjF?Xpv1!*`s5-{ z@Dhl8|63xF6p3d&*UAbcgT%X7G#sVo+}1Qze)RVeVlGsX-&cB`|9W`~Dr4Yu5{tW+ypWwfJvm+D-=4-&D}K|=%ad}>uKn^LZ?Y$9upSh zr6uhFfZbs>^Npx#$tU3xqITN1UQ2Xoyp81}w33;WO5Jh>7^V@R|?Z>!zZ@ zFTJja7p3TAiA`Mb=L8WG!psMW8PAHP33(sLw=u@yWPrE`zc4GL6x8crRILrPUAt2j zyR@@i{#pK+{!0Ih%IW(054(}C8>@5mCrL;*g1I>G{cU4e!+6#xT6lcmj{y%DgL2J|AK)c6C|9_i z0m@~u%XDKe^g|Yzp#D6l&pg~QAas1oR|~XgCBguBEr_x+^hUX{uPlFWz{tqejd{7= za^o%C*rCTp=IZeox$^?Xm*zyQFGrvi-sTb>&!W7xwPdiXXsi}_jAKrGb?(A);~U+0 z4NIqd- z!rf(KURsgWbx|vD!3Ee8TizQGI-zCtU>EQ@=+BiVLvNNF`^)m5r>;DDJU9>87g`=7&orx_zPd zrPU4qnyB{=K(}b97Oh|y5})p71~mGTP4XnJzv|)b1ZlTC0~vrYP-=g;WC}>WQx@*@ zLPgOJ=xcz8s12gVK+DZFyCr$_L%pg~>ZJ2E^pcGwckYQ-SyLB@Z|$h64MnGj>7CAPfp3%yH= z1EzLMloQ<5nXb#V=#6$6_CznqXkBM3J&Lg|&r&oFm&}9$|KVT-&ke+bnXWQzYd_uC zu0579@$k+JEjk3Uwj5cYMb5#j~hm% z#-SLTK}89)=+mi6XcFtiL1c?xxZ_lpYtFUk6;UsiOA_tXS(57m192W6^P770R$xw8 z!Z+oE5^hzCo=DpPBcHy0C*9ZF`TlwS`TlGD3$UX=xs2y&QD-IShqu9={wjJ{o#i@Q zvexZ^9^$J2{pWJ@k1W@4EizKfbeA5##$DJGwhHv>-Hv_7GoIArMSkhU+bO5`Ah;i@ z6g}jxfT4fVqVz3vqxf5=w>|q;^{0sG`A_R#9?-Y_qpv>Z$eM?!FQ|7PR-^rGKLLU3 zi=kjYv`tlEo;c7l=y?kdn}e3&PUtn(%P=pgAIun?*Sdl#j68q9z z&EEBUC?k;NX77Y1{!AI!V{5g8?T-G#W&x7B(No!BKh@@BzJ^+Vs$H~8P(RgXZz~!e zdi7JyJMeL2K#hI6yvh&5kI|KQg84ulOsc!7e|}& zEoe-aRfGW$I-UVV`rDcSfrfjKpoVjx;SC$r7-4R?0oWnb7uZVx^tWNENbKiRu;(B{ zB4rGR1`Cw1-+>hNhrWg-t;n{nLt|jP5?K?SYCtFx9SR!_$}NTV1&n?A5Oh@?eVOHb z0SqCd1L(bZ<{tDA`X6+dAUzQsboDONs*}Ma`jf9j##2Q`-tU8qA;@UWg2~w?^^{}| zLS_YWFJ$iTD5>$bK175q;+VI>*A;f`TM4E`8c0NhSMC7*#+pl*u@@VD)`?&)s)Lrs zZ=jZVNgOdPQex#suw^j(O1_3tbMo_R`V{T_gE^O3gZ9&_br|%b~ zs_`$};B}|&Y3kK4=<$p@^!Uh*SU)|wJJ`o?mveaIssHlmZY}bqSbKJ{9?x9&G)2tb z^D`jECT9p1 zS8N39F#w5mH=bCQ4&E4>>=BH^1dz_NHpU+Wqi=9_Z1MoXnBmZ1`;x{@!YLy-sw~`I zELWmSVuNn=hrb5%KOzrrpclnkY4mk1vKTKq$AECVcg(JJFQRI8wfu%#))(z@BQ_<+ z-&yPhe>hS-$C?3Cr6024=-e)h3cC9%$sHxR9B*P%d?@F$%jH>DB+3cASGhbnVxYz; zyBx$LzdWm7qMX;?NooXptl!e9GA@cuA-oqkk7Xd&=}(exlk->&$#POJJ0r(hEg7Vj z2f1m}WHj31;)<%TzYa)aab*a+fZ27D}CzvDNSIcv_Fa!E0< z?i@N3@dlZycwD@+xA2F0iV8|{M;EroCVALfKY+GD zvhejl{QQSH7|IP>9p+=)4R}}|LQZFVrYqPB@hsR4@{<7vP0t+5d&} zc=&R!qU+$?Y~ye_M_8W)9G~D!a(J(`#hZyI3_}h*aHusap}H~iJ=*PWpfnZ$G2&CY`89Pg2jh;ZvTN*vQcBmd}^v3#uQ_xRoqkCbJdi8iO zye{~-bT{_YG4xow07s3D*Hnd10#DFB@(fhW(bVqeQVvo;Ko)5nQ@iBocZ@LUyliYu($c2 zlH?DiTslM1tyJZ%V7VWr^C#XYg*Qy$ol3mx68v)#{IgX4VGJbJB%6N@#zyh0R6vgP zD8{Um{#ZR;@>3L>WAP}96Q9IXzT2)qj)ZC{D$PNS`JAiuo6cUe>d1GV3Js9Z+BN^tB>gUjF z6jPy>@D$&`4~E26WeC>U4%TqI5Njd<^lM)LJ+{gtSnD0E**4Z_!Ro6;81=+f4G^qm z2kR#`){TOdp+$ZNtm&BD6hE0k{6UXPEb;*Cf-fyJ0VDb2`U+ZNwbwEkP`Xt_9wUL5`eYOvuG@!pgWY$NEjOoUe1d&xYq& zkMxH3qL*jgh1>)^mb2x^v%?gTBd>G3gejHlNW}Q<4wyd+`}<%}C)h@7shz zukl#dC&~$2m3uwleo;4&8%Qswx2z#-qhQz#CTAa>`pT`F5p<722TzdX$doR-*0Un^oQ&*d$5fibY7G*y~e8^V+?zn^HbyjMn z(QSM(=1VQ|JS<1`a_cNq%O2-gFP-&TnNqJ`yqK)lPZGr(xz(KAKhu_e`(H$Q|16O| ze(BRU(a=~CpSc`_u!?l0D7ghdn`qiqcmtKssA!sPf zi8KHDXQ;X`p*~;vQ>7Slv|PEu2dVC>cNaXO06xDODtNJ!IxmhmyyeET1LNxrwa>h^}Dd1nduiV$3=#0E;8)d0u$?DZbz+F zx6QAegxJqKZYKSjtnRGw2!{E2CHji0AeV1Z$q4CUgv8RQ@g=J3Z~GlsqKyacT24h{ zDQA`(z{%Q+=2xpX-_FA7D5zZHbh{n?Ha45&f{c{*S3}! z|HMqtQ+Rkw9@VV9!cC@U%uKZ2XWCjM*k0A>KqUqiR$nR3C`DXz`%K}s#9LO~DQof> zxm`n*zeM+&;Q3TLsnqtLWmxOQ3<>u2lqWEDWUdzB;gPafsdr+m=;2sjoa56X8^GE> zrW32}$P-`xARsGF!5sOIdTat}T3`NqJawARf`UVsg!7&vvVa9scrR3l((Oj z0rKY0XS3!&fg0JHkQ_R}$j8QjpLO39UKeDvMoywVHf2aMXM*e(9LU92sy%9{59m-!rlU zHGFaf-?qUFX7-GHMJm)Y@_7i`PgS&s8h{b7{-aGlx&Ta|0G42VB7Oy00hL$9-2H8* zQB(1(r$H1-+JUl4MaQEY%RIp{@yrJ|E8PTQxqFbjNs>eNm`$_5Y!>Kk13iD+U{LeV z0KFSQPY8i%J&0CTP@Ft?vWMv+lxbQEQv{lZ%|UOI&-(ZAeJKVs}re$GFm%=jz3=gTtj!TpQyO*yCF z=yi1|4AB>?K*Jf8@Na`U#63e?FZ)l@f5dLWlKOt#z+M+Rs`-1gBZprP&cD6@+x6+O z*`BTk(K(dA!SatdiF>H@!~V7gT3S4-QZ?Am0X6?)x=8NDREwNa7N-yo^x99~2o?1T z_ErK=*dD&Zqis0vc!XO0xR@0mcp7!cz2DDJS-m>rW*pq%%^1YzCU;S`dME`JyX(=^ z*w&Fw--KWrLx%aF3`otGbMFtJCm07gYM4RPlRM4>zy{UKDTx(eEklb$B;8D%1gMjP z=42c50GR02D9cE*IAFYwOtzL^6uaddhyZV|;{mpa?5MnwnC^`gXIGd{9l=mH+ds!| zbdc*^qy^e1LN@d?Y@6m5Y>`T!^D%TCh>EZaegk8%@-ma_PvNyWXKEX6c?N;bL~hIY zpz#!qpa(DK`al82@B{f21W=ktCUb%Sw$U2{Xz51yy{lp)zf9J15oDok|HxAHETHQv z8FOgeUW$a;;Iyf#IOrFXK5M_l#~0%u_A#&WDpiSwz|9Qo7Ozn)_J<_-y_FP9hYL9H@iXXmoz)|9QEy z#CC=TiiIc)R8b0GAkQ&B!U0CK>NSz}00KvB>iq?xQdJ4VKxq=B1SE)!8a&_U?nQAL ztzUxH0{?8b!E!|5a|$2Pa~@Jh9F=NQI&mDgN^;<8^H8BKM;x_G8XtSW#S zh+$!6hTG2=v{50i`A7JjKn%uC*~_DcL-VW+=ruCV$0kZ8O0kY^>jWGf?yLpXlBk7< zVZM!H(@Gn`IpwkCSRhn%wMYTTmeFFN7e(@UN6n&RyHTlTKRByORwx}Cq&gQW)}poO zi!fY^^CLB0n}0^nO4v_5hA7Ad`!VlJh0g>W=H%Blb-*{289QKJeou!MeX73{;HIL- zY(WuQUp?QeuXQ9Jvt5Zehhb1L8;tuWVn>)A(uN>J!lYDXSY5WC?Gnhbmo?vq3((g%mUtz8N%3!a=A{*+)ng9EQ*zBy@YdyTMej#Eg;s$Tu)FJtN`a}K)&bC^ z#X8MeFu!&IdNsz=NG?Z_lqJpu@PC+XaA_w#hJ_|2RQ7M%!3ALDzs5gTxiK#` zIjIJ$PvBrIeIhJC+#G^#^&N+AC@h#(htj4!2`~@9W?u+GHj}!AR{&w z{q!Z(MXy2^{p#Q9r5ka$e9pC^kkH-M8H^hf_lM~XJ%C``&!Yz)WKD+6CiCfr0oNiU z09JBg5r}d%K@XU0yrujo>9k`g=Cch?C*Jc*=xhC2K!~>jv9&&!cfiKbi%0}EtPCf58AP%^hI>}^^SAvF{a-5Mgs=~Biy)r01%e@vI&cHJ z`djdq)WBwx)1gs%hRN_PY`@o39GTJy3+$YB3(UV8N#p;{gkX)8v>h zs$>cWX|{oVg5gjy6xB;S^bJByOags{uZti1FN`?;*raTp+tigSpdWyipr_bW@0L@A zi5-S00nAqqsya|-7DEvF{7 z;?9=3AaYOD&j3gvBMXK80O2gPqsQjvNoT(uBzskFuwA_;h?(jIGVF}|kzv~kdk5N| zbw=%-YX20c;+5B51ypeINX6l8?y}C70d=D9Xf;r+kyeArAjqJMrt%hs4e7zL6&}>S z0IV@fSjSnyt>8uwBLj~2jZVG9S(L&BQ3D4lKCncB(n6ee3gmjQ#B%#Ov`|TYci436 z_h4CeXCuP6oN)IUT4X1RguBnwBG=It;#vKdrZm_4;D+WJg?2)7UAsgz*Q`=B*Cmiu zkEK->KHMeW1yF59i|*B2^RbLVKXQ%r8Qd-wb0+%O+2~AAIf=Mw#&(OJ25MOA0gXg| z7z6?c!l|tP28CWd^-Q2A5I@6-cA6(KHvsc{MYZ|lu$Jkf{s{!ct6MsXC;V+2N4#Es zs&AF(=w#>(whT30b{$;Rh!W`NM3i*=+dtC%+cOKL+-pjyDA!Yqa$iqCxmVyElYV~} zQVxId|0!v6E;>LW`Z;l8a(-O`es*Z1f;3n#b<7xnI^J@w)H-!$NNdS4AIE`p|Fw+g zBNu^2@4)5__Ifq+^GmXvM8|Sq{NtUmz?ngTmAE0t5M(l9$F*uUay9u#P@_e*DOt>) z^UevEHxPU#hJFZb-Vc*wKzbWZ?=d)bXP+US^=?gy!7{dl!A8*p(C8nKMgQ`f@hG(( zrCR-MgVNh?!U8EW4MkM@tw1TWnMJ0ch`)`k-K)h}h7*1M z@JjaF^&B8dW!S+-9d%UI{1|-- zumD{YG@?%4jKKKFrI3@g=yZry9$Sk@sV8)@Ma9_>ixvrr78$qMBh@s~R_HBi;y3W# zn7;aGgq*%&fTI1eqIMVe;#KMlXTlk*nwXo<;{3aECsf-4knv+nyJRemO|F>=62&Oy za!eXjIiuF86-CxR{d#l(XNKPbV&dFns6H9QSWhlI-?p9uwb+n#1g@IaQ6}+=y^2Nq z@S5vHJ-DTS(Nm&dXNWpzF1E8yL>A?DYFfA(wZibUWgvu-arwvxc0?U5_LvLtv|i7hJbcSUmZOc_NO%C=!t_Ab}KNe+a^YtFvpqD zC)NwTeJ`;@6%tQ6p+L@5nom8-Pz>|ive+u@zDxLk!%NEX&=@e;VQZJ8n@bDxQizw+ zO<-ZY3xQ2p%&!zdUnTa+F0G24iegmD6vkWBu3A}w%~?FIowCHt+geyO#y%7ljIk#E z8IJ^77SxHDR2d|nyQO7>BK(HB;oz?!`LKjM0L za>5^9j`M=30xxkvkY(l7A4_k;t)G9G^FSKce1^bnt?_PUfNACf*VgV2I;lCF-7Xdt z;_@A4;l(*+m|o!YFVt{4a=ESRErqww>%HOGB1L0scu4)=EOfPY+4cM%7(Pbi5!i_k z2Z&+$YmA@f$z!4ACm;s#pA>w5+jfQA56HlK5qMI@eD87~x2Eh*U|&(L{N=#02VxDL z6cdW%dH}?xRjkxCjzgFhIf8%7s>^fyvC2V#*k$ARYd>1_5&RpkJE;f9sS95iW38zM zeZBe<;~5;F9K~B32;nfH;T5JaZw514M$Zc!GWO>0Dv1~M@vlb{fyfW91Q8rc54h1v zMcI1w9`ma|!Q)i#z_y<8s=uYE-@M?tW31J`u^xqfTJ`E}0@I?uK@t(V@r>|^PjEXV zBcnlb%u~VD`aOPaYev7KdM$#J9Ikaa{`f3+(S^Z`qIse7%By?gSM?dcF{7yKmqktL z)K*b2q!FhLnGwaC6kgZkO*f*;vPB}b6QL&uIhH<(Gc7jE$ z{>ZY}42W?ehUPgD`*N&ONQ%6LA{EoD z3sY!ql8j8$wd+sBl}#~?RmP^+US(zb>cq;Hk#fofXIV8OAo8XnE#LVIYw8&;qU?*BXTG`>J?$;|O{b-8z^H7dNY z{A_>UQuy?GU;duXiTpf^%fPZdWDj=_3ry&(n{YYR!D3^5sN>N^U| zHVfB86IXb{AFjlK&eYC~u@c9?``8+v3Bw-8d55;4FPc`M%X5eE>Q1cc&2bCR)7(Y_X8Q)+XxXFnqMC ztfXhzs4=g@eL7`4f}4LQ$Z@@RRDy@H5qO3KvaE^R z3e0k>+@o=u@!w~-Kp?d}SE`NctWVgC%Z(3tVY}EH;~)U_WPp$s?T?mT{}ojgJU;vt z1pSK3YZZg^>P8F{qK&e?mzfh7G8j)$O44ACbv%lOyYsXt&#hANs(Z6`o3O6v9N+1N zg3DkAiDrKs5{0|-aef0b^4dbkNurCrXUg3;AhlQ;Z+tiNt7!M&b*EpjjEeg)lJ z-%kmE3*RKf=6sFnQr&TUveiSs(T#d|Uhz`1&=>q|PpF>o^pe!|UpV5AX0tmW?sOq~ zU?2h)up|+0Xwe`Ugm+?NH4$^j?a&rf4Q4(*r>yUBqDYjjh>ahF-Zl|D&XqIW@Ui80_ZJ>1bJyx$$f2M03>HfCIgdJgI+$?)Mm{ewL{ykUND z3-sc$B{O!I(;k9xjXf7FgPsc;N5#Ec^MGRN!;&=`g6`G3Hp)UPV7m}hs)dmY)#8*E zN59$3)?Ex@)jon#zxCAUQR??OD)fJ@->tv1>zAzBV*sIQxSj^3ChCQiF`Tx$VW=E_ z%-~5XG@BM#g}Q%#4L_CRYpRa)_r2@*C-lJDR`aJ=Tmqf)=({+NiE6X?t-F{f^^Y~d zW3Kb!=z;mk5LyjmOnevs+7LzJVrI4N3*ZDkhTsHCps80E59-M1D|oC#Y{?+=51`=~ z+t8y5o{T@a7@rW^j~2sM1|Z*IOiu0g`HnqLPGNDc=8PEt=v5G>TgR+@5PZ5b0$c<^> z!kt>aSfduX7`0BwZf?tG1sd$U5JtWBqH`iha`T{UEiy4p`Vu3A*9_`i{?` zL@$-(n-J#jJPi3Jg}`ygZt-@bS@rzvTpW6Qo3#b@+BePfAf020G1NOni;niFx`467 z+gY5$^)h320X1l~V5EcbMF3)>;8^<4F^vaH2B@nm>H5E>BI8*RW-ZqS%}N8cgdQ zkd+_@@Be^hy$5q#`;PhS1xc$~4U1PMPD65xN;hWHpm_KL-NC0LM5kXXpdZS!24f-D zuFB7JR%NWJWF`c*-oc=2XFegSlpU#fmd?!kk|noSu;h%se;r3m+kYxOSSSMYQ4V7inMl?pN&G_-iT^C{yPmVP)g?CCtVsL@BrJClzg_VA zqGDbDY2*JvKwTsevq3)*(3!xpLg+6xGy*8qQmROIA=ZR+_e^or@*o_W^#HadZ2TEE zezwB@TH!BA;NM96mB2S!e~Fp8J^PI<@WlGsv#?z*Oub~*dy+ltJ)W~fs{kw8So=*( zU)w39TDDu5oWAm$5{9l|dF(2z$IQshR(-)9|o-0DV@bKfejxS z7A#(WL>T9Vj|>T(Law}eT!N*B!geNF(Ul;btLt5RTtFJ5t*h&$WEyi1SC{jhCOREi z8@LY&;M#yaUoBB;xw;&tyd;kr?)s5EUwx7cT$@~tiMtB8mK>RblgIWM-yFn_12zV7 zPtapGc(4c+uXs6p;G5vkaQ7A3hKHbb%w?AME7R`lY`Oq}Jyu~;Pd%yn2=0$;+%w`8 zuY`|euQ*wck2`xd)K{H-x9&dDbD*|$i|g$EN5+8Px<|v^Lqpz{)ww6Qg1<|SM>q-3 zuVok$JZ1rVy(}l-iUd0mj=l%tgMR?g1Mz{U$|LhS(2sfJ<6p*V295zhS>YoP`+ih8 zd?bH)e~A5e?dD6-lW>6DUP1ie9Hflx5|B&);odEZg>kPjKCvj_AHrld^dfb<36!Xh z3&Q3_;qL4eXQ5O65TjdS{@yu02aXeqBQ5wBKC;PI&Y6|*O6T}IW;mDbBFuKJKXN=o z);4gHL)nUU%XHWw*x6Z;IVh#waxDs_OC+(?4TJdVPNwf~{)E=894^>06Xt%I^;*(zq zf0eCmxCifL)dOw|xcB$$xAo?iPfI+``i=QSyKRw_8;4_-NNw7F&K|Db4*}tlc$wQO zL2jZP+S4V4yE?f3`glL43GCX8bodB~oU=>7U5~fgH^KUV0@TU&S%1U>&0swr)N5O% z&Dn)HpT9+}+NpLw5&FG3kT^H46u6bv{W$-zCW4}0XK;?qB6Mv;N={%?# zda?$Qv6By>pm_yke@2>hu?7H%X_4=4Kt#Sv0afjkvZ2@ao(FZtZq{Y|`3?^cHEIGg(IJzeQT_gk+i)ZG8<)l@D)m0wFH} zgZf7r0kzB8nM`Lf{YWw`jdy=CeH`;QCDX?<9YGqGLPo;gwn%}EV2Nv)e}&e-;}qg7 z1Pj_x^m(!8@EQrw_lqfdAAj0Wy1H+BV&3Fw_?Lb=4w%{KjH?7H`Z4-7GOJm zJ`NfI#8CHLtaZwOAG*%s_b8NpQ`XBqVQg$Kbbk#iDw7yN=K*2)M352k_;E)TSBQn~ zWV35BqppR} zKU8%eqEqhY#0SPpQ6a9c-Rq;WUa+nPA*cT_UU61fy$pP9|IAf3ujV;4HnLi+9Rve8CHl&>NK&eMVdb z`aJ?APi(>VoSdCD}@s$Zx_}7GJJ#>VP929Ce*B0m0u5 zXF*BSzI6u3wx-;V!iOwylTyd#Bb@p;{(km~p(5Y%GwdA+?}{@)=I59W!K126QQUrr z%tCKoUWq$kIeOo|ka7c}OdO3wzo>|nVtwtV9M0&^ff`W!x$)FERK0(&QZ+BmB4$io z3{Lt}*xHJl!rd1x|E0bK&FgX@n%A}awm1KX5y)^H-LcoIA7k|4>`Qe(AQ_`%lY*Gp zoG_@^Jx^maxd_7ikC93RaV~0nB@j{>qV+No;)KQ|_MW@l^)w=bk@U^%A?Co@+ zRTJCU=%CmX<2r8+7Ki+pU}wv3R#9%s3E~t}i~b#Mn+)XYb7EoKoxx!qtsfV`3%sw^%5298XZRiohI;<@QRd-gks~ z|0bm_E~5H7N<=_hT!oONJOSPun;lPzVk+=eGu@c3b}q63a+Z+(1EjIzT=J)AY%;pSH2Bhmu+5d&Si;S^ zq!>Q}TB;bbneqoA!jG^~TqpZ8NQ?_YSZH4VmD1L?Qmvb0(Rno_uv;EeNHy|vsJ0{g?x_}CMbAKM zKnkYzwpiP{{74;^8FzBokqeE=POtqArB6Ufx=WL7R5ESK11VW1<-F_xIAUjE6e7Vt z*xH3}FULRwTT7UkTHqW~nj>rZIl$Np&;JZoIbXp7u*FXcN*s`==UAK{boyMo1Qy9| z&+kt4?)HOu;>qqrci7m;uFo61vNze!R*9pH_Zfe2(UsbOKy@#2fmJLeU{fhA{Am5? z{#0mWwv=`zj}Ibta#-#c9ZkpHUVSMlE(*7ouA!i!+(hlUftd%aLr?<_H$Vj)Q~|r+ zTa+bn|01D>5psG=ZP2y>CDr$EJGi$V2109&P`BFHNj*#gzUW~%6e6C;wu-k9h9#_G zU$3?B5}|@hv+2!8Pfly{(~>o3lf!8KMUx*weV=oh{A2R$Ro|^^Seg#^T2OVW(E&VG z+R~|@q<@lea^M8^pJ}T9c&?)H%MNdGa*^&5L7736zNes<9YC~&1-fe_@}zs?Jd^yTTohr2zwL9ye+eje;d=|5?%WP&{cXyMS*S&Zt60TQF6B+dRen2ymQ;a6HaPnRDQ-t(D0lKN!!3xr~J4S+@b(I*vlB z4)J~gGq0i^<5^P~P{#-U6Pgo>7?=i-*pMVKM3BIjARwN#j>NWbNWm_@3Kb(_1vf%8 zoM4tda1D@VS}!7AvspF^%Vl8M6(h*s76&>y=QYTH6!|CsDV94@uwO>ugsuCNG=7af z>u-}$9`W@=WOBSricuiG!DbLuM6LS(1t-n&E{vux%ohk7P8EF8|kysc9_A z_mk|;lvMjLkc)22PvIi1laj=KX5$Y~6ub=Q?d^S}m}^W&XoU3>bPt@rzs5@l#BnnpGR^*2Ke447r^m9j*ZXX1{C^(a>EV*y6r4Ott&PO4zHEu;!&syB|RTnZlV~1519h7*af^PKe>;>sFG( zLOV~2frpR3G=PBf1MSp6Ztru%I5(!{2k@N_|_oKLup&HM5?3chB{Abf-lY^#h6dHVOAz0sOR?w)IpH@#EX(6^A?Uz4~>~EPe(dvI5VR z_c0N<3qG{yLM?Iy#&Q0Z!YDlB6>qm38LVwMKvwZ_KSdwYHop+dzB~GY`d$K$=H`gh z9aORLX{qr=qW!haeZuBBMT50lu1A$h3=__nZ8r$V4z!2?7`{3aD%3Xj+X7GI!bjC2 zDP7BFgX zZ1`i&zcA%^uMDVS8x-}2zuXx z(Jx=^-R>?Ob5M)&+reoKAxr;ggQ6i?;4q!G#9u5k!PB<-nf_0;;m z%+Ou@6#Q2ULRqRV?W{{^f3JG<(IVqu$=no)?xY}_`-Qt}L;YLUx1Bh zND^n>Crc9caThRQGYxHAg+u!wNrSigC*)PmDI=H5u=r-2h^=iNg)ejU?K%^DTaL^N z4lW-3O7IklhGYE5IR!Gqs@J`P%+T;uI3x$~!XSPO;oQ)x$mwI3==!T|N9@`1B~^A0 zIW4X97Vhqc@8w_ywgvuznyx}*VQ$840<==}Ib^hPz19jq=j?B##y8gAU<&95=n>p) zXzR!Pp?CunJ zxd<_?9ZrBKVyE5MgFxAdC~MB|RjiwH+W3r9+-TrS(6o*G*^J8}Ev=&uN<)ENr;0tK zdc4SKKtYi{C{he-c*p7|HFS^DqCdmjus+lUbio$tUoQ?a;e+w9$wOZrz`|$ZaEOVRJ02?E2aEczU7|qL# z9(*_SQ7*N07IVr6Z&!h#bl@ETaj5LzKd)g_#ve{<@PVo14lDrrPy+EadgMu%lw#3( z2flyTrekD&(uIGzZbrZJ&urs6PEOzlRUq17O+OUHoqY_=`nQnoIsEr`EO+oA zgAbMDw=KRx$KbAp@QmD^=PB`UN~{*rY*BkjMY-jO6+Y+hNQ8TL>i!n4STXoOn?8*P zf6)uy9OU_EqdWS7h|61Yhcx}~_|kK*j5B5g5GA>X!rx9>du{mRV#tY)L$4f-dUN^e z6-Qf?U9Du<%F&IL1yHr_qYOL1unY_X@l5`d4KH9H6555k9qQ|TRoI^VzJB5^iXK~0 zpvTU_dBfl+%n@>^fWZi{Jw1DL?&iHkz#twoRhgl82nGy`#*C;!-G@tE?$FszS^Q}I z8hpQjcRoIzJD;ZUAF{*RBHYW?iUZ!sy4RZl|vxz2&QkUzotSc@DEM6f;W_LjIK8j8|c^W?n z!FRL@mJ92)!3KERjvz#Gb9c1HCc!F`9Atl|%=lKW{qUoGxb=g$`u3lb20loN8Az;R zfZR{y_DUc&YzN$>-#FNHxjin9c^hG-2N%JP^~oBCei;qvO8rTOm#jP2%MZb}GT;yxe}f3)_FlGs9zKmR2(bEqRB^L}_(%6uD+ zG>XjMk+|>$k@?3d3U@9vCO(jeOIA>Gb@2fAcK_H4&$?0N@e&u*`7%zV2Uo*a;Vz?j z7p1~4!DHXky<~v<2B~_$XjgGJdbCTtiXOXCtSsJ`gOSKAhJY*u8UJR>2D;cM@^lvS zkN`fH06e%V*%QW`HDFBe3VZMlMZg=oa1Y%mM7qLud(V_xnPsvB!0VV9C9xH09pCpSm}Tgd?vB_pL#r$U6TLKm>t^Gy#Y7&dS3Lq-!Pv5bv?EW9rUAM zX6cys*Zs|zO4#Sz^w@DJy$Sw_YZA2Yuk<)Rv)ZnAZt&3r2n&DEt>=HWep>+m+pNBy z=@0Dy{)Rj!-#EgZTz<6$h{p42xVmu({Iwiu!)CJ{pOB;D3$dBzYbzmQ9N!`BM9|0} zL+=bHND9lZmUZ3-a|Ht_R{;XB~cKimNe)!5Q^+BWLc8l<{f-Kfo2lUX85lb32iWz!!8d zcD)|@Snn*AV1XVBBaY+OQ8ttF*NoH^t?1DLh^BHY#IZ*B-LfhXjFV*!37f+w2G!KEHM< zu0g{~aKJ7z@D2%kpV?F^NajJ1=-S`AJ$Xa`qq@hugyR)l2>ee^5SRO55Hir2yHQHq zHt6;mjOdr;GyF0P*;NkulKN{vN$OAiE_9h2o8Pa8-c#c3o6B*xRrHTbejU=(=D2Rv zNGIz?UI&6Rw3Qb`aF;<`C`bC_U`YPy^U?ap^q83II{HpPLBk>gotPYx92%OAT{ylLfxXgG7x__3EG$=_v!bfr( zNgm~jKO{L%NdilI4m;;w{}UpGu>`#&A;DbCi4jD}XT@=UT=9p+nXN$*`6&1n?7ZjY zRJ3Fph9_m?1Xef1x5uAJ+4u%j&|^%>2CUd+oq&z8ls1>~C0V?cYuBl5jKMiInK%!l z?&FKG<3daP(nsJ?%!@@ptUkPqYT*;$n65*oo&2@j3}kV}JCrT}x_ks;AXo)xBNrhH zUyv()=9>T$tr9TQGEn`s`40qsf$Brz!>)?2zTyn?0hZ+v9cUECD6nlsqVbhEQ{usb z)A{WK{8S@byOUiSGYV0yc{Pgpjrs7__?E;Ge`hHtlWHDE?Op;tSoa>YBQ&I}8plRr zm1xF;9T|OG<|JgXvjz+^Wq^==X+HA<7)1=B>zC$2GMI_U_$(;H-*y+MS0LVh94XA7 z{(z}pVf%EMH{wIqoVT0b()WaS`lyiuAcR98*f%$?!)w>=ivA6FkL6%pvk$0G2k{3% z$KO^h#6d(IQ{WcXM_{5O6D%jPHGWCrAZ&mFZU&9z{H@hI)1mh#REqSj5qiy}*Y#hm z=GQo7C;aZP^A|~eo#fwcz9HHCxr`N%souu<7;6Row)u92ccFw-69PSk9M;t<5}H|% z2|sV}M~1txB7Rl2}N*( ze+G(Gs$ylx=xR+dZ`)o~xW;S!YTBtjllH5(kxz(dj$DN*DHutyAVlghvtsLunk@SpDaXOrX z&aWFlO)M(04I_7aY4qkUpxQeydF5EcKHZP+ao}f~@%b691PJ9a4DXP@)%B1w9x}J> zVXB1Nnb6W)Y&AkPyx;4zMm`oIOn$%DS_fUD;Su9jsaG ztMV;g+Jx7o%WHg9L0>~{ZF5b~cYfXC{;pud4K?+{gR2^AeB;LXW-v7ai3KHP(@H1I zD4SWjVEWX`X%k9)t5^H}cii!FW|U5tT2ktAs+Xu02ndMjVr+`P2@I^Uv(;F5&URrQN~=Y5xO zGi2u9zoPS#LU4_?9+Z+Q2wU8x0$@kwV-&h5+NZ~A{RrN~QKuL@^Xj_CH|weRe#h!AC#kYyM7qt*C1DH8wRYU%I$vvC_G1HI>a(*VQGofa%pqW zRasx-c-ZPCRZUg!wKU`E3tcl{l*_8>SJA+m;5nLI=P&k!>YHks8|s#$1%(fnx?QDk zE8t~&#-;VmPz|gT?wtl(2ZL{_;+i)*_;r*}K^*&AwDdamqeV5a=SGmP0or2U(t02G zEm|6EKDvy)ybFh3=tJMXaO{P?6*WyYsFWHm@+p_-+h#gdRf6EepX5&$DSKrL+=fU6 z?GgeHP`&>8#Y&};<1|%*u)G|)Ql)c?&#!|GyUwrmx#phVJRfh1IS~4iZC%~4Y*|D7 z#WgEyszbplG&-8Ps@_%Qf{%06)-_ZGIabnlO_^Fz>Z+JJ-8H3j5)w09{*n^c^vdzB z^2&g#q->_E^y+|X3jPaBpYED8ZH6mQdbMj3{_{^Marpt8;KyJ)y)=M<(N#8OdMQxU z;}TZ|t_GA%nIX@K0ri|dW16IA`qgvBv`RaFx;&>$fjB5Pv2uz$1JhNxa({(uTIr0+ zX;WO2EB{}6-yR=Vb)9?8%p7T?kw&s5+wucEeqjT)9=0(iK+wywWy`i=8!Uri^cq>h zdPO5yHkE^ei9>P?u@aJy)CG|^!8C?Ag(gnmHcCUl5R&Vp385)z)ih9Y%Wd7}MQ_v2 z{l2}|K67R~>A=EerZ-@LV1JTTnjx0b}$xhaoF zy4!Gt@9gR7LV5P10ruP#i|aFwXV4y$FQ9w_BXKX*~Nt%#uAMwbtg-hZK zImRG$JFq2jBVJ+*F1E7eLeA59PQaH4c2Gf_TpqWaV^Ki z8iTY>-8fCJY1&D%RutLS);ki5M>^WrEukAQd=d1tX~rv2$f@4fSb_pWZP02|ePM63 zq*F(ZOFo8cFb_X=-KK|Ft_BK-ifPeOeIw}b5&x1-ZDF?bDP2${sdS@CM&hQR2U!$r z6McL{!<>u(wZ4|!e{3jPGl)@yu<6KC!y9@qzD7&1whv?A(t&Jc`!GFdybpt;l)F%a$L5F5btpDG zGSrWfz~GA*2TcfOjYYSO!rj+4)Wcp2Inv*IAX24E0;`x_zW6|2Y`9wqb+fe%C(Bz$ zhnh7!;HI69^LXK>`Xh_4AM=M!ZtV?66^ zvDf0Wad?NyD#RJaS%lM%lSdcEox`#I=rHp?Iy@AMY25tB`g(?=tU*K3u7ROwKQH<2 z_&~~bs6t(pP{fV>Xh%JrkyyN=4R=EE*n3A}topl`Fidx0GUCG(*J|Kpg~j2)V!iR0 z)j!Z5v(U@Ntgbftb@5xH-Ka@cJL{3LPY+=tsJJM*QQc{n25YYI-X>?{)}tD#`!ICN z7?CWzRN3u%K#?@q9X7xMjPOm}<{dy+&wc>Bct~487GAk< zxeRdQBke1a#fA9IsQ6+$w9_-zO|j8b-NB)Y7img<=)ra;l8z3OBp&PK^aLtUigCXq zf3%vei>omWq(6eLyMLIo4MXXL?#Tx|D#8F-JF*&=#Knlioef-gARaeeULkT_QoZ#GrB~eB(-WDJzM-By z-MB|rw`6VJI$_kb>ARNHrt8|3p@J^F9zCU({bKR;bgf`-9_bk9@9bf%^hBH9V_dBL zx(RqE3RNmHmv`1G%)y>p^*Awd@$!-C^G?)du77TvDb+nSqa{5T%O_KvFGbz&r$uf5u^wD1aS4%|=*+D)tsJF4b``e|bRNi5-YNAT9T-LrnByA2#qSbo zq*i#b^hv$^y%c9}H|WS$Xj7RilT92?EL8A9UOq<$hA`amHpEM)AI9GQP6U@?cc$Fs zKNZ-*39WBuD z7HAXbT_+QXd7#U1-Us(;p>$vBSq&NL?8N!>*p$bQbc>dk#Yv{iN^Y^y>KL=MxF<`FGGqKj}L;Df2CS`Gm9- zbTQ7K!F{oFHF)ZA?!Xzq`NkJh&mTOMNbLTS#zqllPA}i?)2LfdLmqr@{aNZgrF12V z@c#059pv$zx+6Cpa|>mY@tTpTq&ZRxY6oYW04g3d-Ba2W@RlB&I+ILHWt0%8o=L=vdYiD0j%TI-wu57_=9(8uUKU z%5LZdZ3TTBbP%)v6JHac5zup>t4a4l9&~CTkvI$5jOXxw3wrFmz%fZSg?Ct913eFV z9yEgYfr6MgI|sS}wDKt8MLx9NpGZuC4syN^lW9}OfP+?J-tSrZA3DGD%e4xj^fq6vG3D9RjPlKKVt^P~&#h_E5e2aMSn~B77 z(DR_J^go9&1n4B_DbRDElc3el2K&L=YfSw0E18RL2;~db_=TJU)jf4kb9`;?^EPH>k zJ+~k?cnlW`;@myx{7s3(6=bN-MNXg&;#`gM%^P6@FDT*ShH%-&qR?pYkag3X>#th1 zcp*?FyBqXz)cKbQ)P#$V`05ICd@mpd9X!M1sAm4WcYi0Mt(QKKeRcK*n_s03~vvZ@G*p+L)$%!U^m?NJ_cJS03Ano z^|nOfN1SM`4HtjJR~s&Sz+W4V+#je7FFWegge#Ba)P&a_&Rriq?elL4gwOakgeQG> zfYt!vA>?};99zM8yKwri@WHhONE77_cYzoJDbCkwr?aqybMr{v36sK`c6XjLCCT>@gVjrunAzCkLSTx6WPBW2eyDf1Izs8 z=flX)CV1aM*lC3QGy2Nkk)QS9L$-g8KTGyOob~4h=xJ_8{ehe1ci4ApemJ-;REG#@ z-)@ArBHWuVgTQtJ!%If$Am6>f_It1hF!u8g0{bm&w){bx+V_Qn+rH^&yE%N`ZULP^I zgSX<~W_(9|X!=L|>%)d9$_6m zoa6g56ned3%OeD4-A9VT~**==tJ{nTn zM}4??cerIsxOGRkdsBF@G2FZ!c|p~wO`|U4|C$|c*%)rE4|msv2RDTGZ$nks5^hFQ zY1yjeYr=!g;buh1va3en{y3f*endlmJ9`j&S(@2USr1Kr)HB}lNcsW zCBID=Ow_)Q0@&-Z#rJIh%pk_i{MaTEMy2l{fcJpG=w!xhf`1Dr<1l_>d>`;838%ss z{4MZPG5LEyZ-(EE;8p;;0T>0on}x#|y+;^KjH;9=!J=T6GeftD2Cqp*fC^J$_%Q$` zn)yWqaZu)?DO3L*8^{45rkeOB0I=9iHrxe&E&NQDDt21QbQ!CCe<2){P#o*vU@VO) z0Ckbk?E+x3;(hS16BSH{?sHGGFTWP!NVXxMhjWkQ9ChvwJmCL`k8ScC0-imTNL=am zH)tdGLy_rk(B=>4tVerp3%9Hbx9$wL)P$Rd^TU<(>ayeZF%9|($9B6Ijr*bZClcK2 zO7~$~-98LbN1P3+54$_958FTu-8efT-+d3pXza_=`e}6?SN*hH$InIl5j*^9WP5lj z(gIo+J{R$A%Ghz&gnVcmui{cU6@A5AB4UL7P!YgfVm+qZY$f-e`g+p)Q3;YHS@xLbwj8*e8%p;^?d^< z#wND^c?T}E30!mpxz~r`h1~?GDbztHDvFJU-U%m$HA=J=qV+K44D7lW_XpeykaitK z$71@@He8K#w+Z2X*Rwu^>quFh>RxMEHXCqq;l2rL{(p@77M;E(rVsT+##ks@cZ}6M zkg+dX4;`%#=b&ifQt&-`ZmM4XG3wUZ3x~q0jY5OEmKUIoPhq}J^(}%$fW79yIL2HJ>{Vd* zQxI2g4wr*McICdBV?<9CXNM0P3IFA9l z0rQKa1W<{O_%KE|jEhmkw+r$ZP1JC301$z2E(hClkhu>sampmy0tV5C1Ig;YiP^8l z$iD>ttKc{HO^1Cx4pEd%#kij?;JhW7akS!G3@ivNCgmmfA;~tP>RcVine`~NCy>(( z7mTcGLb#DwTXzAD65zeTeTbv<&+!Zn+)RV?TS&Lu8z2Xf%^W1UeN;VCMA_#c`w(RB z6j>Q#Ci{Fn#@vzG!Rz$$VV##DYtqYdDFt>GSOxXrAhrzH&w!OHMCaoTz^pGP5_QB- z9LPtMi0)Tf5Vjg&+^SL6&&{l#Timg!>hsmuygoeWJCw0@qGMtCo`9|(<~aV8@vP_Y zycfgs?HHan$asAxDj){MI9NY_0RH>HA0eRZx`Sq^d-wAQJBl#uZI)KQ`b?El{UYZ_ zPJr(z@~QTPHY)8a?cRcA{(jhhVXqI9I3DhVt}@KcR8bcSuou=WhI0&fHE{0dsOzn% z>##-NPAI+;;A=7R$u`j#ji`(iqgm#$glD<}t zLGC={j)I?YJu1|Me=N;!@Q1SC$FtxQ zS@08C@RM2aQ(5rSS@5T`;AgVn&t}0Vv*6EV!Ov#FUw()9IpH^Bp?TaP9>>kS>m0_g8yR{{9gn&@eYdq-=yJXf+y1OQo(c6>@OC)NN{a^@_$wq ze0~;uso*9bx%s5v2lJ8kq{Qd$CQyyGb=fV3A9ul23OM9(1aUaCP z;Ac3GfWz2bDTWEG?yDI79|@fv!}?$1ZxKd3ETPgX=3D2*Um)TCF8=GO?EiyfN=<5B zXvms~#u|j)BD72BKA}g1enRNyg??G+^Fm({`i9V73(Z+zsbi+lD}=5Q+9326p@?QFU&XXiT=OwV@z74nk z!P$%>mkrGS(Cq&~_aIuQ|5J16UKI3G+niG{xXcdB9|h#};Lmbq zLD+WiK~>A=e2(!sCxGWT?}i%>ufW25=Xrz{I4_}zhON2S0m2I2J&zq){(0E6_+@au z>Qn`P1CPCcnpfp+gKEphGZU7xChrVL&<-)=TIDIWk3p}qR(ZOqpus*+W_w!>qe!{9Y&?;3M0Q%w1uSSTUUpvY_1B3khu(o2{ZWv1}KZ|xYfqB>B z@0fDguy~v&r=r;Ne zIiG+Pwo?lZpYs;n0jCh*L9`KQ&0jS0#~^d)uC())+zCVN5rpSo5xNU*%jVaz^OuJ1 zh9_vhf&ctv4n28x6HL#)QhD-iexoY?st{{uf&Ep=T&?Oxu{{AOe|d<_rfk*?kX#XF zOE0r$Er(2bke-M=>k))k1SyXSc*O|#eEhTgOuV0V<=MU-kX$a_hZdH75+r;CfBF2< zPwq#+R?%wIm>Q`YWrXzt5i_s ziTyt1DYkMep~t@49|qrL#oyUW(N9t2yM=UHb~$3O|CjOvEk6^M%V=yYEFyeqZh$?E zeU}}kSlA_{(H)@wj` zO3?pAc+O%36@5I+ZsoTOqQl1mvT}Y7g+*6~*>dJFK018Lr6!X7{EqUKm#-n$C+#9) z*O5&NZ$(0aM+#}g)+Aa5Z@8n7wP~1W@{hT#Mg1=$v{;pOTaZmzaiKYaqd2FKBA+M9 zJk%8?=Tq>9mW7CXgF!Swf_{bKnYKhp{XK)!-y3x4y7mMj^B-r7y8sp}$>S$0ePlr1 z;D7p(9Ex0)3sl9Jl+Ae@0R;>5iL7Ecv)(E`2xXzgdFzaXRr~^M$X}hyFF%V_JIQmU zGI)%rGFbglhh{3imtre(A8^I4s)1NZUEvo~MOaHD*ME${2rUg0`4*Y@XZb%0u^fJ_ zEp&xyh*NIR)&Cn=mbZ1$TR{9Z;JrK-K<*v3sz#i6_qDKK?m*CTYsHt5Qaj>vXk_y> z_``8n6U#mx1$)WeV2Sv`DjDOuOM=QfmqEVq=D-J`Y?*JGO6hpFXn@7@qVZ3R2G&8}OU8e4G%yq4-!}f!qX9m(^c~|rGa6_@F}!N$(-!~u)MzPo zPqN;$OWBb8<7Y?9?pUeKeg=Fz0^@M}lRZNld~P&Q4e%|ySR452Xer*~MQ)U`QTxY_ zqFEPqfMXQ;E7ZusZ_VEh;E-KHmXE2%hR?vlEFSjpTHI%puvg0 z@sHnkFz_!3KW2y3sAzoRU|8SzOj2;ulA$tnBz zi_ltyXRj@Llf975i?)|ypAl=bvF+r+z#Rx^w!*FSH!^Vv@Q zNNeP;5XWCbbDqERTPi|i@tuOH%e7;L8S}LHuk+;!f79`z}@9^=IZya z6vM`*GgH5RJwyEChsI{$$$RSusxte>Umcs#8|$}zI87Jx)R?lO9;|u(X0amp38;Hj z^7!Gg;D?~@uchLi91HRjem@rP>9OF|sHd+9^CM%y|3s$!L_*Ju1&87NTk%eg&A=8? z)|7ZB##SO)s{nt&5Tf{L5wBYQ@$+NB$ASNgc#jWl<0`qn@eL}GkA|oleT?O& z(6oQV--D`XM8n5(+Yh3B?L;C$n`iG0O*rY2RH`4E4<&}Q_O-6dvBy!5Wwb0yPz3hrk=eXn6 zB_cMRt>D&`YS;vmExXY2hu;;tK6G8Ev1Bdoj4(WDEJ1!1S)m0%x&o`ovB19GA&ajJ zqlsle=7Be-+`ehH5nnpHvE+u?yy3{5>CuVWWE7}4s332#6$-sO|t?B}T#zq$iKLW;Y{qU`v#(W# z&nbavAxDWU2vTM)vMOk@9|I< z*h^K8E=5j+LZ}#EM7%U_nJ}vS!&vVMRK=@YlU1pF&_>G0dv&>$UV|`XfQ{A)S0Y1l z%2iTTj7w*G(^;Xa2C}|VXFcP<=uzddvWMwk8M1Gl#gJ8^M%iT4Ans2?frC4rlW^O% z{U6~%682v>;h3L~tFE;MXBW=>IFI4{6P#bf`5l~W_Z0BqMzY9E$N`&01rY}D?%7v` zKmS_(-GsjYvA}x%ZN;CnlYg!F%i)Kab9w(5Jcz%*5&Y$Gypa3H{QE31e8)1+XIj7i z#~AJB{=&%{ai;s0lsECSS? z7qB-vMI%n$uoKaqIV7i?qD@ZTsY}HulCu&2zG3_YT3{JS&W%-0-rLUHfz-&J_-{s* z$*AlG6d+526;1(-+vwzx1k3U7SM~;IZ;rM%hxQf(N1Xg8ox%!d#!jaUf%e1B3=-ef z)1BNcPU$1g%#p)R(Th$vc-AT11fNrQ*jcp8nYYoIIqVdTX9+0nChPPMIx{Mq%XT`8 zwCc-9UU7;xJH?-KilA}YW@oxT-r`JKS?4TTB1%I|PQeo>zDN3l_c*g&5AJfN{mgO3 zDVy`Klb_>TR=VFQ*ze4G(wXL4T&UA9$NDF14GwKw)u9Rcy<7YYkykV%% zd()Zzh7$_D?i4R0 zN1c*?MEqBk5`bd19Q#E{N6G7=K(*-Gp1|SEeiGR2x)D6=%p!Ymm^E>_KmQ&l_mq=& zn3?O`>db|Kvti$5r<~#sIQbuNW)NF|dUmVIL5gm2=8{ld*|Vo-*y6sZ zYID>G=e48JW^60CV^__3NBWR#n9Y4QJN6B$O^c)4(9`N17{Oj=RuntF zGE=b&u~ok#dh?b|Tej}pVwr7knQwc*tu_yiHsZZ!M60$nLCUbdlDqiPplr8bV8A$1)}8n>PABV+H> zj==+{9Eb_#$J+WVy~D1my-Y24QnjMkKzJ{b%|c&kMf=o#YQsZF7AvJGIb;BeSQY9v zY_i#=H>{1~jbLm=n#o#sBWG1mmb>3D>l>4&_1TCWrP zy$zU#p*U%?G~5QXQ47mWk-DmDWyLA7qHh>mZ{4^j)*l?@Zo@YH>b7h)3#g&7VXJ;UoUOSoxgD)kk+{Y7gA8Tf4Gl;64zre9vpn z(b|E5Udt_9ZX}!JVzpJTdz;pRZ4YIeQ@kpT(9S5g?v@&&8ab-!D0aGvcD4<-F$LSV z)@`z)`_$H`MvF1rf`r% zT?)7%qp@t=dq;Zqp#Zjb^rJn{v0wK`aPXylM{Vs-${=2}wKb~|GfPu#Rj%Xk zG(pt~seUk7m3FLa*`dl>Rd7}c$uQ|dbbGy^2CmzJo)vvk=QSW5%)@9qtGE>%=^sI> zTgjS?)?;C3BbAi4-u{t3E6$yyW1XspjO$9kZ;ulYa+M@UZq=MxknvGa_c}40z2$mOdH*^H3c1g$>nh~sk2*k06a7wW0_|W?f zcVhRA@@`961**FnIM7{UKV5VPP3x4evu6NQ)h1BY_Y&9pHFR6;gR0sC;uh;*a9a$y z)c%Zcn26z&RKcZomyp1rVK{JKscs%YtibX7m+}Lz<$tY+zAOjs-mHN2uPJ+Qk`>cU zxZey|$1$VFBclGX00o+fpNs67aL!Tsuxxy2hgR?zO{@k`8&5~K@`e=$4d?M5oaC>( zS$Qq>?j@WtoaB!PKi?DMF@Tf!A;Hbg3E;O4nJ!~Y^2wl&AvFpW7&w?2NT+@+RzZbxACPQ0>HX59=PgrJRFG0y=Rd@1lu z_E!R@9&;0Jrr`J&PoD`rCV2W}G#?^hxtMumQ!ZZ< z{`7hEN#QrM-{`^|>7r&tO8hdP?w)5@hHI5pi4pToT z1vmVrzD)^k>TQAOStj~9N5f+l@J#kB2G0DPlpW16>7$No1V1OZd7poW(m%sGB+rGl zO1m`i9{RSHH+GuzwhBI7^tUQKC>O!m&Ux^$3mZ>+wTgbug7NsU;MHmP!wR2mndcV+ zn%#Oz;d3nW{G!3n3T~cXH0|*xf}7_K4gbH1e)HU+!MUPpTeB_m+@Zne32vS{GK@)F-;O2Qj!+)FL=6OMb^U(vgYxBII!H)=To)92Pt^;16TdC#B1smAKM__DmbQ^ z)xjqUh*ziab3K@LoWMr}@JgQ16Gr`S(V@*n=Ebhv{dkMb8|;|BvWt`*$`ahM=4Bwz~obhL|e zpVt7YlRAE>aPfGElcV{YpU|M;FBN>SPU8ziMm^xTyN=aV|L|Ge05+WUmyt&eNX5sBbQ;=UrwJg;uzeL9Q&7qZ|#75$Z) zv<9`O4xHCy;s1s37eA)?x%A9~n-OKISF=%$OjqRw&8PPDf%7WCr>@bs+Up6eYpjPDlysVw?`kp<61hp+0nv}^S~Ds;^f{MdS}c(LeT zE_k(+mt>!Ht-^)LLpEi>ZwDUn@EK>X=&wAXk(e~R_X$3DO5;aFU>x`~-)#4}bED^B z#czB`dYb&mQEO6Trl0(}=s#Ag@yA8vr&;v;It!kMXL*>P5wYLspAEbi{nCAD{m-*f zj>QXggjYzsmx~@t+HaNMR|(!MI4%e3;J33FZ?%-8!8a>hm^>sZ{O6>6O}jfFxZczQ zPsEAL$AHuR^nU3PqeuFA=|-(51y6r8?Fg*0grG<@<(t}E^`&GdA$ECgnvrfb%g}-X%>~w(r=?0sDpc!5I-ap z=Q_b}GyIYrT&}p?NE3sFMWRmoa2P*v~j`%qUZEUEhEKaVQr&l1-=Qy z4+LSVzr80q+_nev={>^$%*0(shR@WLV^i?>Fg|iLh<9G1QO=!5qi|;WU`^fjojd9_ zlvjBet1>Y{XK$y~ft9>reA@^H@RKfkFk6hTBMtXOv5?e{`BW_N3`FW-Jy zom2L%M`d4$ONLyyl$Q*vH!B35l{Ym*%E@-`g3N^rp5;}tkdt9C6d7QaXj4|@+!BG*c~5+(4Y6a}c6_c# zt<-SAh#wAA%R5o^9k=q8sUR0b<48{mCs%V-L9z;R@As(e>Yf>_g6$V@GC>!BOz;KZ z>P(@wOg!fG#55?=63`$ zZCLHHt`*kg#?b-{Ubs`W-ZDDLi7mV}q(7_V-L=})uTm#g)Xn3 zpJv0FT>D7J-qo(Y^&M4E{cUdJmwIs-?gP?b7LJ~ ztN};YZII9n_1moI`lhY5HBHg24Gr7Z?TGHEscl*pwQ5zIP23+H%lI|Q4z9yhuWxFs zt&6V2D~s2dwKy=SrS#--re58PqVctNa4&Ey19NH46<)R4$kq*D@qN2q<5erVQ7+nJ z2^I-gpV@U~7@=A0+pIGN6|x_T_bDu{E_Wks#nm^M!WC`Yu^F4no2$f*&2_v+?BJK^ zxZs5~{1kSi%zSOn{hD60^lr*(WKCOViU?vI9!Q1u#rySgatfnIcOyGMe6*MktHjg- zc9(hxB86X;(o-;F%l47>c*jsrd(2Xw8db%&B{s@OTKapq#)ehl6u~G<2M-B!bnjIQ z^={y5Z)dF+wAb;I$m($vb}^pSeOHJ@4K52mmUa~RQ@l;{`iA0*sT-7zwlSQb&H@hFF)%lOS>PV|IsziHxQ{wQ{ zHnkeI5)G!soK3 zZLsbcH$Jq92LaUge@%l&)an5tJO~xro1)c|k6=>mVyk{$Dnm$XAfLTCz($7?C0wjiUE6*9mru89T+OZvETT;~9K9 zWJq0zQZ#vO@Fr9$Ee25g+t?GSuaTxms)4hoR;Zi@fAYZ;mwgrcXT3|1n=Z95S^mGbTQ`@+LH#g;EM=kEQ z$~zDABMMFNFr*6E7sDsw`4k1lo>P^`C&0^%CP6P4w>Pq4~|@!XtMuDa&(p#jz3 zm&dy0hN8O@vc{(+^qqy4Fm6gQ3Mf$KwDt9Lz=Q!P#$UOLO>0KuuyCW)r#{14-Wh8j z*#jkQ{d;h0B;KC>E^5(1?f72jKI2o*8%hYW(hH--x&ER4Jsgsl$?XeoGcOt8`i+g# zClY0HA8hEnG)?679*f}NoQaWNEAoa`dzs)*)NdaiSS=zPY+}Nq{MINGLg>_nr?p^a7;b8>3>t?4K?Xt8ku)S-mItf0HcVB zpY!lM4CPx`-XqOxaXFQHmKb@nPl=%$McCMH_zZm?!np^E@tb{63~d$pbo&LiIG@5Y zG4f^~7DLTGEGGW+^mF=%^0hd5n0;LgHT%F&o_Uro|0po#iP3LG;NcNb|L%S_+IM07 zIgS9Yd_?37onoNpFzb1SK87%_ygPZcPzx>ph({PsBWF-f8+ql;I*6f#hEZsG{pb7| z$6du*Fg^Yk)8y65LS+4^<`0%=YW_|68UJ6w z!DnQo*|^UqRsLVikH*)Y^2UFzNDG`xXt`yYxc?^IhUTQnC)abe9;Mus zPPQK-&*;c$?046D)Ei +#include +#include +#include +#include +#include +#include "../src/noja/utils/source.h" +#include "../src/noja/compiler/parse.h" +#include "../src/noja/compiler/compile.h" +#include "../src/noja/common/executable.h" +#include "../src/noja/assembler/assemble.h" + +//Regular text +#define BLK "\e[0;30m" +#define RED "\e[0;31m" +#define GRN "\e[0;32m" +#define YEL "\e[0;33m" +#define BLU "\e[0;34m" +#define MAG "\e[0;35m" +#define CYN "\e[0;36m" +#define WHT "\e[0;37m" + +//Regular bold text +#define BBLK "\e[1;30m" +#define BRED "\e[1;31m" +#define BGRN "\e[1;32m" +#define BYEL "\e[1;33m" +#define BBLU "\e[1;34m" +#define BMAG "\e[1;35m" +#define BCYN "\e[1;36m" +#define BWHT "\e[1;37m" + +//Regular underline text +#define UBLK "\e[4;30m" +#define URED "\e[4;31m" +#define UGRN "\e[4;32m" +#define UYEL "\e[4;33m" +#define UBLU "\e[4;34m" +#define UMAG "\e[4;35m" +#define UCYN "\e[4;36m" +#define UWHT "\e[4;37m" + +//Regular background +#define BLKB "\e[40m" +#define REDB "\e[41m" +#define GRNB "\e[42m" +#define YELB "\e[43m" +#define BLUB "\e[44m" +#define MAGB "\e[45m" +#define CYNB "\e[46m" +#define WHTB "\e[47m" + +//High intensty background +#define BLKHB "\e[0;100m" +#define REDHB "\e[0;101m" +#define GRNHB "\e[0;102m" +#define YELHB "\e[0;103m" +#define BLUHB "\e[0;104m" +#define MAGHB "\e[0;105m" +#define CYNHB "\e[0;106m" +#define WHTHB "\e[0;107m" + +//High intensty text +#define HBLK "\e[0;90m" +#define HRED "\e[0;91m" +#define HGRN "\e[0;92m" +#define HYEL "\e[0;93m" +#define HBLU "\e[0;94m" +#define HMAG "\e[0;95m" +#define HCYN "\e[0;96m" +#define HWHT "\e[0;97m" + +//Bold high intensity text +#define BHBLK "\e[1;90m" +#define BHRED "\e[1;91m" +#define BHGRN "\e[1;92m" +#define BHYEL "\e[1;93m" +#define BHBLU "\e[1;94m" +#define BHMAG "\e[1;95m" +#define BHCYN "\e[1;96m" +#define BHWHT "\e[1;97m" + +//Reset +#define reset "\e[0m" +#define CRESET "\e[0m" +#define COLOR_RESET "\e[0m" + +// NOTE: From https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a + +static void print_error(const char *type, Error *error) +{ + if(type == NULL) + fprintf(stderr, RED "Error" CRESET); + else if(error->internal) + fprintf(stderr, RED "Internal Error" CRESET); + else + fprintf(stderr, RED "%s Error" CRESET, type); + + fprintf(stderr, " :: %s.", error->message); + +#ifdef DEBUG + if(error->file != NULL) + { + if(error->line > 0 && error->func != NULL) + fprintf(stderr, " (Reported in %s:%d in %s)", error->file, error->line, error->func); + else if(error->line > 0 && error->func == NULL) + fprintf(stderr, " (Reported in %s:%d)", error->file, error->line); + else if(error->line < 1 && error->func != NULL) + fprintf(stderr, " (Reported in %s in %s)", error->file, error->func); + } +#endif + + fprintf(stderr, "\n"); +} + +typedef enum { + TokenType_END, + TokenType_TEXT, + TokenType_DIRECTIVE_SOURCE, + TokenType_DIRECTIVE_BYTECODE, +} TokenType; + +typedef struct { + TokenType type; + size_t offset; + size_t length; +} Token; + +static bool startsWithSourceDirective(const char *str, size_t len) +{ + return (((7 < len && !isalpha(str[7])) || 7 == len) + && str[0] == '#' && str[1] == 's' + && str[2] == 'o' && str[3] == 'u' + && str[4] == 'r' && str[5] == 'c' + && str[6] == 'e'); +} + +static bool startsWithBytecodeDirective(const char *str, size_t len) +{ + return (((9 < len && !isalpha(str[9])) || 9 == len) + && str[0] == '#' && str[1] == 'b' + && str[2] == 'y' && str[3] == 't' + && str[4] == 'e' && str[5] == 'c' + && str[6] == 'o' && str[7] == 'd' + && str[8] == 'e'); +} + +static bool startsWithDirective(const char *str, size_t len) +{ + return startsWithSourceDirective(str, len) + || startsWithBytecodeDirective(str, len); +} + +static Token nextToken(const char *str, size_t len, size_t *cur_) +{ + size_t cur = *cur_; + + while(cur < len && isspace(str[cur])) + cur += 1; + + Token token; + if(cur == len) { + token.type = TokenType_END; + token.offset = cur; + token.length = 0; + } else if(startsWithSourceDirective(str + cur, len - cur)) { + token.type = TokenType_DIRECTIVE_SOURCE; + token.offset = cur; + cur += strlen("#source"); + token.length = cur - token.offset; + } else if(startsWithBytecodeDirective(str + cur, len - cur)) { + token.type = TokenType_DIRECTIVE_BYTECODE; + token.offset = cur; + cur += strlen("#bytecode"); + token.length = cur - token.offset; + } else { + token.type = TokenType_TEXT; + token.offset = cur; + + while(1) { + // Consume all characters until + // the next '#'. + while(cur < len && str[cur] != '#') + cur += 1; + + if(cur == len) + break; + + // If it's the start of a directive, + // stop the loop. If it was not, skip + // the '#'. + + if(startsWithDirective(str + cur, len - cur)) + break; + + cur += 1; // Skip the '#' + } + + token.length = cur - token.offset; + } + *cur_ = cur; + return token; +} + +typedef struct { + Source *source; + Source *bytecode; +} TestCase; + +static bool parseTestCaseSource(Source *source, TestCase *testcase) +{ + const char *str = Source_GetBody(source); + size_t len = Source_GetSize(source); + assert(str != NULL); + + bool no_source = true; + size_t source_offset; + size_t source_length; + + bool no_bytecode = true; + size_t bytecode_offset; + size_t bytecode_length; + + size_t cur = 0; + while(1) { + Token token = nextToken(str, len, &cur); + + if(token.type == TokenType_END) + break; + + if(token.type == TokenType_TEXT) { + fprintf(stderr, RED "Error" CRESET " :: Expected a directive at offset %ld\n", token.offset); + return false; + } + + assert(token.type == TokenType_DIRECTIVE_SOURCE + || token.type == TokenType_DIRECTIVE_BYTECODE); + + Token directive = token; + + size_t directive_body_offset; + size_t directive_body_length; + + size_t backup = cur; + token = nextToken(str, len, &cur); + switch(token.type) { + case TokenType_END: + case TokenType_DIRECTIVE_SOURCE: + case TokenType_DIRECTIVE_BYTECODE: + // Right after the previous directive + // there's either the end of the file + // or another directive. We assume + // that the directive is followed by + // and empty string. + directive_body_offset = token.offset; + directive_body_length = 0; + + // Put the cursor back so that this + // token is tokenized again at the + // start of the next iteration. + cur = backup; + break; + + case TokenType_TEXT: + directive_body_offset = token.offset; + directive_body_length = token.length; + break; + } + + if(directive.type == TokenType_DIRECTIVE_SOURCE) { + + if(no_source == false) { + fprintf(stderr, RED "Error" CRESET " :: #source directive specified twice.\n"); + return false; + } + + no_source = false; + source_offset = directive_body_offset; + source_length = directive_body_length; + + } else { + assert(directive.type == TokenType_DIRECTIVE_BYTECODE); + + if(no_bytecode == false) { + fprintf(stderr, RED "Error" CRESET " :: #bytecode directive specified twice.\n"); + return false; + } + + no_bytecode = false; + bytecode_offset = directive_body_offset; + bytecode_length = directive_body_length; + } + } + + if(no_source == true && no_bytecode == true) { + fprintf(stderr, RED "Error" CRESET " :: Missing both #source and #bytecode directives.\n"); + return false; + } + if(no_source == true) { + fprintf(stderr, RED "Error" CRESET " :: Missing #source directive.\n"); + return false; + } + if(no_bytecode == true) { + fprintf(stderr, RED "Error" CRESET " :: Missing #bytecode directive.\n"); + return false; + } + + { + Error error; + Error_Init(&error); + + testcase->source = Source_FromString("", str + source_offset, source_length, &error); + if(testcase->source == NULL) { + print_error(NULL, &error); + Error_Free(&error); + return false; + } + + testcase->bytecode = Source_FromString("", str + bytecode_offset, bytecode_length, &error); + if(testcase->bytecode == NULL) { + print_error(NULL, &error); + Source_Free(testcase->source); + Error_Free(&error); + return false; + } + Error_Free(&error); + } + return true; +} + +static Executable *build(Source *src) +{ + Executable *exe; + + // Create a bump-pointer allocator to hold the AST. + BPAlloc *alloc = BPAlloc_Init(-1); + + if(alloc == NULL) + { + fprintf(stderr, RED "Internal Error" CRESET " :: Couldn't allocate bump-pointer allocator to hold the AST.\n"); + return 0; + } + + Error error; + Error_Init(&error); + + // NOTE: The AST is stored in the BPAlloc. It's + // lifetime is the same as the pool. + AST *ast = parse(src, alloc, &error); + + if(ast == NULL) + { + assert(error.occurred); + print_error("Parsing", &error); + Error_Free(&error); + BPAlloc_Free(alloc); + return 0; + } + + exe = compile(ast, alloc, &error); + + // We're done with the AST, independently from + // the compilation result. + BPAlloc_Free(alloc); + + if(exe == NULL) + { + assert(error.occurred); + print_error("Compilation", &error); + Error_Free(&error); + return 0; + } + + Error_Free(&error); + return exe; +} + +typedef enum { + TestResult_PASSED, + TestResult_FAILED, + TestResult_ABORT, +} TestResult; + +static TestResult runTest(const char *file) +{ + Source *source; + { + Error error; + Error_Init(&error); + + source = Source_FromFile(file, &error); + if(source == NULL) { + print_error(NULL, &error); + Error_Free(&error); + return TestResult_ABORT; + } + Error_Free(&error); + } + + TestCase testcase; + { + if(!parseTestCaseSource(source, &testcase)) { + Source_Free(source); + return TestResult_ABORT; + } + } + + Source_Free(source); + + Executable *exe1 = build(testcase.source); + if(exe1 == NULL) { + Source_Free(testcase.source); + Source_Free(testcase.bytecode); + return TestResult_ABORT; + } + + Executable *exe2; + { + Error error; + Error_Init(&error); + + exe2 = assemble(testcase.bytecode, &error); + if(exe2 == NULL) { + print_error("Assemblation", &error); + Error_Free(&error); + Executable_Free(exe1); + Source_Free(testcase.source); + Source_Free(testcase.bytecode); + return TestResult_ABORT; + } + + Error_Free(&error); + } + + bool passed = Executable_Equiv(exe1, exe2, stderr, " " WHT "Log" CRESET " :: "); + + Executable_Free(exe1); + Executable_Free(exe2); + Source_Free(testcase.source); + Source_Free(testcase.bytecode); + + return passed + ? TestResult_PASSED + : TestResult_FAILED; +} + +static void runTestSuite(const char *folder) +{ + DIR *handle = opendir(folder); + if(handle == NULL) { + fprintf(stderr, RED "Error" CRESET " :: Failed to open folder '%s'.\n", folder); + return; + } + + size_t total_tests = 0; + size_t passed_tests = 0; + size_t failed_tests = 0; + size_t aborted_tests = 0; + size_t skipped_files = 0; + + size_t folder_len = strlen(folder); + + // If the folder path ends with a "/", + // but it's not the first character, + // then pop it. + if(folder_len > 0 && folder[folder_len-1] == '/') + folder_len -= 1; + + // Now we know for sure that the provided + // folder needs a trailing "/". + + char test_path[1024]; + + // Make sure the buffer can contain the + // folder's name (plus the trailing "/" + // and the null byte). + if(folder_len+2 > sizeof(test_path)) { + fprintf(stderr, RED "Error" CRESET " :: Suite folder name is too long\n"); + return; + } + memcpy(test_path, folder, folder_len); + test_path[folder_len] = '/'; + test_path[folder_len+1] = '\0'; + + // Now count the "/". + folder_len += 1; + + struct dirent *dir; + while((dir = readdir(handle)) != NULL) { + + const char *name = dir->d_name; + size_t name_len = strlen(name); + + if(dir->d_type != DT_REG) { + fprintf(stderr, " " WHT "Log" CRESET " :: Skipping '%s' (not a regular file).\n", name); + skipped_files += 1; + continue; + } + + if(folder_len + name_len >= sizeof(test_path)) { + fprintf(stderr, " " WHT "Log" CRESET " :: Skipping '%s' (path is too long).\n", name); + skipped_files += 1; + continue; + } + + // Remove the previous file's name + // by truncating the parent folder + // name with a null byte. + test_path[folder_len] = '\0'; + strcat(test_path, name); + + TestResult res = runTest(test_path); + switch(res) { + + case TestResult_PASSED: + fprintf(stdout, " " BLU "Test" CRESET " :: Passed (%s)\n", name); + passed_tests += 1; + break; + + case TestResult_FAILED: + fprintf(stdout, " " YEL "Test" CRESET " :: Failed (%s)\n", name); + failed_tests += 1; + break; + + case TestResult_ABORT: + fprintf(stdout, " " RED "Test" CRESET " :: Aborted (%s)\n", name); + aborted_tests += 1; + break; + } + total_tests += 1; + } + + fprintf(stdout, "\n"); + fprintf(stdout, "@-----SUMMARY-----@\n"); + fprintf(stdout, "| Total : %-3ld |\n", total_tests); + fprintf(stdout, "| Passed : %-3ld |\n", passed_tests); + fprintf(stdout, "| Failed : %-3ld |\n", failed_tests); + fprintf(stdout, "| Aborted : %-3ld |\n", aborted_tests); + fprintf(stdout, "@-----------------@\n"); + + closedir(handle); +} + +int main(int argc, char **argv) +{ + if(argc < 2) { + fprintf(stderr, RED "Error" CRESET " :: No test suite folder name was provided.\n"); + fprintf(stderr, WHT "Usage" CRESET " :: %s \n", argv[0]); + return -1; + } + + const char *folder = argv[1]; + runTestSuite(folder); + return 0; +} \ No newline at end of file