From b55e17bde0f6ee546938f4ee518bfccee2d9100a Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Sun, 29 Dec 2024 17:36:25 +0200 Subject: [PATCH 01/30] Basic bot code! --- bun.lockb | Bin 195208 -> 199474 bytes package.json | 1 + scripts/cfgbot.ts | 169 +++++++++++++++++++++++++++++++++++++ scripts/render-function.ts | 2 +- scripts/scan-codebase.ts | 2 +- src/control-flow/ranges.ts | 2 +- 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 scripts/cfgbot.ts diff --git a/bun.lockb b/bun.lockb index 49708d5e18c03f1c2676336bf3ee2268a58ef88d..c9859b7c95c8cd60793a43b585f43521ec60377e 100644 GIT binary patch delta 30371 zcmeHw3wTXO_xIjg4%v~ohKQ5M#U+SDu1*L!t|20bTU;6>5`;v^1#vr}ij1}6Y5h)@rW;(U zD0EfPN(MJ_Ou4UePWz19;}XYYW~ZhWC`$htqQ^af z`BHK!*+QQHU zXp^f)r{v~M!pP*L=jUdOE>QlBd}{D~wAK~)36O^BbE%(}k;4tGH{vv(wu|62(i)eW zJz;c4X8LUKG*F|)W{=NEOUW6XD~4z}oJ`G|FL9Ldtmfkwk1-7OwUqg3l9e1XQ82pL(SrcipdBQ4{ z!e~sHfNE{v$)A0j3IBEgQa7#wNiSyEnm^bD?sYQJRn)?0HnFq3W%U8yxvmOo|iviLVBj6OhP~DS{W$%;M7W# zy9FMj@1U?iyP<##3Wse#s$dh4V*DBu3C~Lm4i*ji7Z8zC=mkXd7LLiDm_`*X22YdR z>MI)L6sjm*;4g$IN+aMApcVKE$MH&`frK{%Q)R|5$=iWc&<{ujU15+$X>NpQP(ey& zW=3wFa^FYD%OWur$bTEim!;gyj8rse8F=zwZ$qu>;h1C-{)uTL{F{p0TFC7uu^W)w zU|M-zmVULZ=;j>o)ZHAZpP!$>yS=oX@b8Rh;l3;&wRQ-Q+|g6;?F!o=K^3-?XcZZS zMIoU%y$wsB+=VZcmU!zksk0E8yqLKZ%vuJ8k7vY*XU=7IMm-#Kb3VBvWR(eoc zs$vCCUAqP(y_DR738Thm)b_OOto*!;%-pfysg^<@`R&U%VfPr2%FXO4d@xk%jmpgpnld)U zCC@EhC@eG*ojv^8^%7ie%2e*&f|UGeW{esOJxungRIo`u(WP5JioJ;`d1HgHiXAGq zKOtjcN^Y(h`EuYK%Y<;F<6X& zsV}w=2Ub$C1S(flQSs+M>iOi1JnoT+*|~Wb5M}vLk-rc~4VsWLA)Dq+PIhKy{zOHY zku27LkwBXBT_oQUNORg1NNd2w$KWmC`#@g@5-%b_8S{aSf$2c3)`hV^imm{OI*|0f zA134<0=>ZR09t{YfaLl^=m0IF=~z4_~KC|=`)XX;4wMn1*P1<7OQ&dWo%o8wph~E;pzkB`Z_0BD!g;t7Zwj1D<-g3_QgO1#3!Xu9BWRF((~1x)|H5 zdpLZu#cER>NZt4WgG6&ZKPMw-92TgF`4b98VXJx|<>!E8)nk&t&%slzhb4XoISu$% zK&o|*lHINCnf<(Ukk}8lp5HhQd)eNeX%asd2fJ(=%y0 zzA;C{`dT1Wob$L?1r~s(5cQlZ_-R1mr$SCcFb+r+CIe}#6M@u$ZW5z`G$f%Mjo=y$ z9be2AqO0>o6Z$O>MVEpnlV`wF+m8cjbT>dw)9wV2!tQ2~kY50j{vj#f0VH3H%0ZVf zHgmvJeki(1)A-;*-gKA3QQ4W9q^PNg5{06)p@D#&_f|_Sa<}AwA{2Z6r~x^ zA4o3qG-lWIaJ-3Js;=5{fu^IEK~7sg0){Ww!&ZoDQ^8YjZ-S@h^pWL@nP^5dc=Dp# z)1sCj@HN2OR*IxL@G=z1AD>9v1w`ls@hweZ|wc z3r5fHcE4AX>8{;pZGUfJYo|st7d$-GYxwB{zZ~+o5!B|J%iX&=_Mf>_|H|>rM@lEZ z7PIH{i??h8!hU>aXzh%J+qYjysB(G4?i+4<$B!;}aq&{$9_kPy&eyI!V-(?U9itR~ zhZ(w`UHz94hrf4>BK)-*rT9D3(EaV|Q6tXZuDMu@C;em87^4)TXAHfu-STWTMd@w0 zHIBCYfK)G@igq?r^UG78l&5?U7d^RN22wqEYCBII4QTIFF6xA65Sc~gsj~8vCxS}k zrj(~%Kq^)=g{Kmlws(S$V~wMYWrCWyAEK^Y%i?CH;>uIS<*70=rPo8GL}HAp8?}2! zIfID-qZzeZ*wi&fQ470Py{2)vMU37T1Ky5jmH67!Cylt4cKv%hb-oeT%C4R;idxxqS4>yZ zQH?S`o7&mXgY2qd#NjV)*zmWxQ5s~|#~|pUpjE{vYiZLrgQFT@*KX6#fr|iVF-lt4 z^!k_=VO*2)dVp&S&V{f^Z2E3+q%Zn$ z0~`$nG-0b_14Zc!j<;1^Wa!~`^?f5Q+-`Y@oZd!4c(f$}bGsK$tt?NSFHePHWf8eE zk?P6y-b1PfPu0ZwfQXA~@5B>iZhd*`yYf_PFSEdu^3>k)R5fpkN8S{kDvoUL#1n22 zGC_GhjVLfFHL5)Ie0l0}d8#!wYObf}A|)J2Vb;$mYHQb@^F>Rz>&TG_SgB|z6@v!q zX(O(k-SRnP5fos$yT1rijohqGHS}n^eiAZry=o-(h;qiQq+sA{ODec1z6vQa z3nj$zNh7YkUG*}G+S@I?nvn5?=xDtVsaDW&;&Jnm5!b=4UxG|Tabi20>SL5bHl(Sd zgg~!~kr-`Li;TD!yX7ck5r$iMw60>8pqb=Ev1myK*V#zu*WQS;+x2_ksC8AS28%ra z^%)7RqV*!AdPB}*!g3s36v@?^Mo~w*-l4gOAK{YehTh4p?l#Q#X8_Nx1g6d`9V#>LiEkz@EII8`P($02ulc9I9tM`mJ{Eae-@Hf*a z?PAwA2AXaw=|amwTvxlAWE6F^>u#+NOk6e>l=!a z&_Gn6EmOfgR?dA2ZcsVb5;L}fD+D*RJn!Uh@mgoWC;0`}@4r+bCY2-L{t5Fnhx152jyO9tS ztq0h|Duh)T;Xd7n>t)wphK!;VB|>fb32+o`eBIXTVQz`3M3I?n#3k6(^+r(wY$7L` z$EOv`Ce^6&C?99U^|q_;8b!UaJ+w0p_r~^s!J@H51-)(h7I5T!XTAuZ0!PzG=boM) zZ7yt>&H5p5!WkGRy*gUe7n+E&E;h?Ba9B@TM(fWaMNP!?@UvMi5y$1B9n8{*3w;VW zgab{nSes=daYjO5w5G%uF8yNkK8WVWkj+Cv-vSQp0#`C4$|&uJwHfY)(;Y>xv+t+(*Hw}PWc!id2m7r>G4`1q+!jJQ<0 zJ{nV>dW5-x)nfxVn%uD5!Dcx|oZ%K5t-E37Qc3g({f-9*Ikw-0NMSui-^l(=aAGB+ zp7uG>;e;8Vh|KIANc-SHOw+LYu}d$vN|d?VW)lkKg&~ zH^7n4s`3$y9t6%filuA{QWQs6;xG~?z~O)C`_@UKiLi&BWr3r*oOu*K5030ror z!<<_XR%W=fH(PA4?84{bu8Yx5YA>Dmg447>RY@XyFo}@}tZX%WSLhVwvUJ z4O&t<*{-jJjK*F(GMxfPK`54rfK1UYvFtq#t_||U?a5oziNFiH#T>X(K-&91l2Ht&%rw3dUz zLLnSsIS($@C=QL*11E}*!_Er_jQ~gU8*8$!&BDMzt9`V3+9;iFSDP6647)yjlG*Iq zn6c|5C+5_3a1_pH4c7Z0j6SuE-{R_Hz>#f!|E#YENA3fMfjliaOeEN}=9NXW? zGEc2U3a=C^g1`(a5MIQzfu{<<3BS?K_&PZ9n@)R+9L)77}FcVx`epk2=seY!GtX~)IadOX0#aBx11`Rt3tWO{uyQU3T$gfgFF2Yd@B?B3?;w}k z^`0eSGs4UnZ?ilB4zo2W8c$YnE9{!{Qsc=LF=}t4bcJ1AZ0O9cA6#nQE>M><3`N1h zkD;Kc{|UGbyt=YUHr;Po`M}^oVi>pxkvGexF9t_0MlfM&?*~Vrh(W_lXuBMoQ5+bp zJCLFk9u7rN?FZMFYoTVfwo$s$u8uYIRd^G#LJR{huT^8lldEF%cqR(-P|{a`6EQ$r zM=3Zl>SWmCX)!8TS8 zgQM{ic`>WShzYK!GFSUkaBZQf;Bo_b7^-goy&7>#T~m>RK6MV{FHBZLYA1aovAxam z6Sxkv`CD4ArDX#tHwRL!p-~0a8eo`2xvh}WJP~vI3b>Bou*9{s>8;k4zm|x#xy=Q~ zX9LPVL~BI(Hz1{nilK>ZZPfC0ax_Oi5nY5-Um!8K2vtxk5XsRV&W8Z$BGf>rhv!EE z=_0g%+JQVkm=XMX1Y*&|^@xoBB{`-UU4Pbqs>n~`S^}wOkAbM?!%1!=toMi`Kcx_d zt1^;26+}1+L>D2IP6v^EG>9%j&Dg!(hg-u0<=3N-Mk7n+6Oy%T$rCz*rVwvRRARI=@Ms3`79t^gw!S~L|6!-s}MwE$v|}d4HRSiHwygS3jRW0jxjaB6Z8s* zJn$;W6|@ILJ>3hUi_i&l5JY4BKB$T@^I74?55UnyNTrTS{18YNA&pKMh-&!+L|0`b z`Da}ETcmPdNI4hL}c&;*heE3_!&eq=MIPp-j#Sy;;%qz$U~40awet!3Pm{mX#ujO;}4;WG)!0n zd;=g&4__c<`OzPVl=R0R@>CNb@lENEL`pVu$6r!tF0m!Bin04&9(-UzNCy{THK2`h zf$gN6&;@)~$^YLV)gCMD(o#XDda8Vx;R&z;sp15w(3`YyRYo$@7jn|;FZBjUy~;=f zIh5qaq33);fyywcMM%lV@Q17om-5P3#c5s*a1Qcg&A=qna< zRYsDRNI4-Tm#Q*qm&y!6YS41Y6H;=8+Tbb$ z8-P`emp6$4d0t8@BdNb2<&}{{FX9j37Ktwbsk&Ez)ScI*{0$&ogd~4U@|92w$Szr6 zw^Sgc0{bNYcaRqI4`lg|Wcf#79q64k2jr|&{9i}DTJV;LSUr!1Gy$)Z|9^)hsxIY! zhUB>#QtweneXWUnQgPJAUlP=j1qg|+3nWoJDJLX(eaREjitQtLLdy3AlE`1m2}#~q z@|6({9j!Ez3IS4qkY;V5CrQ#6?KZexQn?u<{2h|qA?1X` z&jFHXF8)yY1ya6n07-Ez1X8jXe`qUSE#-uiTq}7(O0LHrGQ2^`e}hKx7O{qHlv5@{dCD>p|p`yWW*}hz6Dnl}ZC2NCSjq@FR)G zfK>1knNLW`&m>Ps0dZRL|2@i0?bMz8Aul)>HQ-4UufhheG`AEz+IU^Nc;mJxxNbAK_01!KQshxlCK4%{Cbja03;I) zCGQO+&ol+n^?P*uUhro;848jWv<6be5z;UrH8@K0gw)fvlK%q~{%;ONBDx5v%>T|K zH0uAeCs%6F|I_>)|78UqyL3|7oN`z{__Y8 zU(gi)M)>46NNdKoAX-Z<%6vi!_A)f95{&|EZ zZ~XHJP0#AW6aRt#JVO8T2+h}ue;%R#d4&Gw5t`qX{{Qv}y~DA))5}=MAJlw+W&XBY z>S@7uN*YwKwA!&(U+6YAXzHtNPqglTW$(lIEjL|;Jr>=oaNMoMwcps*^v;K~pBwj~ zPuffQhf_9fezR@B(iCg6CM&yKOey*fwoJQsD%;(;W!Hl1^|Hn`I^Q6-#gdaxv^wf= zo1+|xAN%}s_7OKS_rcOHPFFH~ zx3b}miOsf6yR`V1vD%p9zi6M{-=bRYUK#YM=lYtv*PO}!wfcesi;|{X$XWa8wB-Z6 zPMtY&CZ#dk_{QNM$2;CxGpyshLGM>Ge6O-$N91c^ZhiZn?>waAtyr~Hjjz^zdSRMd z+33vYtK{$cbe4C}xi_=ADzAOJ=|pPggpeDLYyML$Z+v|Igpzxw_^^R5BUE_e*UENn zyZn1kf9b-Hj=;ME9zN`~YxVV^UOA<|ydC>yuO|CK8YSf%Sa5s&x$HynA2>Q*2z;;q z>2z0@R~Hv_t@5*T`VyyEqb^l4e7~~cRcG%N*17*m@tB;(o=IDepa11>^Ey`#tSA`J zFn`a%B_DR^HSd)n-{=Efvd$D7TRHe`M_zQm#-9#Uoq2J`cXPMiKHs0>z#Kb@3-faF zuC@Kx)~9~)IQ9J5_u_u3)ja0M{<~vXK+i#6InD9(Zg;2WuP4TPoDRQp{g@g--cZ*#u(5g+P_SbJDu7w_>JJ?{Qg?fZT99d!Ei!DX*+U;cXJ_#5Y( zo(c`#Go#fKZQs^;U*>(-;hXSt6Q?#`ZToWf>}f;aZ@TrPvSGy;iBsnO+vllD4dz>t zY_Ze(+g+E`Ka$>dVCR25-(_}c*u}C-F;!~sTYbF2j#W2~-M%tmel_b(M~jOa-p$S1 z*X~xE&OK`tt8eZpTzA8xekaR}{2rAIt8`y%8h-r)*RBgTC+v6B`yhVWL)WI6ou5Cu zOnEKEz31v0^Tw}R)U1Wyi!pB%Y)t)oq02_cYM1lmF#K> zl?{)zg)7nLcZaSj4*4dhU0m36X;H(MOzH4N=FQQ+{<`A(V~Y*%XO|b|zWaXHBUS9L z_rBTa^&wB`1p@*S-z@ETZN&_$%F4#8KjCXGPP%yj(DI#S6V*@DooB|Y5mo*(=b@@D z+gX;Qj?mQCc82DwBb?NAJ4*^wn`+_5Sg`#I@ik)@WUZ#F&X&TC_z0E$@sS5NWu9U2 zU}y4lb+waaI=*dGi?aLk?C^g41J}Y1JCh65)|z^b70*%o)bkxC{;4bV+&o+$b?5at z>U332-kCL5J)&CjGoeG47W2wKo`=exnSghsJ0bf{WA7HJbJcn~A_J(#wBVs-g0v9c0l%!tQmNHs|DA@p1SITJppd`I%^AIwJLUW(e4M77zbZJsX zW8NQW>SYxvqcLBB0@P3^Df31;N*LrrNJ)x=w?TAOm3lr%zby5tK}H{RD83MGlS2B8 zl9GO)SES5E%KRbQE@jnuVSeBS{{jRxz)k8kL7F}urpoBEN-EM6v;zsUL7!8StQqJv zPbsV=g}3Dz+g@kw9;nTj*G=_lzW`{`2LR1L0ifof7NC}(Ku|08`c2g*xe1bVm*fee z4Vn)s&LBE0%@stapwR~zGy~|9kqe;jKo>!mK;MHdgU*2H6Bzmc_W}#OrCQZ% zEa8?qqAB@~J_w)>cs>Um2YmrL0Xhjf1^SYe-BLXqOOad-S^;99r$H-0t3dQQ(#IhB z_>4YND+0|1(Tt-H{lCC4QLd1M1dfxUE6|EA*1(UVMvFAA~c1KzoY678yNb^!oNVzf#|c{ zAdn}>3i1Lq0@VRgw9$uuFM!s9=$YtM&<;=*XaXo3MBi)~2pR-R0u2TY0i_}@4MYn? zJgB3F#jC5v?%q*jLeD^fzFcq=L|?X84WdP19cVphCFp4oy}qJvhdjkH?y7;FSx62C zjR1`V(YKJQgIw9mch!KZG`*cwcJi+3@2H0K8TjEW=p1M}h`!4=36ulM1LcG0`+(y? z*qr#MJNjn?M^uzL5EW>Vn)g3;})IItfHy z?syhd0-6n)19}`Z7c>(z3zP~<0nxXK`hf8DBPAZx3uFV)GSe311@Z>zpl{gedur=} zyV0KEpedjXP!eb`Xc!3V4F6!=AI+!-$^p@LOlURQ3Zeyw793h&7J>>veL;yV^+IncKSqAD4dIj=a5bXj9u2C3sqlcYh5Xq2NlPAVft8n$IPm8a=g~I*z z&Xfo0V%MP>vL1q0f%k#5H_}E*`{o@G$%t2^o~c9m)F~?a07P<17uFYb^&H3Ux@fer zE6k_5=Hr1)w$MBY#98PdFNj4~*F5Q@G=8p0^&bbmQMF^KtFt=1O>jssR1$zSa68)Cc6#=^$4r(3O$ND(mPzW#qRi!*}phzIG8CN$IvO)ncY8s-q7&5jcUe>gnDRj zL~tmg7CE>%D*RyY*cUgy^$PVCeGNy}B$kY#R+=TaD=Ms+zq|UpP91xi3K7a;Rssce zE!#q{jU94Bg@@Q3g3nn{4HUj2tvB6W+-l;UJ@YlHw`~OadxxdeKtJeaur92(Dw}ul z@Jl08p%4}vN)2nkw!uOqO=fo}eE4RS{B?)jns5sk4{RYaC$INNpJS6QTtQAqFg0B1 z$?l_Qn;)T22SqRD#rJkS(DGNV5Nbo&-+PbO8bU_3Dy(lUt)2ypnG8o#Wra1h243bF zrrmFxne206Xd_W&Te2w1IiXwxJ6coo^!m#>m`5$m+six-)h)lr&2LXNERapXKnjyf z^+gu(FmRk*t_43|Vtx%Y_o|`6k(e0Fr#5_K9z?qTe97jz9^G#_sTe}8U>;T4rq>5= zJiGL@X%#u4EDJ@g=HaFrUYIrRnsd`*6$(l0StzLE*gFI>+4b^TOV|%ot9gFv0p(iv zW|y>%6-CXXI8Qx3IyQ90$^8{MM_GqDsMS1@HMRb)dQBhqn-vN-*d!=udNsDFj@Cf+ zX8S$O&W5qQ$ci+NeRcEbdTHI($?sK^HIES85}T2nV-HwUk(0;V>Y@`*vVgkU>`3z* z)=>L%O=mn_yCGa29vl|j7Aqhg32^sb_~Ey^CqDhujj#D&gi>65&Q8OE)jVl+Z>!ZO zqX#zV358G;4aWlntL?6ZYidmv>#l{V-C3fC=I+PC(LC3+!_WJ+AFdTS7KLmg#+_=h zt?uaNAod;#tDV?AcQo_gtX@4W!D^n;x-f0o_FX4S(HN`~H0sKHHnScYV;<)k?bBoU zw52`cWn&OJ>)CcFsI%FTdN8|)JtR)TY!+T$n{73ZL|ZrSoi3a2*Vv?~vm((b6rRsM zst@($>{5Lc9>u&pz={4;#oWZdHKM+e!gzpnBo;jKS?|^dHh3OB=#ExkVIiN5XX`yM z#my67Qztd)(6M^WC@4@zU}z4zN*2uXa=-d&@7gV4yW8?{;|ng2bx-vki*5kDM}|4q z^I_Rgh@=yGX)y`uylTYGsx`F=Lyrt|UX+KqyDv|hC;vLvjtP8IUG_mmc^>|rY6+|E ziH1Bf^0|e#m>mj{=262f>ogBn7k=|>g`r1AJ}=56UpB-%!ub80g+4Q$8)#IN=M%zH z4P&RMA&;C3+}dzfvmq4tp~>(_=Ew~XKQDQ!Q-z@?cXny0wN}+FEZ3^}S>Nyxi_8J1 z5wUSQe!PT1LhMpF>}A`m=#qI%=i-&!$Npq}5${4U%zVi-Pp$LO&J9nzc5Narij^35 zOKf`_8gU-BX~%u7-_orYvvV@I0_}O&%tE~oN%vVi!0PHNcAB99``gT3GT@l30&TD| z2kEt9FZ%&HupM5At9ekzd@XEn@j-C>;{1Pebu8Aj*{a^qyTYshuUpcr*R3axJ*NLU z4SE!RXkS%7*4JALvwHdoOM6@rll=o*--iY!8fgTuwcgqo)y6E1P-7?PQAfNQ|NP-| zp@V25hXnJzsF+P^rFpW+=HkzGHNr~Jm;KNPjY(#@52jNF>p-xFP4a>8 z1GbQ$m~HgIl+5rK{XcTmGPqN-y=`DV9Gf&cbcS{EMXA0lpP&PKL2##i;WhKX-*_I~O!ypHCBGCU7`01 z%Z5T5^PJ%~zO9qx|Mg306$<7_#Qm2Xy}tK&x5A1Xr%<-1DV#Z%9dD|wwzkKTt|O`% z`=3o$L(;ZLZ(!KFv628Z!aOwiq5r~(EBjq}lPg40T%@x7&Cu8!b_!rM4-F38I{nhG zO`h4x_1e-LUdC#|P^5XvaO0PJk2T!Xg*FUKa*8nX9O5TzFEqHjed%6aRLpLc9Du^z&?EbKTJ1tV+mDi^On%`!jEwpep zvN;A~5}V!}Giwt2ra6LB!?puEud<^kUC|P2%Ziq?q3uNUQrPmeng=z< z*8SpxJvrU(NpC=p8}d|_vYjol&6+18zy9+pKQ4Qx{!h@0z-=TdY{`5A;WhIpUun@gN{!~W`PMgsJsXJb zi*>_lp1wTUzI0m5oxV<*N|O;yRj#s5LFlH?Goc<^h}A}I$~LxwLz=P^1b@#I%L~$a zV?j9>grY;)tsr>9JfV5W-fNaMUtMj^Jr){l!?(}bh+tHh#0r9;Ze=eM*OI+Q+%j|0Xiu%9l^Y{v z9`rmT*=gI<-03@!LmMA_e~ev4gX@|nKi_flf4yGEXMWVwEan-e`KfL!It=aoP2JSJ zddw$Wv#?!ZS_C?cHsBV3H4TS@&C{W8&35_NUaQj*+;fG}K!+&!Tp=7iyv&{thx$l% z3Y?dDJao5%qedMK_&Enn!=^+ot=gN_ionM8h+8W>UXjKemK*^S=DE|mNZBYNbPbd z$=m+SqPyG>uVOB{PkKMI&^B<72t%uR{`9&RmtL-4@7NPik3x&E&V@1u^c({QiihLd zcc+g%FnHNUP3-^)J(!zEReSrNZM;wSe@nIqy?q=SbT^SUI6vYL1G?T zJ@At&O{Qhn8p5mNk-Zff^z0n!a{Y44Sa(d@Rgk#j1B$|t%xZ%NLc!A=!~IP4m5Kgc zPok{6IZk7JxWXXupg&Yyb1L@Mdb)YS%1-X*3Vhkz$<~tU0jN?RUdj%AE%Vx_bx@_% z2JRlo%AkN}{mZ2H$9)WCK2aFaKUOeL-oBDHbl~as`?{hE>>uRW5j(S@v?{9AJbSjJ z_0ZyGuXkwcu6hp@H{H=}VOun!JKNY6w@T&_+_^kPQQPnTKa5uDW)u<1=LsoYaWmFn$}PKgO1{(;7sY$8GOu>XaOE z@rk+>8s>rAQ9n-aQ=``WvWlE^b{fX5<_X@nhp(I!vF7l@3WXP0?PxUOeTJjkJgw#t z;7ezF4Vg9hM)L|iS2h9)k>;7<_G*cva~70h<2PGro+|D=t&e(s#%I|0&76_!WwLD^ zL%twqb-?Kbac&g~_t;r#LtS={pcSjvUJF!vu}()o!X7fArtt_Df%$jFf`%dop zN{)NaeQ6Qgx``!1LH&V^CwRb~?FMjT z>%m2ur;vwb?)dreM`P(0mL@cU#XP?}x7YZABfHF@sZQ$`a_sCXRcIci{oU4rlB9E! zZb6}Zg8A7|#Z>0#qSbGN31yz0{?xOt4!l3}qk&wN*RqIZLp9Pobv<|2z%5&~n%B7k zuk0XR#p1KB!gitY0~*f$nieuzw_)@w+igb#C$j5yEl~HtYg>9VxP>+C2x%D`LQse8 z?}((ap-z}ce?07yS>4WBc%*sa z`WC;z8}2^wu!(FNTx6cfe&u%R=@E6EBPu$)j7>*TwF}$U87@j>rJWIH^Vp5fnqQ=O z7Q5&BCHm_N=11v+v<#7jgU7T*Q-xUW4U%~RTsRlkrr-3Z#x z^+IV|GS6pEIpBAw`_rB`c#HUs_8mLg1x*pKqANCi;S?4@X@m9Yid-+2(G}IKVQaxz zKf>EkdhswQ@ksBKTCcqaWAvUD9Y4v+y26nkF&_%D?^xSzD0Px$5%&#S+#MhTP);}= z0Q{w&@&o&U)G*6?00PO+kqR!V+_j!I~nwxTGeR^pYQCoc{l;={u z8*@ty8_E~k=4*3w2@4Y@it=An{sn8AfC+bwB_?PCt)X+oEkKWr6SjWgQ?(jQ$=keW zwm$(a>c&n1tclR0dx;fq#Q)3L_AV`Tc-Ejm*+gq={U|I)$?R{xBaxgUHnyDY&bH{^6KaIeZAbvLnXq%ATVA@dKqikxojejh|BztJ$8T}AFd_1W~k znz!{23S+O7KA*#m_C<9|*~0#S4NOf0>|j=a)%=-&h)y@#_Bz7S(K)&BoM8KhYV-~d ztuTL-;E3z;McWrP@cF$~Rjws1FJ;w-qJn;`jOyV+clIz*vslgFE;!`6E#H6LJ11a9 zZg6LqPd`%`_uBKtJ@4U?yB<4J`_1CjAs|B(Zd8W)1(rMvWp1)(spxU`4uPyyg6Kn~ z)`^C|6&=VGm93RG9!?53oy1)h&RhpzVLHeH255oSFBXbz(fo;s%PY=~X;-|WK`n%K z)&MQM!^K7Xn*rs&gm7&||G}AF5ns{k%22$U$HQ2$xXWzQwPxzPYR2G-ob&AT01S%x z;|k3k1vcYc^NSSsA*}TvZH(2^5buAwetvM}w|g6G<0DKfiQ><;4}zgYc7&i6J3mP4h5-mn z(h~H3C`C^LN7(EntR=PB%SoE2%emR&w;ZZ1XCEhNE%k=W`FCUrE9O)(^BxRi;(b1g zAFO$lE6L#aj{x}nG`PyHk_oAYJ2jld#r%dqG>k;{mo2@G=I9{ z{GIUe-!`s-Pafzw99y_z{Rp|}7FB^BTLea*yLD3<8=K?=`D1VazfkZ1fW{>&H80N27 zAg8T(PhftN(EL3MmTMXb->(Tdf|^N z`h&ir+5gcY7vTnEiCay_pLiYLo}yb<^VdZ*m3C>&qd)2LBdy`d65kx6FMi;fO5fqa z<#u_ARA+@s##I3{JsK^i0sWl$-VndQOpRkYd4If%VHXMJw*7i@l zW_<^qnpJY@tyNxYirjw!Ul;ts~!2Z(>e%UUuu0i5WZNhHKBS zW^Q*hFGnSD>mbSu63c&haNd|{DN|B1@`5t6M~%CwF=p20u{OSJ#u^Kq> delta 27882 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 JPLW^re*o4!98UlM diff --git a/package.json b/package.json index aa06c9e9..478d474d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "esbuild-plugin-copy": "^2.1.1" }, "devDependencies": { + "@atproto/api": "^0.13.23", "@biomejs/biome": "1.9.4", "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-go": "^6.0.1", diff --git a/scripts/cfgbot.ts b/scripts/cfgbot.ts new file mode 100644 index 00000000..bd84c1b5 --- /dev/null +++ b/scripts/cfgbot.ts @@ -0,0 +1,169 @@ +import * as process from "node:process"; +import { AtpAgent, RichText } from "@atproto/api"; +import { Graphviz } from "@hpcc-js/wasm-graphviz"; +import { $, Glob } from "bun"; +import type { SyntaxNode } from "web-tree-sitter"; +import { + type ColorScheme, + getDarkColorList, + listToScheme, +} from "../src/control-flow/colors.ts"; +import { graphToDot } from "../src/control-flow/render.ts"; +import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; +import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts"; +import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; +import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; +import path from "node:path"; + +async function main() { + const password = process.env.BLUESKY_PASSWORD; + const identifier = process.env.BLUESKY_IDENTIFIER; + if (!password || !identifier) { + console.error("No credentials provided"); + return; + } + + const agent = new AtpAgent({ service: "https://bsky.social" }); + await agent.login({ identifier, password }); + + const { png: imageData, text } = await preparePost( + "C:\\Code\\sandbox\\function-graph-overview\\src", + ); + + // const imageData = await Bun.file( + // "C:\\Code\\sandbox\\function-graph-overview\\media\\screenshots\\color-scheme\\custom.png", + // ).bytes(); + const response = await agent.uploadBlob(imageData, { encoding: "image/png" }); + if (!response.success) { + throw new Error("Failed to upload image"); + } + + const richText = new RichText({ text }); + await richText.detectFacets(agent); + + const postResponse = await agent.post({ + text: richText.text, + facets: richText.facets, + embed: { + $type: "app.bsky.embed.images", + images: [ + { + alt: "Control flow graph", + image: response.data.blob, + }, + ], + }, + }); + + console.log(postResponse.uri); +} + +await main(); + +/* +1. Select function to render + - Choose random file + - Choose random function + - If the function was used before, or is too boring - try again +2. Select color scheme + - Randomly out of the options +3. Render the function +4. Post it, along with source attribution + */ + +function normalizeFuncdef(funcdef: string): string { + return funcdef + .replaceAll("\r", "") + .replaceAll("\n", " ") + .replaceAll(/\s+/g, " ") + .trim(); +} + +function getFuncdef(sourceCode: string, func: SyntaxNode): string { + const body = func.childForFieldName("body"); + if (!body) { + return ""; + } + return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); +} + +export function buildCFG(func: SyntaxNode, language: Language): CFG { + const builder = newCFGBuilder(language, { flatSwitch: true }); + + let cfg = builder.buildCFG(func); + + cfg = trimFor(cfg); + cfg = simplifyCFG(cfg, mergeNodeAttrs); + return cfg; +} + +export function iterSourceFiles(root: string): IterableIterator { + const sourceGlob = new Glob( + `**/*.{${fileTypes.map(({ ext }) => ext).join(",")}}`, + ); + return sourceGlob.scanSync(root); +} + +type Func = { file: string; func: SyntaxNode }; + +async function chooseFunction( + root: string, + isAlreadySeen: (file: string, func: SyntaxNode) => boolean, + attempts = 5, +): Promise { + const sourceFiles = Array.from(iterSourceFiles(root)); + + for (let attempt = 0; attempt < attempts; attempt++) { + const sourceFile = + sourceFiles[Math.floor(Math.random() * sourceFiles.length)]; + if (!sourceFile) { + continue; + } + const language = getLanguage(sourceFile); + // const functions = Array.from( iterFunctions(sourceFile, language), ); + const code = await Bun.file(path.join(root, sourceFile)).text(); + const functions = [...iterFunctions(code, language)]; + const func = functions[Math.floor(Math.random() * functions.length)]; + if (!func) { + continue; + } + if (!isAlreadySeen(sourceFile, func)) { + return { file: sourceFile, func }; + } + } + return undefined; +} + +function isAlreadySeen(file: string, func: SyntaxNode): boolean { + const cfg = buildCFG(func, getLanguage(file)); + return cfg.graph.order < 5; +} + +function chooseColorScheme(): ColorScheme { + return listToScheme(getDarkColorList()); +} + +async function renderFunction( + func: Func, + colors: ColorScheme, +): Promise { + const graphviz = await Graphviz.load(); + const cfg = buildCFG(func.func, getLanguage(func.file)); + const svg = graphviz.dot(graphToDot(cfg, false, undefined, colors)); + await Bun.write("cfgbot.svg", svg); + await $`uvx --isolated --from cairosvg cairosvg cfgbot.svg -o cfgbot.png -s 5`; + return Bun.file("cfgbot.png").bytes(); +} + +async function preparePost( + root: string, +): Promise<{ png: Uint8Array; text: string }> { + const func = await chooseFunction(root, isAlreadySeen, 100); + if (!func) { + throw new Error("No function found!"); + } + + const image = await renderFunction(func, chooseColorScheme()); + + return { png: image, text: getFuncdef(await Bun.file(path.join(root, func.file)).text(), func.func) }; +} diff --git a/scripts/render-function.ts b/scripts/render-function.ts index 6af2423b..dcb93fe0 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -24,7 +24,7 @@ function normalizeFuncdef(funcdef: string): string { .trim(); } -function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { +export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { const builder = newCFGBuilder(language, { flatSwitch: true }); let cfg = builder.buildCFG(func); diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index c6bf2da3..937f4bb2 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -11,7 +11,7 @@ import { Glob } from "bun"; import { newCFGBuilder } from "../src/control-flow/cfg"; import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; -function iterSourceFiles(root: string): IterableIterator { +export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( `**/*.{${fileTypes.map(({ ext }) => ext).join(",")}}`, ); diff --git a/src/control-flow/ranges.ts b/src/control-flow/ranges.ts index 66c0bd27..b052275d 100644 --- a/src/control-flow/ranges.ts +++ b/src/control-flow/ranges.ts @@ -62,7 +62,7 @@ export function getValue( ranges: SimpleRange[], pos: number, ): T | undefined { - return ranges.findLast((range) => pos >= range.start)?.value; + return ranges.findLast((range) => pos >= range.start).value; } export function* iterRanges( From cf50af6e0bc0f52f7d64729f9edc5cbd49392a21 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Sun, 29 Dec 2024 20:37:09 +0200 Subject: [PATCH 02/30] glibc --- scripts/cfgbot.ts | 53 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/scripts/cfgbot.ts b/scripts/cfgbot.ts index bd84c1b5..cc1521ee 100644 --- a/scripts/cfgbot.ts +++ b/scripts/cfgbot.ts @@ -1,5 +1,5 @@ import * as process from "node:process"; -import { AtpAgent, RichText } from "@atproto/api"; +import { AtpAgent, RichText, UnicodeString } from "@atproto/api"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import { $, Glob } from "bun"; import type { SyntaxNode } from "web-tree-sitter"; @@ -26,8 +26,9 @@ async function main() { const agent = new AtpAgent({ service: "https://bsky.social" }); await agent.login({ identifier, password }); - const { png: imageData, text } = await preparePost( - "C:\\Code\\sandbox\\function-graph-overview\\src", + const { png: imageData, text, alt, link } = await preparePost( + // "C:\\Code\\sandbox\\function-graph-overview\\src", + "C:\\Temp\\2024-12-29-glibc\\glibc", ); // const imageData = await Bun.file( @@ -38,18 +39,30 @@ async function main() { throw new Error("Failed to upload image"); } + const linkText = new UnicodeString(text); + + const richText = new RichText({ text }); await richText.detectFacets(agent); const postResponse = await agent.post({ text: richText.text, - facets: richText.facets, + facets: [ + { + index: { byteStart: 0, byteEnd: linkText.length }, + features: [{ + $type: 'app.bsky.richtext.facet#link', + uri: link + }] + } + ], embed: { $type: "app.bsky.embed.images", images: [ { - alt: "Control flow graph", + alt, image: response.data.blob, + // aspectRatio: { height: 2, width: 1 } }, ], }, @@ -71,6 +84,12 @@ await main(); 4. Post it, along with source attribution */ +function makeLink(func: Func): string { + const line = func.func.startPosition.row + 1; + const link = `https://sourceware.org/git/?p=glibc.git;a=blob;f=${func.file.replace('\\', '/')};hb=0852c4aab7870adbd188f7d27985f1631c8596df#l${line}` + return link; +} + function normalizeFuncdef(funcdef: string): string { return funcdef .replaceAll("\r", "") @@ -136,7 +155,7 @@ async function chooseFunction( function isAlreadySeen(file: string, func: SyntaxNode): boolean { const cfg = buildCFG(func, getLanguage(file)); - return cfg.graph.order < 5; + return cfg.graph.order < 50; } function chooseColorScheme(): ColorScheme { @@ -151,19 +170,33 @@ async function renderFunction( const cfg = buildCFG(func.func, getLanguage(func.file)); const svg = graphviz.dot(graphToDot(cfg, false, undefined, colors)); await Bun.write("cfgbot.svg", svg); - await $`uvx --isolated --from cairosvg cairosvg cfgbot.svg -o cfgbot.png -s 5`; + await $`uvx --isolated --from cairosvg cairosvg cfgbot.svg -o cfgbot.png --output-height 2000 -b ${colors["graph.background"]}`; return Bun.file("cfgbot.png").bytes(); } async function preparePost( root: string, -): Promise<{ png: Uint8Array; text: string }> { +): Promise<{ png: Uint8Array; text: string, alt: string, link: string }> { const func = await chooseFunction(root, isAlreadySeen, 100); if (!func) { throw new Error("No function found!"); } const image = await renderFunction(func, chooseColorScheme()); - - return { png: image, text: getFuncdef(await Bun.file(path.join(root, func.file)).text(), func.func) }; + console.log(makeLink(func)) + const funcdef = getFuncdef(await Bun.file(path.join(root, func.file)).text(), func.func); + const text = `${func.file}:${func.func.startPosition.row + 1}:${funcdef}` + const link = makeLink(func); + return { png: image, text, alt: `Graph of the ${funcdef} function from ${func.file}`, link }; } + + +/* +Next steps: + +1. Create script to export (filename, function start position, node count) + of a full codebase into JSON +2. Write a script that takes a (filename, start position) and renders a function to SVG +3. Convert the rest of the bot to Python, so that image manipulation is easy. +4. Add link to the web-demo with the selected code & language & color-scheme +*/ \ No newline at end of file From 39e696b225b0767c5993566f7d86cbe42e3f6b40 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Mon, 30 Dec 2024 14:00:07 +0200 Subject: [PATCH 03/30] Make the bot utils usable from other languages --- scripts/cfgbot.ts | 51 ++++++++++++++++++++++++-------------- scripts/render-function.ts | 7 ++++-- scripts/scan-codebase.ts | 35 ++++++++++++++++++-------- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/scripts/cfgbot.ts b/scripts/cfgbot.ts index cc1521ee..04f7d152 100644 --- a/scripts/cfgbot.ts +++ b/scripts/cfgbot.ts @@ -1,19 +1,19 @@ +import path from "node:path"; import * as process from "node:process"; import { AtpAgent, RichText, UnicodeString } from "@atproto/api"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import { $, Glob } from "bun"; import type { SyntaxNode } from "web-tree-sitter"; +import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; +import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts"; import { type ColorScheme, getDarkColorList, listToScheme, } from "../src/control-flow/colors.ts"; +import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; import { graphToDot } from "../src/control-flow/render.ts"; import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; -import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts"; -import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; -import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; -import path from "node:path"; async function main() { const password = process.env.BLUESKY_PASSWORD; @@ -26,7 +26,12 @@ async function main() { const agent = new AtpAgent({ service: "https://bsky.social" }); await agent.login({ identifier, password }); - const { png: imageData, text, alt, link } = await preparePost( + const { + png: imageData, + text, + alt, + link, + } = await preparePost( // "C:\\Code\\sandbox\\function-graph-overview\\src", "C:\\Temp\\2024-12-29-glibc\\glibc", ); @@ -41,7 +46,6 @@ async function main() { const linkText = new UnicodeString(text); - const richText = new RichText({ text }); await richText.detectFacets(agent); @@ -50,11 +54,13 @@ async function main() { facets: [ { index: { byteStart: 0, byteEnd: linkText.length }, - features: [{ - $type: 'app.bsky.richtext.facet#link', - uri: link - }] - } + features: [ + { + $type: "app.bsky.richtext.facet#link", + uri: link, + }, + ], + }, ], embed: { $type: "app.bsky.embed.images", @@ -86,7 +92,7 @@ await main(); function makeLink(func: Func): string { const line = func.func.startPosition.row + 1; - const link = `https://sourceware.org/git/?p=glibc.git;a=blob;f=${func.file.replace('\\', '/')};hb=0852c4aab7870adbd188f7d27985f1631c8596df#l${line}` + const link = `https://sourceware.org/git/?p=glibc.git;a=blob;f=${func.file.replace("\\", "/")};hb=0852c4aab7870adbd188f7d27985f1631c8596df#l${line}`; return link; } @@ -176,21 +182,28 @@ async function renderFunction( async function preparePost( root: string, -): Promise<{ png: Uint8Array; text: string, alt: string, link: string }> { +): Promise<{ png: Uint8Array; text: string; alt: string; link: string }> { const func = await chooseFunction(root, isAlreadySeen, 100); if (!func) { throw new Error("No function found!"); } const image = await renderFunction(func, chooseColorScheme()); - console.log(makeLink(func)) - const funcdef = getFuncdef(await Bun.file(path.join(root, func.file)).text(), func.func); - const text = `${func.file}:${func.func.startPosition.row + 1}:${funcdef}` + console.log(makeLink(func)); + const funcdef = getFuncdef( + await Bun.file(path.join(root, func.file)).text(), + func.func, + ); + const text = `${func.file}:${func.func.startPosition.row + 1}:${funcdef}`; const link = makeLink(func); - return { png: image, text, alt: `Graph of the ${funcdef} function from ${func.file}`, link }; + return { + png: image, + text, + alt: `Graph of the ${funcdef} function from ${func.file}`, + link, + }; } - /* Next steps: @@ -199,4 +212,4 @@ Next steps: 2. Write a script that takes a (filename, start position) and renders a function to SVG 3. Convert the rest of the bot to Python, so that image manipulation is easy. 4. Add link to the web-demo with the selected code & language & color-scheme -*/ \ No newline at end of file +*/ diff --git a/scripts/render-function.ts b/scripts/render-function.ts index dcb93fe0..5e102a38 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -79,6 +79,7 @@ async function main() { const possibleMatches: { name: string; func: Parser.SyntaxNode }[] = []; const sourceCode = await Bun.file(filepath).text(); + const startIndex = Number.parseInt(functionName); for (const func of iterFunctions(sourceCode, language)) { const body = func.childForFieldName("body"); if (!body) { @@ -87,7 +88,7 @@ async function main() { const funcDef = normalizeFuncdef( sourceCode.slice(func.startIndex, body.startIndex), ); - if (funcDef.includes(functionName)) { + if (funcDef.includes(functionName) || startIndex === func.startIndex) { possibleMatches.push({ name: funcDef, func: func }); } } @@ -117,4 +118,6 @@ async function main() { } } -await main(); +if (require.main === module) { + await main(); +} diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index 937f4bb2..1e6579b0 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -18,6 +18,23 @@ export function iterSourceFiles(root: string): IterableIterator { return sourceGlob.scanSync(root); } +async function* iterInfo(root: string) { + for (const filename of iterSourceFiles(root)) { + const filepath = path.join(root, filename); + const code = await Bun.file(filepath).text(); + const language = getLanguage(filename); + for (const func of iterFunctions(code, language)) { + const builder = newCFGBuilder(language, {}); + const cfg = builder.buildCFG(func); + yield { + file: filename, + startIndex: func.startIndex, + nodeCount: cfg.graph.order, + }; + } + } +} + async function main() { const { values } = parseArgs({ args: Bun.argv, @@ -31,17 +48,13 @@ async function main() { }); const root = values.root ?? "."; - - for (const filename of iterSourceFiles(root)) { - const filepath = path.join(root, filename); - const code = await Bun.file(filepath).text(); - const language = getLanguage(filename); - for (const func of iterFunctions(code, language)) { - const builder = newCFGBuilder(language, {}); - const cfg = builder.buildCFG(func); - console.log(filepath, func.startPosition, cfg.graph.order); - } + console.log("["); + for await (const entry of iterInfo(root)) { + console.log(`${JSON.stringify(entry)},`); } + console.log("]"); } -await main(); +if (require.main === module) { + await main(); +} From 2ec9823d43e31572a4498e59bd2dd384ffc6930b Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Mon, 30 Dec 2024 14:11:26 +0200 Subject: [PATCH 04/30] Better outputs --- scripts/file-parsing.ts | 19 ++++++++++++++++++- scripts/render-function.ts | 19 +++++-------------- scripts/scan-codebase.ts | 3 ++- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/scripts/file-parsing.ts b/scripts/file-parsing.ts index e7d0cfaf..8b68d80d 100644 --- a/scripts/file-parsing.ts +++ b/scripts/file-parsing.ts @@ -1,8 +1,9 @@ import path from "node:path"; import type Parser from "web-tree-sitter"; +import type { SyntaxNode } from "web-tree-sitter"; import { - type Language, functionNodeTypes, + type Language, supportedLanguages, } from "../src/control-flow/cfg.ts"; import { initializeParser } from "../src/parser-loader/bun.ts"; @@ -71,3 +72,19 @@ export function* iterFunctions( yield* visitNode(); } + +function normalizeFuncdef(funcdef: string): string { + return funcdef + .replaceAll("\r", "") + .replaceAll("\n", " ") + .replaceAll(/\s+/g, " ") + .trim(); +} + +export function getFuncDef(sourceCode: string, func: SyntaxNode): string { + const body = func.childForFieldName("body"); + if (!body) { + throw new Error("No function body"); + } + return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); +} \ No newline at end of file diff --git a/scripts/render-function.ts b/scripts/render-function.ts index 5e102a38..f18a111c 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -10,20 +10,12 @@ import { } from "../src/control-flow/cfg.ts"; import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; import { graphToDot } from "../src/control-flow/render.ts"; -import { getLanguage, iterFunctions } from "./file-parsing.ts"; +import { getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; function isLanguage(language: string): language is Language { return supportedLanguages.includes(language as Language); } -function normalizeFuncdef(funcdef: string): string { - return funcdef - .replaceAll("\r", "") - .replaceAll("\n", " ") - .replaceAll(/\s+/g, " ") - .trim(); -} - export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { const builder = newCFGBuilder(language, { flatSwitch: true }); @@ -81,13 +73,12 @@ async function main() { const sourceCode = await Bun.file(filepath).text(); const startIndex = Number.parseInt(functionName); for (const func of iterFunctions(sourceCode, language)) { - const body = func.childForFieldName("body"); - if (!body) { + let funcDef: string; + try { + funcDef = getFuncDef(sourceCode, func); + } catch { continue; } - const funcDef = normalizeFuncdef( - sourceCode.slice(func.startIndex, body.startIndex), - ); if (funcDef.includes(functionName) || startIndex === func.startIndex) { possibleMatches.push({ name: funcDef, func: func }); } diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index 1e6579b0..d46f5cc7 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -9,7 +9,7 @@ import * as path from "node:path"; import { parseArgs } from "node:util"; import { Glob } from "bun"; import { newCFGBuilder } from "../src/control-flow/cfg"; -import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; +import { fileTypes, getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( @@ -30,6 +30,7 @@ async function* iterInfo(root: string) { file: filename, startIndex: func.startIndex, nodeCount: cfg.graph.order, + funcDef: getFuncDef(code, func), }; } } From 7fff60307d79d0937f87c301f72fcf01c2be1c4e Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Mon, 30 Dec 2024 16:56:37 +0200 Subject: [PATCH 05/30] Now with nicer rendering and better scan outputs! --- scripts/cfg-helper.ts | 14 ++++++++++++++ scripts/cfgbot.ts | 10 +--------- scripts/render-function.ts | 29 +++++++++++------------------ scripts/scan-codebase.ts | 20 +++++++++++--------- 4 files changed, 37 insertions(+), 36 deletions(-) create mode 100644 scripts/cfg-helper.ts diff --git a/scripts/cfg-helper.ts b/scripts/cfg-helper.ts new file mode 100644 index 00000000..ef429004 --- /dev/null +++ b/scripts/cfg-helper.ts @@ -0,0 +1,14 @@ +import type Parser from "web-tree-sitter"; +import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; +import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts"; +import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; + +export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { + const builder = newCFGBuilder(language, { flatSwitch: true }); + + let cfg = builder.buildCFG(func); + + cfg = trimFor(cfg); + cfg = simplifyCFG(cfg, mergeNodeAttrs); + return cfg; +} \ No newline at end of file diff --git a/scripts/cfgbot.ts b/scripts/cfgbot.ts index 04f7d152..aa8b40aa 100644 --- a/scripts/cfgbot.ts +++ b/scripts/cfgbot.ts @@ -14,6 +14,7 @@ import { import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; import { graphToDot } from "../src/control-flow/render.ts"; import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; +import { buildCFG } from "./cfg-helper.ts"; async function main() { const password = process.env.BLUESKY_PASSWORD; @@ -112,15 +113,6 @@ function getFuncdef(sourceCode: string, func: SyntaxNode): string { return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); } -export function buildCFG(func: SyntaxNode, language: Language): CFG { - const builder = newCFGBuilder(language, { flatSwitch: true }); - - let cfg = builder.buildCFG(func); - - cfg = trimFor(cfg); - cfg = simplifyCFG(cfg, mergeNodeAttrs); - return cfg; -} export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( diff --git a/scripts/render-function.ts b/scripts/render-function.ts index f18a111c..18e957f4 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -2,30 +2,16 @@ import * as path from "node:path"; import { parseArgs } from "node:util"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import type Parser from "web-tree-sitter"; -import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; -import { - type Language, - newCFGBuilder, - supportedLanguages, -} from "../src/control-flow/cfg.ts"; -import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; +import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts"; import { graphToDot } from "../src/control-flow/render.ts"; import { getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; +import { buildCFG } from "./cfg-helper.ts"; +import { deserializeColorList, listToScheme } from "../src/control-flow/colors.ts"; function isLanguage(language: string): language is Language { return supportedLanguages.includes(language as Language); } -export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { - const builder = newCFGBuilder(language, { flatSwitch: true }); - - let cfg = builder.buildCFG(func); - - cfg = trimFor(cfg); - cfg = simplifyCFG(cfg, mergeNodeAttrs); - return cfg; -} - function writeError(message: string): void { Bun.write(Bun.stderr, `${message}\n`); } @@ -50,6 +36,9 @@ async function main() { out: { type: "string", }, + colors: { + type:"string", + } }, strict: true, allowPositionals: true, @@ -100,7 +89,11 @@ async function main() { const func: Parser.SyntaxNode = possibleMatches[0].func; const graphviz = await Graphviz.load(); const cfg = buildCFG(func, language); - const svg = graphviz.dot(graphToDot(cfg)); + + + const colorScheme = values.colors?listToScheme(deserializeColorList(await Bun.file(values.colors).text())):undefined; + + const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme)); if (values.out) { await Bun.write(values.out, svg); diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index d46f5cc7..ced578eb 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -8,8 +8,13 @@ import * as path from "node:path"; */ import { parseArgs } from "node:util"; import { Glob } from "bun"; -import { newCFGBuilder } from "../src/control-flow/cfg"; -import { fileTypes, getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; +import { + fileTypes, + getFuncDef, + getLanguage, + iterFunctions, +} from "./file-parsing.ts"; +import { buildCFG } from "./cfg-helper.ts"; export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( @@ -24,13 +29,13 @@ async function* iterInfo(root: string) { const code = await Bun.file(filepath).text(); const language = getLanguage(filename); for (const func of iterFunctions(code, language)) { - const builder = newCFGBuilder(language, {}); - const cfg = builder.buildCFG(func); + const cfg = buildCFG(func, language); yield { file: filename, startIndex: func.startIndex, nodeCount: cfg.graph.order, funcDef: getFuncDef(code, func), + startPosition: func.startPosition, }; } } @@ -49,11 +54,8 @@ async function main() { }); const root = values.root ?? "."; - console.log("["); - for await (const entry of iterInfo(root)) { - console.log(`${JSON.stringify(entry)},`); - } - console.log("]"); + + process.stdout.write(JSON.stringify(await Array.fromAsync(iterInfo(root)))); } if (require.main === module) { From 2618b7020d530d115bd3cec4752a0c2e1f6aaf87 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Mon, 30 Dec 2024 17:42:00 +0200 Subject: [PATCH 06/30] More rendering options! --- scripts/cfg-helper.ts | 2 +- scripts/cfgbot.ts | 207 ------------------------------------- scripts/file-parsing.ts | 4 +- scripts/render-function.ts | 29 ++++-- scripts/scan-codebase.ts | 2 +- src/control-flow/ranges.ts | 2 +- 6 files changed, 27 insertions(+), 219 deletions(-) delete mode 100644 scripts/cfgbot.ts diff --git a/scripts/cfg-helper.ts b/scripts/cfg-helper.ts index ef429004..7fc12f80 100644 --- a/scripts/cfg-helper.ts +++ b/scripts/cfg-helper.ts @@ -11,4 +11,4 @@ export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { cfg = trimFor(cfg); cfg = simplifyCFG(cfg, mergeNodeAttrs); return cfg; -} \ No newline at end of file +} diff --git a/scripts/cfgbot.ts b/scripts/cfgbot.ts deleted file mode 100644 index aa8b40aa..00000000 --- a/scripts/cfgbot.ts +++ /dev/null @@ -1,207 +0,0 @@ -import path from "node:path"; -import * as process from "node:process"; -import { AtpAgent, RichText, UnicodeString } from "@atproto/api"; -import { Graphviz } from "@hpcc-js/wasm-graphviz"; -import { $, Glob } from "bun"; -import type { SyntaxNode } from "web-tree-sitter"; -import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts"; -import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts"; -import { - type ColorScheme, - getDarkColorList, - listToScheme, -} from "../src/control-flow/colors.ts"; -import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts"; -import { graphToDot } from "../src/control-flow/render.ts"; -import { fileTypes, getLanguage, iterFunctions } from "./file-parsing.ts"; -import { buildCFG } from "./cfg-helper.ts"; - -async function main() { - const password = process.env.BLUESKY_PASSWORD; - const identifier = process.env.BLUESKY_IDENTIFIER; - if (!password || !identifier) { - console.error("No credentials provided"); - return; - } - - const agent = new AtpAgent({ service: "https://bsky.social" }); - await agent.login({ identifier, password }); - - const { - png: imageData, - text, - alt, - link, - } = await preparePost( - // "C:\\Code\\sandbox\\function-graph-overview\\src", - "C:\\Temp\\2024-12-29-glibc\\glibc", - ); - - // const imageData = await Bun.file( - // "C:\\Code\\sandbox\\function-graph-overview\\media\\screenshots\\color-scheme\\custom.png", - // ).bytes(); - const response = await agent.uploadBlob(imageData, { encoding: "image/png" }); - if (!response.success) { - throw new Error("Failed to upload image"); - } - - const linkText = new UnicodeString(text); - - const richText = new RichText({ text }); - await richText.detectFacets(agent); - - const postResponse = await agent.post({ - text: richText.text, - facets: [ - { - index: { byteStart: 0, byteEnd: linkText.length }, - features: [ - { - $type: "app.bsky.richtext.facet#link", - uri: link, - }, - ], - }, - ], - embed: { - $type: "app.bsky.embed.images", - images: [ - { - alt, - image: response.data.blob, - // aspectRatio: { height: 2, width: 1 } - }, - ], - }, - }); - - console.log(postResponse.uri); -} - -await main(); - -/* -1. Select function to render - - Choose random file - - Choose random function - - If the function was used before, or is too boring - try again -2. Select color scheme - - Randomly out of the options -3. Render the function -4. Post it, along with source attribution - */ - -function makeLink(func: Func): string { - const line = func.func.startPosition.row + 1; - const link = `https://sourceware.org/git/?p=glibc.git;a=blob;f=${func.file.replace("\\", "/")};hb=0852c4aab7870adbd188f7d27985f1631c8596df#l${line}`; - return link; -} - -function normalizeFuncdef(funcdef: string): string { - return funcdef - .replaceAll("\r", "") - .replaceAll("\n", " ") - .replaceAll(/\s+/g, " ") - .trim(); -} - -function getFuncdef(sourceCode: string, func: SyntaxNode): string { - const body = func.childForFieldName("body"); - if (!body) { - return ""; - } - return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); -} - - -export function iterSourceFiles(root: string): IterableIterator { - const sourceGlob = new Glob( - `**/*.{${fileTypes.map(({ ext }) => ext).join(",")}}`, - ); - return sourceGlob.scanSync(root); -} - -type Func = { file: string; func: SyntaxNode }; - -async function chooseFunction( - root: string, - isAlreadySeen: (file: string, func: SyntaxNode) => boolean, - attempts = 5, -): Promise { - const sourceFiles = Array.from(iterSourceFiles(root)); - - for (let attempt = 0; attempt < attempts; attempt++) { - const sourceFile = - sourceFiles[Math.floor(Math.random() * sourceFiles.length)]; - if (!sourceFile) { - continue; - } - const language = getLanguage(sourceFile); - // const functions = Array.from( iterFunctions(sourceFile, language), ); - const code = await Bun.file(path.join(root, sourceFile)).text(); - const functions = [...iterFunctions(code, language)]; - const func = functions[Math.floor(Math.random() * functions.length)]; - if (!func) { - continue; - } - if (!isAlreadySeen(sourceFile, func)) { - return { file: sourceFile, func }; - } - } - return undefined; -} - -function isAlreadySeen(file: string, func: SyntaxNode): boolean { - const cfg = buildCFG(func, getLanguage(file)); - return cfg.graph.order < 50; -} - -function chooseColorScheme(): ColorScheme { - return listToScheme(getDarkColorList()); -} - -async function renderFunction( - func: Func, - colors: ColorScheme, -): Promise { - const graphviz = await Graphviz.load(); - const cfg = buildCFG(func.func, getLanguage(func.file)); - const svg = graphviz.dot(graphToDot(cfg, false, undefined, colors)); - await Bun.write("cfgbot.svg", svg); - await $`uvx --isolated --from cairosvg cairosvg cfgbot.svg -o cfgbot.png --output-height 2000 -b ${colors["graph.background"]}`; - return Bun.file("cfgbot.png").bytes(); -} - -async function preparePost( - root: string, -): Promise<{ png: Uint8Array; text: string; alt: string; link: string }> { - const func = await chooseFunction(root, isAlreadySeen, 100); - if (!func) { - throw new Error("No function found!"); - } - - const image = await renderFunction(func, chooseColorScheme()); - console.log(makeLink(func)); - const funcdef = getFuncdef( - await Bun.file(path.join(root, func.file)).text(), - func.func, - ); - const text = `${func.file}:${func.func.startPosition.row + 1}:${funcdef}`; - const link = makeLink(func); - return { - png: image, - text, - alt: `Graph of the ${funcdef} function from ${func.file}`, - link, - }; -} - -/* -Next steps: - -1. Create script to export (filename, function start position, node count) - of a full codebase into JSON -2. Write a script that takes a (filename, start position) and renders a function to SVG -3. Convert the rest of the bot to Python, so that image manipulation is easy. -4. Add link to the web-demo with the selected code & language & color-scheme -*/ diff --git a/scripts/file-parsing.ts b/scripts/file-parsing.ts index 8b68d80d..9a6a3d79 100644 --- a/scripts/file-parsing.ts +++ b/scripts/file-parsing.ts @@ -2,8 +2,8 @@ import path from "node:path"; import type Parser from "web-tree-sitter"; import type { SyntaxNode } from "web-tree-sitter"; import { - functionNodeTypes, type Language, + functionNodeTypes, supportedLanguages, } from "../src/control-flow/cfg.ts"; import { initializeParser } from "../src/parser-loader/bun.ts"; @@ -87,4 +87,4 @@ export function getFuncDef(sourceCode: string, func: SyntaxNode): string { throw new Error("No function body"); } return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); -} \ No newline at end of file +} diff --git a/scripts/render-function.ts b/scripts/render-function.ts index 18e957f4..9eb8934a 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -3,10 +3,13 @@ import { parseArgs } from "node:util"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import type Parser from "web-tree-sitter"; import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts"; +import { + deserializeColorList, + listToScheme, +} from "../src/control-flow/colors.ts"; import { graphToDot } from "../src/control-flow/render.ts"; -import { getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; import { buildCFG } from "./cfg-helper.ts"; -import { deserializeColorList, listToScheme } from "../src/control-flow/colors.ts"; +import { getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; function isLanguage(language: string): language is Language { return supportedLanguages.includes(language as Language); @@ -37,8 +40,8 @@ async function main() { type: "string", }, colors: { - type:"string", - } + type: "string", + }, }, strict: true, allowPositionals: true, @@ -61,6 +64,12 @@ async function main() { const possibleMatches: { name: string; func: Parser.SyntaxNode }[] = []; const sourceCode = await Bun.file(filepath).text(); const startIndex = Number.parseInt(functionName); + let startPosition: { row: number; column: number } | undefined; + try { + startPosition = JSON.parse(functionName); + } catch { + startPosition = undefined; + } for (const func of iterFunctions(sourceCode, language)) { let funcDef: string; try { @@ -68,7 +77,12 @@ async function main() { } catch { continue; } - if (funcDef.includes(functionName) || startIndex === func.startIndex) { + if ( + funcDef.includes(functionName) || + startIndex === func.startIndex || + (startPosition?.row === func.startPosition.row && + startPosition.column === func.startPosition.column) + ) { possibleMatches.push({ name: funcDef, func: func }); } } @@ -90,8 +104,9 @@ async function main() { const graphviz = await Graphviz.load(); const cfg = buildCFG(func, language); - - const colorScheme = values.colors?listToScheme(deserializeColorList(await Bun.file(values.colors).text())):undefined; + const colorScheme = values.colors + ? listToScheme(deserializeColorList(await Bun.file(values.colors).text())) + : undefined; const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme)); diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index ced578eb..9635ac8a 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -8,13 +8,13 @@ import * as path from "node:path"; */ import { parseArgs } from "node:util"; import { Glob } from "bun"; +import { buildCFG } from "./cfg-helper.ts"; import { fileTypes, getFuncDef, getLanguage, iterFunctions, } from "./file-parsing.ts"; -import { buildCFG } from "./cfg-helper.ts"; export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( diff --git a/src/control-flow/ranges.ts b/src/control-flow/ranges.ts index b052275d..66c0bd27 100644 --- a/src/control-flow/ranges.ts +++ b/src/control-flow/ranges.ts @@ -62,7 +62,7 @@ export function getValue( ranges: SimpleRange[], pos: number, ): T | undefined { - return ranges.findLast((range) => pos >= range.start).value; + return ranges.findLast((range) => pos >= range.start)?.value; } export function* iterRanges( From 7f4341dca3fbdaa2f28038251b7a791557c6138d Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 15:19:35 +0200 Subject: [PATCH 07/30] Working, but very ugly version of the renderer! # Conflicts: # scripts/file-parsing.ts # scripts/render-function.ts # scripts/scan-codebase.ts --- eslint.config.js | 1 + package.json | 1 + scripts/file-parsing.ts | 90 ------------------------- scripts/render-function.ts | 20 +++++- scripts/scan-codebase.ts | 4 +- src/file-parsing/bun.ts | 17 +++++ src/file-parsing/file-parsing.ts | 65 ++++++++++++++++++ src/file-parsing/vite.ts | 17 +++++ src/github/README.md | 9 +++ src/github/index.html | 13 ++++ src/github/jsconfig.json | 32 +++++++++ src/github/public/favicon.png | Bin 0 -> 1768 bytes src/github/src/App.svelte | 110 +++++++++++++++++++++++++++++++ src/github/src/app.css | 66 +++++++++++++++++++ src/github/src/defaultDark.json | 73 ++++++++++++++++++++ src/github/src/main.js | 9 +++ src/github/src/reset.css | 30 +++++++++ src/github/src/vite-env.d.ts | 2 + src/github/svelte.config.js | 7 ++ src/github/vite.config.js | 13 ++++ 20 files changed, 486 insertions(+), 93 deletions(-) delete mode 100644 scripts/file-parsing.ts create mode 100644 src/file-parsing/bun.ts create mode 100644 src/file-parsing/file-parsing.ts create mode 100644 src/file-parsing/vite.ts create mode 100644 src/github/README.md create mode 100644 src/github/index.html create mode 100644 src/github/jsconfig.json create mode 100644 src/github/public/favicon.png create mode 100644 src/github/src/App.svelte create mode 100644 src/github/src/app.css create mode 100644 src/github/src/defaultDark.json create mode 100644 src/github/src/main.js create mode 100644 src/github/src/reset.css create mode 100644 src/github/src/vite-env.d.ts create mode 100644 src/github/svelte.config.js create mode 100644 src/github/vite.config.js diff --git a/eslint.config.js b/eslint.config.js index ab55d605..a6fa1e8e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,6 +12,7 @@ export default tseslint.config( "webview-content", "src/frontend", "src/jetbrains", + "src/github", "src/demo", "src/components", ".vscode-test.mjs", diff --git a/package.json b/package.json index 478d474d..9ebf6ce0 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "publish": "bun run package && bun run vsce-publish", "clean": "rm -r ./dist", "web": "bun run --cwd ./src/frontend/ vite", + "github": "bun run --cwd ./src/github/ vite", "web-tests": "bun run ./scripts/collect-comment-tests.ts", "demo": "bun run --cwd ./src/demo/ vite", "build-demo": "bun run --cwd ./src/demo/ vite build --outDir ../../dist/demo --base '/function-graph-overview/'", diff --git a/scripts/file-parsing.ts b/scripts/file-parsing.ts deleted file mode 100644 index 9a6a3d79..00000000 --- a/scripts/file-parsing.ts +++ /dev/null @@ -1,90 +0,0 @@ -import path from "node:path"; -import type Parser from "web-tree-sitter"; -import type { SyntaxNode } from "web-tree-sitter"; -import { - type Language, - functionNodeTypes, - supportedLanguages, -} from "../src/control-flow/cfg.ts"; -import { initializeParser } from "../src/parser-loader/bun.ts"; - -type FileType = { ext: string; language: Language }; - -// ADD-LANGUAGES-HERE -export const fileTypes: FileType[] = [ - { ext: "c", language: "C" }, - { ext: "cpp", language: "C++" }, - { ext: "h", language: "C++" }, - { ext: "hh", language: "C++" }, - { ext: "hpp", language: "C++" }, - { ext: "cc", language: "C++" }, - { ext: "py", language: "Python" }, - { ext: "go", language: "Go" }, - { ext: "ts", language: "TypeScript" }, - { ext: "js", language: "TypeScript" }, - { ext: "tsx", language: "TSX" }, - { ext: "jsx", language: "TSX" }, -]; - -export const parsers: { [language in Language]: Parser } = Object.fromEntries( - await (async () => { - const parsers = []; - for (const language of supportedLanguages) { - parsers.push([language, (await initializeParser(language)).parser]); - } - return parsers; - })(), -); - -const extToLanguage: Map = new Map( - fileTypes.map(({ ext, language }) => [`.${ext}`, language]), -); - -export function getLanguage(filename: string): Language { - const ext = path.extname(filename).toLowerCase(); - const language = extToLanguage.get(ext); - if (!language) { - throw new Error(`Unsupported extension ${ext}`); - } - return language; -} - -export function* iterFunctions( - code: string, - language: Language, -): IterableIterator { - const tree = parsers[language].parse(code); - - const cursor = tree.walk(); - - function* visitNode(): IterableIterator { - if (functionNodeTypes[language].includes(cursor.nodeType)) { - yield cursor.currentNode; - } - - if (cursor.gotoFirstChild()) { - do { - yield* visitNode(); - } while (cursor.gotoNextSibling()); - cursor.gotoParent(); - } - } - - yield* visitNode(); -} - -function normalizeFuncdef(funcdef: string): string { - return funcdef - .replaceAll("\r", "") - .replaceAll("\n", " ") - .replaceAll(/\s+/g, " ") - .trim(); -} - -export function getFuncDef(sourceCode: string, func: SyntaxNode): string { - const body = func.childForFieldName("body"); - if (!body) { - throw new Error("No function body"); - } - return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); -} diff --git a/scripts/render-function.ts b/scripts/render-function.ts index 9eb8934a..69437960 100644 --- a/scripts/render-function.ts +++ b/scripts/render-function.ts @@ -2,19 +2,37 @@ import * as path from "node:path"; import { parseArgs } from "node:util"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import type Parser from "web-tree-sitter"; +import type { SyntaxNode } from "web-tree-sitter"; import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts"; import { deserializeColorList, listToScheme, } from "../src/control-flow/colors.ts"; import { graphToDot } from "../src/control-flow/render.ts"; +import { getLanguage, iterFunctions } from "../src/file-parsing/bun.ts"; import { buildCFG } from "./cfg-helper.ts"; -import { getFuncDef, getLanguage, iterFunctions } from "./file-parsing.ts"; function isLanguage(language: string): language is Language { return supportedLanguages.includes(language as Language); } +function normalizeFuncdef(funcdef: string): string { + return funcdef + .replaceAll("\r", "") + .replaceAll("\n", " ") + .replaceAll(/\s+/g, " ") + .trim(); +} + +export function getFuncDef(sourceCode: string, func: SyntaxNode): string { + const body = func.childForFieldName("body"); + if (!body) { + throw new Error("No function body"); + } + return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex)); +} + + function writeError(message: string): void { Bun.write(Bun.stderr, `${message}\n`); } diff --git a/scripts/scan-codebase.ts b/scripts/scan-codebase.ts index 9635ac8a..6d66de7c 100644 --- a/scripts/scan-codebase.ts +++ b/scripts/scan-codebase.ts @@ -11,10 +11,10 @@ import { Glob } from "bun"; import { buildCFG } from "./cfg-helper.ts"; import { fileTypes, - getFuncDef, getLanguage, iterFunctions, -} from "./file-parsing.ts"; +} from "../src/file-parsing/bun.ts"; +import {getFuncDef} from "./render-function.ts"; export function iterSourceFiles(root: string): IterableIterator { const sourceGlob = new Glob( diff --git a/src/file-parsing/bun.ts b/src/file-parsing/bun.ts new file mode 100644 index 00000000..d069fa94 --- /dev/null +++ b/src/file-parsing/bun.ts @@ -0,0 +1,17 @@ +import type Parser from "web-tree-sitter"; +import { type Language, supportedLanguages } from "../control-flow/cfg.ts"; +import { initializeParser } from "../parser-loader/bun.ts"; +import { makeIterFunctions } from "./file-parsing.ts"; +export { getLanguage, fileTypes } from "./file-parsing.ts"; + +const parsers: { [language in Language]: Parser } = Object.fromEntries( + await (async () => { + const parsers = []; + for (const language of supportedLanguages) { + parsers.push([language, (await initializeParser(language)).parser]); + } + return parsers; + })(), +); + +export const iterFunctions = makeIterFunctions(parsers); diff --git a/src/file-parsing/file-parsing.ts b/src/file-parsing/file-parsing.ts new file mode 100644 index 00000000..46cc38ca --- /dev/null +++ b/src/file-parsing/file-parsing.ts @@ -0,0 +1,65 @@ +import type Parser from "web-tree-sitter"; +import { type Language, functionNodeTypes } from "../control-flow/cfg.ts"; + +type FileType = { ext: string; language: Language }; + +// ADD-LANGUAGES-HERE +export const fileTypes: FileType[] = [ + { ext: "c", language: "C" }, + { ext: "cpp", language: "C++" }, + { ext: "h", language: "C++" }, + { ext: "hh", language: "C++" }, + { ext: "hpp", language: "C++" }, + { ext: "cc", language: "C++" }, + { ext: "py", language: "Python" }, + { ext: "go", language: "Go" }, + { ext: "ts", language: "TypeScript" }, + { ext: "js", language: "TypeScript" }, + { ext: "tsx", language: "TSX" }, + { ext: "jsx", language: "TSX" }, +]; + +const extToLanguage: Map = new Map( + fileTypes.map(({ ext, language }) => [`.${ext}`, language]), +); + +function extname(filename: string): string { + return `.${filename.split(".").pop() ?? ""}`; +} + +export function getLanguage(filename: string): Language { + const ext = extname(filename).toLowerCase(); + const language = extToLanguage.get(ext); + if (!language) { + throw new Error(`Unsupported extension ${ext}`); + } + return language; +} + +export function makeIterFunctions( + parsers: { [language in Language]: Parser }, +): (code: string, language: Language) => IterableIterator { + return function* ( + code: string, + language: Language, + ): IterableIterator { + const tree = parsers[language].parse(code); + + const cursor = tree.walk(); + + function* visitNode(): IterableIterator { + if (functionNodeTypes[language].includes(cursor.nodeType)) { + yield cursor.currentNode; + } + + if (cursor.gotoFirstChild()) { + do { + yield* visitNode(); + } while (cursor.gotoNextSibling()); + cursor.gotoParent(); + } + } + + yield* visitNode(); + }; +} diff --git a/src/file-parsing/vite.ts b/src/file-parsing/vite.ts new file mode 100644 index 00000000..8ea2af26 --- /dev/null +++ b/src/file-parsing/vite.ts @@ -0,0 +1,17 @@ +import type Parser from "web-tree-sitter"; +import { type Language, supportedLanguages } from "../control-flow/cfg.ts"; +import { initializeParser } from "../parser-loader/vite.ts"; +import { makeIterFunctions } from "./file-parsing.ts"; +export { getLanguage, fileTypes } from "./file-parsing.ts"; + +const parsers: { [language in Language]: Parser } = Object.fromEntries( + await (async () => { + const parsers = []; + for (const language of supportedLanguages) { + parsers.push([language, await initializeParser(language)]); + } + return parsers; + })(), +); + +export const iterFunctions = makeIterFunctions(parsers); diff --git a/src/github/README.md b/src/github/README.md new file mode 100644 index 00000000..7f867d43 --- /dev/null +++ b/src/github/README.md @@ -0,0 +1,9 @@ +# JetBrains Frontend + +This is the code rendering the graphs in the JetBrains plugin. + +To build it, run: + +```shell +bun build-jetbrains +``` diff --git a/src/github/index.html b/src/github/index.html new file mode 100644 index 00000000..ecc65dd3 --- /dev/null +++ b/src/github/index.html @@ -0,0 +1,13 @@ + + + + + + + Function Graph Overview - Demo + + +
+ + + diff --git a/src/github/jsconfig.json b/src/github/jsconfig.json new file mode 100644 index 00000000..5696a2de --- /dev/null +++ b/src/github/jsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/src/github/public/favicon.png b/src/github/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..25db8c4152bf707a34dc7a1465da3e2c80f3e969 GIT binary patch literal 1768 zcmcJQ`9ISS9LGPK#%SZ=__~TT$_VL;kejWIa%M9cwk#4ka*RAMM{?%Kku$9l3y+$V zBS)#et}JSfnWE)LY`%`K++Y0>Js!{3>-BiNe|$Y2ug5#h$pHfwQxpRL0B(y#x$GzH zx1hrN-epei+YiwYta}&$h{A9BcL zo#5+!rF>IG01%F_MOnE?$m+BWofB|3e!G$%BE9K{an zmms3HDx+#tj-{s;3D7ZCr|=oo*Vd9A-LyXcnXT_qBX4B|k>pNL41eO`&b*cH3SPJR}g(N zh-~h{HkpZWehAPUlC{}z(fGD*shoRI$Yn%tqq9(Di~0Rl?<;qzM_QN1s4JB->Tj?k zn=n$D$RCIJ9neqSraT|XYg+DcOqm0lr&J4|C7R!@qt^zGUfXh4QJMg)`C;NFaxzHE zl!V<1t)yE_kM5h`^XTU6>VZA9H^Sv0@z#>AU6C4^^_7DxZ*?hatkLN;=k96f>AxFH za!=VcW01WdQ}VrDlhHV(dx9MV#Hod#1|mWhK=lBD?T#5SsH+_tg6@hxV{TL#Vg!Bu z?b{)TeJz+0H1&ic*NEfuv2rq<3Wic+U@n{sRV$5is#`Px`?n%g+8oI*;|k6x9*cGb!%WB1`kJY$>E{qdse zDxWp?{UyZe?%55KrBud$g<*9}5vP`CSb&?j1oW0LILY;`wYF#CE9lYo_UK_E|9!`R z-V#evSlrjZLLnXM{bU(4DZ#kGlnTIW(T2bXgU+?b)=icN@ZMbP2p^A+*zAPnD3{Uq z8ev_6OhyXyjdT8EnjbELnHS(@O4E~-ZkCD48GG&QF@Duw;P7mh>HBt6RRd?V``QTO z#k&5DHxv*jA=Cl!Vxu=nvIevvIzkS-4mJdES+lqR3{~aYI5@BIL<|YG(!12^v&ujB zvs>=dn;)2mjvpCfL%sLtuS9^N*R~PnV~e1FP@{fnN+q%Ts9+=C>BqRcAT9P z^jy&~Q-%5<7zq1ZhFX(UJj~Yj&rVrvckuE40wML;*F-6Vs0>D z?Zxb3|G$!@kt`qvXP~scTZjVC2p2d}h$|7OEuOhJ0h-;ZJ~}h;fklNy9(iC2e^t%M zRUXH`$rB^h?ey|PyRnCZ$THkGFfKroiWgjRLi3W#BH6{##WXcugso-@`kx*HTmD&F2fz`IWsV9zh zkL!blvQKSI`o4=_976$NY2A2Vo2J4~-Wd`J@j!OX{8s3qw)hMfsTmt_ZCILdLu+8? q?YlrAXm}{k_BtQGdSohYdvU%m&m)gGnr2t#B1FGJdl=we>ktmV? literal 0 HcmV?d00001 diff --git a/src/github/src/App.svelte b/src/github/src/App.svelte new file mode 100644 index 00000000..6033fc6e --- /dev/null +++ b/src/github/src/App.svelte @@ -0,0 +1,110 @@ + + +
+  {#await render()}
+    Loading code...
+    {:then svg}
+    {@html svg}
+  
+	
+{/await}
+
\ No newline at end of file diff --git a/src/github/src/app.css b/src/github/src/app.css new file mode 100644 index 00000000..ee33003d --- /dev/null +++ b/src/github/src/app.css @@ -0,0 +1,66 @@ +/*[data-theme="light"] {*/ +/* color-scheme: light;*/ +/* --page-background-color: lightgray;*/ +/* --panel-background-color: white;*/ +/* --panel-shadow-color: gray;*/ +/* --text-color: black;*/ +/* --color-picker-border: black;*/ +/* --link-hover-background: lightskyblue;*/ + +/* --theme-toggle-emoji: "🌞";*/ +/* --theme-toggle-shadow-color: skyblue;*/ +/*}*/ + +/*[data-theme="dark"] {*/ +/* color-scheme: dark;*/ +/* --page-background-color: black;*/ +/* --panel-background-color: #1e1e1e;*/ +/* --panel-shadow-color: #1e1e1e;*/ +/* --text-color: white;*/ +/* --color-picker-border: white;*/ +/* --link-hover-background: #31607f;*/ + +/* --theme-toggle-emoji: "🌚";*/ +/* --theme-toggle-shadow-color: white;*/ + +/* !* Color Picker Dark Mode *!*/ +/* --cp-bg-color: #333;*/ +/* --cp-border-color: white;*/ +/* --cp-text-color: white;*/ +/* --cp-input-color: #555;*/ +/* --cp-button-hover-color: #777;*/ +/*}*/ + +/*body {*/ +/* margin: 0;*/ +/* padding: 0;*/ +/* height: 100dvh;*/ +/* width: 100dvw;*/ +/* overflow: hidden;*/ +/* display: flex;*/ +/* justify-content: center;*/ +/* align-items: center;*/ + +/* color: var(--text-color);*/ +/* background-color: var(--page-background-color);*/ +/*}*/ + +/*#app {*/ +/* width: 100%;*/ +/* height: 100%;*/ +/*}*/ + +/*svg {*/ +/* width: 100%;*/ +/* height: 100%;*/ +/*}*/ + +/*#overview {*/ +/* width: 100%;*/ +/* height: 100%;*/ +/*}*/ + +/** {*/ +/* width: 100%;*/ +/* height: 100%;*/ +/*}*/ diff --git a/src/github/src/defaultDark.json b/src/github/src/defaultDark.json new file mode 100644 index 00000000..62d1aaf0 --- /dev/null +++ b/src/github/src/defaultDark.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "scheme": [ + { + "name": "node.default", + "hex": "#707070" + }, + { + "name": "node.entry", + "hex": "#48AB30" + }, + { + "name": "node.exit", + "hex": "#AB3030" + }, + { + "name": "node.throw", + "hex": "#590c0c" + }, + { + "name": "node.yield", + "hex": "#0a9aca" + }, + { + "name": "node.border", + "hex": "#000000" + }, + { + "name": "node.highlight", + "hex": "#dddddd" + }, + { + "name": "edge.regular", + "hex": "#2592a1" + }, + { + "name": "edge.consequence", + "hex": "#4ce34c" + }, + { + "name": "edge.alternative", + "hex": "#ff3e3e" + }, + { + "name": "cluster.border", + "hex": "#302e2e" + }, + { + "name": "cluster.with", + "hex": "#7d007d" + }, + { + "name": "cluster.tryComplex", + "hex": "#344c74" + }, + { + "name": "cluster.try", + "hex": "#1b5f1b" + }, + { + "name": "cluster.finally", + "hex": "#999918" + }, + { + "name": "cluster.except", + "hex": "#590c0c" + }, + { + "name": "graph.background", + "hex": "#2B2D30" + } + ] +} diff --git a/src/github/src/main.js b/src/github/src/main.js new file mode 100644 index 00000000..e6d5c7a3 --- /dev/null +++ b/src/github/src/main.js @@ -0,0 +1,9 @@ +import "./reset.css"; +import "./app.css"; +import App from "./App.svelte"; + +const app = new App({ + target: document.getElementById("app"), +}); + +export default app; diff --git a/src/github/src/reset.css b/src/github/src/reset.css new file mode 100644 index 00000000..e9ee1863 --- /dev/null +++ b/src/github/src/reset.css @@ -0,0 +1,30 @@ +/*html {*/ +/* box-sizing: border-box;*/ +/* font-size: 13px;*/ +/*}*/ + +/**,*/ +/**:before,*/ +/**:after {*/ +/* box-sizing: inherit;*/ +/*}*/ + +/*body,*/ +/*h1,*/ +/*h2,*/ +/*h3,*/ +/*h4,*/ +/*h5,*/ +/*h6,*/ +/*p,*/ +/*ol,*/ +/*ul {*/ +/* margin: 0;*/ +/* padding: 0;*/ +/* font-weight: normal;*/ +/*}*/ + +/*img {*/ +/* max-width: 100%;*/ +/* height: auto;*/ +/*}*/ diff --git a/src/github/src/vite-env.d.ts b/src/github/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/src/github/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/github/svelte.config.js b/src/github/svelte.config.js new file mode 100644 index 00000000..de2ddd65 --- /dev/null +++ b/src/github/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/src/github/vite.config.js b/src/github/vite.config.js new file mode 100644 index 00000000..43bff108 --- /dev/null +++ b/src/github/vite.config.js @@ -0,0 +1,13 @@ +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], + build: { + copyPublicDir: true, + outDir: "../../dist/jetbrains", + emptyOutDir: true, + }, + base: "", +}); From b18373d6f7430ce2aab173f70393fa0421ea6717 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 15:24:04 +0200 Subject: [PATCH 08/30] Use prettier for formatting svelte files --- package.json | 6 +++-- src/components/Demo.svelte | 17 ++++++++++---- src/demo/src/App.svelte | 2 +- src/github/src/App.svelte | 47 ++++++++++++++++++++++++-------------- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 9ebf6ce0..f66501dd 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,10 @@ "build-jetbrains": "bun run --cwd ./src/jetbrains/ vite build", "oxlint-fix": "bunx oxlint --ignore-path=oxlint-ignore.txt --fix -D correctness -D perf -D suspicious -A no-await-in-loop", "oxlint-ci": "bunx oxlint --ignore-path=oxlint-ignore.txt -D correctness -D perf -D suspicious -A no-await-in-loop", - "lint": "bunx biome check --fix && bun oxlint-fix && bunx eslint --fix && bunx tsc --noEmit", - "ci": "bunx biome ci && bun oxlint-ci && bunx eslint && bunx tsc --noEmit", + "prettier-format": "bunx prettier **/*.svelte --write", + "prettier-check": "bunx prettier **/*.svelte --check", + "lint": "bunx biome check --fix && bun oxlint-fix && bun prettier-format && bunx eslint --fix && bunx tsc --noEmit", + "ci": "bunx biome ci && bun oxlint-ci && bun prettier-check && bunx eslint && bunx tsc --noEmit", "generate-parsers": "bun run ./scripts/generate-parsers.ts", "typedoc": "bunx typedoc --treatWarningsAsErrors" }, diff --git a/src/components/Demo.svelte b/src/components/Demo.svelte index e172f203..4dca9e28 100644 --- a/src/components/Demo.svelte +++ b/src/components/Demo.svelte @@ -2,7 +2,7 @@ import { go } from "@codemirror/lang-go"; import { cpp } from "@codemirror/lang-cpp"; import { python } from "@codemirror/lang-python"; - import {javascript} from "@codemirror/lang-javascript"; + import { javascript } from "@codemirror/lang-javascript"; import Graph from "./Graph.svelte"; import type { Language } from "../control-flow/cfg"; import * as LZString from "lz-string"; @@ -20,7 +20,8 @@ "C++": "void main() {\n\tif (x) {\n\t\treturn;\n\t}\n}", Python: "def example():\n if x:\n return", TypeScript: "function main() {\n\tif (x) {\n\t\treturn;\n\t}\n}", - TSX: "function getGreeting(user) {\n" + + TSX: + "function getGreeting(user) {\n" + " if (user) {\n" + " return

Hello, {formatName(user)}!

; }\n" + " return

Hello, Stranger.

;}", @@ -50,8 +51,16 @@ text: "C++", codeMirror: cpp, }, - {language:"TypeScript" as Language, text:"TypeScript (experimental)", codeMirror: ()=>javascript({typescript:true})}, - {language:"TSX" as Language, text:"TSX (experimental)", codeMirror: ()=>javascript({typescript:true, jsx:true})}, + { + language: "TypeScript" as Language, + text: "TypeScript (experimental)", + codeMirror: () => javascript({ typescript: true }), + }, + { + language: "TSX" as Language, + text: "TSX (experimental)", + codeMirror: () => javascript({ typescript: true, jsx: true }), + }, ] as const; const urlParams = new URLSearchParams(window.location.search); diff --git a/src/demo/src/App.svelte b/src/demo/src/App.svelte index b4518c8f..4d7e6f07 100644 --- a/src/demo/src/App.svelte +++ b/src/demo/src/App.svelte @@ -4,7 +4,7 @@ import demoCodeC from "./assets/demo.c?raw"; import demoCodePython from "./assets/demo.py?raw"; import demoCodeCpp from "./assets/demo.cpp?raw"; - import demoCodeTypeScript from "./assets/demo.ts?raw" + import demoCodeTypeScript from "./assets/demo.ts?raw"; import { isDark } from "../../components/lightdark"; import { onDestroy } from "svelte"; import type { Language } from "../../control-flow/cfg.ts"; diff --git a/src/github/src/App.svelte b/src/github/src/App.svelte index 6033fc6e..e8bf99ed 100644 --- a/src/github/src/App.svelte +++ b/src/github/src/App.svelte @@ -4,13 +4,17 @@ import { getLanguage, iterFunctions } from "../../file-parsing/vite"; import type Parser from "web-tree-sitter"; - import {type SyntaxNode } from "web-tree-sitter"; + import { type SyntaxNode } from "web-tree-sitter"; import { type Language, newCFGBuilder } from "../../control-flow/cfg"; import { type CFG, mergeNodeAttrs } from "../../control-flow/cfg-defs"; import { simplifyCFG, trimFor } from "../../control-flow/graph-ops"; import { Graphviz } from "@hpcc-js/wasm-graphviz"; import { graphToDot } from "../../control-flow/render"; - import { getDarkColorList, getLightColorList, listToScheme } from "../../control-flow/colors"; + import { + getDarkColorList, + getLightColorList, + listToScheme, + } from "../../control-flow/colors"; /** * A reference to a function on GitHub @@ -23,24 +27,27 @@ /** * The line-number for the function */ - line:number; + line: number; }; /** * Get the line number raw file URL from a GitHub URL * @param githubURL URL pointing to a specific file and line */ - function parseGithubUrl(githubURL:string):GithubCodeRef { + function parseGithubUrl(githubURL: string): GithubCodeRef { const url = new URL(githubURL); // Remove the `#L` that precede the number - const line = Number.parseInt(url.hash.slice(2)); + const line = Number.parseInt(url.hash.slice(2)); if (Number.isNaN(line)) { throw new Error("Missing line number."); } - const rawURL = githubURL.replace(/(?https:\/\/github.com\/)(?\w+\/\w+\/)(blob\/)(?.*)(#L\d+)/, "https://raw.githubusercontent.com/$$"); + const rawURL = githubURL.replace( + /(?https:\/\/github.com\/)(?\w+\/\w+\/)(blob\/)(?.*)(#L\d+)/, + "https://raw.githubusercontent.com/$$", + ); - return {line, rawURL}; + return { line, rawURL }; } function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { @@ -53,11 +60,15 @@ return cfg; } - function getFunctionByLine(code:string, language:Language, line:number):SyntaxNode|undefined { + function getFunctionByLine( + code: string, + language: Language, + line: number, + ): SyntaxNode | undefined { for (const func of iterFunctions(code, language)) { // GitHub lines are 1-based, TreeSitter rows are 0-based if (func.startPosition.row + 1 === line) { - return func + return func; } } return undefined; @@ -65,16 +76,16 @@ async function render() { const urlSearchParams = new URLSearchParams(window.location.search); - const githubUrl = urlSearchParams.get("github")??""; - const colors = urlSearchParams.get("colors")??"dark"; + const githubUrl = urlSearchParams.get("github") ?? ""; + const colors = urlSearchParams.get("colors") ?? "dark"; if (colors !== "light" && colors !== "dark") { throw new Error(`Unsupported color scheme ${colors}`); } - const {line, rawURL} = parseGithubUrl(githubUrl); + const { line, rawURL } = parseGithubUrl(githubUrl); const response = await fetch(rawURL); const code = await response.text(); // We assume that the raw URL always ends with the file extension - const language = getLanguage(rawURL) + const language = getLanguage(rawURL); const func = getFunctionByLine(code, language, line); if (!func) { @@ -82,7 +93,9 @@ } const cfg = buildCFG(func, language); - const colorScheme = listToScheme(colors==="light"?getLightColorList():getDarkColorList()); + const colorScheme = listToScheme( + colors === "light" ? getLightColorList() : getDarkColorList(), + ); const graphviz = await Graphviz.load(); return graphviz.dot(graphToDot(cfg, false, undefined, colorScheme)); } @@ -102,9 +115,9 @@
   {#await render()}
     Loading code...
-    {:then svg}
+  {:then svg}
     {@html svg}
   
 	
-{/await}
-
\ No newline at end of file + {/await} + From c81997d270dbf7faccb703d848dc79c56ddfa555 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 15:28:02 +0200 Subject: [PATCH 09/30] Rename the render directory to render --- eslint.config.js | 2 +- package.json | 2 +- src/{github => render}/README.md | 0 src/{github => render}/index.html | 0 src/{github => render}/jsconfig.json | 0 src/{github => render}/public/favicon.png | Bin src/{github => render}/src/App.svelte | 0 src/{github => render}/src/app.css | 0 src/{github => render}/src/defaultDark.json | 0 src/{github => render}/src/main.js | 0 src/{github => render}/src/reset.css | 0 src/{github => render}/src/vite-env.d.ts | 0 src/{github => render}/svelte.config.js | 0 src/{github => render}/vite.config.js | 0 14 files changed, 2 insertions(+), 2 deletions(-) rename src/{github => render}/README.md (100%) rename src/{github => render}/index.html (100%) rename src/{github => render}/jsconfig.json (100%) rename src/{github => render}/public/favicon.png (100%) rename src/{github => render}/src/App.svelte (100%) rename src/{github => render}/src/app.css (100%) rename src/{github => render}/src/defaultDark.json (100%) rename src/{github => render}/src/main.js (100%) rename src/{github => render}/src/reset.css (100%) rename src/{github => render}/src/vite-env.d.ts (100%) rename src/{github => render}/svelte.config.js (100%) rename src/{github => render}/vite.config.js (100%) diff --git a/eslint.config.js b/eslint.config.js index a6fa1e8e..798de98f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -12,7 +12,7 @@ export default tseslint.config( "webview-content", "src/frontend", "src/jetbrains", - "src/github", + "src/render", "src/demo", "src/components", ".vscode-test.mjs", diff --git a/package.json b/package.json index f66501dd..05607774 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "publish": "bun run package && bun run vsce-publish", "clean": "rm -r ./dist", "web": "bun run --cwd ./src/frontend/ vite", - "github": "bun run --cwd ./src/github/ vite", + "render": "bun run --cwd src/render/ vite", "web-tests": "bun run ./scripts/collect-comment-tests.ts", "demo": "bun run --cwd ./src/demo/ vite", "build-demo": "bun run --cwd ./src/demo/ vite build --outDir ../../dist/demo --base '/function-graph-overview/'", diff --git a/src/github/README.md b/src/render/README.md similarity index 100% rename from src/github/README.md rename to src/render/README.md diff --git a/src/github/index.html b/src/render/index.html similarity index 100% rename from src/github/index.html rename to src/render/index.html diff --git a/src/github/jsconfig.json b/src/render/jsconfig.json similarity index 100% rename from src/github/jsconfig.json rename to src/render/jsconfig.json diff --git a/src/github/public/favicon.png b/src/render/public/favicon.png similarity index 100% rename from src/github/public/favicon.png rename to src/render/public/favicon.png diff --git a/src/github/src/App.svelte b/src/render/src/App.svelte similarity index 100% rename from src/github/src/App.svelte rename to src/render/src/App.svelte diff --git a/src/github/src/app.css b/src/render/src/app.css similarity index 100% rename from src/github/src/app.css rename to src/render/src/app.css diff --git a/src/github/src/defaultDark.json b/src/render/src/defaultDark.json similarity index 100% rename from src/github/src/defaultDark.json rename to src/render/src/defaultDark.json diff --git a/src/github/src/main.js b/src/render/src/main.js similarity index 100% rename from src/github/src/main.js rename to src/render/src/main.js diff --git a/src/github/src/reset.css b/src/render/src/reset.css similarity index 100% rename from src/github/src/reset.css rename to src/render/src/reset.css diff --git a/src/github/src/vite-env.d.ts b/src/render/src/vite-env.d.ts similarity index 100% rename from src/github/src/vite-env.d.ts rename to src/render/src/vite-env.d.ts diff --git a/src/github/svelte.config.js b/src/render/svelte.config.js similarity index 100% rename from src/github/svelte.config.js rename to src/render/svelte.config.js diff --git a/src/github/vite.config.js b/src/render/vite.config.js similarity index 100% rename from src/github/vite.config.js rename to src/render/vite.config.js From 85bba33742ff5a53f45856656fe36993a2b328e8 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 15:35:27 +0200 Subject: [PATCH 10/30] Add todo items --- src/render/src/App.svelte | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/render/src/App.svelte b/src/render/src/App.svelte index e8bf99ed..e2548672 100644 --- a/src/render/src/App.svelte +++ b/src/render/src/App.svelte @@ -50,6 +50,11 @@ return { line, rawURL }; } + /** + * Build the CFG with the same configuration as the CFGBot + * @param func The function to generate a CFG for + * @param language The code language + */ function buildCFG(func: Parser.SyntaxNode, language: Language): CFG { const builder = newCFGBuilder(language, { flatSwitch: true }); @@ -60,6 +65,13 @@ return cfg; } + /** + * Find the function that starts at a given line in the code. + * Assumes there is only one. + * @param code The source code to search in + * @param language Source code language + * @param line Line number, 1-based. + */ function getFunctionByLine( code: string, language: Language, @@ -100,15 +112,17 @@ return graphviz.dot(graphToDot(cfg, false, undefined, colorScheme)); } - /* - 1. URL should contain: - - GitHub URL (if not raw - convert!) - - Line number - - Optional - column number (usually not needed, but should be used by the bot just in case) - - Optional - color scheme (start with dark/light, add custom later) - 2. Fetch code from URL, parse and get the relevant function - 3. Render the SVG to screen - 4. Add a "download SVG" button + /* TODO: + - Add controls + - Download SVG button + - Zoom buttons + - Zoom in + - Zoom out + - 1:1 + - Fit to screen + - Page background should match SVG background + - Show more detailed progress to the user + */ From 0166c881ad53c580662527ba2d57964dd6c986ba Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:08:07 +0200 Subject: [PATCH 11/30] Basic zooming capacity --- src/render/src/App.svelte | 127 ++++++++++++++++++++++++++++++++++---- src/render/src/app.css | 70 ++------------------- 2 files changed, 118 insertions(+), 79 deletions(-) diff --git a/src/render/src/App.svelte b/src/render/src/App.svelte index e2548672..9dd1e761 100644 --- a/src/render/src/App.svelte +++ b/src/render/src/App.svelte @@ -1,7 +1,4 @@ -
+
+
+ + + +
+
+
{#await render()} Loading code... {:then svg} {@html svg} - - + + {/await} -
+ + + diff --git a/src/render/src/app.css b/src/render/src/app.css index ee33003d..0194a869 100644 --- a/src/render/src/app.css +++ b/src/render/src/app.css @@ -1,66 +1,4 @@ -/*[data-theme="light"] {*/ -/* color-scheme: light;*/ -/* --page-background-color: lightgray;*/ -/* --panel-background-color: white;*/ -/* --panel-shadow-color: gray;*/ -/* --text-color: black;*/ -/* --color-picker-border: black;*/ -/* --link-hover-background: lightskyblue;*/ - -/* --theme-toggle-emoji: "🌞";*/ -/* --theme-toggle-shadow-color: skyblue;*/ -/*}*/ - -/*[data-theme="dark"] {*/ -/* color-scheme: dark;*/ -/* --page-background-color: black;*/ -/* --panel-background-color: #1e1e1e;*/ -/* --panel-shadow-color: #1e1e1e;*/ -/* --text-color: white;*/ -/* --color-picker-border: white;*/ -/* --link-hover-background: #31607f;*/ - -/* --theme-toggle-emoji: "🌚";*/ -/* --theme-toggle-shadow-color: white;*/ - -/* !* Color Picker Dark Mode *!*/ -/* --cp-bg-color: #333;*/ -/* --cp-border-color: white;*/ -/* --cp-text-color: white;*/ -/* --cp-input-color: #555;*/ -/* --cp-button-hover-color: #777;*/ -/*}*/ - -/*body {*/ -/* margin: 0;*/ -/* padding: 0;*/ -/* height: 100dvh;*/ -/* width: 100dvw;*/ -/* overflow: hidden;*/ -/* display: flex;*/ -/* justify-content: center;*/ -/* align-items: center;*/ - -/* color: var(--text-color);*/ -/* background-color: var(--page-background-color);*/ -/*}*/ - -/*#app {*/ -/* width: 100%;*/ -/* height: 100%;*/ -/*}*/ - -/*svg {*/ -/* width: 100%;*/ -/* height: 100%;*/ -/*}*/ - -/*#overview {*/ -/* width: 100%;*/ -/* height: 100%;*/ -/*}*/ - -/** {*/ -/* width: 100%;*/ -/* height: 100%;*/ -/*}*/ +body { + margin: 0; + background: black; +} \ No newline at end of file From 6be43f063cf0507e154e544e1055946797520c20 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:37:09 +0200 Subject: [PATCH 12/30] Make the SVG zoomable! --- package.json | 1 + src/render/src/App.svelte | 65 ++++++++++++--------------------------- src/render/src/app.css | 2 +- 3 files changed, 21 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 05607774..5c764161 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@codemirror/lang-python": "^6.1.6", "@codemirror/theme-one-dark": "^6.1.2", "@eslint/js": "^9.12.0", + "@panzoom/panzoom": "^4.5.1", "@rollup/plugin-wasm": "^6.2.2", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/bun": "latest", diff --git a/src/render/src/App.svelte b/src/render/src/App.svelte index 9dd1e761..3c33d2be 100644 --- a/src/render/src/App.svelte +++ b/src/render/src/App.svelte @@ -12,7 +12,8 @@ getLightColorList, listToScheme, } from "../../control-flow/colors"; - import { tick } from "svelte"; + import Panzoom, { type PanzoomObject } from "@panzoom/panzoom"; + import { onMount } from "svelte"; /** * A reference to a function on GitHub @@ -127,12 +128,6 @@ return rawSVG; } - - function getSVGSize(svg:string):{width: string, height:string} { - const {width, height} = / fit => calc(100dvh + 0 * height) - tick == 10 => 1:1 => calc(0 * 100dvh + height) - */ - const sizeRatio = tick / tickMax; - const screenUnits = 100 * (1-sizeRatio); - const width = `calc(${sizeRatio} * ${size.width} + ${screenUnits}dvw)`; - const height = `calc(${sizeRatio} * ${size.height} + ${screenUnits}dvh)`; - return {width, height} - } - - function scaleToFit() { - const {width, height} = calculateScale(getSVGSize(rawSVG), 0); - document.querySelector("svg").style.width = width; - document.querySelector("svg").style.height = height; - } + let panzoom: PanzoomObject | undefined; - function setScale(tick:number) { - if (!rawSVG) return; - const {width, height} = calculateScale(getSVGSize(rawSVG), tick); - document.querySelector("svg").style.width = width; - document.querySelector("svg").style.height = height; + function resetView() { + panzoom?.reset(); } - function onScaleChange(event) { - setScale(event.target.value); + function makeZoomable() { + const elem = document.querySelector(".svgContainer"); + panzoom = Panzoom(elem, { maxScale: 100, minScale: 1 }); + elem.parentElement.addEventListener("wheel", panzoom.zoomWithWheel); } /* TODO: - - Add controls - - Zoom buttons - - Zoom in - - Zoom out - - 1:1 - - Fit to screen - Show more detailed progress to the user */ - const tickMax = 10; - let scaleTick = 0; - - $: setScale(scaleTick) + onMount(() => { + makeZoomable(); + });
- - +
{#await render()} - Loading code... +

Loading code...

{:then svg} {@html svg} - - + {:catch error} +

{error.message}

{/await}
@@ -225,6 +195,7 @@ display: flex; justify-content: right; width: 100%; + z-index: 1000; } .controls { margin: 1em; @@ -234,5 +205,7 @@ flex-direction: column; align-items: center; justify-content: center; + width: 100dvw; + height: 100dvh; } diff --git a/src/render/src/app.css b/src/render/src/app.css index 0194a869..c766c391 100644 --- a/src/render/src/app.css +++ b/src/render/src/app.css @@ -1,4 +1,4 @@ body { margin: 0; background: black; -} \ No newline at end of file +} From 5872e4306b59a1931a3704626383ce51729fdc64 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:41:53 +0200 Subject: [PATCH 13/30] Publish the render website --- .github/workflows/pages.yaml | 3 +++ package.json | 3 ++- src/demo/vite.config.js | 1 + src/render/vite.config.js | 4 ++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 6f13d67a..5ab410ad 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -17,6 +17,9 @@ jobs: - name: "Build Demo Website" run: bun build-demo + - name: "Build Render Website" + run: bun build-render + - name: "Build docs" run: bun typedoc --out ./dist/demo/docs diff --git a/package.json b/package.json index 5c764161..52e06797 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "render": "bun run --cwd src/render/ vite", "web-tests": "bun run ./scripts/collect-comment-tests.ts", "demo": "bun run --cwd ./src/demo/ vite", - "build-demo": "bun run --cwd ./src/demo/ vite build --outDir ../../dist/demo --base '/function-graph-overview/'", + "build-demo": "bun run --cwd ./src/demo/ vite build", + "build-render": "bun run --cwd ./src/render/ vite build", "build-jetbrains": "bun run --cwd ./src/jetbrains/ vite build", "oxlint-fix": "bunx oxlint --ignore-path=oxlint-ignore.txt --fix -D correctness -D perf -D suspicious -A no-await-in-loop", "oxlint-ci": "bunx oxlint --ignore-path=oxlint-ignore.txt -D correctness -D perf -D suspicious -A no-await-in-loop", diff --git a/src/demo/vite.config.js b/src/demo/vite.config.js index 2c3c3daa..ef0f34d4 100644 --- a/src/demo/vite.config.js +++ b/src/demo/vite.config.js @@ -6,6 +6,7 @@ export default defineConfig({ plugins: [svelte()], build: { copyPublicDir: true, + outDir: "../../dist/demo", emptyOutDir: true, }, base: "/function-graph-overview/", diff --git a/src/render/vite.config.js b/src/render/vite.config.js index 43bff108..e8cb1c73 100644 --- a/src/render/vite.config.js +++ b/src/render/vite.config.js @@ -6,8 +6,8 @@ export default defineConfig({ plugins: [svelte()], build: { copyPublicDir: true, - outDir: "../../dist/jetbrains", + outDir: "../../dist/demo/render", emptyOutDir: true, }, - base: "", + base: "/function-graph-overview/render/", }); From ba7447069f741d2c0c34b9f720c9bd83449fc309 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:47:39 +0200 Subject: [PATCH 14/30] Updated readme --- src/render/README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/render/README.md b/src/render/README.md index 7f867d43..193114be 100644 --- a/src/render/README.md +++ b/src/render/README.md @@ -1,9 +1,20 @@ -# JetBrains Frontend +# Render Frontend -This is the code rendering the graphs in the JetBrains plugin. +This is a frontend for rendering code from GitHub directly. -To build it, run: +Navigate to it, and use `?github=`, +making sure to include the line number for your function (`#L123`) at the end of the URL (encoded as `%23`). + +To choose color scheme pass `colors=light` or `colors=dark`. Default is dark. + +Served at [`/render`](https://tmr232.github.io/function-graph-overview/render). + +## Useful Commands ```shell -bun build-jetbrains -``` +# Run locally +bun render + +# Build +bun build-render +``` \ No newline at end of file From 944e5ddf61b8d260b313cbbbf441401238b60e8e Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:48:15 +0200 Subject: [PATCH 15/30] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7210833e..11a86cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ### Added - The JetBrains plugin can now change settings: flat switch, simplification, highlighting, and color scheme +- `/render` page to render code directly from GitHub, given a URL with a line number. ## [0.0.12] - 2024-12-18 From c832a3070e76d980181ac1833cd15e74bf885e0a Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:49:08 +0200 Subject: [PATCH 16/30] Nicer title --- src/render/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/index.html b/src/render/index.html index ecc65dd3..2c366fc8 100644 --- a/src/render/index.html +++ b/src/render/index.html @@ -4,7 +4,7 @@ - Function Graph Overview - Demo + Function Graph Overview Viewer
From c6de3b6a7d9828a795be584a2b297638d16124f3 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:52:00 +0200 Subject: [PATCH 17/30] Remove unused code --- src/render/src/defaultDark.json | 73 --------------------------------- src/render/src/main.js | 1 - src/render/src/reset.css | 30 -------------- 3 files changed, 104 deletions(-) delete mode 100644 src/render/src/defaultDark.json delete mode 100644 src/render/src/reset.css diff --git a/src/render/src/defaultDark.json b/src/render/src/defaultDark.json deleted file mode 100644 index 62d1aaf0..00000000 --- a/src/render/src/defaultDark.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "version": 1, - "scheme": [ - { - "name": "node.default", - "hex": "#707070" - }, - { - "name": "node.entry", - "hex": "#48AB30" - }, - { - "name": "node.exit", - "hex": "#AB3030" - }, - { - "name": "node.throw", - "hex": "#590c0c" - }, - { - "name": "node.yield", - "hex": "#0a9aca" - }, - { - "name": "node.border", - "hex": "#000000" - }, - { - "name": "node.highlight", - "hex": "#dddddd" - }, - { - "name": "edge.regular", - "hex": "#2592a1" - }, - { - "name": "edge.consequence", - "hex": "#4ce34c" - }, - { - "name": "edge.alternative", - "hex": "#ff3e3e" - }, - { - "name": "cluster.border", - "hex": "#302e2e" - }, - { - "name": "cluster.with", - "hex": "#7d007d" - }, - { - "name": "cluster.tryComplex", - "hex": "#344c74" - }, - { - "name": "cluster.try", - "hex": "#1b5f1b" - }, - { - "name": "cluster.finally", - "hex": "#999918" - }, - { - "name": "cluster.except", - "hex": "#590c0c" - }, - { - "name": "graph.background", - "hex": "#2B2D30" - } - ] -} diff --git a/src/render/src/main.js b/src/render/src/main.js index e6d5c7a3..7ece250a 100644 --- a/src/render/src/main.js +++ b/src/render/src/main.js @@ -1,4 +1,3 @@ -import "./reset.css"; import "./app.css"; import App from "./App.svelte"; diff --git a/src/render/src/reset.css b/src/render/src/reset.css deleted file mode 100644 index e9ee1863..00000000 --- a/src/render/src/reset.css +++ /dev/null @@ -1,30 +0,0 @@ -/*html {*/ -/* box-sizing: border-box;*/ -/* font-size: 13px;*/ -/*}*/ - -/**,*/ -/**:before,*/ -/**:after {*/ -/* box-sizing: inherit;*/ -/*}*/ - -/*body,*/ -/*h1,*/ -/*h2,*/ -/*h3,*/ -/*h4,*/ -/*h5,*/ -/*h6,*/ -/*p,*/ -/*ol,*/ -/*ul {*/ -/* margin: 0;*/ -/* padding: 0;*/ -/* font-weight: normal;*/ -/*}*/ - -/*img {*/ -/* max-width: 100%;*/ -/* height: auto;*/ -/*}*/ From 5e3e11b59d88c94b18008a043ef53510fd01e5d8 Mon Sep 17 00:00:00 2001 From: Tamir Bahar Date: Tue, 31 Dec 2024 18:59:46 +0200 Subject: [PATCH 18/30] Fix toplevel async stuff --- src/file-parsing/vite.ts | 29 +++++++++++++++++++---------- src/render/src/App.svelte | 9 +++++---- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/file-parsing/vite.ts b/src/file-parsing/vite.ts index 8ea2af26..215bf585 100644 --- a/src/file-parsing/vite.ts +++ b/src/file-parsing/vite.ts @@ -4,14 +4,23 @@ import { initializeParser } from "../parser-loader/vite.ts"; import { makeIterFunctions } from "./file-parsing.ts"; export { getLanguage, fileTypes } from "./file-parsing.ts"; -const parsers: { [language in Language]: Parser } = Object.fromEntries( - await (async () => { - const parsers = []; - for (const language of supportedLanguages) { - parsers.push([language, await initializeParser(language)]); - } - return parsers; - })(), -); +let parsers: { [language in Language]: Parser }|undefined; +export async function initParsers() { + if (parsers) return; + parsers = Object.fromEntries( + await (async () => { + const parsers = []; + for (const language of supportedLanguages) { + parsers.push([language, await initializeParser(language)]); + } + return parsers; + })(), + ); +} -export const iterFunctions = makeIterFunctions(parsers); +export function iterFunctions(code: string, language: Language) : IterableIterator { + if (!parsers) { + throw new Error("Must initialize parsers by calling `initParsers()`"); + } + return makeIterFunctions(parsers)(code, language); +} diff --git a/src/render/src/App.svelte b/src/render/src/App.svelte index 3c33d2be..4daa2939 100644 --- a/src/render/src/App.svelte +++ b/src/render/src/App.svelte @@ -1,5 +1,5 @@