From a402fe6ce8c7a6217486284a532eb7d96297b999 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Thu, 24 Oct 2024 18:46:27 +0800 Subject: [PATCH 01/31] chore: add @tailwindcss/typography --- apps/frontend/package.json | 1 + .../blocks/field-value/long-text-field.svelte | 14 +- apps/frontend/tailwind.config.js | 121 +++++++++--------- bun.lockb | Bin 553768 -> 556040 bytes 4 files changed, 75 insertions(+), 61 deletions(-) diff --git a/apps/frontend/package.json b/apps/frontend/package.json index c560e59e0..e88c60bdf 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -22,6 +22,7 @@ "@sveltejs/adapter-static": "^3.0.5", "@sveltejs/kit": "^2.7.1", "@sveltejs/vite-plugin-svelte": "^3.1.2", + "@tailwindcss/typography": "^0.5.15", "@tanstack/eslint-plugin-query": "^5.59.7", "@types/eslint": "^8.56.12", "@types/lodash.unzip": "^3.4.9", diff --git a/apps/frontend/src/lib/components/blocks/field-value/long-text-field.svelte b/apps/frontend/src/lib/components/blocks/field-value/long-text-field.svelte index 40a7af841..3aaf341e0 100644 --- a/apps/frontend/src/lib/components/blocks/field-value/long-text-field.svelte +++ b/apps/frontend/src/lib/components/blocks/field-value/long-text-field.svelte @@ -1,6 +1,10 @@ {#if !value} @@ -8,5 +12,13 @@ {placeholder || ""} {:else} -
{value}
+
+ {#if field.allowRichText} +
+ {@html value} +
+ {:else} + {value} + {/if} +
{/if} diff --git a/apps/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js index fa1cb36b6..59ee0cca7 100644 --- a/apps/frontend/tailwind.config.js +++ b/apps/frontend/tailwind.config.js @@ -1,64 +1,65 @@ -import { fontFamily } from "tailwindcss/defaultTheme"; +import { fontFamily } from "tailwindcss/defaultTheme" /** @type {import('tailwindcss').Config} */ const config = { - darkMode: ["class"], - content: ["./src/**/*.{html,js,svelte,ts}"], - safelist: ["dark"], - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px" - } - }, - extend: { - colors: { - border: "hsl(var(--border) / )", - input: "hsl(var(--input) / )", - ring: "hsl(var(--ring) / )", - background: "hsl(var(--background) / )", - foreground: "hsl(var(--foreground) / )", - primary: { - DEFAULT: "hsl(var(--primary) / )", - foreground: "hsl(var(--primary-foreground) / )" - }, - secondary: { - DEFAULT: "hsl(var(--secondary) / )", - foreground: "hsl(var(--secondary-foreground) / )" - }, - destructive: { - DEFAULT: "hsl(var(--destructive) / )", - foreground: "hsl(var(--destructive-foreground) / )" - }, - muted: { - DEFAULT: "hsl(var(--muted) / )", - foreground: "hsl(var(--muted-foreground) / )" - }, - accent: { - DEFAULT: "hsl(var(--accent) / )", - foreground: "hsl(var(--accent-foreground) / )" - }, - popover: { - DEFAULT: "hsl(var(--popover) / )", - foreground: "hsl(var(--popover-foreground) / )" - }, - card: { - DEFAULT: "hsl(var(--card) / )", - foreground: "hsl(var(--card-foreground) / )" - } - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)" - }, - fontFamily: { - sans: [...fontFamily.sans] - } - } - }, -}; + darkMode: ["class"], + content: ["./src/**/*.{html,js,svelte,ts}"], + safelist: ["dark"], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border) / )", + input: "hsl(var(--input) / )", + ring: "hsl(var(--ring) / )", + background: "hsl(var(--background) / )", + foreground: "hsl(var(--foreground) / )", + primary: { + DEFAULT: "hsl(var(--primary) / )", + foreground: "hsl(var(--primary-foreground) / )", + }, + secondary: { + DEFAULT: "hsl(var(--secondary) / )", + foreground: "hsl(var(--secondary-foreground) / )", + }, + destructive: { + DEFAULT: "hsl(var(--destructive) / )", + foreground: "hsl(var(--destructive-foreground) / )", + }, + muted: { + DEFAULT: "hsl(var(--muted) / )", + foreground: "hsl(var(--muted-foreground) / )", + }, + accent: { + DEFAULT: "hsl(var(--accent) / )", + foreground: "hsl(var(--accent-foreground) / )", + }, + popover: { + DEFAULT: "hsl(var(--popover) / )", + foreground: "hsl(var(--popover-foreground) / )", + }, + card: { + DEFAULT: "hsl(var(--card) / )", + foreground: "hsl(var(--card-foreground) / )", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: [...fontFamily.sans], + }, + }, + }, + plugins: [require("@tailwindcss/typography")], +} -export default config; +export default config diff --git a/bun.lockb b/bun.lockb index c034b4429b58fe057c17e4583b154852a14d9055..b31617e3792680dcc65b6986854891a31733b48f 100755 GIT binary patch delta 85226 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
Km&*Kg&vXM68;B7JHti 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 delta 83864 zcmeFadstP~{`S4*f|YA}Dl`(aR4UABTVd%!P>9mPOpOf1bEr!}1>~fF8KO~`n$e8D zSea5$n5B}EK$%jZm|3yS(oRNYX{AM_qUG+r?>WXo_I~>9XaC;c^}f#^kL&8<9`|RA zInHCuG3P=mYz{8}Hu#Ziui8E&uHkq|uLmo(uaNB*Uf0=u@s93q-4y)&^h@Uc?dVO< zrIe(*9sFEfG^U5s*{ve`9y9MDr^9if!?AF`;c&Ett{m%dbb!7APrY`W!_gi(BRyeG zV%ls+hw%=_#qdX=J)t|Gy#5ntXXq)Ur~eshLZ@YTGg;>6@QmLL?F7ARlI`;rMj+4y z;PS~1#|6+SSs7DOa~zH>@GST|=%vstI^$RFaX5nE%b={_GvI;HyQZjuZ-z3TrQpo> zJ}A>Ioa%6Nga*N11ifw=^UZTC{1Ji60X~EVL2IF`=_Oj7`T}21_*(W~w5X*)D-s=! z^O4V-tdyLosnfi^9{PNS%Kw`rR45M#K1G0Cm5`AVIy1xJm@_vkGii2)V+S}}R-x6` zwZ0+nW#}6UUl09jkNA4RH&kb)I~-k+FMB4hb=M>&WF+=U%5?Zzn3FJjc2Y*BV;wGF z*R8>vpdLdd+d(ttdS_>P(T!OSM>lwXC^K$^vg?1s$g=^D%vJt4l>Q(%ho~Xb5oKri zHUKNKMe8~!$9}ceVkjr)Qms=nGWukud%c@-tY_Ny%KJT6_4EVtRkF;4DN_=r%=T_i zSLKenS7kRB@htZsJm;VnJ^;E8?P0yIg|gV6a2=;e%6)1|eYim7`!?d4{&B>!{WmYP z^Uqtj2?2Ht>gCPAJbN)s4RK#^7I=k@pOc!Ai8PKcFzjqkN z*RgT2!_for&qJB61j_s|K&kUm9FEiXtNx0y<1vTlBu&SXaX4-SxDW~5I>R$4h&BC1 zYf?s^8M9NTIJ!NkGCr&;QU=cf_)vQqPRZTy)MRh^3~yhD!LQrtJ zH!Fj6C|aUgKItLVmFuBg1e*)3nH{^E4=hy;x!Wr0*d=eua#hdCVEqtX+Q06g0hyexQ^psHwEdhenh2DPD)9dlaP^dAw0*g9h9|CD^j(c2Icin zqekG?mFEYv{pC?rqu*CpH=o}t?^O(OM)I1w{Is9noWl#^a2hhSG6(6V0BvE0I6Py;$^wl@p2(-8{KrniIkg4R5)Ji9F2o9%F00RcV2 z-&?D$T8mhAXhy;`yOqi5No=g+aq!OI-i+iv-junHY2LXBv$MR8C68M(1G?uGyreSC zhO%D~nKUIe)8UWHIO~2bR@ug{SH;XsRoS-Pp!jL<3y|)6tsm)hUuyMEpYENCCI1cb z;UxOUMpfQXD5{&c@N)#1K|=bh%$zx1NAFiuf=i*SzZaAt>9q3F*0!y}nuT*|(~S3OIZF*Ge@zGBeOn+&df{06HPU*YW}+q@M@v2$e0W zMA!YQ-L00%iQY+NVL;kVGi2pP=JMG9KkGoSvGVoRN|+CmbLU ze&1_qUhagl0#lQx&FYge*D-Z=Mm9&Z8`58PEP^KFQWmg~FrYdlxPPKSFJQv#3blgiE z4o7dKqwlp-<@+#{6p;dAf$v9wZ0Va&`lq05z=N6((A+L4%?5q3p6LT5pAN5?upj`t!9vzEz#54nP_I7L*lx9(pPC5$!XfjQ==Y zm9x%Bx152ar(E)p>VV9ol$?w?IgZZooGWt@uz#XMrY20qPlw|?aL%#1eX0XiLfMnE zQ?syi9F7z49HGNdPMuG*ZrNwm1a|NG?g5p2E0l}zIYe_NU1K!`_Q-n#j)i7qPP2E4 zQdE+oz$3z3oB;FSIUSOqY+O8)!+O(~dZ9qsDK|h_$&rm}R6=xo2DWtvJ|n(TBgI)|@ z17$@@p&Y#eD0_XDj-Lo+@2noKx+4G;ypYGp^AKQno;j#$_$!n>@GX=rnwjC9g9bR_ zeo!rogtA9=Af6SUne5Hrki7!W1@Ih{1w8;|MZft%4bOSURQ`M6*@LT~7v>>wNJs34 zvcNlWA;&Xe=3JKR`12<veJ`2gJ%WqL3-XQ!l0bTU7+kK?=KEV2=q4; zbUCyZ+6h_#W&VZGyg&qc{ib>#6MzLwfHI?D7;9FjKa}adIj%1J7)rhy%9R#^c($l3 zlzut}mK8rAKiQM#K{>~crKx-yQ7iJ)IbLgNP=~zmQ!4&yC@Z!X={S^Ip`3HRQ&}lI z^C^b1oCl%o>BQ8mX*fYRGX9~4e#05X)4fyCRSt*uPvtRA=}E~6j;)AiK1oQAdURX3 z$ziA!pOrKls}YC6{`#vNKKiZG~4?rJm=j>G=;Uf3$@|AiiQS2m%9zSm;1tVn6H4c zwHc;ik5Uqpy*LTVe7Yc>qq458UCu2F`?fRe@xB0h86u7&g5CPBMSzfl=VB@#5Z^g5H)+s$R>rMs_yrQJw9bo5ERf zBRuOJto0fw+ds|gozo{N!*L(3V|)9zvHK-&;U9=4SQ)4;Oh*C^|8OYh_svjtK+=qq z)O2i!)00xB^}$(hLKnqjwa)1?J9R3~tI(umJS`;zsrbIQp409$JS#TKn`1A$?5>O- zjQKn}Av4oE&Ed^Z&v)L8v@CCWjw9n@bphv<7X|k~M%-CXfU`xN;aSn6-K_7sbJlj#+LuDV{Q)wS4 zum1qb;(26Dz~kssxDM6GTUdwyN9xmH!(Pv+@Z2irU!W@BtN&hjrhE2sm9ejVzV>WK zJlpA8mv(J1vM=fDR_gePm>FEZvC#J1YWMU~L;EI_Blaqk9kfbouil1zL02eS;)1dw zVaR|LZM@2G^njK@xj}7(UI7ifM&(lg-xEFv>8T_Brh1}3G$;?CD+0W^v_XOZ=*c;1 z$N2`z7F}_zD)?h~R-gvT^f^!#$U6Zm6bHQkdNY*QT@Gc%&Vw?2Q>xlA^9+?9Bch(z z2ZgF0xK``_zN*I6P-gHXlye~)$~`+?#}Cl)mqK}0b3j?px5$Tgzj`RIdkDk8E*}nM z15z_)qbd%^VULk#FQV-O)FzM=u3DPxO`e{C1S8?uqMM*xY&SwVG;5H)Co~_*3QpJ8 z$3R(N2$U6dL7DDQn9?0kre6zXMVE!4FBnJx;DvWV*^)3QTX4Xm3Lby6s$c|^8D0Wq zMuz5JN2}|%Ls{?|DAO&}ngC_`!BCcWrB*W!0Tz7pCROlWC<}fP1#@}vA(VZUojkiQ zA2!~MQ9L6d8`R;r51u`;c&M6`+zZHKZa3_?v=hpn>p4tK?is0brZK)0oq1*?yLpk}%k&a_Lc%*7TI6Q9*AyD3? zyFyvf%*6E6tQm=YC){A~pLq*2&;l;InXxLt4Beuu?@}$e1j>v$LD?h!KspvUQTIf0 zQnI(tv?-3+snhVn$>F#goE07dWy>=%(^ImN9gYFMd^Num+JWmoAtNVcD!L>OUCyx| zGg@WvG?W>fgt7v5x>-q?v$LiqB_REtF=}#dgEBrjUZ;byrCE9iM~qeZ%}Y(6HV^MT zhiE?o&$cMv;rMc#x?l&C8GWdIHI!r2Zi0$;LfNu%aPAYuP_FX<6V-M1Ywq)>z}Zu) zF)r-6SE0QA$Rss{Um!k@_ufog$YnMe%CWlt9_2@BJ{5PcKA3$|vQki8ha+N&nzX&3 zOy3sDo;#VKD)c3khlaPHob4N+tcXB4cM{U4W+tJ_u@B^Nay!POt2pa#hq7g!>1uLa z4&{Xd!Pzs{==k^J3F2ANbSSS|tFOBop368RF)0~uw;UIw zs)|)Y+2TJ^R6~mAs0!T&<@Lw%5W$-F*BNw#_J+?&R~yF9P>vlcJcB*en4vOg&|0f? zJ(P1{6_h==OlvrlJ(LNxyB^APBcWWrd3U3LU<*ys0lP)@?%p&W`M zP+s^&j;hF|NPiW)?|rxL{kQME_qPkx-EhVtHHo)Cx!nC1+dgmMjD+l@)C`9sD~sb3GgJNvx{c@OpS&zX9pEKZNW2 zCLo?WY~SZq`kqiu=C7el=gdQZ8^^b0YNH7#R|UNX&kP4618RRL=g5Unw*2T?wH)_C znbF5Ds_S<{dGmR8o$9%zP+q_0B~|dfP|lT2NJpOcJOW(bzT4$mbQxN!5>2H^ZzykI4jGbEMWcHss~m;SwImkWW`R`sS14uWd_OM9LwQ4{%=sW zw7vGrSpa+umY4}G!O*g&28K=j_=#@^@A~?~uP)f+fAt;arh9JK-+5E~Z5On^VE&IY z_7ryd;j&pzRKN7ajzi7TINEuWwebaWQ}w77msv4SUvFj4y}&Aby0^74-o2@6ZuXVd z#-|INuZ^|BSKnubk8|MJ6bEn1xYuJ27>~08+yz$tXpfl*cbAGA?QwUQ;BZWWlM0XD z_14D4rd7Qr%G?Ax1Qc7GW)g-G1@P^3_#~px%6+Ojc=D~&8E`N;2d<%{>H7sMI$JOQ@ zha=uvur$J$Y@K>G%KRNZ7O^O5qQ~5vfQm4!L|*tCrC9_P9# zR(M&Y*$g<$&b)S<$Gincok4Ies}|kxFkF=FP*Zac+(@|dto(S7k!0mOALZ(fqscUD z!SfNWMF@?sLmwbCm?87R=?=$GT;Xrk;`)hjBj5}=XZ3XJ^b3*3Mb?1wC^HTR2Gy6f zk9(X?%&=<8Bh8-yIS6jn#kkoTur|tkI?>_4y^48^^0*Ge4YUSLiZHK8YRMqpVL31Mvk5#=c%6657VWs>mV}F=?c1x+4CJAu%G{HPniMQ!^boe-r;cpFiIm)=h%6U1; zS(I$myc}tMj>A4Hirf}^%#akkB!xrgqj9-#gRHGsbgM` z18HQ7OMzpYzdfzWQ>~f}_c>2xST*A!jk~OzjZtP;mcua`DeP`>KAdHp-Wcg>1{`4x zni65&I#>0r)2hWpdI4?(5<9Hg7d>X%dA@!}OWb$EakA#SQ)hsfkcA61p4cHXr+5|M%ZqZ4EZnBf|LQjrz=mAW|d2j;}=j4Plx5L>p+wRQs ztm@5C<}JA@1y;slkFzk>3a^ZGZp*cDDKGOz$F90eexIo8ZEb0`tl1F@J#L@;je{ zVobD7y%yzuf3d@XNxqPq<@MIjkDKoMbp}$q)Ce=~w`yLGG~WbdK9~w+VeSH7$D;x6 z;c#rR%79B^ISBVUHE(L+?uPScmED04s4BNK&^;04W?rv0Q(V7Vlk@B*H^QlD%()Zt zpehY{kM+2`bh4m}G0=_Bt#+B{cW3j1*6D4LW>}$G4^Dd$&eaZ+5Oe86IJU@;e1GhV zOH?IX+-+SW;BK)NER1kIx5Ub=jWj<6U>z`HbCLKV)s3h_iN~A?H<)qukZgs!UAaP! z*=DJlJxGbkb~_v=8#ZRR$F##_jtg`DQ&jt9s*?~kIm`{ib*FlB6P(H;`W?*2x1*eM zms>S&N1D5rw{+7QkIVJ2!*RWJW`2Yjj}W^T)kC4r!?7XggQhTlMp;`YMwpkaP`#)2 ze9FD$rC9p}F4`^rKubfqnSvBuO8V>8!J5k1PYryU(=O2$-xw|8cSyuJ# zDAz+P9gfM?*4+`VBM42jL$^NRV`T`9vDqI8CD@^fPvSQo?9e+1dF{}3Px)dWL1>oE zen%+D4ozEyFCOjC&X&;CPxFd1dj|R=pdybUq$)6MHC`(40_XFqt<&#Ex*7putTTHD z`me#{vd+98;mSbh4m-3BAw+_;DfO{Y2&r_d5K^&+T4F<=aX9X@ugFJ8<+2B%yKL6u zSzl}_LbqFkJ{ahafJ)|i&X?VAgcMtekjm}|LaNvs$}ode5eVrb5K`$becs2C5K`5v zKxmwO*BShRT9_F52_ExtxM(#GvTz@P>#s&1E3bRGZ!Yk5Faa(FarR<&zEf`HejI6b zTC3(ewr^}zcfhHwYLL2nyO)7*tFkvAxxTYu`hyI#&csEyE_@Lcv5#4Y-34xb8vd7Pi!UmLXh>MTKtt7LHXn?c09Ojmlw> zejH|1e-Y(+6l9onCM&{dV+}YMW!z@v9E@^Gg;jG9x5WzU^ub6Y)Ee+*lxx;2sJAuf z%LsEVLTnFCvnRs%J11-ab^)B@rdb0Tqs*tkxUSt2-3>b|++a8~e`A`4;N1ET)sz&Oo-}K!au*KyS7oLXwwc3kaup%D9;n-St< zu=6q(!SNa#d$K*|8`?S5Qy84SZ%*=1fdR%?qMf0sezeKslsia_Eg56^c`fZ?lkLpO|fMKtKYe{5wdQbJ6 z9p{=3H{IHLVxSuVT_`TO?ETg*8;;|mF8L5{oV9i7K=%i&u=x-i8>-6u>Yw5w8&vaD z1`oqcvIe0=t+6@q@2#?PmB8I83#J(Zt(=ol#ssVSWRzL7S50nvLon;$`l3X8eL7F> zwQ`#y&8t6DN$h87*DSa&&N=6^A6nt3B3)ks^tC5pr+vPq#-WXe3$qsdG0^`b?19$S zQ4yxa5VFS^=cO>>NE-)z?3-!0%@`xB0l!DN^12&`oy8Jm3or^@YqO2yq>&Tgb<7;r7f6_(WCPKJ=Kw;ne)(YF`1T zS37o(18`iASj*TwZv3=m^BL`NrNgQGUP6eo3d;!mFrBp^KEk-dI`wCiIps57AMicN zQ*fO1>iOg|xCpqm+#cOmeXb^1%j$PoAY-jFJ0r}FU-9;2SV|{%IUU7q9eJYuXk`xGW~zSi5$_mljAYzA-JR# z_bZ&Y#l`=s?$Jp3DyBaidl~Me$NUVAg`6kG2%KVmQ;WeU&Q~VJPKZ_G-29v5USLGJ zT*q-bu+A)saAqEt(;((=Aa@`gwng;uO(%S3z34Sz{xE~N;GOTBkkbK3eDg_F7k|l* zL=kW|s=B=Fan_uanodabJ19>W$Pi0DrWubJaJWew^f+H_mfX&$#tBfIX>nAz<&^I@ zkdM^!;qr}(xEeCqbby`h#Op7fZqm!Jv5VPsgKh$`r+3^h=FGWF^p_iRe6~%jq zNgiXmoC-wme+C#KX9A4~bJ;)CD#Q-G+vEKHA5wFX5o!EQPF;kIGyYTy0o!IAo&n&( zk+HqdjmxCEixK6Q;4rNDO9z?nIt=?fV!!h;uQm*O2H=e@8j}GRh6}Nt@;&Z#aCgG# z1Nv^$4bUGzo%%wZhCNmN_@dO60f%A2QMno+HQpQ^>_NJb#&o=_@>4lr#bFN3g<}WU zrw?;8++a9tlZb1E<2Gzp({;N)D$Ra%mm_p5_}Qn1V<23BxDJQkYg~r?{#9*qGvT<| zo-g?^7=1X7zIwL)29A>-qmCDeJ>HbLs|@IYVSB=@Dr>*XbRKX^O%GgnscAUW108pb z2jRGCv8OHybHlK2QA_kux3;Qvm}A)QXUVCaT(E$Va;B#d;Tqfy1IS);J%-RsYim}7 z>mLYBV8|TTUiAig>Nz}BwU^w>j7aCs_EG~i9CGS1+*zi}fXh+Q^&M1|)B*P=IQFCJ zxyw7MNn_s%&GB$*?r|m->p0X9N4!07QMeB0kas+0`|}O8>5I|Pz*7sX-D;`r1p5MP zirrF-j?qi1Lr{(W0fu7$pvk3amUdE|fz0uo)7VLJd!hG6bT;(vz{iCr;8<1ralw2K zj@^gvAF$#&Ug+yg92m_JaB4F0p?Vn{n}NKSddwYg+_ccdNglro0u3u-${^PW{BGbd zYtYjX?q?BVzUrO#^-q~@fWZLhTk)7zUSv4ts>`t=R>5&@sjLseamnFef=YDhqKZ`q zkwiWV69?Z{iz*h0Nj9!RUBqB=HUW3HXm)kCbAWdgVff3+xI7& zTF2aAV!NqMR;^tN$6iwF@q~^;J@D%FMmcpYMm+x#)jj9}j94|CTC%LtaX79FheTh8 z*?td#_73h^d#Qm3cpS&fV+f4}x96sDn^a$qJRj|@DuFriDc*0x@vdXfA~UE*>lqdc z#|qlrYHonj+bWuJq$Q4xzO|=s!10u1J_N^2N-c>4aGY5xmoC9-(c-rDcoqp!J$w7fMW+^m`Zs{Jar>Bfen|V zIx?sS?i;@%$O(p*hACn0D^xY{q>cywR5)I$W>l?q$Y`m@)j7m)^s~+!i7;ZMx<7ht z6#x^XpDf{RzVVz9!p^7{VK`T>q>hjO^YxRWk47zwFzJd-`bf2d#kIl zjiI+z!SUdQsfC;JH@zi%0M_wPusvdSKLp3xV-8_=`{sH%9c8>>-Vv(mjlRNG@{o4uN8Blz`x^FnTeFXG}#9@`%CUE zs9l*yZ6&BP+H(?)JGxP97#{P}0eUIYIp>DU>A~ou?cw@BAVvtr0gnBF741Z#hzO|} zg0t6s5xDot)*+b9uOT?KCC}~y4F`{;s)Rf^d$HT)?T4G(66YCY*vD0sat++sT>Nmi zJL{&yoy~J6+{Bh#u83;w^5M>w@Fg5aPn8gHlVLxmDfcLx-4pir=5udu*_7~f_&%In zx*gZ|7G1jSEI7S=A?1&7c6oN(u)(@Tw%ZJMHkU3#TDx30yG3@&PvPtq*)HtX)~*=t zYze=?*)6hDj=W8`h|c-^Z4y2lmD3}zgvQ=3;Ukg#N4NWKyqxaW3{&?tT!{se49EMH zA^F2Gb>QqRSMrPC?$FDdDP4o<>@8stLcD>XhB%$>2V6kfC;b?c4a~ z>rP*X=HrYo2~PJ4w!~-PSWnyw(e5wcxIN(pf|R{ueZ9c9RCD2Y9gc@9!`#~7{ElzK zBksaI#C{lAf)MX<_FiH(!0~piT)-%O7v(L%=^Z8EqcH%_kCI%ds}Zr`GRTdk*X?fK z){~DL-V8X_Nxi(Qfa6UDBQwV1J_5%&oo#uaIF(wRALqewtn9nBb8DRB#-pfTL8sZ< z;yt5PN$Py^G8{`nb;o<$t}&`9XY(EoVvni)`q~Z035mYK7W6rs>Q!u_W^jBN!C!5H|(S~V^mGJTCj;UiM7wUdyY^xO3oM%io0PpQ+3WnyAafbc$-I4cV z_m6OPH~7A`bzeVTSKdd=Y!IeVO?@3scMK}uWkO5k;l{(UB;0^7K~}=?e4r-CcW`HK zI-+1`Fy?l4KksUu|^9CvZ#f;`v4abDnCjqk$TlUt8L6da#k>|?a+ zQMmDZBsY&D#B(LS>_i3w?ooF|JWOKNJ_ct$ri(EVD-w>?x4$EBc2AJp1dRE#1az2n z=8*{3*eTeu?9l57#oD0`Q?VV{q1gxxw?owk#o3{b)3B}Cp;UxM+MzcP8pDto=xv#i zU*NS0oV{0AwTHs|VeYoJ-Z#*V06QPc1&<0tryIWCV@SXQ032tXiu(wTwO32#!Wq7S z;g>;PxG2P-Z^}I8PB;z>ZsO>Xz{Hldy1-+Of#cl7D!}fu9FDWirJjFwolT0V+AgW( zLbx$-92p$;@s3E~R5o;b;P{k|s!jJeC(e}IL?hDg{aJY0e}0gwU^X9|`O8LMBQ)9$ z4M@g$$qqe<&|M5Uk0#sKIb%|6bv>4XyS*$(#JUej^_}&(q9((QMsXNN96tBJO|T2+ zj(pV|jE(K`n?0^FIJ^tNR}W2d}q_AP>Nlc>#=lXsDh^v zyh+Z?#6cuOjUK)Z!V9mEOy4TzSN|=B3%snwv1bm!=~Fo3uF3NCD7Vz)7RT#0z^N^U z*PVjXJ&L%Yb6eIVUK6Z<Pr#Rv4@&Ml^ucQn%IN|l%3WwUW~j6`VoQOu zue4v(`7e>&Y-}mFF2Q8727MOc+Kf=5Y@LTLy5u1V&p|#9JS4f)c1xuO>YBV12VpyL z9YWI>a$dDea&u9!jmy-4VQa_0H^K20ES^>Iz1a=R)!N158K%fuIIem7-Hz)d9KN)u z8R&l4H~)AX$bjQLxMe(CuaVgIXTKFKOAP%t2#zC%6^)Zt-UwktwI?4WZ&k>uWogs%h5R;c%*a+>JTN9~2S0IT`}%Qvl~@!oPY zT&BwO9R#PynH!Pm-yTDs$O0sHJ%Zp&MNcb=t-&>^m}Rsdr{(kmxMKZkbp;*0 z+2fWn^;z|dX1|hke+c(a8JfMGQ+30xgSQHqaI68Y#ciw}j)zyY;$yrHE%U`;?wXl! zeD*^et{eKi%GXcwA3zVmabDYHnrGltF1XGa_kx^Wif*oafxG&drI>eDm#a3RdzO35 zg>b40O#WfHoL+|HSFOd$lQYXOmu4cEBxfGLmBx!Lo8y~d{xD;0?*0`)-h*0t$vJYJ zUw`*aZMv&cZzlCxQ zUXNqEs#C%%Dgv#U?s2VyyVL$^@^^%$%9)2z?fW*VN~jU~3XWsqC(%8yAa2}jH;?I7 zA;{Ne*ly}Q?vLOm!KtdxsWsh|>XBVtXg&eUT~+n^CvYvjZuWiE*QtEv{s0{Bf-2>^ zaJ+rn6?RYFf<@C(3Vt!sy%U5Z-O^=daFu$D(FH#T%S~6?E2~@OXFd$aoeMW=Y>QvQ zsZ$GG&l=x`lfMhU)1j(}rM%CXgP;&mGBb8uHR}80;Z8O;0-miroUl0GStjEfFNcNCt2TZ zEwl_l3>>L@yWvQ)GdGtaIM~-N*ZXj@RGRSGmS7ozGh|Q+uKeUp!!b`Kh zFwPE|O$c&6seX!hTXlx*T)W`L%hnZ`5?9ozNnl4f7uDI#a(3FG+A?EDOV+O-m~0p5 z?6Ffd{{Ee;j@^(S5cJwX^Uiv8w6SyaTT_oi;@m;bgY{cZKW#i}=Cya=SIL|XhYNZu z)PUXw?Ffy7GCl#y57i&)g%ZusoXYDGwI*raT8U=i2Q^v8L-Xtm(*T%3y4DPxptZ7s z**ZQ)Yc7;L59$Y91m%Z%9<&fj#6M!k{FiE81Z6{toq70a2UG?nh`@i2C$v7L6Rv@F z0Dqa0S{dAcAFRM;&8hU&P!?FDd240-Hl6Nm)|?6Jplm_CzJN-;OY?WN-vedM8=(A9 z8QhB>y#6C73;0y~FQDv+Z=n288Gi&yl=lOEvY;R7w9+5dzO~}Ll;c*E!l)Q1=g4T5 zs+Bxmdn)}nDA7dC|3&lc2o^X=C!o?#)_M<=QB(OtD>L?L-&)zSnVM7CQz_6k&@3qP zovY)i_CE)nrz5hVPC4_T(It;Pmap+5V)*-^{Ipi`2f#0aKBdz=t<$wu*5et?sl4tv zDA5a=w^Fqz*69T6b%NH)hHL<*Zq)Hq`U>s;oig1kI{hY{p2~_<`pKe~jEnQwnKe3& z%4=TJX|`(rhK{H5+U=UxYTjDih~K3-mHEA+b+_hJ=Cj8SI|E1ULmkmti9W^;X0TuD z0iEuzl%shN@yxhUr)#a`Uu#Zf`XheW8OVOb59&|+@h{2?n>7DVl+&aQ=EC{Vi=fP)CCZHD$#F0t9#w^m-4 ztT~lFMSCjeT$=WOr7S1IA4`ZI1ndklpI?r9Wk&bu>wOcF$}AS+2P^Ub&0i|M zN$^E5qe3KPOP1&h{+%-2LpuFZoxZg?WkCftYL@>fqN&9?`Qth{m3*c4)C=KXhH}Mi z)A4UYS=?J%--hx-<@GzE9H@6SZ>4#5L<1t&zP&mDl^1>lWyT*v8TF~wgEaV1neHp? zsq}}S%3aQ2Riw7r8KlS#TEseyF@Dbl1MM zGF=bN|4LcWU>#5G3Lg&TbrG6V(R4?Y_EE0Ra2l|NH}e9ybhFXJzB>+sWxm7pb+J(V z=NQc&{~P6X@jBiA3soI678$T!6QFF!MChf^TZ=2FQ$6E;S!nbuq zz4p7Hyl}Vn?`quxwNF&q?}PHwT8TcVCMeUK;E#XN zwgl);uDY&Jrtb!2#+N{ONidY3*2?(Hz}fO%I{lSY&cELJg4W9Wf2ihvrOdFe&gcf6 zj>@B06qE(s0%Zk@){;!k;j@I#1 z`Y}*eY`o@Bwf+d0;Y6Kq5|jld=!Acz?GQg(r%%@Ds63S3tN8-WsmyPo_GeX<|4$L@ z`XxGphoG$S!%#jqKMB18x(&(?mBBjgscgtjC|g{w`G2W=L}S8T`oi7%g8xKW&>o$h z%6#6_`o7i=bUc;u4NzXUS9>Y{+Gvm8nCw$ybYa6C`w-zL6JhyC@Ay=w`wYsC_)_~u zC_n#+GM}$?dMfMjjrLTQdsOpdP^SOcH#HEjFKB}D^H<8D{Y}TWR%UcUb1H*>;0G&k z2FmNWmoS|ZO6~_`L))~`Q=<(x8yl#ErcT%v%BTPx|5wV40uj%Gf^@pp$_jM{X9ard zcq)&#z4H)YjjqxOu7PsW^AA1pL*=TCfU=+{&8fWZCauw$Q(4e0T8HTPTeaS%H3rI0 z9s|s1sMgzc1eJ?n43q_r*PO}P`2bnDBs;wL0OU4p{(#$ zt#4@Erggj4S|~sNUNQdvKEZz$&kDYe0$IVmy5RpvIsg8ng#Vj#tUx0QW{-WXE6`fW z4{1*2ocbQhqlx1u1lXlN>jYH#CMe@iXx>^Ge^PTQ)BOSEYOk%Pow6bq>2&|A;_Evm=%N$+l`=j^$5UCs zi=nL8rBKFq*YT~D>3V8Tb;{Ol*l^iVy*2(T<>I;l@yzx{ovyVq+y0vWmGU}|j;C^r zBDIJ1g;jtV-J~P_o#NqYyU_!03|4K&yMvduBlKVWu-_rvjy#$79Z>esNbO^FS*?|o zy$hU!Ia;TS*XgLtXPow}m3)GZp9Ez-lR2CCLBP&%DwOLY4a#1~hVsH(t@lEC;Q}Zt zkgv5s^9P{3ZaI`6DjW2O_EcW?nD&pkQ9d&))_}_4egevMu?EVha{S;KZZpk)Q`vx5 z^>zP?=Cz7oO{?Kq@HQx0vIEKr?S?X=Jy3qAY)OOm)aw|5yEgOjvri35d7TTM`Lu=7 zw}-Ofc^z~@Did_lp2~z5YQ0GFzfz_PLOe6yKwO=dVzXM7Ko z6_}Y|Zb5GT%Jy7eiU$LhYC7_!TDRCNC@kpjtZOah>o9 ztxs!S3T3)ywSOMUjMqU~(U&ye0A)iqX}%fCbk*9|K-sg~SwnudLz(fLP+qu0`+6Nu zWdXaOoQ@6J{~gMV_G|tnl=*z6^&2R!JEZknD0}7zloiT5ra_ZV@C%d~{HFB;lnH;= z{tT3h#vjjvOm`lX8JgO+)7k;bh6HHeS!-a_pj&vIKa}xqsQvo2osQ_JBRWCxpW{-^duo0K zlm%P^(6})&Tqpx=~{pL%^spZ_boU& z;@r33=!A3Mg7aH(?xp|9x8F!+r#ts8ID6th{8Ibex8Uq7^=&ve3!~7aC?b#$03(Xqz4bX|Alg|KKCs+I*1){pYAZeKjb6Lxo^Q&>#5UP`ABu{Tkvz= zf}i^qd?A+tK4+CLVEzfHw@c@~1wZ#KxSks4z6C$`E%^WB+wgPWg7Zjn?pyG4--6?B zNaD3W&+X^F1wZ#KIKTbohsrr|?pttl1i!mE_bvFjZ^6%f3;uu8x8PC#!*9X2T>Oji z^>uP4-Z@NSE^~H}xUtS*Th?6WoX}r>9PPYAV#Ye#+aaTm#Ex^0k>;^ZuXGrX^yT9a zHEq1}4*WsHit&hwACC+ICpd>0H%P(+0QUrdI)Z)@FcF}NAZsFkM`{TYCjx{_0vI4^ zlK_Gy0qi4)kl@Jx^#lcz0R~9}LH1;Te)j-GN&Y|;EiW2~Ckw${T z1b`t^0ES4>6o9ZP0LKY#ljx}chY8B20t}TVf|98Kank^XN$E6zm}vkmFTik#^#U{# zR1%C7V>&>&7r;9mAXX{};->=y&HxxC2{QoPGXUxc;v^supo$E5MY%w5M(a|=$8+$TJrM&dglWiA}E#6MF9H=iWdPq zD~$w&ivWf!1}KxF#Q^VhR9U4**800aQ!cLjXY!0qi4qO@fyK)Dsje1=uPL1ldag`Yi+4Ci%+%dM^VwL{KZC%K`Qi z6fXyOOBx9ZmjeuW7@$sy9tH?|7~nX;PKjOtaG0QM1;8$8A}Cn_5cdecZYg~PAm$MO zR}sJ-i7f(XCa5HMUyMfq%8LNJj{-DE1ws6y0D%_3UP-V3+!jC`!9EFi44{f2>oI_j zrIsM^F@TU_fCG|N3=mWdu#ezV34R=)o}l1yfX}6YAp3EEegbe%^2IsE_)-o~8YQ#@ z@|7&1d@YTXZ)Ctq$RR1Bd@DyN-%0cnki)W)azvUa-^gfg8VFFDNRyA`9;j9A-_rjHa!S%Dr)3xAcL^?q z{2@7%GtvNY{=<-~pK*?M{%Ob}DE{frfoBkFNa(W=r!1lPNh8Hy20RCGNfG5dIYMb8 z(Pa>~tfZLI1d)<5bX?r?=(u)L`aD3)^8l_F06IwQ3joaol?3ODQ4Uc40)V$1AV4Yz z;>!U7*8+5wgtY+fwE%SlffDc{KovpOivV4umLTy(fRJ?nT_tTDK+rmXeFWVk_$7dP zf`XR-E|ms??3V!gy$sMp@?Qq%{W8EIf?x?<53rx0cs;=7(nwIa9$?4@fDkF#01&nT z;5fmR61@@NFhSWyfUBg5pkyOJTm`^2Qd$8JQvu+51)z__z5>upP)Tr|7@GjfUjguL z0tl4~g7{4Uftvwtkc7@5KOssN%SzY3st6~G~aXbG(b*iTSg4RDJz5)@Vg45;R?=z#f@%Tw z5sa7MHv#Gi3f=^mC=CSJZvymt3t+P3zXj0yEr3G=2@?7?zE3n1_vfDB1^2f+OfKpjDr1ndT=BFNeeFi&a;5_bcHybF*cY3~9A zy$i69V7>(J0jMV^*aNUY8VIuY0Q7qgAW!n&1L*x8z#)Qs34I@6KSA;P0E?xOpzwWw zAs+w~NYMuXVIKe-CwNez8vqUylr;b>ktTwY27tJ~11y!&zXQbl9l*60V7bKZ1!yLy zBv>KFhXCb!0lXgq6iEd^{D%O6`v5FS*azU=2T(^)ECC+@R1su-1RzpNkoXZm$j1OH zCGBH?ppOCe5j-iu`vK|+3ibo6k_Lk8{Q&(A0IZh$0|31b030GHmC#QB_7fC;0`ROf z5)^&{FyvE!GAa5LAna3s;{-2A^k)Ev3CcbLSSw8gC7%JreGafrN1>QcIB7 z2oUlWK((ZO1rYQVz&?W4B=~E9dV+$l0k%p5LH5@G{k{R%Ci&k0^!^6m5J9bk9>Oi+ zO<6*DOByL}%Ybhoby7sxAx9`XCHgx^y{x3{k|xSKGW0NHx0F)el~a^G5_<&lo~)(3 zFUI#Mvit~&^nQ;b8>E6D{(FGH9{~1B!VduM9{}nI_DR5x096E8KLUI#wFHSj0)!j| zI3Q_90fLSK>?8P8f{y{z6BHZ+_*@zYvX24u`w8HnodmNxiDhT3_0|cG`_*D{40Ju*8)Daw)fRg}K1X(8mPD(97 z;z@vzW`I+Y)(jBT46u*jcL_cPP)|^B3gC=15M-YM=y%#V%=xEN7M*sEmfoj94uSA| z@jH_2Cn)|Mz)u5=K|;<`7VIo zE`UP>!4i5Nzk4j`~Szzven9>ComppKxQ z1attXBFO3h;E`H_#0~%<9RUVNT1SAOjsW`zA|&{HfO>+0^8p4)13~ur0R1iih?4vZ z0D4~laEKsULIVKy6BGvk+#-zxg#iFVIspukqD}x|odAv#+$PbT0S*(Cbp{wJO#~&K z0pczM7$&6`0>oSh;0gp7F0p|C%>f~+8bF;YvA7z7Z~6=1BSbp;6O3b2o0yaZniP)|^BF~CG=AjrNLpkFtD z$&%jK0S*(Cbq7e2 zCW4ag0C7D4W=UxefS4WtuATtN65A7?nV^y&Rg7SO@}2qw*imwD%ER6((R{{*_4NxFOy#c~{0~{xKP@=B_I80D> z6~Gc{A}F~EAns~_rBZq|K+M$uu4@36OYAiO%>0NQ-oF79Nd-at-v9#p z09caH2f*D2ppKwe0i||t+I0Xy*8%J!cv6C|2dF0~xE^4Y zG!SH856~|ZV726j0`v|AI7Cn?p?v}N6BPFacvcz-3i|>KxdEU|if#Z1y8$4sAHWMz z+7I9`fvZ2jT8ZrsP;w(cCBZr|JODBM0K6W6m-%;4(I5T%+RN)6zdLZwNC*Qc?~fF9 zVMtLS0R#NT7_UeYWs}rWHcQuVNTsAvUX@*xEfO37sgfK@wKPy_qdI8gjNX@oTfje%YD$9y42Jw&k|=wn zmhz!=9Rk@WX_Sv-7v*CKz7?`xawrF+f%1u5bsOYU$)|iK2PmIQXbj{FSwcA|jg&8C zz)(n|6j8pCBb2Wt`gX`SvXXL0nke7O&|#48q!c2Dhoa)HJ5cc>5_<@RKBr04To$ppKwP0!9MF4+qE^3Gl1b61YbIgxm>mT+;3Y zs3O=$a8iO}0TM?76vP6Yk_LjHI|2ILjRt5VrK1536S&3z zm=Ze%pd=2UlAxU!@c=QS0le`59i)PwnILd1!1^!) zTvGt9lGrH#CHDYS5?mw3RDhTS0Pj?QK2kx@Ob|E?;5tc|22ef)ppGC^0=xk6QvtGg zvbsTP0r0;r>pC6MPtqv;Wf#RG!80IXl0z9F4U}-XDiIPP`ILcjfHFuzlOU0@gc2o< zl$&J0Oh~j8L1cd-iab6OMcyLOvj7T{0Lo?o43Q>+u$cgHvjJ|C(%Arq30%nlLnSsD zpkx+6CBZN;QUGFR19(#ahD!xOGeKY~z(`3*1t?Djs3VA#fH?s1DF9h>07gkIfjbo- zBn===($WB`2=)<-k>GTI#5n*3=>TJ;fgmUipkD^Sc*)NIs3$l?Fi}D?0kYEpiZcNw zOCv$=41gh700~l*1+bssIKfnjo(oWz2~ai{z$;AzVOaoi^8jW@={$hL1g>m=B#F%i zD47dTNia){9Dta40NxycWT_x%CJ4+0NR@i+_?ZD z_X1=|+Pwf(1p5f)N$>)I#Q6XP3jlJYfgtE!fPVJ@%$NN80O|=25iF3&40L6I# zdD2MG`#yjn3jy+_Xd%FUg5v~>B|0CVFb|+CAD}>*2*MTu#4Q4NP)Zj8942rr23R7o zivddV0V)ZWig78LV&~v0165LR!Rdw(1QT|mH<2{`AY!m2@VmglF)|$vI_x< z9|Bk{jRd`y01R0QP%1@B0roHP>nJCd`rVFGLi93#!iSKeY#CCNNfSZXQh>PS053@C za)84Gu7?5EO68m`rIx_`BtXb2fPIp-3ZRN$AHl~G{4_w~Qvd}|100YBf}m9Z{Z<2fD*3Ac z>In`Jd@iAD0J5J3C|(0_P#Ot(uLc-W3eYG;r2zX0juU(>(a!)Bt^p`}2H=o15rmZj z#61h}os>QcaG1dL9KaEYeGZ`H8GuTHAH*mFhN?r!2BxonbMu3?00N#xN9i)PwnINzN z;CxA_04U!8P)86T0j~hWZv@DC1)#Ik61Xb>LN);eO4=rXDuR6kT_kujK;kO^1)Bl7 zN&`XACV+mG0No_N5}=;o5W%Gq`YJ&7W`N>X0eVOyLGMa{AzJ`~rDzMleuCo!mrHaN zK;f$ZWmNzn(nJup1t6{(;7Td21~_bk%kT!~sx7Cg{q}aTHpcsRSc!i!j=xB-@Ofz9 zmgq*mgD#n~-S1~RGGS_JdJ2AVD=!!Scwme1so!;m-1Cl~zXW~aHyGVI@e{v!uChh= zyA~4snxCDaneNR2Tn$qAtva;?FqPIe0VDSsebFLiBfjTl~y!J5}zNZ!v!Gd%@|tb1%}W zdQ459dISGn(j@6{%x@(A-=!0e`Q6Z=^GEhSoLIOA+ClXJtKfQW9}1J@C;Wu{wb`!L z6-|C~3|Gn#G*1GK`Q6g?*Mq8QPkg`S+h6?NG&-!u|Gyl+4Y%-#?-6k}H?u2h$tw4i zrsICMna9%bM{!^WNc0K6+i);ja>DNg*N!vz+g9x9QEC*Ho%EaSnr;}XUW=Ww@U)-5 zYjj(~5y({IS*PH+Nts@Dz<=4TXNT_WK=k?D?_P2*rZ2;nwNGK2yb>e&Wkcj<;CB;GinO)&}NP<`EeovAkzG)Z@#Q48TZzieR_V3=J zXRkIrjTF%2&n1oV%9gZ35fxY|>GvwKcGB-Z@81ebDtzgGq4)2h{Gac7%@#w6uCdGi zN80~i@+-AlDn&(K<9WkWIo10tTFEGv4t&XL(zze5OTy3rv~3skyExTWHGMk@1-<{kO=Jd2qz%kM!SSQzkFEj?&*xhGmH< zl((hJ8<6C;)Rd*i9%0IsnKH&JmzJh%xha!R2@E%7D@+-qtxF3jMt&q4HF#y;DW9W-Uxkj*t^hfG;^WXq6=Sl$sYC7c6(Hf2XlSx#hA zkxAS~kx7l^f*JBoTlpPFDAk=Cl9)1ir@ScUfwZRVj2U2FWbVkMaO9oy{JHo;22<~X zsh1DgOIj#%qy8>>an27POj%T%saODEM}*Qs<)!qJlK>cJHp1)3Br(!n#v_wPYrV8S z5c>pEPhMOveha~mrrvE+CX0ZVrrsSRyeRpQs*9Zi`Q0-WL$J>wHfizqkx4E?VZ5Y( zpS;XoA_;?sSfs^2Mm~Q$*jWhZKkJ3}ML4P<{a=3i+wU>uyGD`QIKTI%>?`cgkVy~vV9NMfqRTX7(t|#lvXa3R6}IS!Yw0(v($37A79}r9vi&sRBh!nS2O9tW_ag zUNtHGKCLONhW!~$G%Kd{rGV<#?;*>EDPIi`&ov+zne+k=Q&tmuxG76#%4#8#SBXo{ zkS`5L`;h9DcVFkj%&1ZSlEOOB&lGx_o~6!PAd~LmgG@%edLXY3kYAvwS0DQ@>Q-8J zA!IVpG=SmA^N36ucO_HSNZQ|gNddpg2*uhM7MO}vO+|U%#bQ$?pB@lB zzJ}+r)RfgQWzCR1p@^hs$fpUUwD`cB%QI6}8<`A3&EW@#lI|j(6p-9SLI;pvH`8+q z>>WY6OLtS&61xN{y`_gKYlU4(LV8P2Q`Q>$A1Peq^S3U&Oj#T3a;LuZmfqIK9@?5h z=`MXt#dg@Y$*{$*uPJMfy`?GZXUZhOd(B4J-;_y$zb40(F$b8kPT0$sGV5Cjou&Vm zL?|uP`d~sA>?fs+_{ql;aCYemwNi|rnmEe@hMRF}aX(!^h2S|=Z z9&O5GFxpK<+DZKnGleYa^*3TlE{B`4-q_{!pz@P3(cSon=H z{r1By-!>Bc(Wb0F_8SrizcI)nB!vUurYRh2Dh@<;+mww%CSeZ(DJ=&m=6nJAuNU^uoQYiEa|xh4Y12{urXXGJx*kq!4J}dyxHv`Jc7sY1fJSmRD=9l zUUyeAFKfhNrYDa#pV4=KN^Fe+n00B@C0-+EDK?sCG z7!+2S3TVMxSu>V(wTQho)B#!2l~V%>Xe)!{m4z=r7G<&sYYvgn0%QT!3R*)OXbbI9 zDE|QM=cq%p8hI&bRWce$|BywPyz5uq6I=)Cg1l3>CIq2V8uM!?17)Ecl!ppX5o(}Q z6Ka9Ha5*pdgRI$PjaC2xpdbXgx~RDYH7#>?4st+F$OXAU9&614Uasmzn3h?cE~>dj z$?EM2Tm^Y$`EtleEAj#vkYxatGY2PR0(pOWQ)mVe&>SM61+;`#&>Gr+7Xz_3B!lFT z0#ZUMNDXP^t4V3GxPd!(KozJ4HK7)iA)wOmHIxE*!+d@y0J5YEghCJm!Qc%(kOERd zDo71!AT7v>Q&yOBV6KBEFi*t=YU!eua=a2&!D?6oYhfL%hYhe1Ho+E%fvxZpY=<2% z5hlSDmRoKGH`|D@*#l~SW-h8kX5G#qz749W&|%-LVD#JB4{j-3fbV=mK4pcaWB^ zk$iIRI~WCHLDr?R=9~oI!!(!<3TD6$Aa6aAp(q#RhCGlL@K%_ z`X^@|a;70?7VDC#gF#x+)IVaElR7zxo2}e~wPJ-jp(^Kt-@{Yv&)_e34lh7fxv$|3 zyoGmaNU-KxU=Y^XbZz-2rL1KqLVaimji3oMg$QUKb0%0TXp53pn95S~Db%HZ*9Uo@ zYJMmH0Z_SQ-3Pm&9LURk<-NR@F+=I!^5W`~ zaLScarqfu?z*#s4f53UT02f_V-%xE#A$do5YmgG0FS$+*0r zyn$*Kre)VkVJ)nN6{OvVU=0O7$O=B-3mG6ixPu3z15a>+>-3`=5C_*_5A20sU?2Pn zzrlVu0KdaQI0T2`2pk2zji8d`*WAO_aFKx1eE9_Xh7Pmqss+=4rB7v$?6*Wfb5!WB3V7vL-$2l=weVc0;s*@d|q z_P}1)2lD;N+3-d>ZZiZ8pb<2N^E4v)+DuKT1+}3PRE8>04T2yEdPzZp>>THWOppZz zfP56_B3y!2MAi`MgDgk(LJTaFh4dmUn_&*Dg=Me;X6NY*>D*pgM)N&;F1 ztKm4DfRk_v;idhth``YJ_@`Iw!==4HM@KPnX}v|{WbY@h^#KugZdz! z07wJ!Z2_k^K@+#pbdu=y3mhCXv{*vvk zl;8&L;0Z6O_c!nk-opo2M40C=JIZ1}c9tf>kMIg)6Q}{mCeK>1_E=<3r7=CG3Unm$ zO1QC`pnuUrK0cBPLP$_3ghAn?>S0N(dQ=uN@)rq_O@@0Q{^Wa8NsuRn7ua9IE0E7B zpM$E{WwRj^3PU)^ra~9!3f-VP^oD*Qdj`wN%TdyQ41R(A5R-&00{KF)d_qAs3|hfF zB9L?aMIh(*OF+))m%$2{qTZL#vPa2Tw46f<*(mtT-~-7(_V#S>3whoSvtc@X14BVh zgyqCn&fH3JEcq4W8x& z7-#ygj$JZZ*_2g~YaQab8pug0Gmx$z$ClkPDsajV&11`trPBp@9DX`uO6iDh zCx}BPbj2q?k!y3ikIz^$y-bPjp^mzJkBt8Ek>gAS1v= zn1;><%sBiW0=iRCeS=3nSRm(56JQKVay~T~b0DUiQ`Lh>9FM@$x0te}Qb&!hu4O4u z32Q~Tg!m#%g^6OK+5=P6C2b>NggS$lsDW6y9PZK=dP6VhW19U;S%33b+y|R>F@-@e z6uyDsApS)zQHYzgP07V~u%Bc|^b=qlAdhl6fN)d-$I;lwLL72Y9B=YHrWAu@Jr_to zG6aczig_$~mU6uS=iv`n1+!ot?1k;H0oKDh>AIN-WHtwDU?r@8v)+H3_V59Q!% zC=JD-7!-kE2!#BQ4{}0w$Oc)#3EvXY1CX{M-QO449Z3MDH+Vrx7=l|m%nWva)_Yjo zAq~iQA)aI5F-Ub>0Vzh+qoU@g4VEMIRc)=9BM$3Tn;KeMs~`0g`x8h_0Og4>6{Zza z3hc=f1T2k4A`!iW5xSw17Cb>x=K&eP53)dJ$OPhlG=6hn&kO!`iZKtC+>px@ibFvN zfC3N%g-pUR#jP-eK`2Of3BeTIq9BpTuN3x@Af@pYW(OiJVIG&kl&UCeFJly&Qhdw4g@h!&#k8W6<3YH~v4mi> z`HvsPpM)&Q6Mb=$V>xqK4$DBoTnejU6-c(}&QbcvBL~7$9G<`;R2k8Kj0jkf|GCpj>9q74%^@-h=HEA5GD#NKZ7;TN5wu7bG9v2Y7+!k-}LQ3=Dj z$FX3B5#=KN#tKmEqWl;V2A(hgktaTu%)Uha0-l3a6AA4ilf}$V+~2@#>>2D_=N1Q9 zROsyh2bnHEV4g5#b{2a!a4}73aivsbBh6J4uf5YDlLlMiH4k??Y3&kho<6MR4x3Er4#ZQKaphD9Q@ zF7xS*c$R?DKu45QW5!_$>9Ko4I*_})JWM+iw0xJ!CAP9i%D~r98oVjBlrRLh zQXFT%o*qhKPlNdtrqsMtX$kN|X3gE@IZnJ5kZYpJ(Xsr9DLTX(^+`A!rCvCU!)y&> zL6SQfa}-R+E>$%Gq@~K~iuf4@Q7{zxL0^!~!d@U-!aYFRS4k4k8T&ONmMWG6G?S^P zBf|F34%&c3E*-czW+Ra0vFwsJ02w&yW7dP(Pzx$UC6Gi_$E*o8pc+(#Dj;$>7PAhd zBl5bKO(f-wp(!+j2#ADM&;nXQTaYu04w#Zqv3D}%;wFjhj@b>mKv$4l_XbITUW{}xlKW+bNMTDO}C6kRKEB9}9c zF{YmAN)<|2!W60h$yg@AM3AN{XAt618lG>l)C)f%xumv{5CfEoY zU_FRGGn`s-QS<}Ha)I|CKtmiLnSB!UqeYaidzZHkC?LGrth_> zxaL~A^s9*`fTYPbnzeILsX&TL-h? zSC~jKoJ6)$4U5#WlsJg>cQ^q1;WzjdcELXQ1@^*j*aN;0Cqe6|*hno~)E~&s!C5#1 zr{NSN_Rp;X`Yi);>ww%sAh%h`Ed{QS4CI=I3w*%uJGcqg;ZL{$x8WA#fVc1lUQ3(4 zgXJYWg~xCo?!rBI0FPiXMfuQ_$(ivJ?9UUpiQY4Kkw_+C{RN^cI+h=izk)lm^)Hc0 zZ&m~%|zsr~uMr zMP3EFm{l?JQi5_-vAQX%hgl7oTutl>&E@u!2rTuXDh_oq>p*4fH887SN+7kdi$}Q~ zMQ%$Gcj@usCh~-)WtZ^knL1X)A`^E(WXTX)RF2@|)k5_*(?dLfi6zP&*rlT^y$R;p+p?15dd1GcNs zHd?-@by(NJ8j!n4R>4YG4$ELEEQUp}5Ej66m^9Ps{eunk0s1>GKC}zAYnuaJ#0&q zGb>Q5jqFU|Zu#4d%+j;s_yt*CQ@&UH$Pi-Hk>%0y^cxB?8VlF3$H7(j15U$1_#NVK z7esyz#7*RfK;&mJPnfdfm`C6k9EK=S5P_ISO;hZaT$IFq22%n)3097wbDP-X=Xi<{;5bM8`Fi4!Y&HzW>_#9fyu#0pSy zZxx*sk0eHNXO+Nx?Bekr+yqi@1`=NteJN&goQ36-sY z?Cb)5@#h$O2^1yd}I>F1CTIMV9LFPQF2$R+@0zRJ|MZ2yH_(p z29W0um&W)G-ohKH z|JPVv!AnS3Owy92B&6jgEHY^)(wJ_+3-}A3z+BFfIEtssE=4 zL?}gO)r)wtWTGsQS~5Ad>RDt7{XIAJL@tT8A`;W8;=jqH{zX}Y(hV)857^&>rEGN{ z(RHCHEx9~&W7*|l8?j5{6+L;-M%skf#gvC^#IBj915@w+BD4ZY7`P>qdX@kZ7M)DD z(pE$+^)B_CFvlVn|57bh0_6Bp31KI^C|xOGaatZNPoE3aa)DOr(7e^2OH5-VE3n**|gAILr5k{D@I(k>DXP*!3@ZYA7D z?*B{J1w~1^pL98^yII{#0#4Y)tnMXUO1hKur2nVADC*z3kx@kP3H;xXIQG*?_5Yhb zZY53zt%Ox5gISgYGP&{9YWaGVQgzvoNoKOj{hv~-iR)TB~%b(n_sxOvjPhft<=`hE_f1=t`}&&Hh6pm2I^aWV6TATdgIr z*}PPvHCi>_fW~wN7nkT;uAM_XdT$EPW{U_7C>#*T^r-f%(Y)>reG%x1@6xjxi z&i>rx(5bE|g6+15LV*E+0U<6ijg{M4G!CJ`rg!w4H6|2&$h}KgQ z8W0rhQX3ccQls4(o*I}wy~iRe7lAGgRcO5yY|E}@Zq>Zi(Dhn~J*K(Zv|g*~TpY=K zjHgJy`dh};3`%CV6(R+NDESSMY66K>AsaLg*HftFK~3%6re#pAH)!Fuuhr}gnzyrd zOBMjQ1Ws%?q2}oueTlUQA%wF<*I4;&)cozIBGrctnx8$%nhy}Szr6e`5? z75TR4sc61M^A3BDo;P~GrMY^$`o*Ic&2=v9ukjv7RlD^(%SgLYy7~WgPCZ zps`xBh4w(nM37~gOB_K->v@Ox^NlW?Rw<1xFve(ftz)#ZpBfmAWCL>F%k7M&FlF!i zzE{ddHz3MF0U^?`im8t=B)YZ=-l_#lLu|j5j$^b8`^5HY%T^l2dam(He+bPnxuS zVX460j`ZqEjHZ3AUr4Kr?-!ErT-%60K|eA4+Kpyg_EXELtuAh(OpPKAG8>)xdb{S4 z!fITGTiH*?1iFgqu&PCZq(zF~bUSEomN$BQ#T{Bzn&C9W$*pD>t$yF3wex+#LzGg6 z&JR;pZEUn~W*(zGkOzmVvQzW+&EB0!2!(kowLHl}{a>OWV@@#p&ngOq(0VAip>SQi3;y-PS0T-wkJDXflWd=;BY3Sj83pOpzK3%zUzRfSBou-e z`B0dH3->KWPg}4sV~g!cMw?y<^k-b88y#pmcwwCn1q$F28W36}ps>ri?yBZ4%`5Z< z8ZyB2m|Ar7nqv>^>3%~3im;@>#SNGG>ej;Mjf=0*{RRgV3UNuMX73`iUOkMF?!e)r zcYSI`1fUQeAY-gcc+81iT45V6s~r9_np=4|M@e`-&ba&0c)$zy}WjN8fF8p=PIN z8H4ZZA9$mMgYL0@l0}7I+ znmfmx8Fwq_3}&grVmr5^Lwgmzi6AnR-z$I ztn#3N+sajRy^u}92$u%38y6XJMjbri9;3Qv!6i5#G=M$*b3N7JedP2J8WR8ZQvq+% zO-sKJ4ZXGt1#9Z#K3bo9FHX`>*!a*ROS2r_E+_;MI2GZGi!_7Pr&`=g{!37mc$WZG z?N@CW?;bk#D`iy!Jt>3LwFmk?I?!ejdQ7RIwBe>I({Bu!?NN}@4jorxNvSJKyXXpm zlFA;qNZwbyJU(~hi;7;jn1kuyUaIqN3^=3Fkhy)vgH(~n`b2L*gAO1qd|EFx4~?(| zXh)<6mrxm3_w`Z_@oPWROS$fscF{`} z->;RG_gn7Q>V=K%ZInsH#a-9@k;TDeNwUZfk(w?=e5!Xm;hyITE;N5}ncG{XIzW&s zd#h5{vqY%uFA1*JtGRPAHHBY0yd4WEAYr`>KfFnVWR|j7b75xsi+XSx8hSBJm|L zuie{p`VeyD2f`>sYr_Slt9Z62Q2s)$jgksD$mF$gfKe{_&MfSCqSJ<^dT{!h#K@Ex znwMI0Q1khhQv7nA?Cz;P93)w92P&^aTG`|&1{rgAn`tK;p2;X6cHjo7=tHQW`r{#5 z!ncFe(L=mBtBwji%=~F+hUrx@Sj{<%<`u&XJw3!|BsYHZOyfK~Pnv>M9$_Z#b8gan z^!^&4?j5G%bX8rBX!&vsAIj+m<(ash5|UIhMK!&r+I~d4Z9f#HmK-H@r=!#bF=L}t znqxGr+nlpV=k;A4b!E-1%`Wk2c#4bktU2FqE3@ovLl8sA_? zvzcFw84R?)=7qyeWQ#V?8-m$La};Ym39BDeK=i@QpZnfZS9jstMF4=Ro_jf=GfL# z*Kfb@JJn1^rQh%}xVfhYdd(R1Gm6fCmiSO}Ec%66J{g{!J}T5D&kt9G0wkFM(6 zX^Lo4UzO?%39Y3Xo#9-j<#=N>yIZT(pqgXz3?*T-DVcji)t)nWE2S=qQB6HR!(!ye z3C8efIQqVwXk^RV!=rQE>}gNq+Z+Z`s%B?dSbM6KXPK4!CmF3{q+8R@ok!Sa>Dgyi zD&%6E-uUO}kA@Eg+ zw)5J>Ugxx|=}O}uqg(4nzwDipcAjRp}!{Lx~MPE&KvbM|ln4QWv~rm2(X zwW{`K)0E!@S~6eNx}bfZBkgo!)s%R}1xhxhL;P|)x2w3Ce`DTHpCXF9$nJ1^6q5!%M3;} zRJqGqI~f+&TqaM8l#!=xo*6uvP5fbaQd>wskiPierPRC2l*>glWa(M!$A=F~%{%cS zUgM2Yfw3(0ym*#KG&bf;+AQ$)$aL`yi0vGB$nZ!KML9L`YPgw zp&_#levDU`iA!c&ZkEpJ`n*ipeeo_o%}|Gl)_ER{9B51#=4>>y@AEwI8t-RJze2iv ze=tVslF4ed?K$fA*6|78{+ab53j2h98}^ z*>x@0^XPJ;t_M%v(>U2~KL)xi;=9S_OF4BLWZB|`x(SKEpI(v(*dM!q!xUKnX;XkoC zXta*mKdG9x8OV#N*|*83VYvEkGlohPcu32iqZli;j0Bf>S;`_n&aDHv!(y9CbBDS( zvrXl>!^ra#|1vfqZ+b_I_!NT1@>Q0;skan89g(H_S9)#gEA!qvR4gHcP1<2B?n~|{ zl4`70Z$Z4q5?rL0Wb>}yvPq3Rf5f}|vP0#+D_sx`IYBJ&epQf1nWH!3HJC(e-sE6l3uaogEQ+KL&lCaHac;R<@e%p5st8EF4*Eq6M1>PfJx6#OqMw9T2_l{hA zA{#B{DT`y5k+AZ$Cf0PXu%uSJOI9`H9#cvY6f)qob-zQ?b9~*jO}v7;T91PLyItxq zaX4q8C{xPjs_l9e|FupyiZZ3pG}r8!exI`6Ylc+!*!PQOsJXj!1lSW@t%)J$TZ#=;M=j^`6RVjI~T3T%;|If3&&$qPP;f z%y>xSxZNuA19CAN4aufUv&X4BjDCM7J|$~$kt*ChY}(XUKM!@qh1M(W_rPw|8Na@l z(U8`%Of{dOeE@AKm8`bcyH-?uVHBu7t{K{XL2__) zkI@-(YznPfGA8LM!ZgQ6UCY}}M&{g7kz z2ujxHWS}a3-APK`E03P`&Nb%*U6Y*!S$~q)o%sDn)^rWB7S2m{pFE_3j5Mn(kJ!Z; zX9l;lVd?hgk`1%!n^GpRr5=%#&nA}fHrS=&K4UDs|Hsgn^*4qa-GLnIH6-O`#N_S# zZ1EMAN;V7FN>cL2OmwEqxrV0bNlusLA6PQD;m>xxiu5v|DMmb|96y_Q&OY?SOG(== z?^dhO^u6<|F}Zb^`LMyBjN69{$Cj8(ccU^gHxIp)K@8$j_oKfBnkd50YJN`m zuZdB;UuYio+b3g2z0gY7WKGolE!*%{Uvjlz(ixTE6~+C-8TIfD!ojaN^VVNyO(SyV z@>x5iQ|M#8PwF#S%o$^afAevbYt{4CWA>#JNt<-}^^7|FiXy&XYHV5cNBD@re*5Az zZl9U{npX7!1sOzYFE|+UeWyyS+pHiQ>d9LQJDsZb8pEkdzQf3^hP~#rCy<40I(!vY zn_sg7RqCuUYB;LwpS$Y9qd}gZ0;#N$ywNsW`LUC%;$-&Iae~bISdPvw>|Bs7Q{7CA zTJTn@&Ubc6X0R2al};n^&K>8COA<+%dp|p!c9k)O>r0@+=hcLFoEhg+Y2R!4!ZKbq z#^J>01(danx^%~Xo_Kt7S#^J}d1<##GU@3Hl;rmbMs^gMs+I5Qk-cM$?jKV6MUkq9 zl+1Qv#)Yv_vFa**!)BWrk9u*3?Ebd3bq)G#u*-T}q+;CPUR|2K)u4yEi@u4mCsqY~ z!0#zEWEdUuvYcmxox_JzReP@)_!2>tBHa{KiBo_@p=KSvNa~VzYMC8>iP#QIP6;#GNaJsX6@In zc6Yq1E9lpw7U7Zxm(yQ8UmawBb{UtTA^{}8Wy2L!^&_LoZ&y_GM{;lzO*s+u`t{M4 zu@U!JFP}jod9vQl-gvNUrHDTD-xCI-zeNB3iaLs4U&mEr}=*1-{MS68Raad`RGh%>6ct5&AH z_uGp{&B8bU$r~ehORsPdr~@#UUM!Zagk-==09&Yy#8S&r;L`% z{OhWO-QgAXGa9~VOkXqYNZ)%WM#O8JF+&}m=Gn5#*G~Q%@ABxn8i!xsG&hV&JCGxG z+14cohsJBqi_LA!>+|bi}pXfi6f9*#ayete{cYE<=S%R$#joJ&3by337Nbr`?4HtM5BnHz@P z%#|J25Qih^AIGy})jUaZE_KYocJ^nps4t8Do<+Oq&zU$qdTANwfw8JHAv-@Cq+V)f z7*?8`iR)Gmi@KGPmy-0)QRO+eYtqsVVagfWA-`GIy0m%o5>0cpdhWR@?n;^8c&_TZ zI;x7f$kh>%-20`ms@6B%GpcOK9bwjHuIicGkze0%NbcB_eDN!ztNW{FDJaIGs$U96 zFvd!bobBEiTQ8HYhQ$>AH86#z&0iWazcFvr)fCj+95kf2?3lkdXnEZgf5mI;z$GUx zm%B8&|Lr%$Tg1Cud!qtU(vP&a#!7w8ZV?+TLX@y zm^ZU)yk0A{7KO0>DEJZ8(TkP)&GtTWFkWE>F4=K8@I$t&`<}%$iFeudR(&85VHeSm zap+dN!GFFzy7W@K#!Fm$aXA|t9XEb=g%R;CJ9?<9sT>iuj%sBp$7tu;_eMV-xj*;f zv@IWvrgqHBW|O(lnVQw~cWSFHX~^(r?k|yhB} z^9OH+7Gv8vUve3hs9baRSq=Ab5cB^%N>Vi-qd3nahhY;7dJcE zS}o$sX!Osm)eK33U5cu0zK(L5esN2cyv}Vzvz?A)YLu(PBjNUHL#M-EOYdfLagSH> z9mH{F+GVfp^;WL2r0Whx7$rM&BToIwd#|Jh1MxIwIY+l|EpWXkb@Z7^y=K9Yj2)2tQ$ROt=qC!Znl)} zM%$A|JvZ%B<9Ny|i7Dsiv zu`uwWl~wbj$hZfUGH6I*i%&6vJ(fXK~s7tuv(+f<^`ilq27|IAMrJhxF%SPY*A-&p}jm}gn zqtQp}*E&?ah1;h6gezA-QEaN7pvWf~OJ=7!-*~CU*&S7#{k;wSE_pMzTrss}4ZBUw zyeMy%UOsAXc1Mu!3m+q;Gp3f>cY13>FG7^t0_a$JDdx*D(buM5G<$$4d*?0oX8pCo z4DaVCh8TOl1Qn5k-fEdfk*7~eH0{(%YQlc+k@_tM$@xMpll+mHRX|P#r)gQ#jhv2X z-vWMo=84!=W^K6O&B%{A^a9i~T2w{lV%j*4f+Xi&DnIX(!*d3qAXy`*i&@pCT#l-= z0$XlJzU*Lc)_Pil>+j1n5-MyG{g}cJ; z2sJ2uzMf_wGZ!4mrN-gcH#W-H7U(s);)hL(a||{MhO!P(yYkZX{u%hI+(zJqo`W~B^2|iPIC_h~>Ow|gYsTzUvH8k%6 z6ouLGjo`g&hW9X*7;Vp&+)%GT_@8dJ2KkK=_~+1T<@U2fuim%+>rzzC0OouvK(lC# z1P7Z*Q*#0wKF%CG)-BB_XU+Y$b8UM{!?M=UjG}Tg3s;UFkW2g~E9l7gDM;3iPYW`2 z#qYW#u&QWRhpR1ZOrtkov6~{{UmzqVek6!8byNCIyAx>q9Lp8 zBdav;{FhVy6|d1B7g=rp^;2-CikIGAjdz(Dq&`UKKcOL~3)jAL>9YPz-8AtUe+0z@ z1v@(0>@&jDoDfG_=W^cjAv4#D3Fq?OJv!@oHW^I93R6ql!jxYqf&GDoER$V{Gff1x4k*oa-3Z?rmIX*Xer3e#3f-X!9NONz6p3m3lXI3DlU zx3JnSq31(GdPdPJZJRf&bu8BO%XCq^uzHM!y>eldB8)Xclfp42!W;|i_CAHxwQvMc zg;jb7I9_8IE)s8^+v(EYF6YI)5mv6|6;Yk>>)dVneNigS%KFWE7KqmfDXQk7 zVJ}rwxs`Bu1=J{N)J_?k0lQM4tdTiN@627iSG-G$(kj(gsC~6x zUe%BgWaY}lT(q)^M#EmQvf5q7kvz9}PeHArF#C}!O*3zVJ zXH|7y0;TKG# zNcX6pY<%9Mdib@&C+sx7Wa3|tX2wr_#&0WSMn~SioDehS|D04crVMjg*&0Ug+SdPW ztv|}Ymoox0M@C6|Ng<{wOBQxg+_EYu8S%95n=*5)vkDSpXtZ^1j5ano76hNIK6mkJ zsecKJmeaA8nnW03A8HxJaCz9A;HzPS&Y{8CrK}D;Y8z|wSxJU|?`*QgC|P{7l~Y^A z;@9q9TfHjlXlrj!UqzPVn4x%JIqGIkeYJ+;ux7MAS=|@pJKPb6TFA;%T6CcxmzVX8 zS;e#eP~RN$u5vAWsF}xI4b(gQI*&9kMyI=u{5v|v-dL)qT#vm)LlsyajV=w1Fow?= zHSbJR)B46kFfzh-X{cJ4CmrP)sTt*2eOGT}l#}n-fq@MMgfO04=an@YPp?2in>RKZ zd0OuW0iiP*gcBKe!b&mq#>I^+44ZjXJ@t=$iwh&26vBwcs=D}{hDJIxQumlp#^?ID zS7?}LCd+Vf$0gF^yPRvL3><-r?4(e%Ke1neU;A%t^i;$=&0dKdUu~@JRv_V~Jj~wI zNO-O5wW7)<-}**R7X`@Fii>n?&#tM0g6F@Ii$QXpMY@VKQ=KKu*kONiUs|Z0;`b^VvNlM)rB1~z`Sadj!+f7)0 z>v{U%c)uxHs#G$A=W1zGO3PFkD>U{9yb!NZtfeYdnH2tQ$}ej_(?*@H?C^7DZ)+rL z@QLgG+fTmEPS&K(SdbKMtK6#Kv0*z^wh9e*Q9HG&3T3sSo%*c`+bz{PsESpQXX>cd zRYhLDqdHg>{pB51U>%Olhn1YWJK9`wllW8VMy+T(r6)DZO{DtutXmyb)oLU@O(&cF z=vAwWC91V<6GzjxG{QQmXwj&PhD=D=_owt(Q*0>rUt1d1qfk9NsiSE6jz&|KGPVIl z8m95vQCb>0`$$wkL1nAX(tTkk<*ZKf%x9u)-W^rl+4A@OBtnq;L5i7&9pF zO|~7l$cb(U!&xs?UHpziLx!dse-#<^ZNCToO}`8uvrU(e&u)$x^}Ngj-9<0vHNDg< z{5tobA?3F8_v(dbyG-90AMc4?>NpzqxL)dRT_$@yq3Jams_FMrk(ouM=G0@8>Ax(; zl>JpIDQ2q^diA%-tBIrU&mG%t>idV2<2%q7Dk4z;_C)n&B<41ZwXz-wzk)Y zG|V+-auISVo0pVs>;Sd50o6~nyb!Z)r|B*5uwtN*oLvk1HLLaKOWC|+JtLjZ$;O?@BZz--l8a{5@+IA9=Xf4E>bpH|)ajoNL>cDyF;Wd)Tj|)O|@==4d0{ z+XIGejcGqluKt*ndC||V>GlL{7rn!@DLbjjKbrZ8Tyf(+2wSLbR@O68?JUYV`PdMuF^!{nDiT! zCxXDoeWRL2F!8&OQ2QcSF*X@tjIE3OW5@QXl(#a;F;_=JMymYH(fDkF^@X8K51kza zRE_!$Eo=|@lA$>5v;uva`}FrP@+Fh^X9M77okA`~0D6iu%V_td>}38g6X9IWaz$2Q zuln3S^HazSi4*PO)zf_umE@nplmO3t@jZ?i_6aRv7YFTTlH_v$G)`m=U z8?PeT&>n}3H`WxDjysb#9dveR{AtKoTx1L6&*~4CpPUotV!AMe&KR#&wK3Y`RWbh- zrKg(I){*%?%k-q(Xq(u(m!0QusV27 zf2tY7=cmm4y`s!aS@Z6f7p{cDv-@6ZOh-q^7jp0~%U(%CZTiiz4i3#IK{Gvx5})WW zzHt7v%#>#r67{)eV)WF{r)x5!&{g6+@95+`TAA~u7u8RED5hRcN`+QO= zI!{IPOrVUCUtf-E&r{LMNaEUg>Z-W^K2LpEY6POYUz?`_dU5=Go?3_LOg-PG-^7q| zz@VpPk8Ld)KjP+^ueuK*BttXEeqz4L|2@n5cfGjEX?nMtO3pw&*CG2etDAIb(Y}_t zq6ectR%7L+;zD(_w~>%ZQ@9{$rgz6eb$B45R_{YIArq#XWcX7-B=DLr530mi3dAfO z`_aX!#0bK_xmZO{z_c$>ZcB{##FNu+i7~Z=m$BJiyvUZ+%qSabK}%Grp(G)CRD$5H zFHz(A{Vll(Q|7)*1%CJUNKEI1T2=)oNPj}-Q_Izw0R&>HSta#gxq2sZD`#fGe<}{- zdXBzab!Lw2X5m`3WM8sEbyo>u+_*wjmlk$tg_<)9(<-~fDM`>0^$IwLzftI0@feNH z%Z#V0)kvLVR~oItGewrrruipXjf>Z>ShZ!vlsJZkD^-a(31TqXS=GcTbPir+tcorV z@VNKuD*Z_qSujwmi9@u~I&YO~Jd}mRmwhH`g;w<>E+nfBSXr>D?Mo4}SL_*0#N*c( zg|c_UjLA##%;BWNeC*d{@ftNanoRFnGkpQZX&z?d(0LyZvX&`!BlEr_Bd#1XMi5!^ zSoQgFjf$Shyz0j{ab$fPzAMwiJgwArT{BFU6=h79RfVcY?f>HzGcKc$?$UOxsyd8V z#-SmXIKIl?KKsqW!$!qxtX!+2(XgLft9DB3xxZE&B{sX+ex2#o85yd+FZsO&w=y+| z_nB{Vf%He)C_z?ty49nMb2HP=7`Vj#384JrP};@tVxTr8I3sP zjOF+`H9>rs)#JQ{rkpvw*jICE-=?2l6i_J8#dW>%`-ZRztv7doersB$MUqAG_@J~I z*2~tQS!2EGil)8&dUc7godfV8qg%V|7l%*I9p`S2ZtQGLUaz)Gm@Cnc%XK;HymITY z{9~W^(dDQ0>am1nj_&_OpRk5@t2bIhxYf`SHL!mf*{s3d>`l%Kq*5-h6**aa)3lry za+;}R`{Ct!)pR7O^xmMxNxuu~{2b~ndGyWbLZ^E;*w%iWSE8nuk^ z{xg9eAH|Z@Dr`KPW41o>S(DCv7oM(rF!Gx;jUSPTU9T)nm0+X<)RD}Vs? zOqo?Cb6bdW*jA%IySJFu`fwe0xmIGfR8!rV<|kwN4%@QvK+o@{)YUcgi@;WZLw-_j z<0wsz^`xbqugZ?2bdK{F=5pi=Z?SWk{Z@=)_A?J;AipX~X#X`~ z3FkO#1h59t1a+dXX%0^yR%;lul&nF(8gUcUg{hW;DE!1Zx2Ae4u?hU^iMA#&s~LW& z67>>D;Q4Qfv}XM;MPl|7)p0PznQE`9K8>j*|6XHQjrikebmrve%Eu4#)o_tL?Du_A z?;ahImOJTXfh*Hfm%VBhew~v|ja;3 zOV4VTiKj~Ls5j@i&(E!&D!`mU|78|TJo{NK+nn1zWx;B^iDodnHM&`i+L~+3ImT|z zB=-N)=L@UpCaMstX8&(R$l~??i;3H;h<_|EjXonC__L$St~F{B^M!NbK4ZN&abx94 z)2_^t`|QniY$9*YHE7ChHH(kjDSueakS%?g#aMNi(~N#*tN(M}`qfzL-rthK!&apW z`#;uTVl|LAzp6F!5)2vE5RiDNu!iz4xwE=HJfCnr-wyxpK%nmu{O?>iS_6SqvHz*U zEIo5Za(;P6`d>cp{7WCPQe~yy8rH2vyVd-x$vD9xR-e(W{hGw#SpKY;?SEx=JHFiE zp(0jl*%U8_^+_4N(80|cyf%NaL**Oj>Z8)GaO8~%TjB7qWg8l0-2JOQ1b^LmT@{bS z@7Ws~r8=&3WX}3%obe#%#d?DlHCXBA{f1+?&0$!^dTBm-hQvD5@|BJ(*>2o5w&gwQ zXIq-#u20vSSB!^VtY=4W+*Nl8F8j8R#)GQ|#%y@^OU+tK4sk4Zv|R5!yn|coN*B+3 zbW!86hMbIq1i4%DN5uHGrSn=UThmH$9jp3Cp=x-msycSF5Y4_!Sb)c{vdTZ&OV N4C#!XF{sAc{s)Lx$WQ

Date: Thu, 24 Oct 2024 22:23:02 +0800 Subject: [PATCH 02/31] feat: add basic formula parser --- bun.lockb | Bin 556040 -> 557184 bytes packages/formula/.gitignore | 177 +++ packages/formula/README.md | 15 + packages/formula/package.json | 19 + packages/formula/scripts/generate-parser.ts | 3 + packages/formula/src/function/registry.ts | 88 ++ packages/formula/src/grammar/FormulaLexer.g4 | 95 ++ .../formula/src/grammar/FormulaLexer.interp | 156 ++ .../formula/src/grammar/FormulaLexer.tokens | 59 + packages/formula/src/grammar/FormulaLexer.ts | 282 ++++ packages/formula/src/grammar/FormulaParser.g4 | 41 + .../formula/src/grammar/FormulaParser.interp | 90 ++ .../formula/src/grammar/FormulaParser.tokens | 59 + packages/formula/src/grammar/FormulaParser.ts | 1374 +++++++++++++++++ .../src/grammar/FormulaParserListener.ts | 325 ++++ .../src/grammar/FormulaParserVisitor.ts | 218 +++ packages/formula/src/index.ts | 5 + packages/formula/src/types.ts | 52 + packages/formula/src/util.ts | 18 + packages/formula/src/visitor.ts | 124 ++ packages/formula/tsconfig.json | 27 + 21 files changed, 3227 insertions(+) create mode 100644 packages/formula/.gitignore create mode 100644 packages/formula/README.md create mode 100644 packages/formula/package.json create mode 100644 packages/formula/scripts/generate-parser.ts create mode 100644 packages/formula/src/function/registry.ts create mode 100644 packages/formula/src/grammar/FormulaLexer.g4 create mode 100644 packages/formula/src/grammar/FormulaLexer.interp create mode 100644 packages/formula/src/grammar/FormulaLexer.tokens create mode 100644 packages/formula/src/grammar/FormulaLexer.ts create mode 100644 packages/formula/src/grammar/FormulaParser.g4 create mode 100644 packages/formula/src/grammar/FormulaParser.interp create mode 100644 packages/formula/src/grammar/FormulaParser.tokens create mode 100644 packages/formula/src/grammar/FormulaParser.ts create mode 100644 packages/formula/src/grammar/FormulaParserListener.ts create mode 100644 packages/formula/src/grammar/FormulaParserVisitor.ts create mode 100644 packages/formula/src/index.ts create mode 100644 packages/formula/src/types.ts create mode 100644 packages/formula/src/util.ts create mode 100644 packages/formula/src/visitor.ts create mode 100644 packages/formula/tsconfig.json diff --git a/bun.lockb b/bun.lockb index b31617e3792680dcc65b6986854891a31733b48f..e1b441ca59368eb3510c347531eb917a2cd0724a 100755 GIT binary patch delta 93232 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 &Vm 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 B#P0wA delta 93143 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%0Anh-r?Pl2>+0)Te>g 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 diff --git a/packages/formula/.gitignore b/packages/formula/.gitignore new file mode 100644 index 000000000..c10d3591e --- /dev/null +++ b/packages/formula/.gitignore @@ -0,0 +1,177 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +.antlr \ No newline at end of file diff --git a/packages/formula/README.md b/packages/formula/README.md new file mode 100644 index 000000000..8c766f5e0 --- /dev/null +++ b/packages/formula/README.md @@ -0,0 +1,15 @@ +# @undb/formula + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/index.ts +``` + +This project was created using `bun init` in bun v1.1.31. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/formula/package.json b/packages/formula/package.json new file mode 100644 index 000000000..0458ffdb5 --- /dev/null +++ b/packages/formula/package.json @@ -0,0 +1,19 @@ +{ + "name": "@undb/formula", + "module": "src/index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "antlr4ts-cli": "^0.5.0-alpha.4" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + }, + "scripts": { + "generate-parser": "bun run scripts/generate-parser.ts", + "generate": "bun generate-parser" + } +} diff --git a/packages/formula/scripts/generate-parser.ts b/packages/formula/scripts/generate-parser.ts new file mode 100644 index 000000000..b36fcecbe --- /dev/null +++ b/packages/formula/scripts/generate-parser.ts @@ -0,0 +1,3 @@ +import { $ } from "bun" + +await $`antlr4ts -visitor src/grammar/*.g4` diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts new file mode 100644 index 000000000..06eb65797 --- /dev/null +++ b/packages/formula/src/function/registry.ts @@ -0,0 +1,88 @@ +import { ParamType, type ExpressionResult } from "../types" + +interface FunctionDefinition { + paramPatterns: ParamType[][] + returnType: ParamType +} + +export class FunctionRegistry { + private functions: Map = new Map() + + register(name: string, paramPatterns: ParamType[][], returnType: ParamType) { + this.functions.set(name.toUpperCase(), { paramPatterns, returnType }) + } + + get(name: string): FunctionDefinition | undefined { + return this.functions.get(name.toUpperCase()) + } + + isValid(name: string): boolean { + return this.functions.has(name.toUpperCase()) + } + + validateArgs(name: string, args: ExpressionResult[]): void { + const funcDef = this.get(name) + if (!funcDef) { + throw new Error(`Unknown function: ${name}`) + } + + const isValidPattern = funcDef.paramPatterns.some((pattern) => { + if (pattern.length > args.length) { + return false + } + + for (let i = 0; i < pattern.length; i++) { + const expectedType = pattern[i] + if (expectedType === ParamType.VARIADIC) { + // 剩余的所有参数都应该匹配 VARIADIC 的前一个类型 + const variadicType = pattern[i - 1] + return args.slice(i).every((arg) => this.isTypeMatch(arg, variadicType)) + } + if (!this.isTypeMatch(args[i], expectedType)) { + return false + } + } + return true + }) + + if (!isValidPattern) { + throw new Error(`Function ${name} arguments do not match: ${JSON.stringify(args)}`) + } + } + + private isTypeMatch(arg: ExpressionResult, expectedType: ParamType): boolean { + if (arg.type === "functionCall") { + return arg.returnType === expectedType + } + + if (arg.type === "variable") { + return true + } + + switch (expectedType) { + case ParamType.NUMBER: + return arg.type === "number" + case ParamType.STRING: + return arg.type === "string" + case ParamType.BOOLEAN: + return arg.type === "boolean" + case ParamType.DATE: + // TODO: 假设有日期类型的处理 + return false + case ParamType.ANY: + return true + default: + return false + } + } +} + +export const globalFunctionRegistry = new FunctionRegistry() + +// 注册函数,支持多种参数模式 +globalFunctionRegistry.register("ADD", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("SUBTRACT", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("MULTIPLY", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("DIVIDE", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("SUM", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) +globalFunctionRegistry.register("CONCAT", [[ParamType.STRING, ParamType.VARIADIC]], ParamType.STRING) diff --git a/packages/formula/src/grammar/FormulaLexer.g4 b/packages/formula/src/grammar/FormulaLexer.g4 new file mode 100644 index 000000000..e2468ce5b --- /dev/null +++ b/packages/formula/src/grammar/FormulaLexer.g4 @@ -0,0 +1,95 @@ +lexer grammar FormulaLexer; + +// 运算符 +ADD: '+'; +SUBTRACT: '-'; +MULTIPLY: '*'; +DIVIDE: '/'; +MODULO: '%'; +POWER: '^'; + +// 比较运算符 +EQUAL: '='; +NOT_EQUAL: '!='; +LESS: '<'; +LESS_EQUAL: '<='; +GREATER: '>'; +GREATER_EQUAL: '>='; + +// 逻辑运算符 +AND: A N D; +OR: O R; +NOT: N O T; + +// 括号 +LPAREN: '('; +RPAREN: ')'; +LBRACE: '{'; +RBRACE: '}'; +LBRACKET: '['; +RBRACKET: ']'; + +// 分隔符 +COMMA: ','; +SEMICOLON: ';'; +COLON: ':'; +DOT: '.'; + +// 函数名和标识符 +IDENTIFIER: LETTER (LETTER | DIGIT)*; + +// 数字 +NUMBER: DIGIT+ ('.' DIGIT+)?; + +// 字符串 +STRING: '\'' ( ~'\'' | '\'\'')* '\''; + +// 布尔值 +TRUE: T R U E; +FALSE: F A L S E; + +// 空值 +NULL: N U L L; + +// 日期时间 +DATE: D A T E; +TIME: T I M E; +DATETIME: D A T E T I M E; + +// 空白字符 +WS: [ \t\r\n]+ -> skip; + +// 注释 +COMMENT: '//' ~[\r\n]* -> skip; +MULTILINE_COMMENT: '/*' .*? '*/' -> skip; + +// Fragments +fragment DIGIT: [0-9]; +fragment LETTER: [a-zA-Z]; + +fragment A: ('A' | 'a'); +fragment B: ('B' | 'b'); +fragment C: ('C' | 'c'); +fragment D: ('D' | 'd'); +fragment E: ('E' | 'e'); +fragment F: ('F' | 'f'); +fragment G: ('G' | 'g'); +fragment H: ('H' | 'h'); +fragment I: ('I' | 'i'); +fragment J: ('J' | 'j'); +fragment K: ('K' | 'k'); +fragment L: ('L' | 'l'); +fragment M: ('M' | 'm'); +fragment N: ('N' | 'n'); +fragment O: ('O' | 'o'); +fragment P: ('P' | 'p'); +fragment Q: ('Q' | 'q'); +fragment R: ('R' | 'r'); +fragment S: ('S' | 's'); +fragment T: ('T' | 't'); +fragment U: ('U' | 'u'); +fragment V: ('V' | 'v'); +fragment W: ('W' | 'w'); +fragment X: ('X' | 'x'); +fragment Y: ('Y' | 'y'); +fragment Z: ('Z' | 'z'); \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaLexer.interp b/packages/formula/src/grammar/FormulaLexer.interp new file mode 100644 index 000000000..9e853d136 --- /dev/null +++ b/packages/formula/src/grammar/FormulaLexer.interp @@ -0,0 +1,156 @@ +token literal names: +null +'+' +'-' +'*' +'/' +'%' +'^' +'=' +'!=' +'<' +'<=' +'>' +'>=' +null +null +null +'(' +')' +'{' +'}' +'[' +']' +',' +';' +':' +'.' +null +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +ADD +SUBTRACT +MULTIPLY +DIVIDE +MODULO +POWER +EQUAL +NOT_EQUAL +LESS +LESS_EQUAL +GREATER +GREATER_EQUAL +AND +OR +NOT +LPAREN +RPAREN +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +SEMICOLON +COLON +DOT +IDENTIFIER +NUMBER +STRING +TRUE +FALSE +NULL +DATE +TIME +DATETIME +WS +COMMENT +MULTILINE_COMMENT + +rule names: +ADD +SUBTRACT +MULTIPLY +DIVIDE +MODULO +POWER +EQUAL +NOT_EQUAL +LESS +LESS_EQUAL +GREATER +GREATER_EQUAL +AND +OR +NOT +LPAREN +RPAREN +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +SEMICOLON +COLON +DOT +IDENTIFIER +NUMBER +STRING +TRUE +FALSE +NULL +DATE +TIME +DATETIME +WS +COMMENT +MULTILINE_COMMENT +DIGIT +LETTER +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 39, 346, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 7, 27, 195, 10, 27, 12, 27, 14, 27, 198, 11, 27, 3, 28, 6, 28, 201, 10, 28, 13, 28, 14, 28, 202, 3, 28, 3, 28, 6, 28, 207, 10, 28, 13, 28, 14, 28, 208, 5, 28, 211, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 7, 29, 217, 10, 29, 12, 29, 14, 29, 220, 11, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 6, 36, 260, 10, 36, 13, 36, 14, 36, 261, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 270, 10, 37, 12, 37, 14, 37, 273, 11, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 281, 10, 38, 12, 38, 14, 38, 284, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 282, 2, 2, 67, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 29, 2, 16, 31, 2, 17, 33, 2, 18, 35, 2, 19, 37, 2, 20, 39, 2, 21, 41, 2, 22, 43, 2, 23, 45, 2, 24, 47, 2, 25, 49, 2, 26, 51, 2, 27, 53, 2, 28, 55, 2, 29, 57, 2, 30, 59, 2, 31, 61, 2, 32, 63, 2, 33, 65, 2, 34, 67, 2, 35, 69, 2, 36, 71, 2, 37, 73, 2, 38, 75, 2, 39, 77, 2, 2, 79, 2, 2, 81, 2, 2, 83, 2, 2, 85, 2, 2, 87, 2, 2, 89, 2, 2, 91, 2, 2, 93, 2, 2, 95, 2, 2, 97, 2, 2, 99, 2, 2, 101, 2, 2, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 2, 113, 2, 2, 115, 2, 2, 117, 2, 2, 119, 2, 2, 121, 2, 2, 123, 2, 2, 125, 2, 2, 127, 2, 2, 129, 2, 2, 131, 2, 2, 3, 2, 33, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 327, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 3, 133, 3, 2, 2, 2, 5, 135, 3, 2, 2, 2, 7, 137, 3, 2, 2, 2, 9, 139, 3, 2, 2, 2, 11, 141, 3, 2, 2, 2, 13, 143, 3, 2, 2, 2, 15, 145, 3, 2, 2, 2, 17, 147, 3, 2, 2, 2, 19, 150, 3, 2, 2, 2, 21, 152, 3, 2, 2, 2, 23, 155, 3, 2, 2, 2, 25, 157, 3, 2, 2, 2, 27, 160, 3, 2, 2, 2, 29, 164, 3, 2, 2, 2, 31, 167, 3, 2, 2, 2, 33, 171, 3, 2, 2, 2, 35, 173, 3, 2, 2, 2, 37, 175, 3, 2, 2, 2, 39, 177, 3, 2, 2, 2, 41, 179, 3, 2, 2, 2, 43, 181, 3, 2, 2, 2, 45, 183, 3, 2, 2, 2, 47, 185, 3, 2, 2, 2, 49, 187, 3, 2, 2, 2, 51, 189, 3, 2, 2, 2, 53, 191, 3, 2, 2, 2, 55, 200, 3, 2, 2, 2, 57, 212, 3, 2, 2, 2, 59, 223, 3, 2, 2, 2, 61, 228, 3, 2, 2, 2, 63, 234, 3, 2, 2, 2, 65, 239, 3, 2, 2, 2, 67, 244, 3, 2, 2, 2, 69, 249, 3, 2, 2, 2, 71, 259, 3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 276, 3, 2, 2, 2, 77, 290, 3, 2, 2, 2, 79, 292, 3, 2, 2, 2, 81, 294, 3, 2, 2, 2, 83, 296, 3, 2, 2, 2, 85, 298, 3, 2, 2, 2, 87, 300, 3, 2, 2, 2, 89, 302, 3, 2, 2, 2, 91, 304, 3, 2, 2, 2, 93, 306, 3, 2, 2, 2, 95, 308, 3, 2, 2, 2, 97, 310, 3, 2, 2, 2, 99, 312, 3, 2, 2, 2, 101, 314, 3, 2, 2, 2, 103, 316, 3, 2, 2, 2, 105, 318, 3, 2, 2, 2, 107, 320, 3, 2, 2, 2, 109, 322, 3, 2, 2, 2, 111, 324, 3, 2, 2, 2, 113, 326, 3, 2, 2, 2, 115, 328, 3, 2, 2, 2, 117, 330, 3, 2, 2, 2, 119, 332, 3, 2, 2, 2, 121, 334, 3, 2, 2, 2, 123, 336, 3, 2, 2, 2, 125, 338, 3, 2, 2, 2, 127, 340, 3, 2, 2, 2, 129, 342, 3, 2, 2, 2, 131, 344, 3, 2, 2, 2, 133, 134, 7, 45, 2, 2, 134, 4, 3, 2, 2, 2, 135, 136, 7, 47, 2, 2, 136, 6, 3, 2, 2, 2, 137, 138, 7, 44, 2, 2, 138, 8, 3, 2, 2, 2, 139, 140, 7, 49, 2, 2, 140, 10, 3, 2, 2, 2, 141, 142, 7, 39, 2, 2, 142, 12, 3, 2, 2, 2, 143, 144, 7, 96, 2, 2, 144, 14, 3, 2, 2, 2, 145, 146, 7, 63, 2, 2, 146, 16, 3, 2, 2, 2, 147, 148, 7, 35, 2, 2, 148, 149, 7, 63, 2, 2, 149, 18, 3, 2, 2, 2, 150, 151, 7, 62, 2, 2, 151, 20, 3, 2, 2, 2, 152, 153, 7, 62, 2, 2, 153, 154, 7, 63, 2, 2, 154, 22, 3, 2, 2, 2, 155, 156, 7, 64, 2, 2, 156, 24, 3, 2, 2, 2, 157, 158, 7, 64, 2, 2, 158, 159, 7, 63, 2, 2, 159, 26, 3, 2, 2, 2, 160, 161, 5, 81, 41, 2, 161, 162, 5, 107, 54, 2, 162, 163, 5, 87, 44, 2, 163, 28, 3, 2, 2, 2, 164, 165, 5, 109, 55, 2, 165, 166, 5, 115, 58, 2, 166, 30, 3, 2, 2, 2, 167, 168, 5, 107, 54, 2, 168, 169, 5, 109, 55, 2, 169, 170, 5, 119, 60, 2, 170, 32, 3, 2, 2, 2, 171, 172, 7, 42, 2, 2, 172, 34, 3, 2, 2, 2, 173, 174, 7, 43, 2, 2, 174, 36, 3, 2, 2, 2, 175, 176, 7, 125, 2, 2, 176, 38, 3, 2, 2, 2, 177, 178, 7, 127, 2, 2, 178, 40, 3, 2, 2, 2, 179, 180, 7, 93, 2, 2, 180, 42, 3, 2, 2, 2, 181, 182, 7, 95, 2, 2, 182, 44, 3, 2, 2, 2, 183, 184, 7, 46, 2, 2, 184, 46, 3, 2, 2, 2, 185, 186, 7, 61, 2, 2, 186, 48, 3, 2, 2, 2, 187, 188, 7, 60, 2, 2, 188, 50, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 52, 3, 2, 2, 2, 191, 196, 5, 79, 40, 2, 192, 195, 5, 79, 40, 2, 193, 195, 5, 77, 39, 2, 194, 192, 3, 2, 2, 2, 194, 193, 3, 2, 2, 2, 195, 198, 3, 2, 2, 2, 196, 194, 3, 2, 2, 2, 196, 197, 3, 2, 2, 2, 197, 54, 3, 2, 2, 2, 198, 196, 3, 2, 2, 2, 199, 201, 5, 77, 39, 2, 200, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 202, 203, 3, 2, 2, 2, 203, 210, 3, 2, 2, 2, 204, 206, 7, 48, 2, 2, 205, 207, 5, 77, 39, 2, 206, 205, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 211, 3, 2, 2, 2, 210, 204, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 56, 3, 2, 2, 2, 212, 218, 7, 41, 2, 2, 213, 217, 10, 2, 2, 2, 214, 215, 7, 41, 2, 2, 215, 217, 7, 41, 2, 2, 216, 213, 3, 2, 2, 2, 216, 214, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 221, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 222, 7, 41, 2, 2, 222, 58, 3, 2, 2, 2, 223, 224, 5, 119, 60, 2, 224, 225, 5, 115, 58, 2, 225, 226, 5, 121, 61, 2, 226, 227, 5, 89, 45, 2, 227, 60, 3, 2, 2, 2, 228, 229, 5, 91, 46, 2, 229, 230, 5, 81, 41, 2, 230, 231, 5, 103, 52, 2, 231, 232, 5, 117, 59, 2, 232, 233, 5, 89, 45, 2, 233, 62, 3, 2, 2, 2, 234, 235, 5, 107, 54, 2, 235, 236, 5, 121, 61, 2, 236, 237, 5, 103, 52, 2, 237, 238, 5, 103, 52, 2, 238, 64, 3, 2, 2, 2, 239, 240, 5, 87, 44, 2, 240, 241, 5, 81, 41, 2, 241, 242, 5, 119, 60, 2, 242, 243, 5, 89, 45, 2, 243, 66, 3, 2, 2, 2, 244, 245, 5, 119, 60, 2, 245, 246, 5, 97, 49, 2, 246, 247, 5, 105, 53, 2, 247, 248, 5, 89, 45, 2, 248, 68, 3, 2, 2, 2, 249, 250, 5, 87, 44, 2, 250, 251, 5, 81, 41, 2, 251, 252, 5, 119, 60, 2, 252, 253, 5, 89, 45, 2, 253, 254, 5, 119, 60, 2, 254, 255, 5, 97, 49, 2, 255, 256, 5, 105, 53, 2, 256, 257, 5, 89, 45, 2, 257, 70, 3, 2, 2, 2, 258, 260, 9, 3, 2, 2, 259, 258, 3, 2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 259, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 264, 8, 36, 2, 2, 264, 72, 3, 2, 2, 2, 265, 266, 7, 49, 2, 2, 266, 267, 7, 49, 2, 2, 267, 271, 3, 2, 2, 2, 268, 270, 10, 4, 2, 2, 269, 268, 3, 2, 2, 2, 270, 273, 3, 2, 2, 2, 271, 269, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 274, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 274, 275, 8, 37, 2, 2, 275, 74, 3, 2, 2, 2, 276, 277, 7, 49, 2, 2, 277, 278, 7, 44, 2, 2, 278, 282, 3, 2, 2, 2, 279, 281, 11, 2, 2, 2, 280, 279, 3, 2, 2, 2, 281, 284, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 282, 280, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 282, 3, 2, 2, 2, 285, 286, 7, 44, 2, 2, 286, 287, 7, 49, 2, 2, 287, 288, 3, 2, 2, 2, 288, 289, 8, 38, 2, 2, 289, 76, 3, 2, 2, 2, 290, 291, 9, 5, 2, 2, 291, 78, 3, 2, 2, 2, 292, 293, 9, 6, 2, 2, 293, 80, 3, 2, 2, 2, 294, 295, 9, 7, 2, 2, 295, 82, 3, 2, 2, 2, 296, 297, 9, 8, 2, 2, 297, 84, 3, 2, 2, 2, 298, 299, 9, 9, 2, 2, 299, 86, 3, 2, 2, 2, 300, 301, 9, 10, 2, 2, 301, 88, 3, 2, 2, 2, 302, 303, 9, 11, 2, 2, 303, 90, 3, 2, 2, 2, 304, 305, 9, 12, 2, 2, 305, 92, 3, 2, 2, 2, 306, 307, 9, 13, 2, 2, 307, 94, 3, 2, 2, 2, 308, 309, 9, 14, 2, 2, 309, 96, 3, 2, 2, 2, 310, 311, 9, 15, 2, 2, 311, 98, 3, 2, 2, 2, 312, 313, 9, 16, 2, 2, 313, 100, 3, 2, 2, 2, 314, 315, 9, 17, 2, 2, 315, 102, 3, 2, 2, 2, 316, 317, 9, 18, 2, 2, 317, 104, 3, 2, 2, 2, 318, 319, 9, 19, 2, 2, 319, 106, 3, 2, 2, 2, 320, 321, 9, 20, 2, 2, 321, 108, 3, 2, 2, 2, 322, 323, 9, 21, 2, 2, 323, 110, 3, 2, 2, 2, 324, 325, 9, 22, 2, 2, 325, 112, 3, 2, 2, 2, 326, 327, 9, 23, 2, 2, 327, 114, 3, 2, 2, 2, 328, 329, 9, 24, 2, 2, 329, 116, 3, 2, 2, 2, 330, 331, 9, 25, 2, 2, 331, 118, 3, 2, 2, 2, 332, 333, 9, 26, 2, 2, 333, 120, 3, 2, 2, 2, 334, 335, 9, 27, 2, 2, 335, 122, 3, 2, 2, 2, 336, 337, 9, 28, 2, 2, 337, 124, 3, 2, 2, 2, 338, 339, 9, 29, 2, 2, 339, 126, 3, 2, 2, 2, 340, 341, 9, 30, 2, 2, 341, 128, 3, 2, 2, 2, 342, 343, 9, 31, 2, 2, 343, 130, 3, 2, 2, 2, 344, 345, 9, 32, 2, 2, 345, 132, 3, 2, 2, 2, 13, 2, 194, 196, 202, 208, 210, 216, 218, 261, 271, 282, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaLexer.tokens b/packages/formula/src/grammar/FormulaLexer.tokens new file mode 100644 index 000000000..58c5ff007 --- /dev/null +++ b/packages/formula/src/grammar/FormulaLexer.tokens @@ -0,0 +1,59 @@ +ADD=1 +SUBTRACT=2 +MULTIPLY=3 +DIVIDE=4 +MODULO=5 +POWER=6 +EQUAL=7 +NOT_EQUAL=8 +LESS=9 +LESS_EQUAL=10 +GREATER=11 +GREATER_EQUAL=12 +AND=13 +OR=14 +NOT=15 +LPAREN=16 +RPAREN=17 +LBRACE=18 +RBRACE=19 +LBRACKET=20 +RBRACKET=21 +COMMA=22 +SEMICOLON=23 +COLON=24 +DOT=25 +IDENTIFIER=26 +NUMBER=27 +STRING=28 +TRUE=29 +FALSE=30 +NULL=31 +DATE=32 +TIME=33 +DATETIME=34 +WS=35 +COMMENT=36 +MULTILINE_COMMENT=37 +'+'=1 +'-'=2 +'*'=3 +'/'=4 +'%'=5 +'^'=6 +'='=7 +'!='=8 +'<'=9 +'<='=10 +'>'=11 +'>='=12 +'('=16 +')'=17 +'{'=18 +'}'=19 +'['=20 +']'=21 +','=22 +';'=23 +':'=24 +'.'=25 diff --git a/packages/formula/src/grammar/FormulaLexer.ts b/packages/formula/src/grammar/FormulaLexer.ts new file mode 100644 index 000000000..6b4d918b7 --- /dev/null +++ b/packages/formula/src/grammar/FormulaLexer.ts @@ -0,0 +1,282 @@ +// Generated from src/grammar/FormulaLexer.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { NotNull } from "antlr4ts/Decorators"; +import { Override } from "antlr4ts/Decorators"; +import { RuleContext } from "antlr4ts/RuleContext"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + + +export class FormulaLexer extends Lexer { + public static readonly ADD = 1; + public static readonly SUBTRACT = 2; + public static readonly MULTIPLY = 3; + public static readonly DIVIDE = 4; + public static readonly MODULO = 5; + public static readonly POWER = 6; + public static readonly EQUAL = 7; + public static readonly NOT_EQUAL = 8; + public static readonly LESS = 9; + public static readonly LESS_EQUAL = 10; + public static readonly GREATER = 11; + public static readonly GREATER_EQUAL = 12; + public static readonly AND = 13; + public static readonly OR = 14; + public static readonly NOT = 15; + public static readonly LPAREN = 16; + public static readonly RPAREN = 17; + public static readonly LBRACE = 18; + public static readonly RBRACE = 19; + public static readonly LBRACKET = 20; + public static readonly RBRACKET = 21; + public static readonly COMMA = 22; + public static readonly SEMICOLON = 23; + public static readonly COLON = 24; + public static readonly DOT = 25; + public static readonly IDENTIFIER = 26; + public static readonly NUMBER = 27; + public static readonly STRING = 28; + public static readonly TRUE = 29; + public static readonly FALSE = 30; + public static readonly NULL = 31; + public static readonly DATE = 32; + public static readonly TIME = 33; + public static readonly DATETIME = 34; + public static readonly WS = 35; + public static readonly COMMENT = 36; + public static readonly MULTILINE_COMMENT = 37; + + // tslint:disable:no-trailing-whitespace + public static readonly channelNames: string[] = [ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + ]; + + // tslint:disable:no-trailing-whitespace + public static readonly modeNames: string[] = [ + "DEFAULT_MODE", + ]; + + public static readonly ruleNames: string[] = [ + "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", + "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", + "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET", "COMMA", + "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", "STRING", "TRUE", + "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", "MULTILINE_COMMENT", + "DIGIT", "LETTER", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", + "Z", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", + "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", + "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", + "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", + "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", + "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", + "MULTILINE_COMMENT", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaLexer._LITERAL_NAMES, FormulaLexer._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return FormulaLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(FormulaLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { return "FormulaLexer.g4"; } + + // @Override + public get ruleNames(): string[] { return FormulaLexer.ruleNames; } + + // @Override + public get serializedATN(): string { return FormulaLexer._serializedATN; } + + // @Override + public get channelNames(): string[] { return FormulaLexer.channelNames; } + + // @Override + public get modeNames(): string[] { return FormulaLexer.modeNames; } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02\'\u015A\b\x01" + + "\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06" + + "\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r" + + "\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t" + + "\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t" + + "\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t" + + "\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t" + + "\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t)\x04*\t*\x04" + + "+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x042\t2\x043\t3\x04" + + "4\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04<\t<\x04" + + "=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x03\x02\x03\x02\x03\x03\x03" + + "\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03" + + "\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03" + + "\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F" + + "\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x12\x03\x12\x03\x13" + + "\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17" + + "\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + + "\x07\x1B\xC3\n\x1B\f\x1B\x0E\x1B\xC6\v\x1B\x03\x1C\x06\x1C\xC9\n\x1C\r" + + "\x1C\x0E\x1C\xCA\x03\x1C\x03\x1C\x06\x1C\xCF\n\x1C\r\x1C\x0E\x1C\xD0\x05" + + "\x1C\xD3\n\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x07\x1D\xD9\n\x1D\f\x1D" + + "\x0E\x1D\xDC\v\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x03" + + "\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03" + + " \x03 \x03!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03\"\x03#\x03" + + "#\x03#\x03#\x03#\x03#\x03#\x03#\x03#\x03$\x06$\u0104\n$\r$\x0E$\u0105" + + "\x03$\x03$\x03%\x03%\x03%\x03%\x07%\u010E\n%\f%\x0E%\u0111\v%\x03%\x03" + + "%\x03&\x03&\x03&\x03&\x07&\u0119\n&\f&\x0E&\u011C\v&\x03&\x03&\x03&\x03" + + "&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03*\x03+\x03+\x03,\x03,\x03" + + "-\x03-\x03.\x03.\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x03" + + "4\x034\x035\x035\x036\x036\x037\x037\x038\x038\x039\x039\x03:\x03:\x03" + + ";\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03@\x03@\x03A\x03A\x03" + + "B\x03B\x03\u011A\x02\x02C\x03\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06" + + "\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17\x02\r\x19" + + "\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02\x11!\x02\x12#\x02\x13%\x02\x14" + + "\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/\x02\x191\x02\x1A3\x02\x1B5\x02" + + "\x1C7\x02\x1D9\x02\x1E;\x02\x1F=\x02 ?\x02!A\x02\"C\x02#E\x02$G\x02%I" + + "\x02&K\x02\'M\x02\x02O\x02\x02Q\x02\x02S\x02\x02U\x02\x02W\x02\x02Y\x02" + + "\x02[\x02\x02]\x02\x02_\x02\x02a\x02\x02c\x02\x02e\x02\x02g\x02\x02i\x02" + + "\x02k\x02\x02m\x02\x02o\x02\x02q\x02\x02s\x02\x02u\x02\x02w\x02\x02y\x02" + + "\x02{\x02\x02}\x02\x02\x7F\x02\x02\x81\x02\x02\x83\x02\x02\x03\x02!\x03" + + "\x02))\x05\x02\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x022;\x04\x02C" + + "\\c|\x04\x02CCcc\x04\x02DDdd\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02" + + "HHhh\x04\x02IIii\x04\x02JJjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02" + + "NNnn\x04\x02OOoo\x04\x02PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02" + + "TTtt\x04\x02UUuu\x04\x02VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02" + + "ZZzz\x04\x02[[{{\x04\x02\\\\||\x02\u0147\x02\x03\x03\x02\x02\x02\x02\x05" + + "\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03" + + "\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03" + + "\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03" + + "\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03" + + "\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02" + + "\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02" + + "\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03" + + "\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02" + + "\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02" + + "?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02\x02\x02E\x03\x02" + + "\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02K\x03\x02\x02\x02" + + "\x03\x85\x03\x02\x02\x02\x05\x87\x03\x02\x02\x02\x07\x89\x03\x02\x02\x02" + + "\t\x8B\x03\x02\x02\x02\v\x8D\x03\x02\x02\x02\r\x8F\x03\x02\x02\x02\x0F" + + "\x91\x03\x02\x02\x02\x11\x93\x03\x02\x02\x02\x13\x96\x03\x02\x02\x02\x15" + + "\x98\x03\x02\x02\x02\x17\x9B\x03\x02\x02\x02\x19\x9D\x03\x02\x02\x02\x1B" + + "\xA0\x03\x02\x02\x02\x1D\xA4\x03\x02\x02\x02\x1F\xA7\x03\x02\x02\x02!" + + "\xAB\x03\x02\x02\x02#\xAD\x03\x02\x02\x02%\xAF\x03\x02\x02\x02\'\xB1\x03" + + "\x02\x02\x02)\xB3\x03\x02\x02\x02+\xB5\x03\x02\x02\x02-\xB7\x03\x02\x02" + + "\x02/\xB9\x03\x02\x02\x021\xBB\x03\x02\x02\x023\xBD\x03\x02\x02\x025\xBF" + + "\x03\x02\x02\x027\xC8\x03\x02\x02\x029\xD4\x03\x02\x02\x02;\xDF\x03\x02" + + "\x02\x02=\xE4\x03\x02\x02\x02?\xEA\x03\x02\x02\x02A\xEF\x03\x02\x02\x02" + + "C\xF4\x03\x02\x02\x02E\xF9\x03\x02\x02\x02G\u0103\x03\x02\x02\x02I\u0109" + + "\x03\x02\x02\x02K\u0114\x03\x02\x02\x02M\u0122\x03\x02\x02\x02O\u0124" + + "\x03\x02\x02\x02Q\u0126\x03\x02\x02\x02S\u0128\x03\x02\x02\x02U\u012A" + + "\x03\x02\x02\x02W\u012C\x03\x02\x02\x02Y\u012E\x03\x02\x02\x02[\u0130" + + "\x03\x02\x02\x02]\u0132\x03\x02\x02\x02_\u0134\x03\x02\x02\x02a\u0136" + + "\x03\x02\x02\x02c\u0138\x03\x02\x02\x02e\u013A\x03\x02\x02\x02g\u013C" + + "\x03\x02\x02\x02i\u013E\x03\x02\x02\x02k\u0140\x03\x02\x02\x02m\u0142" + + "\x03\x02\x02\x02o\u0144\x03\x02\x02\x02q\u0146\x03\x02\x02\x02s\u0148" + + "\x03\x02\x02\x02u\u014A\x03\x02\x02\x02w\u014C\x03\x02\x02\x02y\u014E" + + "\x03\x02\x02\x02{\u0150\x03\x02\x02\x02}\u0152\x03\x02\x02\x02\x7F\u0154" + + "\x03\x02\x02\x02\x81\u0156\x03\x02\x02\x02\x83\u0158\x03\x02\x02\x02\x85" + + "\x86\x07-\x02\x02\x86\x04\x03\x02\x02\x02\x87\x88\x07/\x02\x02\x88\x06" + + "\x03\x02\x02\x02\x89\x8A\x07,\x02\x02\x8A\b\x03\x02\x02\x02\x8B\x8C\x07" + + "1\x02\x02\x8C\n\x03\x02\x02\x02\x8D\x8E\x07\'\x02\x02\x8E\f\x03\x02\x02" + + "\x02\x8F\x90\x07`\x02\x02\x90\x0E\x03\x02\x02\x02\x91\x92\x07?\x02\x02" + + "\x92\x10\x03\x02\x02\x02\x93\x94\x07#\x02\x02\x94\x95\x07?\x02\x02\x95" + + "\x12\x03\x02\x02\x02\x96\x97\x07>\x02\x02\x97\x14\x03\x02\x02\x02\x98" + + "\x99\x07>\x02\x02\x99\x9A\x07?\x02\x02\x9A\x16\x03\x02\x02\x02\x9B\x9C" + + "\x07@\x02\x02\x9C\x18\x03\x02\x02\x02\x9D\x9E\x07@\x02\x02\x9E\x9F\x07" + + "?\x02\x02\x9F\x1A\x03\x02\x02\x02\xA0\xA1\x05Q)\x02\xA1\xA2\x05k6\x02" + + "\xA2\xA3\x05W,\x02\xA3\x1C\x03\x02\x02\x02\xA4\xA5\x05m7\x02\xA5\xA6\x05" + + "s:\x02\xA6\x1E\x03\x02\x02\x02\xA7\xA8\x05k6\x02\xA8\xA9\x05m7\x02\xA9" + + "\xAA\x05w<\x02\xAA \x03\x02\x02\x02\xAB\xAC\x07*\x02\x02\xAC\"\x03\x02" + + "\x02\x02\xAD\xAE\x07+\x02\x02\xAE$\x03\x02\x02\x02\xAF\xB0\x07}\x02\x02" + + "\xB0&\x03\x02\x02\x02\xB1\xB2\x07\x7F\x02\x02\xB2(\x03\x02\x02\x02\xB3" + + "\xB4\x07]\x02\x02\xB4*\x03\x02\x02\x02\xB5\xB6\x07_\x02\x02\xB6,\x03\x02" + + "\x02\x02\xB7\xB8\x07.\x02\x02\xB8.\x03\x02\x02\x02\xB9\xBA\x07=\x02\x02" + + "\xBA0\x03\x02\x02\x02\xBB\xBC\x07<\x02\x02\xBC2\x03\x02\x02\x02\xBD\xBE" + + "\x070\x02\x02\xBE4\x03\x02\x02\x02\xBF\xC4\x05O(\x02\xC0\xC3\x05O(\x02" + + "\xC1\xC3\x05M\'\x02\xC2\xC0\x03\x02\x02\x02\xC2\xC1\x03\x02\x02\x02\xC3" + + "\xC6\x03\x02\x02\x02\xC4\xC2\x03\x02\x02\x02\xC4\xC5\x03\x02\x02\x02\xC5" + + "6\x03\x02\x02\x02\xC6\xC4\x03\x02\x02\x02\xC7\xC9\x05M\'\x02\xC8\xC7\x03" + + "\x02\x02\x02\xC9\xCA\x03\x02\x02\x02\xCA\xC8\x03\x02\x02\x02\xCA\xCB\x03" + + "\x02\x02\x02\xCB\xD2\x03\x02\x02\x02\xCC\xCE\x070\x02\x02\xCD\xCF\x05" + + "M\'\x02\xCE\xCD\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xCE\x03\x02" + + "\x02\x02\xD0\xD1\x03\x02\x02\x02\xD1\xD3\x03\x02\x02\x02\xD2\xCC\x03\x02" + + "\x02\x02\xD2\xD3\x03\x02\x02\x02\xD38\x03\x02\x02\x02\xD4\xDA\x07)\x02" + + "\x02\xD5\xD9\n\x02\x02\x02\xD6\xD7\x07)\x02\x02\xD7\xD9\x07)\x02\x02\xD8" + + "\xD5\x03\x02\x02\x02\xD8\xD6\x03\x02\x02\x02\xD9\xDC\x03\x02\x02\x02\xDA" + + "\xD8\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDB\xDD\x03\x02\x02\x02\xDC" + + "\xDA\x03\x02\x02\x02\xDD\xDE\x07)\x02\x02\xDE:\x03\x02\x02\x02\xDF\xE0" + + "\x05w<\x02\xE0\xE1\x05s:\x02\xE1\xE2\x05y=\x02\xE2\xE3\x05Y-\x02\xE3<" + + "\x03\x02\x02\x02\xE4\xE5\x05[.\x02\xE5\xE6\x05Q)\x02\xE6\xE7\x05g4\x02" + + "\xE7\xE8\x05u;\x02\xE8\xE9\x05Y-\x02\xE9>\x03\x02\x02\x02\xEA\xEB\x05" + + "k6\x02\xEB\xEC\x05y=\x02\xEC\xED\x05g4\x02\xED\xEE\x05g4\x02\xEE@\x03" + + "\x02\x02\x02\xEF\xF0\x05W,\x02\xF0\xF1\x05Q)\x02\xF1\xF2\x05w<\x02\xF2" + + "\xF3\x05Y-\x02\xF3B\x03\x02\x02\x02\xF4\xF5\x05w<\x02\xF5\xF6\x05a1\x02" + + "\xF6\xF7\x05i5\x02\xF7\xF8\x05Y-\x02\xF8D\x03\x02\x02\x02\xF9\xFA\x05" + + "W,\x02\xFA\xFB\x05Q)\x02\xFB\xFC\x05w<\x02\xFC\xFD\x05Y-\x02\xFD\xFE\x05" + + "w<\x02\xFE\xFF\x05a1\x02\xFF\u0100\x05i5\x02\u0100\u0101\x05Y-\x02\u0101" + + "F\x03\x02\x02\x02\u0102\u0104\t\x03\x02\x02\u0103\u0102\x03\x02\x02\x02" + + "\u0104\u0105\x03\x02\x02\x02\u0105\u0103\x03\x02\x02\x02\u0105\u0106\x03" + + "\x02\x02\x02\u0106\u0107\x03\x02\x02\x02\u0107\u0108\b$\x02\x02\u0108" + + "H\x03\x02\x02\x02\u0109\u010A\x071\x02\x02\u010A\u010B\x071\x02\x02\u010B" + + "\u010F\x03\x02\x02\x02\u010C\u010E\n\x04\x02\x02\u010D\u010C\x03\x02\x02" + + "\x02\u010E\u0111\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02\u010F\u0110" + + "\x03\x02\x02\x02\u0110\u0112\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02" + + "\u0112\u0113\b%\x02\x02\u0113J\x03\x02\x02\x02\u0114\u0115\x071\x02\x02" + + "\u0115\u0116\x07,\x02\x02\u0116\u011A\x03\x02\x02\x02\u0117\u0119\v\x02" + + "\x02\x02\u0118\u0117\x03\x02\x02\x02\u0119\u011C\x03\x02\x02\x02\u011A" + + "\u011B\x03\x02\x02\x02\u011A\u0118\x03\x02\x02\x02\u011B\u011D\x03\x02" + + "\x02\x02\u011C\u011A\x03\x02\x02\x02\u011D\u011E\x07,\x02\x02\u011E\u011F" + + "\x071\x02\x02\u011F\u0120\x03\x02\x02\x02\u0120\u0121\b&\x02\x02\u0121" + + "L\x03\x02\x02\x02\u0122\u0123\t\x05\x02\x02\u0123N\x03\x02\x02\x02\u0124" + + "\u0125\t\x06\x02\x02\u0125P\x03\x02\x02\x02\u0126\u0127\t\x07\x02\x02" + + "\u0127R\x03\x02\x02\x02\u0128\u0129\t\b\x02\x02\u0129T\x03\x02\x02\x02" + + "\u012A\u012B\t\t\x02\x02\u012BV\x03\x02\x02\x02\u012C\u012D\t\n\x02\x02" + + "\u012DX\x03\x02\x02\x02\u012E\u012F\t\v\x02\x02\u012FZ\x03\x02\x02\x02" + + "\u0130\u0131\t\f\x02\x02\u0131\\\x03\x02\x02\x02\u0132\u0133\t\r\x02\x02" + + "\u0133^\x03\x02\x02\x02\u0134\u0135\t\x0E\x02\x02\u0135`\x03\x02\x02\x02" + + "\u0136\u0137\t\x0F\x02\x02\u0137b\x03\x02\x02\x02\u0138\u0139\t\x10\x02" + + "\x02\u0139d\x03\x02\x02\x02\u013A\u013B\t\x11\x02\x02\u013Bf\x03\x02\x02" + + "\x02\u013C\u013D\t\x12\x02\x02\u013Dh\x03\x02\x02\x02\u013E\u013F\t\x13" + + "\x02\x02\u013Fj\x03\x02\x02\x02\u0140\u0141\t\x14\x02\x02\u0141l\x03\x02" + + "\x02\x02\u0142\u0143\t\x15\x02\x02\u0143n\x03\x02\x02\x02\u0144\u0145" + + "\t\x16\x02\x02\u0145p\x03\x02\x02\x02\u0146\u0147\t\x17\x02\x02\u0147" + + "r\x03\x02\x02\x02\u0148\u0149\t\x18\x02\x02\u0149t\x03\x02\x02\x02\u014A" + + "\u014B\t\x19\x02\x02\u014Bv\x03\x02\x02\x02\u014C\u014D\t\x1A\x02\x02" + + "\u014Dx\x03\x02\x02\x02\u014E\u014F\t\x1B\x02\x02\u014Fz\x03\x02\x02\x02" + + "\u0150\u0151\t\x1C\x02\x02\u0151|\x03\x02\x02\x02\u0152\u0153\t\x1D\x02" + + "\x02\u0153~\x03\x02\x02\x02\u0154\u0155\t\x1E\x02\x02\u0155\x80\x03\x02" + + "\x02\x02\u0156\u0157\t\x1F\x02\x02\u0157\x82\x03\x02\x02\x02\u0158\u0159" + + "\t \x02\x02\u0159\x84\x03\x02\x02\x02\r\x02\xC2\xC4\xCA\xD0\xD2\xD8\xDA" + + "\u0105\u010F\u011A\x03\b\x02\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!FormulaLexer.__ATN) { + FormulaLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(FormulaLexer._serializedATN)); + } + + return FormulaLexer.__ATN; + } + +} + diff --git a/packages/formula/src/grammar/FormulaParser.g4 b/packages/formula/src/grammar/FormulaParser.g4 new file mode 100644 index 000000000..c36a998f3 --- /dev/null +++ b/packages/formula/src/grammar/FormulaParser.g4 @@ -0,0 +1,41 @@ +parser grammar FormulaParser; + +options { + tokenVocab = FormulaLexer; +} + +formula: expression EOF; + +expression: + expression op = (MULTIPLY | DIVIDE | MODULO) expression # MulDivModExpr + | expression op = (ADD | SUBTRACT) expression # AddSubExpr + | expression POWER expression # PowerExpr + | expression op = ( + EQUAL + | NOT_EQUAL + | LESS + | LESS_EQUAL + | GREATER + | GREATER_EQUAL + ) expression # ComparisonExpr + | expression AND expression # AndExpr + | expression OR expression # OrExpr + | NOT expression # NotExpr + | functionCall # FunctionExpr + | variable # VariableExpr + | NUMBER # NumberExpr + | STRING # StringExpr + | TRUE # TrueExpr + | FALSE # FalseExpr + | NULL # NullExpr + | DATE # DateExpr + | TIME # TimeExpr + | DATETIME # DateTimeExpr + | LPAREN expression RPAREN # ParenExpr; + +functionCall: IDENTIFIER LPAREN argumentList? RPAREN; + +argumentList: expression (COMMA expression)*; +// 这个表达式定义了一个参数列表,由一个或多个表达式组成,表达式之间用逗号分隔。例如:func(1, 2, 3) 中,1, 2, 3 就是参数列表。 + +variable: LBRACE LBRACE IDENTIFIER RBRACE RBRACE; \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaParser.interp b/packages/formula/src/grammar/FormulaParser.interp new file mode 100644 index 000000000..311641f1a --- /dev/null +++ b/packages/formula/src/grammar/FormulaParser.interp @@ -0,0 +1,90 @@ +token literal names: +null +'+' +'-' +'*' +'/' +'%' +'^' +'=' +'!=' +'<' +'<=' +'>' +'>=' +null +null +null +'(' +')' +'{' +'}' +'[' +']' +',' +';' +':' +'.' +null +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +ADD +SUBTRACT +MULTIPLY +DIVIDE +MODULO +POWER +EQUAL +NOT_EQUAL +LESS +LESS_EQUAL +GREATER +GREATER_EQUAL +AND +OR +NOT +LPAREN +RPAREN +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +SEMICOLON +COLON +DOT +IDENTIFIER +NUMBER +STRING +TRUE +FALSE +NULL +DATE +TIME +DATETIME +WS +COMMENT +MULTILINE_COMMENT + +rule names: +formula +expression +functionCall +argumentList +variable + + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 39, 79, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 33, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 53, 10, 3, 12, 3, 14, 3, 56, 11, 3, 3, 4, 3, 4, 3, 4, 5, 4, 61, 10, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 7, 5, 68, 10, 5, 12, 5, 14, 5, 71, 11, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 2, 2, 3, 4, 7, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 2, 5, 3, 2, 5, 7, 3, 2, 3, 4, 3, 2, 9, 14, 2, 92, 2, 12, 3, 2, 2, 2, 4, 32, 3, 2, 2, 2, 6, 57, 3, 2, 2, 2, 8, 64, 3, 2, 2, 2, 10, 72, 3, 2, 2, 2, 12, 13, 5, 4, 3, 2, 13, 14, 7, 2, 2, 3, 14, 3, 3, 2, 2, 2, 15, 16, 8, 3, 1, 2, 16, 17, 7, 17, 2, 2, 17, 33, 5, 4, 3, 14, 18, 33, 5, 6, 4, 2, 19, 33, 5, 10, 6, 2, 20, 33, 7, 29, 2, 2, 21, 33, 7, 30, 2, 2, 22, 33, 7, 31, 2, 2, 23, 33, 7, 32, 2, 2, 24, 33, 7, 33, 2, 2, 25, 33, 7, 34, 2, 2, 26, 33, 7, 35, 2, 2, 27, 33, 7, 36, 2, 2, 28, 29, 7, 18, 2, 2, 29, 30, 5, 4, 3, 2, 30, 31, 7, 19, 2, 2, 31, 33, 3, 2, 2, 2, 32, 15, 3, 2, 2, 2, 32, 18, 3, 2, 2, 2, 32, 19, 3, 2, 2, 2, 32, 20, 3, 2, 2, 2, 32, 21, 3, 2, 2, 2, 32, 22, 3, 2, 2, 2, 32, 23, 3, 2, 2, 2, 32, 24, 3, 2, 2, 2, 32, 25, 3, 2, 2, 2, 32, 26, 3, 2, 2, 2, 32, 27, 3, 2, 2, 2, 32, 28, 3, 2, 2, 2, 33, 54, 3, 2, 2, 2, 34, 35, 12, 20, 2, 2, 35, 36, 9, 2, 2, 2, 36, 53, 5, 4, 3, 21, 37, 38, 12, 19, 2, 2, 38, 39, 9, 3, 2, 2, 39, 53, 5, 4, 3, 20, 40, 41, 12, 18, 2, 2, 41, 42, 7, 8, 2, 2, 42, 53, 5, 4, 3, 19, 43, 44, 12, 17, 2, 2, 44, 45, 9, 4, 2, 2, 45, 53, 5, 4, 3, 18, 46, 47, 12, 16, 2, 2, 47, 48, 7, 15, 2, 2, 48, 53, 5, 4, 3, 17, 49, 50, 12, 15, 2, 2, 50, 51, 7, 16, 2, 2, 51, 53, 5, 4, 3, 16, 52, 34, 3, 2, 2, 2, 52, 37, 3, 2, 2, 2, 52, 40, 3, 2, 2, 2, 52, 43, 3, 2, 2, 2, 52, 46, 3, 2, 2, 2, 52, 49, 3, 2, 2, 2, 53, 56, 3, 2, 2, 2, 54, 52, 3, 2, 2, 2, 54, 55, 3, 2, 2, 2, 55, 5, 3, 2, 2, 2, 56, 54, 3, 2, 2, 2, 57, 58, 7, 28, 2, 2, 58, 60, 7, 18, 2, 2, 59, 61, 5, 8, 5, 2, 60, 59, 3, 2, 2, 2, 60, 61, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 63, 7, 19, 2, 2, 63, 7, 3, 2, 2, 2, 64, 69, 5, 4, 3, 2, 65, 66, 7, 24, 2, 2, 66, 68, 5, 4, 3, 2, 67, 65, 3, 2, 2, 2, 68, 71, 3, 2, 2, 2, 69, 67, 3, 2, 2, 2, 69, 70, 3, 2, 2, 2, 70, 9, 3, 2, 2, 2, 71, 69, 3, 2, 2, 2, 72, 73, 7, 20, 2, 2, 73, 74, 7, 20, 2, 2, 74, 75, 7, 28, 2, 2, 75, 76, 7, 21, 2, 2, 76, 77, 7, 21, 2, 2, 77, 11, 3, 2, 2, 2, 7, 32, 52, 54, 60, 69] \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaParser.tokens b/packages/formula/src/grammar/FormulaParser.tokens new file mode 100644 index 000000000..58c5ff007 --- /dev/null +++ b/packages/formula/src/grammar/FormulaParser.tokens @@ -0,0 +1,59 @@ +ADD=1 +SUBTRACT=2 +MULTIPLY=3 +DIVIDE=4 +MODULO=5 +POWER=6 +EQUAL=7 +NOT_EQUAL=8 +LESS=9 +LESS_EQUAL=10 +GREATER=11 +GREATER_EQUAL=12 +AND=13 +OR=14 +NOT=15 +LPAREN=16 +RPAREN=17 +LBRACE=18 +RBRACE=19 +LBRACKET=20 +RBRACKET=21 +COMMA=22 +SEMICOLON=23 +COLON=24 +DOT=25 +IDENTIFIER=26 +NUMBER=27 +STRING=28 +TRUE=29 +FALSE=30 +NULL=31 +DATE=32 +TIME=33 +DATETIME=34 +WS=35 +COMMENT=36 +MULTILINE_COMMENT=37 +'+'=1 +'-'=2 +'*'=3 +'/'=4 +'%'=5 +'^'=6 +'='=7 +'!='=8 +'<'=9 +'<='=10 +'>'=11 +'>='=12 +'('=16 +')'=17 +'{'=18 +'}'=19 +'['=20 +']'=21 +','=22 +';'=23 +':'=24 +'.'=25 diff --git a/packages/formula/src/grammar/FormulaParser.ts b/packages/formula/src/grammar/FormulaParser.ts new file mode 100644 index 000000000..5a3f8f813 --- /dev/null +++ b/packages/formula/src/grammar/FormulaParser.ts @@ -0,0 +1,1374 @@ +// Generated from src/grammar/FormulaParser.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { FailedPredicateException } from "antlr4ts/FailedPredicateException"; +import { NotNull } from "antlr4ts/Decorators"; +import { NoViableAltException } from "antlr4ts/NoViableAltException"; +import { Override } from "antlr4ts/Decorators"; +import { Parser } from "antlr4ts/Parser"; +import { ParserRuleContext } from "antlr4ts/ParserRuleContext"; +import { ParserATNSimulator } from "antlr4ts/atn/ParserATNSimulator"; +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; +import { RecognitionException } from "antlr4ts/RecognitionException"; +import { RuleContext } from "antlr4ts/RuleContext"; +//import { RuleVersion } from "antlr4ts/RuleVersion"; +import { TerminalNode } from "antlr4ts/tree/TerminalNode"; +import { Token } from "antlr4ts/Token"; +import { TokenStream } from "antlr4ts/TokenStream"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +import { FormulaParserListener } from "./FormulaParserListener"; +import { FormulaParserVisitor } from "./FormulaParserVisitor"; + + +export class FormulaParser extends Parser { + public static readonly ADD = 1; + public static readonly SUBTRACT = 2; + public static readonly MULTIPLY = 3; + public static readonly DIVIDE = 4; + public static readonly MODULO = 5; + public static readonly POWER = 6; + public static readonly EQUAL = 7; + public static readonly NOT_EQUAL = 8; + public static readonly LESS = 9; + public static readonly LESS_EQUAL = 10; + public static readonly GREATER = 11; + public static readonly GREATER_EQUAL = 12; + public static readonly AND = 13; + public static readonly OR = 14; + public static readonly NOT = 15; + public static readonly LPAREN = 16; + public static readonly RPAREN = 17; + public static readonly LBRACE = 18; + public static readonly RBRACE = 19; + public static readonly LBRACKET = 20; + public static readonly RBRACKET = 21; + public static readonly COMMA = 22; + public static readonly SEMICOLON = 23; + public static readonly COLON = 24; + public static readonly DOT = 25; + public static readonly IDENTIFIER = 26; + public static readonly NUMBER = 27; + public static readonly STRING = 28; + public static readonly TRUE = 29; + public static readonly FALSE = 30; + public static readonly NULL = 31; + public static readonly DATE = 32; + public static readonly TIME = 33; + public static readonly DATETIME = 34; + public static readonly WS = 35; + public static readonly COMMENT = 36; + public static readonly MULTILINE_COMMENT = 37; + public static readonly RULE_formula = 0; + public static readonly RULE_expression = 1; + public static readonly RULE_functionCall = 2; + public static readonly RULE_argumentList = 3; + public static readonly RULE_variable = 4; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "formula", "expression", "functionCall", "argumentList", "variable", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", + "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", + "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", + "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", + "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", + "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", + "MULTILINE_COMMENT", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaParser._LITERAL_NAMES, FormulaParser._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return FormulaParser.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + // @Override + public get grammarFileName(): string { return "FormulaParser.g4"; } + + // @Override + public get ruleNames(): string[] { return FormulaParser.ruleNames; } + + // @Override + public get serializedATN(): string { return FormulaParser._serializedATN; } + + protected createFailedPredicateException(predicate?: string, message?: string): FailedPredicateException { + return new FailedPredicateException(this, predicate, message); + } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(FormulaParser._ATN, this); + } + // @RuleVersion(0) + public formula(): FormulaContext { + let _localctx: FormulaContext = new FormulaContext(this._ctx, this.state); + this.enterRule(_localctx, 0, FormulaParser.RULE_formula); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 10; + this.expression(0); + this.state = 11; + this.match(FormulaParser.EOF); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public expression(): ExpressionContext; + public expression(_p: number): ExpressionContext; + // @RuleVersion(0) + public expression(_p?: number): ExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: ExpressionContext = new ExpressionContext(this._ctx, _parentState); + let _prevctx: ExpressionContext = _localctx; + let _startState: number = 2; + this.enterRecursionRule(_localctx, 2, FormulaParser.RULE_expression, _p); + let _la: number; + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 30; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case FormulaParser.NOT: + { + _localctx = new NotExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 14; + this.match(FormulaParser.NOT); + this.state = 15; + this.expression(12); + } + break; + case FormulaParser.IDENTIFIER: + { + _localctx = new FunctionExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 16; + this.functionCall(); + } + break; + case FormulaParser.LBRACE: + { + _localctx = new VariableExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 17; + this.variable(); + } + break; + case FormulaParser.NUMBER: + { + _localctx = new NumberExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 18; + this.match(FormulaParser.NUMBER); + } + break; + case FormulaParser.STRING: + { + _localctx = new StringExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 19; + this.match(FormulaParser.STRING); + } + break; + case FormulaParser.TRUE: + { + _localctx = new TrueExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 20; + this.match(FormulaParser.TRUE); + } + break; + case FormulaParser.FALSE: + { + _localctx = new FalseExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 21; + this.match(FormulaParser.FALSE); + } + break; + case FormulaParser.NULL: + { + _localctx = new NullExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 22; + this.match(FormulaParser.NULL); + } + break; + case FormulaParser.DATE: + { + _localctx = new DateExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 23; + this.match(FormulaParser.DATE); + } + break; + case FormulaParser.TIME: + { + _localctx = new TimeExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 24; + this.match(FormulaParser.TIME); + } + break; + case FormulaParser.DATETIME: + { + _localctx = new DateTimeExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 25; + this.match(FormulaParser.DATETIME); + } + break; + case FormulaParser.LPAREN: + { + _localctx = new ParenExprContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 26; + this.match(FormulaParser.LPAREN); + this.state = 27; + this.expression(0); + this.state = 28; + this.match(FormulaParser.RPAREN); + } + break; + default: + throw new NoViableAltException(this); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 52; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 2, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 50; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 1, this._ctx) ) { + case 1: + { + _localctx = new MulDivModExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 32; + if (!(this.precpred(this._ctx, 18))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 18)"); + } + this.state = 33; + (_localctx as MulDivModExprContext)._op = this._input.LT(1); + _la = this._input.LA(1); + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << FormulaParser.MULTIPLY) | (1 << FormulaParser.DIVIDE) | (1 << FormulaParser.MODULO))) !== 0))) { + (_localctx as MulDivModExprContext)._op = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 34; + this.expression(19); + } + break; + + case 2: + { + _localctx = new AddSubExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 35; + if (!(this.precpred(this._ctx, 17))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 17)"); + } + this.state = 36; + (_localctx as AddSubExprContext)._op = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === FormulaParser.ADD || _la === FormulaParser.SUBTRACT)) { + (_localctx as AddSubExprContext)._op = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 37; + this.expression(18); + } + break; + + case 3: + { + _localctx = new PowerExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 38; + if (!(this.precpred(this._ctx, 16))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 16)"); + } + this.state = 39; + this.match(FormulaParser.POWER); + this.state = 40; + this.expression(17); + } + break; + + case 4: + { + _localctx = new ComparisonExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 41; + if (!(this.precpred(this._ctx, 15))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 15)"); + } + this.state = 42; + (_localctx as ComparisonExprContext)._op = this._input.LT(1); + _la = this._input.LA(1); + if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << FormulaParser.EQUAL) | (1 << FormulaParser.NOT_EQUAL) | (1 << FormulaParser.LESS) | (1 << FormulaParser.LESS_EQUAL) | (1 << FormulaParser.GREATER) | (1 << FormulaParser.GREATER_EQUAL))) !== 0))) { + (_localctx as ComparisonExprContext)._op = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 43; + this.expression(16); + } + break; + + case 5: + { + _localctx = new AndExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 44; + if (!(this.precpred(this._ctx, 14))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 14)"); + } + this.state = 45; + this.match(FormulaParser.AND); + this.state = 46; + this.expression(15); + } + break; + + case 6: + { + _localctx = new OrExprContext(new ExpressionContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, FormulaParser.RULE_expression); + this.state = 47; + if (!(this.precpred(this._ctx, 13))) { + throw this.createFailedPredicateException("this.precpred(this._ctx, 13)"); + } + this.state = 48; + this.match(FormulaParser.OR); + this.state = 49; + this.expression(14); + } + break; + } + } + } + this.state = 54; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 2, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public functionCall(): FunctionCallContext { + let _localctx: FunctionCallContext = new FunctionCallContext(this._ctx, this.state); + this.enterRule(_localctx, 4, FormulaParser.RULE_functionCall); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 55; + this.match(FormulaParser.IDENTIFIER); + this.state = 56; + this.match(FormulaParser.LPAREN); + this.state = 58; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (((((_la - 15)) & ~0x1F) === 0 && ((1 << (_la - 15)) & ((1 << (FormulaParser.NOT - 15)) | (1 << (FormulaParser.LPAREN - 15)) | (1 << (FormulaParser.LBRACE - 15)) | (1 << (FormulaParser.IDENTIFIER - 15)) | (1 << (FormulaParser.NUMBER - 15)) | (1 << (FormulaParser.STRING - 15)) | (1 << (FormulaParser.TRUE - 15)) | (1 << (FormulaParser.FALSE - 15)) | (1 << (FormulaParser.NULL - 15)) | (1 << (FormulaParser.DATE - 15)) | (1 << (FormulaParser.TIME - 15)) | (1 << (FormulaParser.DATETIME - 15)))) !== 0)) { + { + this.state = 57; + this.argumentList(); + } + } + + this.state = 60; + this.match(FormulaParser.RPAREN); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public argumentList(): ArgumentListContext { + let _localctx: ArgumentListContext = new ArgumentListContext(this._ctx, this.state); + this.enterRule(_localctx, 6, FormulaParser.RULE_argumentList); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 62; + this.expression(0); + this.state = 67; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === FormulaParser.COMMA) { + { + { + this.state = 63; + this.match(FormulaParser.COMMA); + this.state = 64; + this.expression(0); + } + } + this.state = 69; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public variable(): VariableContext { + let _localctx: VariableContext = new VariableContext(this._ctx, this.state); + this.enterRule(_localctx, 8, FormulaParser.RULE_variable); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 70; + this.match(FormulaParser.LBRACE); + this.state = 71; + this.match(FormulaParser.LBRACE); + this.state = 72; + this.match(FormulaParser.IDENTIFIER); + this.state = 73; + this.match(FormulaParser.RBRACE); + this.state = 74; + this.match(FormulaParser.RBRACE); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { + switch (ruleIndex) { + case 1: + return this.expression_sempred(_localctx as ExpressionContext, predIndex); + } + return true; + } + private expression_sempred(_localctx: ExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 0: + return this.precpred(this._ctx, 18); + + case 1: + return this.precpred(this._ctx, 17); + + case 2: + return this.precpred(this._ctx, 16); + + case 3: + return this.precpred(this._ctx, 15); + + case 4: + return this.precpred(this._ctx, 14); + + case 5: + return this.precpred(this._ctx, 13); + } + return true; + } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03\'O\x04\x02\t" + + "\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x03\x02\x03" + + "\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x05\x03!\n\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x07\x035\n\x03\f\x03\x0E\x038\v\x03\x03\x04\x03\x04" + + "\x03\x04\x05\x04=\n\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x07\x05" + + "D\n\x05\f\x05\x0E\x05G\v\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03" + + "\x06\x03\x06\x02\x02\x03\x04\x07\x02\x02\x04\x02\x06\x02\b\x02\n\x02\x02" + + "\x05\x03\x02\x05\x07\x03\x02\x03\x04\x03\x02\t\x0E\x02\\\x02\f\x03\x02" + + "\x02\x02\x04 \x03\x02\x02\x02\x069\x03\x02\x02\x02\b@\x03\x02\x02\x02" + + "\nH\x03\x02\x02\x02\f\r\x05\x04\x03\x02\r\x0E\x07\x02\x02\x03\x0E\x03" + + "\x03\x02\x02\x02\x0F\x10\b\x03\x01\x02\x10\x11\x07\x11\x02\x02\x11!\x05" + + "\x04\x03\x0E\x12!\x05\x06\x04\x02\x13!\x05\n\x06\x02\x14!\x07\x1D\x02" + + "\x02\x15!\x07\x1E\x02\x02\x16!\x07\x1F\x02\x02\x17!\x07 \x02\x02\x18!" + + "\x07!\x02\x02\x19!\x07\"\x02\x02\x1A!\x07#\x02\x02\x1B!\x07$\x02\x02\x1C" + + "\x1D\x07\x12\x02\x02\x1D\x1E\x05\x04\x03\x02\x1E\x1F\x07\x13\x02\x02\x1F" + + "!\x03\x02\x02\x02 \x0F\x03\x02\x02\x02 \x12\x03\x02\x02\x02 \x13\x03\x02" + + "\x02\x02 \x14\x03\x02\x02\x02 \x15\x03\x02\x02\x02 \x16\x03\x02\x02\x02" + + " \x17\x03\x02\x02\x02 \x18\x03\x02\x02\x02 \x19\x03\x02\x02\x02 \x1A\x03" + + "\x02\x02\x02 \x1B\x03\x02\x02\x02 \x1C\x03\x02\x02\x02!6\x03\x02\x02\x02" + + "\"#\f\x14\x02\x02#$\t\x02\x02\x02$5\x05\x04\x03\x15%&\f\x13\x02\x02&\'" + + "\t\x03\x02\x02\'5\x05\x04\x03\x14()\f\x12\x02\x02)*\x07\b\x02\x02*5\x05" + + "\x04\x03\x13+,\f\x11\x02\x02,-\t\x04\x02\x02-5\x05\x04\x03\x12./\f\x10" + + "\x02\x02/0\x07\x0F\x02\x0205\x05\x04\x03\x1112\f\x0F\x02\x0223\x07\x10" + + "\x02\x0235\x05\x04\x03\x104\"\x03\x02\x02\x024%\x03\x02\x02\x024(\x03" + + "\x02\x02\x024+\x03\x02\x02\x024.\x03\x02\x02\x0241\x03\x02\x02\x0258\x03" + + "\x02\x02\x0264\x03\x02\x02\x0267\x03\x02\x02\x027\x05\x03\x02\x02\x02" + + "86\x03\x02\x02\x029:\x07\x1C\x02\x02:<\x07\x12\x02\x02;=\x05\b\x05\x02" + + "<;\x03\x02\x02\x02<=\x03\x02\x02\x02=>\x03\x02\x02\x02>?\x07\x13\x02\x02" + + "?\x07\x03\x02\x02\x02@E\x05\x04\x03\x02AB\x07\x18\x02\x02BD\x05\x04\x03" + + "\x02CA\x03\x02\x02\x02DG\x03\x02\x02\x02EC\x03\x02\x02\x02EF\x03\x02\x02" + + "\x02F\t\x03\x02\x02\x02GE\x03\x02\x02\x02HI\x07\x14\x02\x02IJ\x07\x14" + + "\x02\x02JK\x07\x1C\x02\x02KL\x07\x15\x02\x02LM\x07\x15\x02\x02M\v\x03" + + "\x02\x02\x02\x07 46(visitor: FormulaParserVisitor): Result { + if (visitor.visitFormula) { + return visitor.visitFormula(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class ExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return FormulaParser.RULE_expression; } + public copyFrom(ctx: ExpressionContext): void { + super.copyFrom(ctx); + } +} +export class MulDivModExprContext extends ExpressionContext { + public _op!: Token; + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public MULTIPLY(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.MULTIPLY, 0); } + public DIVIDE(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.DIVIDE, 0); } + public MODULO(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.MODULO, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterMulDivModExpr) { + listener.enterMulDivModExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitMulDivModExpr) { + listener.exitMulDivModExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitMulDivModExpr) { + return visitor.visitMulDivModExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class AddSubExprContext extends ExpressionContext { + public _op!: Token; + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public ADD(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.ADD, 0); } + public SUBTRACT(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.SUBTRACT, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterAddSubExpr) { + listener.enterAddSubExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitAddSubExpr) { + listener.exitAddSubExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitAddSubExpr) { + return visitor.visitAddSubExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class PowerExprContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public POWER(): TerminalNode { return this.getToken(FormulaParser.POWER, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterPowerExpr) { + listener.enterPowerExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitPowerExpr) { + listener.exitPowerExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitPowerExpr) { + return visitor.visitPowerExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class ComparisonExprContext extends ExpressionContext { + public _op!: Token; + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public EQUAL(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.EQUAL, 0); } + public NOT_EQUAL(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.NOT_EQUAL, 0); } + public LESS(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.LESS, 0); } + public LESS_EQUAL(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.LESS_EQUAL, 0); } + public GREATER(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.GREATER, 0); } + public GREATER_EQUAL(): TerminalNode | undefined { return this.tryGetToken(FormulaParser.GREATER_EQUAL, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterComparisonExpr) { + listener.enterComparisonExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitComparisonExpr) { + listener.exitComparisonExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitComparisonExpr) { + return visitor.visitComparisonExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class AndExprContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public AND(): TerminalNode { return this.getToken(FormulaParser.AND, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterAndExpr) { + listener.enterAndExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitAndExpr) { + listener.exitAndExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitAndExpr) { + return visitor.visitAndExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class OrExprContext extends ExpressionContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public OR(): TerminalNode { return this.getToken(FormulaParser.OR, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterOrExpr) { + listener.enterOrExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitOrExpr) { + listener.exitOrExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitOrExpr) { + return visitor.visitOrExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class NotExprContext extends ExpressionContext { + public NOT(): TerminalNode { return this.getToken(FormulaParser.NOT, 0); } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterNotExpr) { + listener.enterNotExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitNotExpr) { + listener.exitNotExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitNotExpr) { + return visitor.visitNotExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class FunctionExprContext extends ExpressionContext { + public functionCall(): FunctionCallContext { + return this.getRuleContext(0, FunctionCallContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterFunctionExpr) { + listener.enterFunctionExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitFunctionExpr) { + listener.exitFunctionExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitFunctionExpr) { + return visitor.visitFunctionExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class VariableExprContext extends ExpressionContext { + public variable(): VariableContext { + return this.getRuleContext(0, VariableContext); + } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterVariableExpr) { + listener.enterVariableExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitVariableExpr) { + listener.exitVariableExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitVariableExpr) { + return visitor.visitVariableExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class NumberExprContext extends ExpressionContext { + public NUMBER(): TerminalNode { return this.getToken(FormulaParser.NUMBER, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterNumberExpr) { + listener.enterNumberExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitNumberExpr) { + listener.exitNumberExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitNumberExpr) { + return visitor.visitNumberExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class StringExprContext extends ExpressionContext { + public STRING(): TerminalNode { return this.getToken(FormulaParser.STRING, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterStringExpr) { + listener.enterStringExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitStringExpr) { + listener.exitStringExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitStringExpr) { + return visitor.visitStringExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class TrueExprContext extends ExpressionContext { + public TRUE(): TerminalNode { return this.getToken(FormulaParser.TRUE, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterTrueExpr) { + listener.enterTrueExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitTrueExpr) { + listener.exitTrueExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitTrueExpr) { + return visitor.visitTrueExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class FalseExprContext extends ExpressionContext { + public FALSE(): TerminalNode { return this.getToken(FormulaParser.FALSE, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterFalseExpr) { + listener.enterFalseExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitFalseExpr) { + listener.exitFalseExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitFalseExpr) { + return visitor.visitFalseExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class NullExprContext extends ExpressionContext { + public NULL(): TerminalNode { return this.getToken(FormulaParser.NULL, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterNullExpr) { + listener.enterNullExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitNullExpr) { + listener.exitNullExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitNullExpr) { + return visitor.visitNullExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class DateExprContext extends ExpressionContext { + public DATE(): TerminalNode { return this.getToken(FormulaParser.DATE, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterDateExpr) { + listener.enterDateExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitDateExpr) { + listener.exitDateExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitDateExpr) { + return visitor.visitDateExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class TimeExprContext extends ExpressionContext { + public TIME(): TerminalNode { return this.getToken(FormulaParser.TIME, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterTimeExpr) { + listener.enterTimeExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitTimeExpr) { + listener.exitTimeExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitTimeExpr) { + return visitor.visitTimeExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class DateTimeExprContext extends ExpressionContext { + public DATETIME(): TerminalNode { return this.getToken(FormulaParser.DATETIME, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterDateTimeExpr) { + listener.enterDateTimeExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitDateTimeExpr) { + listener.exitDateTimeExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitDateTimeExpr) { + return visitor.visitDateTimeExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} +export class ParenExprContext extends ExpressionContext { + public LPAREN(): TerminalNode { return this.getToken(FormulaParser.LPAREN, 0); } + public expression(): ExpressionContext { + return this.getRuleContext(0, ExpressionContext); + } + public RPAREN(): TerminalNode { return this.getToken(FormulaParser.RPAREN, 0); } + constructor(ctx: ExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterParenExpr) { + listener.enterParenExpr(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitParenExpr) { + listener.exitParenExpr(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitParenExpr) { + return visitor.visitParenExpr(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class FunctionCallContext extends ParserRuleContext { + public IDENTIFIER(): TerminalNode { return this.getToken(FormulaParser.IDENTIFIER, 0); } + public LPAREN(): TerminalNode { return this.getToken(FormulaParser.LPAREN, 0); } + public RPAREN(): TerminalNode { return this.getToken(FormulaParser.RPAREN, 0); } + public argumentList(): ArgumentListContext | undefined { + return this.tryGetRuleContext(0, ArgumentListContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return FormulaParser.RULE_functionCall; } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterFunctionCall) { + listener.enterFunctionCall(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitFunctionCall) { + listener.exitFunctionCall(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitFunctionCall) { + return visitor.visitFunctionCall(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class ArgumentListContext extends ParserRuleContext { + public expression(): ExpressionContext[]; + public expression(i: number): ExpressionContext; + public expression(i?: number): ExpressionContext | ExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(ExpressionContext); + } else { + return this.getRuleContext(i, ExpressionContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(FormulaParser.COMMA); + } else { + return this.getToken(FormulaParser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return FormulaParser.RULE_argumentList; } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterArgumentList) { + listener.enterArgumentList(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitArgumentList) { + listener.exitArgumentList(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitArgumentList) { + return visitor.visitArgumentList(this); + } else { + return visitor.visitChildren(this); + } + } +} + + +export class VariableContext extends ParserRuleContext { + public LBRACE(): TerminalNode[]; + public LBRACE(i: number): TerminalNode; + public LBRACE(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(FormulaParser.LBRACE); + } else { + return this.getToken(FormulaParser.LBRACE, i); + } + } + public IDENTIFIER(): TerminalNode { return this.getToken(FormulaParser.IDENTIFIER, 0); } + public RBRACE(): TerminalNode[]; + public RBRACE(i: number): TerminalNode; + public RBRACE(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(FormulaParser.RBRACE); + } else { + return this.getToken(FormulaParser.RBRACE, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return FormulaParser.RULE_variable; } + // @Override + public enterRule(listener: FormulaParserListener): void { + if (listener.enterVariable) { + listener.enterVariable(this); + } + } + // @Override + public exitRule(listener: FormulaParserListener): void { + if (listener.exitVariable) { + listener.exitVariable(this); + } + } + // @Override + public accept(visitor: FormulaParserVisitor): Result { + if (visitor.visitVariable) { + return visitor.visitVariable(this); + } else { + return visitor.visitChildren(this); + } + } +} + + diff --git a/packages/formula/src/grammar/FormulaParserListener.ts b/packages/formula/src/grammar/FormulaParserListener.ts new file mode 100644 index 000000000..e5fa0e4d4 --- /dev/null +++ b/packages/formula/src/grammar/FormulaParserListener.ts @@ -0,0 +1,325 @@ +// Generated from src/grammar/FormulaParser.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; + +import { MulDivModExprContext } from "./FormulaParser"; +import { AddSubExprContext } from "./FormulaParser"; +import { PowerExprContext } from "./FormulaParser"; +import { ComparisonExprContext } from "./FormulaParser"; +import { AndExprContext } from "./FormulaParser"; +import { OrExprContext } from "./FormulaParser"; +import { NotExprContext } from "./FormulaParser"; +import { FunctionExprContext } from "./FormulaParser"; +import { VariableExprContext } from "./FormulaParser"; +import { NumberExprContext } from "./FormulaParser"; +import { StringExprContext } from "./FormulaParser"; +import { TrueExprContext } from "./FormulaParser"; +import { FalseExprContext } from "./FormulaParser"; +import { NullExprContext } from "./FormulaParser"; +import { DateExprContext } from "./FormulaParser"; +import { TimeExprContext } from "./FormulaParser"; +import { DateTimeExprContext } from "./FormulaParser"; +import { ParenExprContext } from "./FormulaParser"; +import { FormulaContext } from "./FormulaParser"; +import { ExpressionContext } from "./FormulaParser"; +import { FunctionCallContext } from "./FormulaParser"; +import { ArgumentListContext } from "./FormulaParser"; +import { VariableContext } from "./FormulaParser"; + + +/** + * This interface defines a complete listener for a parse tree produced by + * `FormulaParser`. + */ +export interface FormulaParserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by the `MulDivModExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterMulDivModExpr?: (ctx: MulDivModExprContext) => void; + /** + * Exit a parse tree produced by the `MulDivModExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitMulDivModExpr?: (ctx: MulDivModExprContext) => void; + + /** + * Enter a parse tree produced by the `AddSubExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterAddSubExpr?: (ctx: AddSubExprContext) => void; + /** + * Exit a parse tree produced by the `AddSubExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitAddSubExpr?: (ctx: AddSubExprContext) => void; + + /** + * Enter a parse tree produced by the `PowerExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterPowerExpr?: (ctx: PowerExprContext) => void; + /** + * Exit a parse tree produced by the `PowerExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitPowerExpr?: (ctx: PowerExprContext) => void; + + /** + * Enter a parse tree produced by the `ComparisonExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterComparisonExpr?: (ctx: ComparisonExprContext) => void; + /** + * Exit a parse tree produced by the `ComparisonExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitComparisonExpr?: (ctx: ComparisonExprContext) => void; + + /** + * Enter a parse tree produced by the `AndExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterAndExpr?: (ctx: AndExprContext) => void; + /** + * Exit a parse tree produced by the `AndExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitAndExpr?: (ctx: AndExprContext) => void; + + /** + * Enter a parse tree produced by the `OrExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterOrExpr?: (ctx: OrExprContext) => void; + /** + * Exit a parse tree produced by the `OrExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitOrExpr?: (ctx: OrExprContext) => void; + + /** + * Enter a parse tree produced by the `NotExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterNotExpr?: (ctx: NotExprContext) => void; + /** + * Exit a parse tree produced by the `NotExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitNotExpr?: (ctx: NotExprContext) => void; + + /** + * Enter a parse tree produced by the `FunctionExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterFunctionExpr?: (ctx: FunctionExprContext) => void; + /** + * Exit a parse tree produced by the `FunctionExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitFunctionExpr?: (ctx: FunctionExprContext) => void; + + /** + * Enter a parse tree produced by the `VariableExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterVariableExpr?: (ctx: VariableExprContext) => void; + /** + * Exit a parse tree produced by the `VariableExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitVariableExpr?: (ctx: VariableExprContext) => void; + + /** + * Enter a parse tree produced by the `NumberExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterNumberExpr?: (ctx: NumberExprContext) => void; + /** + * Exit a parse tree produced by the `NumberExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitNumberExpr?: (ctx: NumberExprContext) => void; + + /** + * Enter a parse tree produced by the `StringExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterStringExpr?: (ctx: StringExprContext) => void; + /** + * Exit a parse tree produced by the `StringExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitStringExpr?: (ctx: StringExprContext) => void; + + /** + * Enter a parse tree produced by the `TrueExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterTrueExpr?: (ctx: TrueExprContext) => void; + /** + * Exit a parse tree produced by the `TrueExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitTrueExpr?: (ctx: TrueExprContext) => void; + + /** + * Enter a parse tree produced by the `FalseExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterFalseExpr?: (ctx: FalseExprContext) => void; + /** + * Exit a parse tree produced by the `FalseExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitFalseExpr?: (ctx: FalseExprContext) => void; + + /** + * Enter a parse tree produced by the `NullExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterNullExpr?: (ctx: NullExprContext) => void; + /** + * Exit a parse tree produced by the `NullExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitNullExpr?: (ctx: NullExprContext) => void; + + /** + * Enter a parse tree produced by the `DateExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterDateExpr?: (ctx: DateExprContext) => void; + /** + * Exit a parse tree produced by the `DateExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitDateExpr?: (ctx: DateExprContext) => void; + + /** + * Enter a parse tree produced by the `TimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterTimeExpr?: (ctx: TimeExprContext) => void; + /** + * Exit a parse tree produced by the `TimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitTimeExpr?: (ctx: TimeExprContext) => void; + + /** + * Enter a parse tree produced by the `DateTimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterDateTimeExpr?: (ctx: DateTimeExprContext) => void; + /** + * Exit a parse tree produced by the `DateTimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitDateTimeExpr?: (ctx: DateTimeExprContext) => void; + + /** + * Enter a parse tree produced by the `ParenExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterParenExpr?: (ctx: ParenExprContext) => void; + /** + * Exit a parse tree produced by the `ParenExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitParenExpr?: (ctx: ParenExprContext) => void; + + /** + * Enter a parse tree produced by `FormulaParser.formula`. + * @param ctx the parse tree + */ + enterFormula?: (ctx: FormulaContext) => void; + /** + * Exit a parse tree produced by `FormulaParser.formula`. + * @param ctx the parse tree + */ + exitFormula?: (ctx: FormulaContext) => void; + + /** + * Enter a parse tree produced by `FormulaParser.expression`. + * @param ctx the parse tree + */ + enterExpression?: (ctx: ExpressionContext) => void; + /** + * Exit a parse tree produced by `FormulaParser.expression`. + * @param ctx the parse tree + */ + exitExpression?: (ctx: ExpressionContext) => void; + + /** + * Enter a parse tree produced by `FormulaParser.functionCall`. + * @param ctx the parse tree + */ + enterFunctionCall?: (ctx: FunctionCallContext) => void; + /** + * Exit a parse tree produced by `FormulaParser.functionCall`. + * @param ctx the parse tree + */ + exitFunctionCall?: (ctx: FunctionCallContext) => void; + + /** + * Enter a parse tree produced by `FormulaParser.argumentList`. + * @param ctx the parse tree + */ + enterArgumentList?: (ctx: ArgumentListContext) => void; + /** + * Exit a parse tree produced by `FormulaParser.argumentList`. + * @param ctx the parse tree + */ + exitArgumentList?: (ctx: ArgumentListContext) => void; + + /** + * Enter a parse tree produced by `FormulaParser.variable`. + * @param ctx the parse tree + */ + enterVariable?: (ctx: VariableContext) => void; + /** + * Exit a parse tree produced by `FormulaParser.variable`. + * @param ctx the parse tree + */ + exitVariable?: (ctx: VariableContext) => void; +} + diff --git a/packages/formula/src/grammar/FormulaParserVisitor.ts b/packages/formula/src/grammar/FormulaParserVisitor.ts new file mode 100644 index 000000000..31cb9c694 --- /dev/null +++ b/packages/formula/src/grammar/FormulaParserVisitor.ts @@ -0,0 +1,218 @@ +// Generated from src/grammar/FormulaParser.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; + +import { MulDivModExprContext } from "./FormulaParser"; +import { AddSubExprContext } from "./FormulaParser"; +import { PowerExprContext } from "./FormulaParser"; +import { ComparisonExprContext } from "./FormulaParser"; +import { AndExprContext } from "./FormulaParser"; +import { OrExprContext } from "./FormulaParser"; +import { NotExprContext } from "./FormulaParser"; +import { FunctionExprContext } from "./FormulaParser"; +import { VariableExprContext } from "./FormulaParser"; +import { NumberExprContext } from "./FormulaParser"; +import { StringExprContext } from "./FormulaParser"; +import { TrueExprContext } from "./FormulaParser"; +import { FalseExprContext } from "./FormulaParser"; +import { NullExprContext } from "./FormulaParser"; +import { DateExprContext } from "./FormulaParser"; +import { TimeExprContext } from "./FormulaParser"; +import { DateTimeExprContext } from "./FormulaParser"; +import { ParenExprContext } from "./FormulaParser"; +import { FormulaContext } from "./FormulaParser"; +import { ExpressionContext } from "./FormulaParser"; +import { FunctionCallContext } from "./FormulaParser"; +import { ArgumentListContext } from "./FormulaParser"; +import { VariableContext } from "./FormulaParser"; + + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by `FormulaParser`. + * + * @param The return type of the visit operation. Use `void` for + * operations with no return type. + */ +export interface FormulaParserVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by the `MulDivModExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitMulDivModExpr?: (ctx: MulDivModExprContext) => Result; + + /** + * Visit a parse tree produced by the `AddSubExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAddSubExpr?: (ctx: AddSubExprContext) => Result; + + /** + * Visit a parse tree produced by the `PowerExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitPowerExpr?: (ctx: PowerExprContext) => Result; + + /** + * Visit a parse tree produced by the `ComparisonExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitComparisonExpr?: (ctx: ComparisonExprContext) => Result; + + /** + * Visit a parse tree produced by the `AndExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitAndExpr?: (ctx: AndExprContext) => Result; + + /** + * Visit a parse tree produced by the `OrExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitOrExpr?: (ctx: OrExprContext) => Result; + + /** + * Visit a parse tree produced by the `NotExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitNotExpr?: (ctx: NotExprContext) => Result; + + /** + * Visit a parse tree produced by the `FunctionExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFunctionExpr?: (ctx: FunctionExprContext) => Result; + + /** + * Visit a parse tree produced by the `VariableExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitVariableExpr?: (ctx: VariableExprContext) => Result; + + /** + * Visit a parse tree produced by the `NumberExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitNumberExpr?: (ctx: NumberExprContext) => Result; + + /** + * Visit a parse tree produced by the `StringExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitStringExpr?: (ctx: StringExprContext) => Result; + + /** + * Visit a parse tree produced by the `TrueExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitTrueExpr?: (ctx: TrueExprContext) => Result; + + /** + * Visit a parse tree produced by the `FalseExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFalseExpr?: (ctx: FalseExprContext) => Result; + + /** + * Visit a parse tree produced by the `NullExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitNullExpr?: (ctx: NullExprContext) => Result; + + /** + * Visit a parse tree produced by the `DateExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitDateExpr?: (ctx: DateExprContext) => Result; + + /** + * Visit a parse tree produced by the `TimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitTimeExpr?: (ctx: TimeExprContext) => Result; + + /** + * Visit a parse tree produced by the `DateTimeExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitDateTimeExpr?: (ctx: DateTimeExprContext) => Result; + + /** + * Visit a parse tree produced by the `ParenExpr` + * labeled alternative in `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitParenExpr?: (ctx: ParenExprContext) => Result; + + /** + * Visit a parse tree produced by `FormulaParser.formula`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFormula?: (ctx: FormulaContext) => Result; + + /** + * Visit a parse tree produced by `FormulaParser.expression`. + * @param ctx the parse tree + * @return the visitor result + */ + visitExpression?: (ctx: ExpressionContext) => Result; + + /** + * Visit a parse tree produced by `FormulaParser.functionCall`. + * @param ctx the parse tree + * @return the visitor result + */ + visitFunctionCall?: (ctx: FunctionCallContext) => Result; + + /** + * Visit a parse tree produced by `FormulaParser.argumentList`. + * @param ctx the parse tree + * @return the visitor result + */ + visitArgumentList?: (ctx: ArgumentListContext) => Result; + + /** + * Visit a parse tree produced by `FormulaParser.variable`. + * @param ctx the parse tree + * @return the visitor result + */ + visitVariable?: (ctx: VariableContext) => Result; +} + diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts new file mode 100644 index 000000000..1263a955e --- /dev/null +++ b/packages/formula/src/index.ts @@ -0,0 +1,5 @@ +import { parseFormula } from "./util" + +const input = "ADD(1, ADD(2, {{ field1 }}))" +// const input = "{{ field1 }} + 2" +const result = parseFormula(input) diff --git a/packages/formula/src/types.ts b/packages/formula/src/types.ts new file mode 100644 index 000000000..67cf8f343 --- /dev/null +++ b/packages/formula/src/types.ts @@ -0,0 +1,52 @@ +export enum ParamType { + NUMBER, + STRING, + BOOLEAN, + DATE, + ANY, + VARIADIC, // 用于表示可变参数 +} + +export type FunctionDefinition = string + +export type FunctionExpressionResult = { + type: "functionCall" + name: string + arguments: ExpressionResult[] + returnType: ParamType + value: FunctionDefinition +} + +export type ArgumentListResult = { + type: "argumentList" + arguments: ExpressionResult[] +} + +export type VariableResult = { + type: "variable" + value: string + variable: string +} + +export type NumberResult = { + type: "number" + value: number +} + +export type StringResult = { + type: "string" + value: string +} + +export type BooleanResult = { + type: "boolean" + value: boolean +} + +export type ExpressionResult = + | FunctionExpressionResult + | ArgumentListResult + | VariableResult + | NumberResult + | StringResult + | BooleanResult diff --git a/packages/formula/src/util.ts b/packages/formula/src/util.ts new file mode 100644 index 000000000..beec02800 --- /dev/null +++ b/packages/formula/src/util.ts @@ -0,0 +1,18 @@ +import { CharStreams, CommonTokenStream } from "antlr4ts" +import { FormulaLexer } from "./grammar/FormulaLexer" +import { FormulaParser } from "./grammar/FormulaParser" +import { CustomFormulaVisitor } from "./visitor" + +export function parseFormula(input: string) { + const inputStream = CharStreams.fromString(input) + const lexer = new FormulaLexer(inputStream) + const tokenStream = new CommonTokenStream(lexer) + const parser = new FormulaParser(tokenStream) + + const tree = parser.formula() + + const visitor = new CustomFormulaVisitor() + const parsedFormula = visitor.visit(tree) + + console.log(JSON.stringify(parsedFormula, null, 2)) +} diff --git a/packages/formula/src/visitor.ts b/packages/formula/src/visitor.ts new file mode 100644 index 000000000..800f89a44 --- /dev/null +++ b/packages/formula/src/visitor.ts @@ -0,0 +1,124 @@ +import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" +import { globalFunctionRegistry } from "./function/registry" +import { + AddSubExprContext, + ArgumentListContext, + FormulaContext, + FunctionCallContext, + FunctionExprContext, + MulDivModExprContext, + NumberExprContext, + ParenExprContext, + StringExprContext, + VariableContext, + VariableExprContext, +} from "./grammar/FormulaParser" +import type { FormulaParserVisitor } from "./grammar/FormulaParserVisitor" +import { + type ExpressionResult, + type FunctionExpressionResult, + type NumberResult, + ParamType, + type VariableResult, +} from "./types" + +export class CustomFormulaVisitor + extends AbstractParseTreeVisitor + implements FormulaParserVisitor +{ + private variables: Set = new Set() + + visitFormula(ctx: FormulaContext): ExpressionResult { + return this.visit(ctx.expression()) + } + + visitMulDivModExpr(ctx: MulDivModExprContext): ExpressionResult { + const left = this.visit(ctx.expression(0)) as NumberResult | VariableResult + const right = this.visit(ctx.expression(1)) as NumberResult | VariableResult + const op = ctx._op.text! + return { + type: "functionCall", + name: op, + arguments: [left, right], + returnType: ParamType.NUMBER, + value: ctx.text, + } + } + + visitAddSubExpr(ctx: AddSubExprContext): ExpressionResult { + const left = this.visit(ctx.expression(0)) as NumberResult + const right = this.visit(ctx.expression(1)) as NumberResult + const op = ctx._op.text! + return { + type: "functionCall", + name: op, + arguments: [left, right], + returnType: ParamType.NUMBER, + value: ctx.text, + } + } + + visitFunctionExpr(ctx: FunctionExprContext): ExpressionResult { + return this.visit(ctx.functionCall()) + } + + visitVariableExpr(ctx: VariableExprContext): ExpressionResult { + return this.visit(ctx.variable()) + } + + visitNumberExpr(ctx: NumberExprContext): ExpressionResult { + return { type: "number", value: Number(ctx.NUMBER().text) } + } + + visitStringExpr(ctx: StringExprContext): ExpressionResult { + return { type: "string", value: ctx.STRING().text.slice(1, -1) } + } + + visitParenExpr(ctx: ParenExprContext): ExpressionResult { + return this.visit(ctx.expression()) + } + + visitFunctionCall(ctx: FunctionCallContext): ExpressionResult { + const funcName = ctx.IDENTIFIER().text + const fn = ctx.text + const args = ctx.argumentList() ? (this.visit(ctx.argumentList()!) as FunctionExpressionResult) : undefined + + if (!globalFunctionRegistry.isValid(funcName) || !args) { + throw new Error(`Unknown function: ${funcName}`) + } + + globalFunctionRegistry.validateArgs(funcName, args.arguments) + + const returnType = globalFunctionRegistry.get(funcName)!.returnType + + return { + type: "functionCall", + name: funcName, + arguments: Array.isArray(args) ? args : [args], + returnType, + value: ctx.text, + } + } + + visitArgumentList(ctx: ArgumentListContext): ExpressionResult { + const args = ctx.expression().map((expr) => this.visit(expr)) + return { + type: "argumentList", + arguments: args, + } + } + visitVariable(ctx: VariableContext): ExpressionResult { + const variableName = ctx.IDENTIFIER().text + this.variables.add(variableName) + const raw = `{{${variableName}}}` + return { type: "variable", value: raw, variable: variableName } + } + + getVariables(): string[] { + return Array.from(this.variables) + } + + protected defaultResult(): ExpressionResult { + return { type: "string", value: "" } + } +} diff --git a/packages/formula/tsconfig.json b/packages/formula/tsconfig.json new file mode 100644 index 000000000..238655f2c --- /dev/null +++ b/packages/formula/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} From 13c236a11d0b1d470c26f1dbd0e1a732665d3838 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 16:12:19 +0800 Subject: [PATCH 03/31] feat: add formula input --- apps/frontend/package.json | 6 + .../aggregate/config/aggregate-config.svelte | 1 - .../components/formula/formula-editor.svelte | 305 ++++++++++++++++ .../formula/plugins/varaible.plugin.ts | 67 ++++ apps/frontend/src/routes/formula/+page.svelte | 5 + apps/frontend/svelte.config.js | 4 + apps/frontend/tsconfig.json | 1 + apps/frontend/vite.config.ts | 7 + bun.lockb | Bin 557184 -> 589368 bytes .../__snapshots__/base.fixture.test.ts.snap | 35 -- .../base/src/fixtures/base.fixture.test.ts | 9 - packages/domain/src/query.test.ts | 2 + .../generated/src/grammar/FormulaLexer.interp | 156 +++++++++ .../generated/src/grammar/FormulaLexer.tokens | 59 ++++ .../generated/src/grammar/FormulaLexer.ts | 282 +++++++++++++++ packages/formula/package.json | 1 + packages/formula/scripts/generate-parser.ts | 2 +- packages/formula/src/function/registry.ts | 23 +- packages/formula/src/grammar/FormulaParser.ts | 265 -------------- .../src/grammar/FormulaParserListener.ts | 325 ------------------ packages/formula/src/index.ts | 9 +- .../__snapshots__/parse-formula.test.ts.snap | 82 +++++ .../formula/src/tests/parse-formula.test.ts | 10 + packages/formula/src/util.ts | 10 +- packages/formula/src/visitor.ts | 87 ++++- packages/formula/tsconfig.json | 4 +- .../src/record/record.filter-visitor.test.ts | 115 ------- .../src/table/table.query-visitor.test.ts | 34 -- .../fields/condition/condition.util.test.ts | 209 ----------- .../modules/schema/fields/field.util.test.ts | 2 +- .../src/modules/schema/fields/field.util.ts | 2 +- 31 files changed, 1108 insertions(+), 1011 deletions(-) create mode 100644 apps/frontend/src/lib/components/formula/formula-editor.svelte create mode 100644 apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts create mode 100644 apps/frontend/src/routes/formula/+page.svelte delete mode 100644 packages/base/src/fixtures/__snapshots__/base.fixture.test.ts.snap delete mode 100644 packages/base/src/fixtures/base.fixture.test.ts create mode 100644 packages/formula/generated/src/grammar/FormulaLexer.interp create mode 100644 packages/formula/generated/src/grammar/FormulaLexer.tokens create mode 100644 packages/formula/generated/src/grammar/FormulaLexer.ts delete mode 100644 packages/formula/src/grammar/FormulaParserListener.ts create mode 100644 packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap create mode 100644 packages/formula/src/tests/parse-formula.test.ts delete mode 100644 packages/persistence/src/record/record.filter-visitor.test.ts delete mode 100644 packages/persistence/src/table/table.query-visitor.test.ts delete mode 100644 packages/table/src/modules/schema/fields/condition/condition.util.test.ts diff --git a/apps/frontend/package.json b/apps/frontend/package.json index e88c60bdf..7fc0be1bd 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -32,6 +32,7 @@ "@typescript-eslint/parser": "^8.10.0", "@undb/commands": "workspace:*", "@undb/domain": "workspace:*", + "@undb/formula": "workspace:*", "@undb/i18n": "workspace:*", "@undb/openapi": "workspace:*", "@undb/queries": "workspace:*", @@ -74,11 +75,16 @@ "type-fest": "^4.26.1", "typescript": "^5.6.3", "vite": "^5.4.9", + "vite-plugin-node-polyfills": "^0.22.0", "vitest": "^2.1.3", "xlsx": "^0.18.5" }, "type": "module", "dependencies": { + "@codemirror/commands": "^6.7.1", + "@codemirror/language": "^6.10.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.1", "@formkit/auto-animate": "^0.8.2", "@internationalized/date": "^3.5.6", "@svelte-put/clickoutside": "^3.0.2", diff --git a/apps/frontend/src/lib/components/blocks/aggregate/config/aggregate-config.svelte b/apps/frontend/src/lib/components/blocks/aggregate/config/aggregate-config.svelte index 6183daf17..548fe4def 100644 --- a/apps/frontend/src/lib/components/blocks/aggregate/config/aggregate-config.svelte +++ b/apps/frontend/src/lib/components/blocks/aggregate/config/aggregate-config.svelte @@ -1,6 +1,5 @@ + +

+
+ {#if errorMessage} +

{errorMessage}

+ {/if} + + {#if showSuggestions} +
    + {#each suggestions as suggestion} + {@const isSelected = suggestion === selectedSuggestion} + + {/each} +
+ {/if} +
diff --git a/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts new file mode 100644 index 000000000..91721e266 --- /dev/null +++ b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts @@ -0,0 +1,67 @@ +import { StateField } from "@codemirror/state" +import { Decoration, DecorationSet, EditorView, WidgetType } from "@codemirror/view" + +// 创建两个装饰器:一个用于整个变量区域,一个用于隐藏花括号 +const variableMark = Decoration.mark({ + class: ` + bg-blue-50 + hover:bg-blue-100 + rounded + px-1 + border + border-blue-200 + mx-[1px] + transition-all + duration-200 + ease-in-out + hover:shadow-sm + hover:border-blue-300 + cursor-pointer + ` + .replace(/\s+/g, " ") + .trim(), +}) + +// 用于隐藏花括号的装饰器 +const hideBrackets = Decoration.replace({ + widget: new (class extends WidgetType { + toDOM() { + return document.createElement("span") + } + })(), +}) + +const variableField = StateField.define({ + create() { + return Decoration.none + }, + update(decorations, tr) { + decorations = decorations.map(tr.changes) + + let matches = [] + let content = tr.state.doc.toString() + const regex = /\{\{([^}]+)\}\}/g + let match + + while ((match = regex.exec(content)) !== null) { + const start = match.index + const end = match.index + match[0].length + + // 按顺序添加装饰器: + // 1. 首先添加整体变量区域的装饰 + matches.push(variableMark.range(start, end)) + // 2. 然后添加开始花括号的隐藏装饰 + matches.push(hideBrackets.range(start, start + 2)) + // 3. 最后添加结束花括号的隐藏装饰 + matches.push(hideBrackets.range(end - 2, end)) + } + + // 确保装饰器按照 from 位置排序 + matches.sort((a, b) => a.from - b.from) + + return Decoration.set(matches, true) // 添加 true 参数表示已排序 + }, + provide: (f) => EditorView.decorations.from(f), +}) + +export const templateVariablePlugin = [variableField] diff --git a/apps/frontend/src/routes/formula/+page.svelte b/apps/frontend/src/routes/formula/+page.svelte new file mode 100644 index 000000000..e60c483bd --- /dev/null +++ b/apps/frontend/src/routes/formula/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/apps/frontend/svelte.config.js b/apps/frontend/svelte.config.js index ebe200df0..57d59a3b3 100644 --- a/apps/frontend/svelte.config.js +++ b/apps/frontend/svelte.config.js @@ -7,6 +7,10 @@ const config = { // for more information about preprocessors preprocess: vitePreprocess(), + vitePlugin: { + exclude: ["@undb/formula"], + }, + kit: { adapter: adapter({ pages: "dist", diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index df2d6ba36..78833c830 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -10,6 +10,7 @@ "noEmit": true, "moduleResolution": "bundler", "experimentalDecorators": true, + "verbatimModuleSyntax": false, "emitDecoratorMetadata": true, "rootDirs": [".", "./.svelte-kit/types", "./$houdini/types", "./$houdini"] } diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index 80caaba37..4358a1f32 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -1,12 +1,19 @@ import { sveltekit } from "@sveltejs/kit/vite" import houdini from "houdini/vite" import { visualizer } from "rollup-plugin-visualizer" +import { nodePolyfills } from "vite-plugin-node-polyfills" import { defineConfig } from "vitest/config" export default defineConfig({ plugins: [ houdini(), sveltekit(), + nodePolyfills({ + include: ["assert"], + globals: { + process: true, + }, + }), visualizer({ emitFile: true, filename: "stats.html", diff --git a/bun.lockb b/bun.lockb index e1b441ca59368eb3510c347531eb917a2cd0724a..508ca4655d6ccc7bcc3c8dc692078265913a7053 100755 GIT binary patch delta 93021 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%0Dr)KRjngKRMwlF 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 diff --git a/packages/base/src/fixtures/__snapshots__/base.fixture.test.ts.snap b/packages/base/src/fixtures/__snapshots__/base.fixture.test.ts.snap deleted file mode 100644 index b67f58017..000000000 --- a/packages/base/src/fixtures/__snapshots__/base.fixture.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`base fixture test > create test base success 1`] = ` -Base { - "id": BaseId { - "props": { - "value": "baseId", - }, - }, - "name": BaseName { - "props": { - "value": "name", - }, - }, -} -`; - -exports[`base fixture test create test base success 1`] = ` -Base { - "id": BaseId { - "props": { - "value": "basttrlpxe2", - }, - }, - "name": BaseName { - "props": { - "value": undefined, - }, - }, - "option": BaseOption { - "props": {}, - }, - "spaceId": undefined, -} -`; diff --git a/packages/base/src/fixtures/base.fixture.test.ts b/packages/base/src/fixtures/base.fixture.test.ts deleted file mode 100644 index 8f968520a..000000000 --- a/packages/base/src/fixtures/base.fixture.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createTestBase } from "./base.fixture" - -describe("base fixture test", () => { - test("create test base success", () => { - const base = createTestBase() - - expect(base).toMatchSnapshot() - }) -}) diff --git a/packages/domain/src/query.test.ts b/packages/domain/src/query.test.ts index 33f858bee..745360977 100644 --- a/packages/domain/src/query.test.ts +++ b/packages/domain/src/query.test.ts @@ -1,3 +1,5 @@ +import { expect, test } from "bun:test" + test("test", () => { expect(1).toBe(1) }) diff --git a/packages/formula/generated/src/grammar/FormulaLexer.interp b/packages/formula/generated/src/grammar/FormulaLexer.interp new file mode 100644 index 000000000..9e853d136 --- /dev/null +++ b/packages/formula/generated/src/grammar/FormulaLexer.interp @@ -0,0 +1,156 @@ +token literal names: +null +'+' +'-' +'*' +'/' +'%' +'^' +'=' +'!=' +'<' +'<=' +'>' +'>=' +null +null +null +'(' +')' +'{' +'}' +'[' +']' +',' +';' +':' +'.' +null +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +ADD +SUBTRACT +MULTIPLY +DIVIDE +MODULO +POWER +EQUAL +NOT_EQUAL +LESS +LESS_EQUAL +GREATER +GREATER_EQUAL +AND +OR +NOT +LPAREN +RPAREN +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +SEMICOLON +COLON +DOT +IDENTIFIER +NUMBER +STRING +TRUE +FALSE +NULL +DATE +TIME +DATETIME +WS +COMMENT +MULTILINE_COMMENT + +rule names: +ADD +SUBTRACT +MULTIPLY +DIVIDE +MODULO +POWER +EQUAL +NOT_EQUAL +LESS +LESS_EQUAL +GREATER +GREATER_EQUAL +AND +OR +NOT +LPAREN +RPAREN +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +SEMICOLON +COLON +DOT +IDENTIFIER +NUMBER +STRING +TRUE +FALSE +NULL +DATE +TIME +DATETIME +WS +COMMENT +MULTILINE_COMMENT +DIGIT +LETTER +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 39, 346, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 7, 27, 195, 10, 27, 12, 27, 14, 27, 198, 11, 27, 3, 28, 6, 28, 201, 10, 28, 13, 28, 14, 28, 202, 3, 28, 3, 28, 6, 28, 207, 10, 28, 13, 28, 14, 28, 208, 5, 28, 211, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 7, 29, 217, 10, 29, 12, 29, 14, 29, 220, 11, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 6, 36, 260, 10, 36, 13, 36, 14, 36, 261, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 270, 10, 37, 12, 37, 14, 37, 273, 11, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 281, 10, 38, 12, 38, 14, 38, 284, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 282, 2, 2, 67, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 29, 2, 16, 31, 2, 17, 33, 2, 18, 35, 2, 19, 37, 2, 20, 39, 2, 21, 41, 2, 22, 43, 2, 23, 45, 2, 24, 47, 2, 25, 49, 2, 26, 51, 2, 27, 53, 2, 28, 55, 2, 29, 57, 2, 30, 59, 2, 31, 61, 2, 32, 63, 2, 33, 65, 2, 34, 67, 2, 35, 69, 2, 36, 71, 2, 37, 73, 2, 38, 75, 2, 39, 77, 2, 2, 79, 2, 2, 81, 2, 2, 83, 2, 2, 85, 2, 2, 87, 2, 2, 89, 2, 2, 91, 2, 2, 93, 2, 2, 95, 2, 2, 97, 2, 2, 99, 2, 2, 101, 2, 2, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 2, 113, 2, 2, 115, 2, 2, 117, 2, 2, 119, 2, 2, 121, 2, 2, 123, 2, 2, 125, 2, 2, 127, 2, 2, 129, 2, 2, 131, 2, 2, 3, 2, 33, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 327, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 3, 133, 3, 2, 2, 2, 5, 135, 3, 2, 2, 2, 7, 137, 3, 2, 2, 2, 9, 139, 3, 2, 2, 2, 11, 141, 3, 2, 2, 2, 13, 143, 3, 2, 2, 2, 15, 145, 3, 2, 2, 2, 17, 147, 3, 2, 2, 2, 19, 150, 3, 2, 2, 2, 21, 152, 3, 2, 2, 2, 23, 155, 3, 2, 2, 2, 25, 157, 3, 2, 2, 2, 27, 160, 3, 2, 2, 2, 29, 164, 3, 2, 2, 2, 31, 167, 3, 2, 2, 2, 33, 171, 3, 2, 2, 2, 35, 173, 3, 2, 2, 2, 37, 175, 3, 2, 2, 2, 39, 177, 3, 2, 2, 2, 41, 179, 3, 2, 2, 2, 43, 181, 3, 2, 2, 2, 45, 183, 3, 2, 2, 2, 47, 185, 3, 2, 2, 2, 49, 187, 3, 2, 2, 2, 51, 189, 3, 2, 2, 2, 53, 191, 3, 2, 2, 2, 55, 200, 3, 2, 2, 2, 57, 212, 3, 2, 2, 2, 59, 223, 3, 2, 2, 2, 61, 228, 3, 2, 2, 2, 63, 234, 3, 2, 2, 2, 65, 239, 3, 2, 2, 2, 67, 244, 3, 2, 2, 2, 69, 249, 3, 2, 2, 2, 71, 259, 3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 276, 3, 2, 2, 2, 77, 290, 3, 2, 2, 2, 79, 292, 3, 2, 2, 2, 81, 294, 3, 2, 2, 2, 83, 296, 3, 2, 2, 2, 85, 298, 3, 2, 2, 2, 87, 300, 3, 2, 2, 2, 89, 302, 3, 2, 2, 2, 91, 304, 3, 2, 2, 2, 93, 306, 3, 2, 2, 2, 95, 308, 3, 2, 2, 2, 97, 310, 3, 2, 2, 2, 99, 312, 3, 2, 2, 2, 101, 314, 3, 2, 2, 2, 103, 316, 3, 2, 2, 2, 105, 318, 3, 2, 2, 2, 107, 320, 3, 2, 2, 2, 109, 322, 3, 2, 2, 2, 111, 324, 3, 2, 2, 2, 113, 326, 3, 2, 2, 2, 115, 328, 3, 2, 2, 2, 117, 330, 3, 2, 2, 2, 119, 332, 3, 2, 2, 2, 121, 334, 3, 2, 2, 2, 123, 336, 3, 2, 2, 2, 125, 338, 3, 2, 2, 2, 127, 340, 3, 2, 2, 2, 129, 342, 3, 2, 2, 2, 131, 344, 3, 2, 2, 2, 133, 134, 7, 45, 2, 2, 134, 4, 3, 2, 2, 2, 135, 136, 7, 47, 2, 2, 136, 6, 3, 2, 2, 2, 137, 138, 7, 44, 2, 2, 138, 8, 3, 2, 2, 2, 139, 140, 7, 49, 2, 2, 140, 10, 3, 2, 2, 2, 141, 142, 7, 39, 2, 2, 142, 12, 3, 2, 2, 2, 143, 144, 7, 96, 2, 2, 144, 14, 3, 2, 2, 2, 145, 146, 7, 63, 2, 2, 146, 16, 3, 2, 2, 2, 147, 148, 7, 35, 2, 2, 148, 149, 7, 63, 2, 2, 149, 18, 3, 2, 2, 2, 150, 151, 7, 62, 2, 2, 151, 20, 3, 2, 2, 2, 152, 153, 7, 62, 2, 2, 153, 154, 7, 63, 2, 2, 154, 22, 3, 2, 2, 2, 155, 156, 7, 64, 2, 2, 156, 24, 3, 2, 2, 2, 157, 158, 7, 64, 2, 2, 158, 159, 7, 63, 2, 2, 159, 26, 3, 2, 2, 2, 160, 161, 5, 81, 41, 2, 161, 162, 5, 107, 54, 2, 162, 163, 5, 87, 44, 2, 163, 28, 3, 2, 2, 2, 164, 165, 5, 109, 55, 2, 165, 166, 5, 115, 58, 2, 166, 30, 3, 2, 2, 2, 167, 168, 5, 107, 54, 2, 168, 169, 5, 109, 55, 2, 169, 170, 5, 119, 60, 2, 170, 32, 3, 2, 2, 2, 171, 172, 7, 42, 2, 2, 172, 34, 3, 2, 2, 2, 173, 174, 7, 43, 2, 2, 174, 36, 3, 2, 2, 2, 175, 176, 7, 125, 2, 2, 176, 38, 3, 2, 2, 2, 177, 178, 7, 127, 2, 2, 178, 40, 3, 2, 2, 2, 179, 180, 7, 93, 2, 2, 180, 42, 3, 2, 2, 2, 181, 182, 7, 95, 2, 2, 182, 44, 3, 2, 2, 2, 183, 184, 7, 46, 2, 2, 184, 46, 3, 2, 2, 2, 185, 186, 7, 61, 2, 2, 186, 48, 3, 2, 2, 2, 187, 188, 7, 60, 2, 2, 188, 50, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 52, 3, 2, 2, 2, 191, 196, 5, 79, 40, 2, 192, 195, 5, 79, 40, 2, 193, 195, 5, 77, 39, 2, 194, 192, 3, 2, 2, 2, 194, 193, 3, 2, 2, 2, 195, 198, 3, 2, 2, 2, 196, 194, 3, 2, 2, 2, 196, 197, 3, 2, 2, 2, 197, 54, 3, 2, 2, 2, 198, 196, 3, 2, 2, 2, 199, 201, 5, 77, 39, 2, 200, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 202, 203, 3, 2, 2, 2, 203, 210, 3, 2, 2, 2, 204, 206, 7, 48, 2, 2, 205, 207, 5, 77, 39, 2, 206, 205, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 211, 3, 2, 2, 2, 210, 204, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 56, 3, 2, 2, 2, 212, 218, 7, 41, 2, 2, 213, 217, 10, 2, 2, 2, 214, 215, 7, 41, 2, 2, 215, 217, 7, 41, 2, 2, 216, 213, 3, 2, 2, 2, 216, 214, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 221, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 222, 7, 41, 2, 2, 222, 58, 3, 2, 2, 2, 223, 224, 5, 119, 60, 2, 224, 225, 5, 115, 58, 2, 225, 226, 5, 121, 61, 2, 226, 227, 5, 89, 45, 2, 227, 60, 3, 2, 2, 2, 228, 229, 5, 91, 46, 2, 229, 230, 5, 81, 41, 2, 230, 231, 5, 103, 52, 2, 231, 232, 5, 117, 59, 2, 232, 233, 5, 89, 45, 2, 233, 62, 3, 2, 2, 2, 234, 235, 5, 107, 54, 2, 235, 236, 5, 121, 61, 2, 236, 237, 5, 103, 52, 2, 237, 238, 5, 103, 52, 2, 238, 64, 3, 2, 2, 2, 239, 240, 5, 87, 44, 2, 240, 241, 5, 81, 41, 2, 241, 242, 5, 119, 60, 2, 242, 243, 5, 89, 45, 2, 243, 66, 3, 2, 2, 2, 244, 245, 5, 119, 60, 2, 245, 246, 5, 97, 49, 2, 246, 247, 5, 105, 53, 2, 247, 248, 5, 89, 45, 2, 248, 68, 3, 2, 2, 2, 249, 250, 5, 87, 44, 2, 250, 251, 5, 81, 41, 2, 251, 252, 5, 119, 60, 2, 252, 253, 5, 89, 45, 2, 253, 254, 5, 119, 60, 2, 254, 255, 5, 97, 49, 2, 255, 256, 5, 105, 53, 2, 256, 257, 5, 89, 45, 2, 257, 70, 3, 2, 2, 2, 258, 260, 9, 3, 2, 2, 259, 258, 3, 2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 259, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 264, 8, 36, 2, 2, 264, 72, 3, 2, 2, 2, 265, 266, 7, 49, 2, 2, 266, 267, 7, 49, 2, 2, 267, 271, 3, 2, 2, 2, 268, 270, 10, 4, 2, 2, 269, 268, 3, 2, 2, 2, 270, 273, 3, 2, 2, 2, 271, 269, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 274, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 274, 275, 8, 37, 2, 2, 275, 74, 3, 2, 2, 2, 276, 277, 7, 49, 2, 2, 277, 278, 7, 44, 2, 2, 278, 282, 3, 2, 2, 2, 279, 281, 11, 2, 2, 2, 280, 279, 3, 2, 2, 2, 281, 284, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 282, 280, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 282, 3, 2, 2, 2, 285, 286, 7, 44, 2, 2, 286, 287, 7, 49, 2, 2, 287, 288, 3, 2, 2, 2, 288, 289, 8, 38, 2, 2, 289, 76, 3, 2, 2, 2, 290, 291, 9, 5, 2, 2, 291, 78, 3, 2, 2, 2, 292, 293, 9, 6, 2, 2, 293, 80, 3, 2, 2, 2, 294, 295, 9, 7, 2, 2, 295, 82, 3, 2, 2, 2, 296, 297, 9, 8, 2, 2, 297, 84, 3, 2, 2, 2, 298, 299, 9, 9, 2, 2, 299, 86, 3, 2, 2, 2, 300, 301, 9, 10, 2, 2, 301, 88, 3, 2, 2, 2, 302, 303, 9, 11, 2, 2, 303, 90, 3, 2, 2, 2, 304, 305, 9, 12, 2, 2, 305, 92, 3, 2, 2, 2, 306, 307, 9, 13, 2, 2, 307, 94, 3, 2, 2, 2, 308, 309, 9, 14, 2, 2, 309, 96, 3, 2, 2, 2, 310, 311, 9, 15, 2, 2, 311, 98, 3, 2, 2, 2, 312, 313, 9, 16, 2, 2, 313, 100, 3, 2, 2, 2, 314, 315, 9, 17, 2, 2, 315, 102, 3, 2, 2, 2, 316, 317, 9, 18, 2, 2, 317, 104, 3, 2, 2, 2, 318, 319, 9, 19, 2, 2, 319, 106, 3, 2, 2, 2, 320, 321, 9, 20, 2, 2, 321, 108, 3, 2, 2, 2, 322, 323, 9, 21, 2, 2, 323, 110, 3, 2, 2, 2, 324, 325, 9, 22, 2, 2, 325, 112, 3, 2, 2, 2, 326, 327, 9, 23, 2, 2, 327, 114, 3, 2, 2, 2, 328, 329, 9, 24, 2, 2, 329, 116, 3, 2, 2, 2, 330, 331, 9, 25, 2, 2, 331, 118, 3, 2, 2, 2, 332, 333, 9, 26, 2, 2, 333, 120, 3, 2, 2, 2, 334, 335, 9, 27, 2, 2, 335, 122, 3, 2, 2, 2, 336, 337, 9, 28, 2, 2, 337, 124, 3, 2, 2, 2, 338, 339, 9, 29, 2, 2, 339, 126, 3, 2, 2, 2, 340, 341, 9, 30, 2, 2, 341, 128, 3, 2, 2, 2, 342, 343, 9, 31, 2, 2, 343, 130, 3, 2, 2, 2, 344, 345, 9, 32, 2, 2, 345, 132, 3, 2, 2, 2, 13, 2, 194, 196, 202, 208, 210, 216, 218, 261, 271, 282, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/formula/generated/src/grammar/FormulaLexer.tokens b/packages/formula/generated/src/grammar/FormulaLexer.tokens new file mode 100644 index 000000000..58c5ff007 --- /dev/null +++ b/packages/formula/generated/src/grammar/FormulaLexer.tokens @@ -0,0 +1,59 @@ +ADD=1 +SUBTRACT=2 +MULTIPLY=3 +DIVIDE=4 +MODULO=5 +POWER=6 +EQUAL=7 +NOT_EQUAL=8 +LESS=9 +LESS_EQUAL=10 +GREATER=11 +GREATER_EQUAL=12 +AND=13 +OR=14 +NOT=15 +LPAREN=16 +RPAREN=17 +LBRACE=18 +RBRACE=19 +LBRACKET=20 +RBRACKET=21 +COMMA=22 +SEMICOLON=23 +COLON=24 +DOT=25 +IDENTIFIER=26 +NUMBER=27 +STRING=28 +TRUE=29 +FALSE=30 +NULL=31 +DATE=32 +TIME=33 +DATETIME=34 +WS=35 +COMMENT=36 +MULTILINE_COMMENT=37 +'+'=1 +'-'=2 +'*'=3 +'/'=4 +'%'=5 +'^'=6 +'='=7 +'!='=8 +'<'=9 +'<='=10 +'>'=11 +'>='=12 +'('=16 +')'=17 +'{'=18 +'}'=19 +'['=20 +']'=21 +','=22 +';'=23 +':'=24 +'.'=25 diff --git a/packages/formula/generated/src/grammar/FormulaLexer.ts b/packages/formula/generated/src/grammar/FormulaLexer.ts new file mode 100644 index 000000000..6b4d918b7 --- /dev/null +++ b/packages/formula/generated/src/grammar/FormulaLexer.ts @@ -0,0 +1,282 @@ +// Generated from src/grammar/FormulaLexer.g4 by ANTLR 4.9.0-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { NotNull } from "antlr4ts/Decorators"; +import { Override } from "antlr4ts/Decorators"; +import { RuleContext } from "antlr4ts/RuleContext"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + + +export class FormulaLexer extends Lexer { + public static readonly ADD = 1; + public static readonly SUBTRACT = 2; + public static readonly MULTIPLY = 3; + public static readonly DIVIDE = 4; + public static readonly MODULO = 5; + public static readonly POWER = 6; + public static readonly EQUAL = 7; + public static readonly NOT_EQUAL = 8; + public static readonly LESS = 9; + public static readonly LESS_EQUAL = 10; + public static readonly GREATER = 11; + public static readonly GREATER_EQUAL = 12; + public static readonly AND = 13; + public static readonly OR = 14; + public static readonly NOT = 15; + public static readonly LPAREN = 16; + public static readonly RPAREN = 17; + public static readonly LBRACE = 18; + public static readonly RBRACE = 19; + public static readonly LBRACKET = 20; + public static readonly RBRACKET = 21; + public static readonly COMMA = 22; + public static readonly SEMICOLON = 23; + public static readonly COLON = 24; + public static readonly DOT = 25; + public static readonly IDENTIFIER = 26; + public static readonly NUMBER = 27; + public static readonly STRING = 28; + public static readonly TRUE = 29; + public static readonly FALSE = 30; + public static readonly NULL = 31; + public static readonly DATE = 32; + public static readonly TIME = 33; + public static readonly DATETIME = 34; + public static readonly WS = 35; + public static readonly COMMENT = 36; + public static readonly MULTILINE_COMMENT = 37; + + // tslint:disable:no-trailing-whitespace + public static readonly channelNames: string[] = [ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + ]; + + // tslint:disable:no-trailing-whitespace + public static readonly modeNames: string[] = [ + "DEFAULT_MODE", + ]; + + public static readonly ruleNames: string[] = [ + "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", + "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", + "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET", "COMMA", + "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", "STRING", "TRUE", + "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", "MULTILINE_COMMENT", + "DIGIT", "LETTER", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", + "Z", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", + "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", + "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", + "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", + "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", + "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", + "MULTILINE_COMMENT", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaLexer._LITERAL_NAMES, FormulaLexer._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return FormulaLexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(FormulaLexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { return "FormulaLexer.g4"; } + + // @Override + public get ruleNames(): string[] { return FormulaLexer.ruleNames; } + + // @Override + public get serializedATN(): string { return FormulaLexer._serializedATN; } + + // @Override + public get channelNames(): string[] { return FormulaLexer.channelNames; } + + // @Override + public get modeNames(): string[] { return FormulaLexer.modeNames; } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02\'\u015A\b\x01" + + "\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06" + + "\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r" + + "\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t" + + "\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t" + + "\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t" + + "\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t" + + "\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t)\x04*\t*\x04" + + "+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x042\t2\x043\t3\x04" + + "4\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04<\t<\x04" + + "=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x03\x02\x03\x02\x03\x03\x03" + + "\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03" + + "\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03" + + "\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F" + + "\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x12\x03\x12\x03\x13" + + "\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17" + + "\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + + "\x07\x1B\xC3\n\x1B\f\x1B\x0E\x1B\xC6\v\x1B\x03\x1C\x06\x1C\xC9\n\x1C\r" + + "\x1C\x0E\x1C\xCA\x03\x1C\x03\x1C\x06\x1C\xCF\n\x1C\r\x1C\x0E\x1C\xD0\x05" + + "\x1C\xD3\n\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x07\x1D\xD9\n\x1D\f\x1D" + + "\x0E\x1D\xDC\v\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x03" + + "\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03" + + " \x03 \x03!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03\"\x03#\x03" + + "#\x03#\x03#\x03#\x03#\x03#\x03#\x03#\x03$\x06$\u0104\n$\r$\x0E$\u0105" + + "\x03$\x03$\x03%\x03%\x03%\x03%\x07%\u010E\n%\f%\x0E%\u0111\v%\x03%\x03" + + "%\x03&\x03&\x03&\x03&\x07&\u0119\n&\f&\x0E&\u011C\v&\x03&\x03&\x03&\x03" + + "&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03*\x03+\x03+\x03,\x03,\x03" + + "-\x03-\x03.\x03.\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x03" + + "4\x034\x035\x035\x036\x036\x037\x037\x038\x038\x039\x039\x03:\x03:\x03" + + ";\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03@\x03@\x03A\x03A\x03" + + "B\x03B\x03\u011A\x02\x02C\x03\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06" + + "\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17\x02\r\x19" + + "\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02\x11!\x02\x12#\x02\x13%\x02\x14" + + "\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/\x02\x191\x02\x1A3\x02\x1B5\x02" + + "\x1C7\x02\x1D9\x02\x1E;\x02\x1F=\x02 ?\x02!A\x02\"C\x02#E\x02$G\x02%I" + + "\x02&K\x02\'M\x02\x02O\x02\x02Q\x02\x02S\x02\x02U\x02\x02W\x02\x02Y\x02" + + "\x02[\x02\x02]\x02\x02_\x02\x02a\x02\x02c\x02\x02e\x02\x02g\x02\x02i\x02" + + "\x02k\x02\x02m\x02\x02o\x02\x02q\x02\x02s\x02\x02u\x02\x02w\x02\x02y\x02" + + "\x02{\x02\x02}\x02\x02\x7F\x02\x02\x81\x02\x02\x83\x02\x02\x03\x02!\x03" + + "\x02))\x05\x02\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x022;\x04\x02C" + + "\\c|\x04\x02CCcc\x04\x02DDdd\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02" + + "HHhh\x04\x02IIii\x04\x02JJjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02" + + "NNnn\x04\x02OOoo\x04\x02PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02" + + "TTtt\x04\x02UUuu\x04\x02VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02" + + "ZZzz\x04\x02[[{{\x04\x02\\\\||\x02\u0147\x02\x03\x03\x02\x02\x02\x02\x05" + + "\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03" + + "\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03" + + "\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03" + + "\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03" + + "\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02" + + "\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02" + + "\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03" + + "\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02" + + "\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02" + + "?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02\x02\x02E\x03\x02" + + "\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02K\x03\x02\x02\x02" + + "\x03\x85\x03\x02\x02\x02\x05\x87\x03\x02\x02\x02\x07\x89\x03\x02\x02\x02" + + "\t\x8B\x03\x02\x02\x02\v\x8D\x03\x02\x02\x02\r\x8F\x03\x02\x02\x02\x0F" + + "\x91\x03\x02\x02\x02\x11\x93\x03\x02\x02\x02\x13\x96\x03\x02\x02\x02\x15" + + "\x98\x03\x02\x02\x02\x17\x9B\x03\x02\x02\x02\x19\x9D\x03\x02\x02\x02\x1B" + + "\xA0\x03\x02\x02\x02\x1D\xA4\x03\x02\x02\x02\x1F\xA7\x03\x02\x02\x02!" + + "\xAB\x03\x02\x02\x02#\xAD\x03\x02\x02\x02%\xAF\x03\x02\x02\x02\'\xB1\x03" + + "\x02\x02\x02)\xB3\x03\x02\x02\x02+\xB5\x03\x02\x02\x02-\xB7\x03\x02\x02" + + "\x02/\xB9\x03\x02\x02\x021\xBB\x03\x02\x02\x023\xBD\x03\x02\x02\x025\xBF" + + "\x03\x02\x02\x027\xC8\x03\x02\x02\x029\xD4\x03\x02\x02\x02;\xDF\x03\x02" + + "\x02\x02=\xE4\x03\x02\x02\x02?\xEA\x03\x02\x02\x02A\xEF\x03\x02\x02\x02" + + "C\xF4\x03\x02\x02\x02E\xF9\x03\x02\x02\x02G\u0103\x03\x02\x02\x02I\u0109" + + "\x03\x02\x02\x02K\u0114\x03\x02\x02\x02M\u0122\x03\x02\x02\x02O\u0124" + + "\x03\x02\x02\x02Q\u0126\x03\x02\x02\x02S\u0128\x03\x02\x02\x02U\u012A" + + "\x03\x02\x02\x02W\u012C\x03\x02\x02\x02Y\u012E\x03\x02\x02\x02[\u0130" + + "\x03\x02\x02\x02]\u0132\x03\x02\x02\x02_\u0134\x03\x02\x02\x02a\u0136" + + "\x03\x02\x02\x02c\u0138\x03\x02\x02\x02e\u013A\x03\x02\x02\x02g\u013C" + + "\x03\x02\x02\x02i\u013E\x03\x02\x02\x02k\u0140\x03\x02\x02\x02m\u0142" + + "\x03\x02\x02\x02o\u0144\x03\x02\x02\x02q\u0146\x03\x02\x02\x02s\u0148" + + "\x03\x02\x02\x02u\u014A\x03\x02\x02\x02w\u014C\x03\x02\x02\x02y\u014E" + + "\x03\x02\x02\x02{\u0150\x03\x02\x02\x02}\u0152\x03\x02\x02\x02\x7F\u0154" + + "\x03\x02\x02\x02\x81\u0156\x03\x02\x02\x02\x83\u0158\x03\x02\x02\x02\x85" + + "\x86\x07-\x02\x02\x86\x04\x03\x02\x02\x02\x87\x88\x07/\x02\x02\x88\x06" + + "\x03\x02\x02\x02\x89\x8A\x07,\x02\x02\x8A\b\x03\x02\x02\x02\x8B\x8C\x07" + + "1\x02\x02\x8C\n\x03\x02\x02\x02\x8D\x8E\x07\'\x02\x02\x8E\f\x03\x02\x02" + + "\x02\x8F\x90\x07`\x02\x02\x90\x0E\x03\x02\x02\x02\x91\x92\x07?\x02\x02" + + "\x92\x10\x03\x02\x02\x02\x93\x94\x07#\x02\x02\x94\x95\x07?\x02\x02\x95" + + "\x12\x03\x02\x02\x02\x96\x97\x07>\x02\x02\x97\x14\x03\x02\x02\x02\x98" + + "\x99\x07>\x02\x02\x99\x9A\x07?\x02\x02\x9A\x16\x03\x02\x02\x02\x9B\x9C" + + "\x07@\x02\x02\x9C\x18\x03\x02\x02\x02\x9D\x9E\x07@\x02\x02\x9E\x9F\x07" + + "?\x02\x02\x9F\x1A\x03\x02\x02\x02\xA0\xA1\x05Q)\x02\xA1\xA2\x05k6\x02" + + "\xA2\xA3\x05W,\x02\xA3\x1C\x03\x02\x02\x02\xA4\xA5\x05m7\x02\xA5\xA6\x05" + + "s:\x02\xA6\x1E\x03\x02\x02\x02\xA7\xA8\x05k6\x02\xA8\xA9\x05m7\x02\xA9" + + "\xAA\x05w<\x02\xAA \x03\x02\x02\x02\xAB\xAC\x07*\x02\x02\xAC\"\x03\x02" + + "\x02\x02\xAD\xAE\x07+\x02\x02\xAE$\x03\x02\x02\x02\xAF\xB0\x07}\x02\x02" + + "\xB0&\x03\x02\x02\x02\xB1\xB2\x07\x7F\x02\x02\xB2(\x03\x02\x02\x02\xB3" + + "\xB4\x07]\x02\x02\xB4*\x03\x02\x02\x02\xB5\xB6\x07_\x02\x02\xB6,\x03\x02" + + "\x02\x02\xB7\xB8\x07.\x02\x02\xB8.\x03\x02\x02\x02\xB9\xBA\x07=\x02\x02" + + "\xBA0\x03\x02\x02\x02\xBB\xBC\x07<\x02\x02\xBC2\x03\x02\x02\x02\xBD\xBE" + + "\x070\x02\x02\xBE4\x03\x02\x02\x02\xBF\xC4\x05O(\x02\xC0\xC3\x05O(\x02" + + "\xC1\xC3\x05M\'\x02\xC2\xC0\x03\x02\x02\x02\xC2\xC1\x03\x02\x02\x02\xC3" + + "\xC6\x03\x02\x02\x02\xC4\xC2\x03\x02\x02\x02\xC4\xC5\x03\x02\x02\x02\xC5" + + "6\x03\x02\x02\x02\xC6\xC4\x03\x02\x02\x02\xC7\xC9\x05M\'\x02\xC8\xC7\x03" + + "\x02\x02\x02\xC9\xCA\x03\x02\x02\x02\xCA\xC8\x03\x02\x02\x02\xCA\xCB\x03" + + "\x02\x02\x02\xCB\xD2\x03\x02\x02\x02\xCC\xCE\x070\x02\x02\xCD\xCF\x05" + + "M\'\x02\xCE\xCD\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xCE\x03\x02" + + "\x02\x02\xD0\xD1\x03\x02\x02\x02\xD1\xD3\x03\x02\x02\x02\xD2\xCC\x03\x02" + + "\x02\x02\xD2\xD3\x03\x02\x02\x02\xD38\x03\x02\x02\x02\xD4\xDA\x07)\x02" + + "\x02\xD5\xD9\n\x02\x02\x02\xD6\xD7\x07)\x02\x02\xD7\xD9\x07)\x02\x02\xD8" + + "\xD5\x03\x02\x02\x02\xD8\xD6\x03\x02\x02\x02\xD9\xDC\x03\x02\x02\x02\xDA" + + "\xD8\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDB\xDD\x03\x02\x02\x02\xDC" + + "\xDA\x03\x02\x02\x02\xDD\xDE\x07)\x02\x02\xDE:\x03\x02\x02\x02\xDF\xE0" + + "\x05w<\x02\xE0\xE1\x05s:\x02\xE1\xE2\x05y=\x02\xE2\xE3\x05Y-\x02\xE3<" + + "\x03\x02\x02\x02\xE4\xE5\x05[.\x02\xE5\xE6\x05Q)\x02\xE6\xE7\x05g4\x02" + + "\xE7\xE8\x05u;\x02\xE8\xE9\x05Y-\x02\xE9>\x03\x02\x02\x02\xEA\xEB\x05" + + "k6\x02\xEB\xEC\x05y=\x02\xEC\xED\x05g4\x02\xED\xEE\x05g4\x02\xEE@\x03" + + "\x02\x02\x02\xEF\xF0\x05W,\x02\xF0\xF1\x05Q)\x02\xF1\xF2\x05w<\x02\xF2" + + "\xF3\x05Y-\x02\xF3B\x03\x02\x02\x02\xF4\xF5\x05w<\x02\xF5\xF6\x05a1\x02" + + "\xF6\xF7\x05i5\x02\xF7\xF8\x05Y-\x02\xF8D\x03\x02\x02\x02\xF9\xFA\x05" + + "W,\x02\xFA\xFB\x05Q)\x02\xFB\xFC\x05w<\x02\xFC\xFD\x05Y-\x02\xFD\xFE\x05" + + "w<\x02\xFE\xFF\x05a1\x02\xFF\u0100\x05i5\x02\u0100\u0101\x05Y-\x02\u0101" + + "F\x03\x02\x02\x02\u0102\u0104\t\x03\x02\x02\u0103\u0102\x03\x02\x02\x02" + + "\u0104\u0105\x03\x02\x02\x02\u0105\u0103\x03\x02\x02\x02\u0105\u0106\x03" + + "\x02\x02\x02\u0106\u0107\x03\x02\x02\x02\u0107\u0108\b$\x02\x02\u0108" + + "H\x03\x02\x02\x02\u0109\u010A\x071\x02\x02\u010A\u010B\x071\x02\x02\u010B" + + "\u010F\x03\x02\x02\x02\u010C\u010E\n\x04\x02\x02\u010D\u010C\x03\x02\x02" + + "\x02\u010E\u0111\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02\u010F\u0110" + + "\x03\x02\x02\x02\u0110\u0112\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02" + + "\u0112\u0113\b%\x02\x02\u0113J\x03\x02\x02\x02\u0114\u0115\x071\x02\x02" + + "\u0115\u0116\x07,\x02\x02\u0116\u011A\x03\x02\x02\x02\u0117\u0119\v\x02" + + "\x02\x02\u0118\u0117\x03\x02\x02\x02\u0119\u011C\x03\x02\x02\x02\u011A" + + "\u011B\x03\x02\x02\x02\u011A\u0118\x03\x02\x02\x02\u011B\u011D\x03\x02" + + "\x02\x02\u011C\u011A\x03\x02\x02\x02\u011D\u011E\x07,\x02\x02\u011E\u011F" + + "\x071\x02\x02\u011F\u0120\x03\x02\x02\x02\u0120\u0121\b&\x02\x02\u0121" + + "L\x03\x02\x02\x02\u0122\u0123\t\x05\x02\x02\u0123N\x03\x02\x02\x02\u0124" + + "\u0125\t\x06\x02\x02\u0125P\x03\x02\x02\x02\u0126\u0127\t\x07\x02\x02" + + "\u0127R\x03\x02\x02\x02\u0128\u0129\t\b\x02\x02\u0129T\x03\x02\x02\x02" + + "\u012A\u012B\t\t\x02\x02\u012BV\x03\x02\x02\x02\u012C\u012D\t\n\x02\x02" + + "\u012DX\x03\x02\x02\x02\u012E\u012F\t\v\x02\x02\u012FZ\x03\x02\x02\x02" + + "\u0130\u0131\t\f\x02\x02\u0131\\\x03\x02\x02\x02\u0132\u0133\t\r\x02\x02" + + "\u0133^\x03\x02\x02\x02\u0134\u0135\t\x0E\x02\x02\u0135`\x03\x02\x02\x02" + + "\u0136\u0137\t\x0F\x02\x02\u0137b\x03\x02\x02\x02\u0138\u0139\t\x10\x02" + + "\x02\u0139d\x03\x02\x02\x02\u013A\u013B\t\x11\x02\x02\u013Bf\x03\x02\x02" + + "\x02\u013C\u013D\t\x12\x02\x02\u013Dh\x03\x02\x02\x02\u013E\u013F\t\x13" + + "\x02\x02\u013Fj\x03\x02\x02\x02\u0140\u0141\t\x14\x02\x02\u0141l\x03\x02" + + "\x02\x02\u0142\u0143\t\x15\x02\x02\u0143n\x03\x02\x02\x02\u0144\u0145" + + "\t\x16\x02\x02\u0145p\x03\x02\x02\x02\u0146\u0147\t\x17\x02\x02\u0147" + + "r\x03\x02\x02\x02\u0148\u0149\t\x18\x02\x02\u0149t\x03\x02\x02\x02\u014A" + + "\u014B\t\x19\x02\x02\u014Bv\x03\x02\x02\x02\u014C\u014D\t\x1A\x02\x02" + + "\u014Dx\x03\x02\x02\x02\u014E\u014F\t\x1B\x02\x02\u014Fz\x03\x02\x02\x02" + + "\u0150\u0151\t\x1C\x02\x02\u0151|\x03\x02\x02\x02\u0152\u0153\t\x1D\x02" + + "\x02\u0153~\x03\x02\x02\x02\u0154\u0155\t\x1E\x02\x02\u0155\x80\x03\x02" + + "\x02\x02\u0156\u0157\t\x1F\x02\x02\u0157\x82\x03\x02\x02\x02\u0158\u0159" + + "\t \x02\x02\u0159\x84\x03\x02\x02\x02\r\x02\xC2\xC4\xCA\xD0\xD2\xD8\xDA" + + "\u0105\u010F\u011A\x03\b\x02\x02"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!FormulaLexer.__ATN) { + FormulaLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(FormulaLexer._serializedATN)); + } + + return FormulaLexer.__ATN; + } + +} + diff --git a/packages/formula/package.json b/packages/formula/package.json index 0458ffdb5..37e949a95 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -2,6 +2,7 @@ "name": "@undb/formula", "module": "src/index.ts", "type": "module", + "types": "src/index.d.ts", "devDependencies": { "@types/bun": "latest", "antlr4ts-cli": "^0.5.0-alpha.4" diff --git a/packages/formula/scripts/generate-parser.ts b/packages/formula/scripts/generate-parser.ts index b36fcecbe..c898b2df3 100644 --- a/packages/formula/scripts/generate-parser.ts +++ b/packages/formula/scripts/generate-parser.ts @@ -1,3 +1,3 @@ import { $ } from "bun" -await $`antlr4ts -visitor src/grammar/*.g4` +await $`antlr4ts -visitor -no-listener src/grammar/*.g4` diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index 06eb65797..1bb0f3731 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -26,17 +26,32 @@ export class FunctionRegistry { throw new Error(`Unknown function: ${name}`) } - const isValidPattern = funcDef.paramPatterns.some((pattern) => { - if (pattern.length > args.length) { - return false + // 检查是否有任何模式的参数数量匹配 + const hasMatchingPattern = funcDef.paramPatterns.some((pattern) => { + // 如果模式中包含 VARIADIC,则参数数量必须大于等于 pattern.length - 1 + // 否则参数数量必须完全匹配 + if (pattern.includes(ParamType.VARIADIC)) { + return args.length >= pattern.length - 1 } + return args.length === pattern.length + }) + if (!hasMatchingPattern) { + const expectedCounts = funcDef.paramPatterns + .map((pattern) => + pattern.includes(ParamType.VARIADIC) ? `at least ${pattern.length - 1}` : `${pattern.length}`, + ) + .join(" or ") + throw new Error(`Function ${name} expects ${expectedCounts} arguments, but got ${args.length}`) + } + + const isValidPattern = funcDef.paramPatterns.some((pattern) => { for (let i = 0; i < pattern.length; i++) { const expectedType = pattern[i] if (expectedType === ParamType.VARIADIC) { // 剩余的所有参数都应该匹配 VARIADIC 的前一个类型 const variadicType = pattern[i - 1] - return args.slice(i).every((arg) => this.isTypeMatch(arg, variadicType)) + return args.slice(i - 1).every((arg) => this.isTypeMatch(arg, variadicType)) } if (!this.isTypeMatch(args[i], expectedType)) { return false diff --git a/packages/formula/src/grammar/FormulaParser.ts b/packages/formula/src/grammar/FormulaParser.ts index 5a3f8f813..5763b9c3f 100644 --- a/packages/formula/src/grammar/FormulaParser.ts +++ b/packages/formula/src/grammar/FormulaParser.ts @@ -23,7 +23,6 @@ import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; import * as Utils from "antlr4ts/misc/Utils"; -import { FormulaParserListener } from "./FormulaParserListener"; import { FormulaParserVisitor } from "./FormulaParserVisitor"; @@ -646,18 +645,6 @@ export class FormulaContext extends ParserRuleContext { // @Override public get ruleIndex(): number { return FormulaParser.RULE_formula; } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterFormula) { - listener.enterFormula(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitFormula) { - listener.exitFormula(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitFormula) { return visitor.visitFormula(this); @@ -697,18 +684,6 @@ export class MulDivModExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterMulDivModExpr) { - listener.enterMulDivModExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitMulDivModExpr) { - listener.exitMulDivModExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitMulDivModExpr) { return visitor.visitMulDivModExpr(this); @@ -735,18 +710,6 @@ export class AddSubExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterAddSubExpr) { - listener.enterAddSubExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitAddSubExpr) { - listener.exitAddSubExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitAddSubExpr) { return visitor.visitAddSubExpr(this); @@ -771,18 +734,6 @@ export class PowerExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterPowerExpr) { - listener.enterPowerExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitPowerExpr) { - listener.exitPowerExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitPowerExpr) { return visitor.visitPowerExpr(this); @@ -813,18 +764,6 @@ export class ComparisonExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterComparisonExpr) { - listener.enterComparisonExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitComparisonExpr) { - listener.exitComparisonExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitComparisonExpr) { return visitor.visitComparisonExpr(this); @@ -849,18 +788,6 @@ export class AndExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterAndExpr) { - listener.enterAndExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitAndExpr) { - listener.exitAndExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitAndExpr) { return visitor.visitAndExpr(this); @@ -885,18 +812,6 @@ export class OrExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterOrExpr) { - listener.enterOrExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitOrExpr) { - listener.exitOrExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitOrExpr) { return visitor.visitOrExpr(this); @@ -915,18 +830,6 @@ export class NotExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterNotExpr) { - listener.enterNotExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitNotExpr) { - listener.exitNotExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitNotExpr) { return visitor.visitNotExpr(this); @@ -944,18 +847,6 @@ export class FunctionExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterFunctionExpr) { - listener.enterFunctionExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitFunctionExpr) { - listener.exitFunctionExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitFunctionExpr) { return visitor.visitFunctionExpr(this); @@ -973,18 +864,6 @@ export class VariableExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterVariableExpr) { - listener.enterVariableExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitVariableExpr) { - listener.exitVariableExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitVariableExpr) { return visitor.visitVariableExpr(this); @@ -1000,18 +879,6 @@ export class NumberExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterNumberExpr) { - listener.enterNumberExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitNumberExpr) { - listener.exitNumberExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitNumberExpr) { return visitor.visitNumberExpr(this); @@ -1027,18 +894,6 @@ export class StringExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterStringExpr) { - listener.enterStringExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitStringExpr) { - listener.exitStringExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitStringExpr) { return visitor.visitStringExpr(this); @@ -1054,18 +909,6 @@ export class TrueExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterTrueExpr) { - listener.enterTrueExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitTrueExpr) { - listener.exitTrueExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitTrueExpr) { return visitor.visitTrueExpr(this); @@ -1081,18 +924,6 @@ export class FalseExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterFalseExpr) { - listener.enterFalseExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitFalseExpr) { - listener.exitFalseExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitFalseExpr) { return visitor.visitFalseExpr(this); @@ -1108,18 +939,6 @@ export class NullExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterNullExpr) { - listener.enterNullExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitNullExpr) { - listener.exitNullExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitNullExpr) { return visitor.visitNullExpr(this); @@ -1135,18 +954,6 @@ export class DateExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterDateExpr) { - listener.enterDateExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitDateExpr) { - listener.exitDateExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitDateExpr) { return visitor.visitDateExpr(this); @@ -1162,18 +969,6 @@ export class TimeExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterTimeExpr) { - listener.enterTimeExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitTimeExpr) { - listener.exitTimeExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitTimeExpr) { return visitor.visitTimeExpr(this); @@ -1189,18 +984,6 @@ export class DateTimeExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterDateTimeExpr) { - listener.enterDateTimeExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitDateTimeExpr) { - listener.exitDateTimeExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitDateTimeExpr) { return visitor.visitDateTimeExpr(this); @@ -1220,18 +1003,6 @@ export class ParenExprContext extends ExpressionContext { this.copyFrom(ctx); } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterParenExpr) { - listener.enterParenExpr(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitParenExpr) { - listener.exitParenExpr(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitParenExpr) { return visitor.visitParenExpr(this); @@ -1255,18 +1026,6 @@ export class FunctionCallContext extends ParserRuleContext { // @Override public get ruleIndex(): number { return FormulaParser.RULE_functionCall; } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterFunctionCall) { - listener.enterFunctionCall(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitFunctionCall) { - listener.exitFunctionCall(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitFunctionCall) { return visitor.visitFunctionCall(this); @@ -1302,18 +1061,6 @@ export class ArgumentListContext extends ParserRuleContext { // @Override public get ruleIndex(): number { return FormulaParser.RULE_argumentList; } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterArgumentList) { - listener.enterArgumentList(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitArgumentList) { - listener.exitArgumentList(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitArgumentList) { return visitor.visitArgumentList(this); @@ -1350,18 +1097,6 @@ export class VariableContext extends ParserRuleContext { // @Override public get ruleIndex(): number { return FormulaParser.RULE_variable; } // @Override - public enterRule(listener: FormulaParserListener): void { - if (listener.enterVariable) { - listener.enterVariable(this); - } - } - // @Override - public exitRule(listener: FormulaParserListener): void { - if (listener.exitVariable) { - listener.exitVariable(this); - } - } - // @Override public accept(visitor: FormulaParserVisitor): Result { if (visitor.visitVariable) { return visitor.visitVariable(this); diff --git a/packages/formula/src/grammar/FormulaParserListener.ts b/packages/formula/src/grammar/FormulaParserListener.ts deleted file mode 100644 index e5fa0e4d4..000000000 --- a/packages/formula/src/grammar/FormulaParserListener.ts +++ /dev/null @@ -1,325 +0,0 @@ -// Generated from src/grammar/FormulaParser.g4 by ANTLR 4.9.0-SNAPSHOT - - -import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; - -import { MulDivModExprContext } from "./FormulaParser"; -import { AddSubExprContext } from "./FormulaParser"; -import { PowerExprContext } from "./FormulaParser"; -import { ComparisonExprContext } from "./FormulaParser"; -import { AndExprContext } from "./FormulaParser"; -import { OrExprContext } from "./FormulaParser"; -import { NotExprContext } from "./FormulaParser"; -import { FunctionExprContext } from "./FormulaParser"; -import { VariableExprContext } from "./FormulaParser"; -import { NumberExprContext } from "./FormulaParser"; -import { StringExprContext } from "./FormulaParser"; -import { TrueExprContext } from "./FormulaParser"; -import { FalseExprContext } from "./FormulaParser"; -import { NullExprContext } from "./FormulaParser"; -import { DateExprContext } from "./FormulaParser"; -import { TimeExprContext } from "./FormulaParser"; -import { DateTimeExprContext } from "./FormulaParser"; -import { ParenExprContext } from "./FormulaParser"; -import { FormulaContext } from "./FormulaParser"; -import { ExpressionContext } from "./FormulaParser"; -import { FunctionCallContext } from "./FormulaParser"; -import { ArgumentListContext } from "./FormulaParser"; -import { VariableContext } from "./FormulaParser"; - - -/** - * This interface defines a complete listener for a parse tree produced by - * `FormulaParser`. - */ -export interface FormulaParserListener extends ParseTreeListener { - /** - * Enter a parse tree produced by the `MulDivModExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterMulDivModExpr?: (ctx: MulDivModExprContext) => void; - /** - * Exit a parse tree produced by the `MulDivModExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitMulDivModExpr?: (ctx: MulDivModExprContext) => void; - - /** - * Enter a parse tree produced by the `AddSubExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterAddSubExpr?: (ctx: AddSubExprContext) => void; - /** - * Exit a parse tree produced by the `AddSubExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitAddSubExpr?: (ctx: AddSubExprContext) => void; - - /** - * Enter a parse tree produced by the `PowerExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterPowerExpr?: (ctx: PowerExprContext) => void; - /** - * Exit a parse tree produced by the `PowerExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitPowerExpr?: (ctx: PowerExprContext) => void; - - /** - * Enter a parse tree produced by the `ComparisonExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterComparisonExpr?: (ctx: ComparisonExprContext) => void; - /** - * Exit a parse tree produced by the `ComparisonExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitComparisonExpr?: (ctx: ComparisonExprContext) => void; - - /** - * Enter a parse tree produced by the `AndExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterAndExpr?: (ctx: AndExprContext) => void; - /** - * Exit a parse tree produced by the `AndExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitAndExpr?: (ctx: AndExprContext) => void; - - /** - * Enter a parse tree produced by the `OrExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterOrExpr?: (ctx: OrExprContext) => void; - /** - * Exit a parse tree produced by the `OrExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitOrExpr?: (ctx: OrExprContext) => void; - - /** - * Enter a parse tree produced by the `NotExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterNotExpr?: (ctx: NotExprContext) => void; - /** - * Exit a parse tree produced by the `NotExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitNotExpr?: (ctx: NotExprContext) => void; - - /** - * Enter a parse tree produced by the `FunctionExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterFunctionExpr?: (ctx: FunctionExprContext) => void; - /** - * Exit a parse tree produced by the `FunctionExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitFunctionExpr?: (ctx: FunctionExprContext) => void; - - /** - * Enter a parse tree produced by the `VariableExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterVariableExpr?: (ctx: VariableExprContext) => void; - /** - * Exit a parse tree produced by the `VariableExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitVariableExpr?: (ctx: VariableExprContext) => void; - - /** - * Enter a parse tree produced by the `NumberExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterNumberExpr?: (ctx: NumberExprContext) => void; - /** - * Exit a parse tree produced by the `NumberExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitNumberExpr?: (ctx: NumberExprContext) => void; - - /** - * Enter a parse tree produced by the `StringExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterStringExpr?: (ctx: StringExprContext) => void; - /** - * Exit a parse tree produced by the `StringExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitStringExpr?: (ctx: StringExprContext) => void; - - /** - * Enter a parse tree produced by the `TrueExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterTrueExpr?: (ctx: TrueExprContext) => void; - /** - * Exit a parse tree produced by the `TrueExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitTrueExpr?: (ctx: TrueExprContext) => void; - - /** - * Enter a parse tree produced by the `FalseExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterFalseExpr?: (ctx: FalseExprContext) => void; - /** - * Exit a parse tree produced by the `FalseExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitFalseExpr?: (ctx: FalseExprContext) => void; - - /** - * Enter a parse tree produced by the `NullExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterNullExpr?: (ctx: NullExprContext) => void; - /** - * Exit a parse tree produced by the `NullExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitNullExpr?: (ctx: NullExprContext) => void; - - /** - * Enter a parse tree produced by the `DateExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterDateExpr?: (ctx: DateExprContext) => void; - /** - * Exit a parse tree produced by the `DateExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitDateExpr?: (ctx: DateExprContext) => void; - - /** - * Enter a parse tree produced by the `TimeExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterTimeExpr?: (ctx: TimeExprContext) => void; - /** - * Exit a parse tree produced by the `TimeExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitTimeExpr?: (ctx: TimeExprContext) => void; - - /** - * Enter a parse tree produced by the `DateTimeExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterDateTimeExpr?: (ctx: DateTimeExprContext) => void; - /** - * Exit a parse tree produced by the `DateTimeExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitDateTimeExpr?: (ctx: DateTimeExprContext) => void; - - /** - * Enter a parse tree produced by the `ParenExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterParenExpr?: (ctx: ParenExprContext) => void; - /** - * Exit a parse tree produced by the `ParenExpr` - * labeled alternative in `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitParenExpr?: (ctx: ParenExprContext) => void; - - /** - * Enter a parse tree produced by `FormulaParser.formula`. - * @param ctx the parse tree - */ - enterFormula?: (ctx: FormulaContext) => void; - /** - * Exit a parse tree produced by `FormulaParser.formula`. - * @param ctx the parse tree - */ - exitFormula?: (ctx: FormulaContext) => void; - - /** - * Enter a parse tree produced by `FormulaParser.expression`. - * @param ctx the parse tree - */ - enterExpression?: (ctx: ExpressionContext) => void; - /** - * Exit a parse tree produced by `FormulaParser.expression`. - * @param ctx the parse tree - */ - exitExpression?: (ctx: ExpressionContext) => void; - - /** - * Enter a parse tree produced by `FormulaParser.functionCall`. - * @param ctx the parse tree - */ - enterFunctionCall?: (ctx: FunctionCallContext) => void; - /** - * Exit a parse tree produced by `FormulaParser.functionCall`. - * @param ctx the parse tree - */ - exitFunctionCall?: (ctx: FunctionCallContext) => void; - - /** - * Enter a parse tree produced by `FormulaParser.argumentList`. - * @param ctx the parse tree - */ - enterArgumentList?: (ctx: ArgumentListContext) => void; - /** - * Exit a parse tree produced by `FormulaParser.argumentList`. - * @param ctx the parse tree - */ - exitArgumentList?: (ctx: ArgumentListContext) => void; - - /** - * Enter a parse tree produced by `FormulaParser.variable`. - * @param ctx the parse tree - */ - enterVariable?: (ctx: VariableContext) => void; - /** - * Exit a parse tree produced by `FormulaParser.variable`. - * @param ctx the parse tree - */ - exitVariable?: (ctx: VariableContext) => void; -} - diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index 1263a955e..96cd68dae 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -1,5 +1,4 @@ -import { parseFormula } from "./util" - -const input = "ADD(1, ADD(2, {{ field1 }}))" -// const input = "{{ field1 }} + 2" -const result = parseFormula(input) +export * from "./grammar/FormulaLexer" +export * from "./grammar/FormulaParser" +export * from "./util" +export * from "./visitor" diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap new file mode 100644 index 000000000..75bca35fd --- /dev/null +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -0,0 +1,82 @@ +// Bun Snapshot v1, https://goo.gl/fbAQLP + +exports[`parse formula test ADD(1, ADD(2, {{ field1 }})) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 2, + }, + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "ADD", + "returnType": 0, + "type": "functionCall", + "value": "ADD(2,{{field1}})", + }, + ], + "type": "argumentList", + }, + ], + "name": "ADD", + "returnType": 0, + "type": "functionCall", + "value": "ADD(1,ADD(2,{{field1}}))", +} +`; + +exports[`parse formula test ADD(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "ADD", + "returnType": 0, + "type": "functionCall", + "value": "ADD(1,2)", +} +`; + +exports[`parse formula test 1 1`] = ` +{ + "type": "number", + "value": 1, +} +`; + +exports[`parse formula test {{field1}} 1`] = ` +{ + "type": "variable", + "value": "{{field1}}", + "variable": "field1", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts new file mode 100644 index 000000000..4ceb1bc43 --- /dev/null +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, test } from "bun:test" +import { parseFormula } from "../util" + +describe("parse formula", () => { + test.each(["ADD(1, ADD(2, {{ field1 }}))", "ADD(1, 2)", "1", "{{field1}}"])("test %s", (input) => { + const result = parseFormula(input) + + expect(result).toMatchSnapshot() + }) +}) diff --git a/packages/formula/src/util.ts b/packages/formula/src/util.ts index beec02800..1f109f24e 100644 --- a/packages/formula/src/util.ts +++ b/packages/formula/src/util.ts @@ -3,16 +3,20 @@ import { FormulaLexer } from "./grammar/FormulaLexer" import { FormulaParser } from "./grammar/FormulaParser" import { CustomFormulaVisitor } from "./visitor" -export function parseFormula(input: string) { +export function createParser(input: string) { const inputStream = CharStreams.fromString(input) const lexer = new FormulaLexer(inputStream) const tokenStream = new CommonTokenStream(lexer) - const parser = new FormulaParser(tokenStream) + return new FormulaParser(tokenStream) +} + +export function parseFormula(input: string) { + const parser = createParser(input) const tree = parser.formula() const visitor = new CustomFormulaVisitor() const parsedFormula = visitor.visit(tree) - console.log(JSON.stringify(parsedFormula, null, 2)) + return parsedFormula } diff --git a/packages/formula/src/visitor.ts b/packages/formula/src/visitor.ts index 800f89a44..ffe1b1f4c 100644 --- a/packages/formula/src/visitor.ts +++ b/packages/formula/src/visitor.ts @@ -1,8 +1,10 @@ import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" +import { ParseTree } from "antlr4ts/tree/ParseTree" import { globalFunctionRegistry } from "./function/registry" import { AddSubExprContext, ArgumentListContext, + ExpressionContext, FormulaContext, FunctionCallContext, FunctionExprContext, @@ -80,7 +82,6 @@ export class CustomFormulaVisitor visitFunctionCall(ctx: FunctionCallContext): ExpressionResult { const funcName = ctx.IDENTIFIER().text - const fn = ctx.text const args = ctx.argumentList() ? (this.visit(ctx.argumentList()!) as FunctionExpressionResult) : undefined if (!globalFunctionRegistry.isValid(funcName) || !args) { @@ -122,3 +123,87 @@ export class CustomFormulaVisitor return { type: "string", value: "" } } } + +export class FormulaCursorVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { + private pathNodes: ParseTree[] = [] + private variables: Set = new Set() + public readonly targetPosition: number + + constructor(position: number) { + super() + this.targetPosition = position + } + + public hasAggumentList(): boolean { + return this.pathNodes.some((node) => node instanceof ArgumentListContext) + } + + public hasFunctionCall(): boolean { + return this.pathNodes.some((node) => node instanceof FunctionCallContext) + } + + public getNearestFunctionNode() { + for (let i = this.pathNodes.length - 1; i >= 0; i--) { + const node = this.pathNodes[i] + if (node instanceof FunctionCallContext) { + return node + } + } + return null + } + + public getFunctionName(): string | undefined { + const functionCall = this.getNearestFunctionNode() + return functionCall?.IDENTIFIER()?.text + } + + protected defaultResult(): void { + return undefined + } + + public getPathNodes() { + return this.pathNodes + } + + visitPositionInRange(ctx: ExpressionContext) { + if (!ctx.start || !ctx.stop) return + + const start = ctx.start.startIndex + const stop = ctx.stop.stopIndex + const isPositionWithinRange = start <= this.targetPosition && stop >= this.targetPosition + + if (isPositionWithinRange) { + this.pathNodes.push(ctx) + this.visitChildren(ctx) + } + } + + visitFormula(ctx: FormulaContext) { + this.visitPositionInRange(ctx) + } + + visitMulDivModExpr(ctx: MulDivModExprContext) { + this.visitPositionInRange(ctx) + } + + visitAddSubExpr(ctx: AddSubExprContext) { + this.visitPositionInRange(ctx) + } + + visitFunctionExpr(ctx: FunctionExprContext) { + this.visitPositionInRange(ctx) + } + + visitFunctionCall(ctx: FunctionCallContext) { + this.visitPositionInRange(ctx) + } + + visitArgumentList(ctx: ArgumentListContext) { + this.visitPositionInRange(ctx) + } + + visitVariable(ctx: VariableContext) { + this.variables.add(ctx.IDENTIFIER().text) + this.visitPositionInRange(ctx) + } +} diff --git a/packages/formula/tsconfig.json b/packages/formula/tsconfig.json index 238655f2c..beaa6792e 100644 --- a/packages/formula/tsconfig.json +++ b/packages/formula/tsconfig.json @@ -9,9 +9,9 @@ "allowJs": true, // Bundler mode - "moduleResolution": "bundler", + "moduleResolution": "Bundler", "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, + "verbatimModuleSyntax": false, "noEmit": true, // Best practices diff --git a/packages/persistence/src/record/record.filter-visitor.test.ts b/packages/persistence/src/record/record.filter-visitor.test.ts deleted file mode 100644 index eefb49722..000000000 --- a/packages/persistence/src/record/record.filter-visitor.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Schema, ViewFilter, type IConditionGroup } from "@undb/table" -import Database from "bun:sqlite" -import { describe, expect, test } from "bun:test" -import { Kysely } from "kysely" -import { BunSqliteDialect } from "kysely-bun-sqlite" -import type { IQueryBuilder } from "../qb" -import { RecordFilterVisitor } from "./record.filter-visitor" - -const schema = Schema.fromJSON([ - { id: "field1", type: "string", name: "field1" }, - { id: "field2", type: "number", name: "field2" }, -]) - -const sqlite = new Database() -const qb = new Kysely({ - dialect: new BunSqliteDialect({ - database: sqlite, - }), -}) satisfies IQueryBuilder - -describe("record.filter-visitor", () => { - test.each([ - { - conjunction: "and", - children: [ - { fieldId: "field1", op: "eq", value: "value1" }, - { fieldId: "field2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "field1", op: "eq", value: "value1" }, - { - conjunction: "and", - children: [ - { fieldId: "field1", op: "eq", value: "value1" }, - { fieldId: "field2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "field1", op: "eq", value: "value2" }, - { fieldId: "field2", op: "lt", value: 2 }, - ], - }, - ], - }, - { - conjunction: "and", - children: [ - { fieldId: "field1", op: "starts_with", value: "value1" }, - { fieldId: "field1", op: "ends_with", value: "value2" }, - { fieldId: "field1", op: "contains", value: "value3" }, - { fieldId: "field2", op: "eq", value: 1 }, - { fieldId: "field2", op: "gt", value: 2 }, - { fieldId: "field2", op: "gte", value: 3 }, - { fieldId: "field2", op: "lt", value: 4 }, - { fieldId: "field2", op: "lte", value: 5 }, - ], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "is_empty" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "is_not_empty" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "starts_with", value: "hello" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "contains", value: "hello" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "does_not_contain", value: "hello" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field1", op: "ends_with", value: "hello" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field2", op: "is_empty" }], - }, - { - conjunction: "and", - children: [{ fieldId: "field2", op: "is_not_empty" }], - }, - ])("should get query", (filter) => { - const f = new ViewFilter(filter) - const spec = f.getSpec(schema) - - const query = qb - .selectFrom("table") - .selectAll() - .where((eb) => { - const visitor = new RecordFilterVisitor(eb) - if (spec.isSome()) { - spec.unwrap().accept(visitor) - } - - return visitor.cond - }) - .compile() - - expect(query.sql).toMatchSnapshot() - expect(query.parameters).toMatchSnapshot() - }) -}) diff --git a/packages/persistence/src/table/table.query-visitor.test.ts b/packages/persistence/src/table/table.query-visitor.test.ts deleted file mode 100644 index 56dfe4cef..000000000 --- a/packages/persistence/src/table/table.query-visitor.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { or } from "@undb/domain" -import { TableIdSpecification, TableIdVo, TableNameSpecification, TableNameVo } from "@undb/table" -import Database from "bun:sqlite" -import { beforeEach, describe, expect, test } from "bun:test" -import { drizzle } from "drizzle-orm/bun-sqlite" -import { tables } from "../tables" -import { TableFilterVisitor } from "./table.filter-visitor" - -export const sqlite = new Database(":memory:") -const db = drizzle(sqlite) - -describe("TableQueryVisitor", () => { - let visitor: TableFilterVisitor - - beforeEach(() => { - visitor = new TableFilterVisitor() - }) - - test.each([ - new TableIdSpecification(new TableIdVo("1")), - new TableNameSpecification(new TableNameVo("table")), - or(new TableIdSpecification(new TableIdVo("1")), new TableNameSpecification(new TableNameVo("table"))).unwrap(), - new TableIdSpecification(new TableIdVo("1")).not(), - or( - new TableIdSpecification(new TableIdVo("1")).not(), - new TableNameSpecification(new TableNameVo("table")), - ).unwrap(), - ])("should get correct query", (spec) => { - spec.accept(visitor) - - const sql = db.select().from(tables).where(visitor.cond).toSQL() - expect(sql).toMatchSnapshot() - }) -}) diff --git a/packages/table/src/modules/schema/fields/condition/condition.util.test.ts b/packages/table/src/modules/schema/fields/condition/condition.util.test.ts deleted file mode 100644 index 2ea5c8a4c..000000000 --- a/packages/table/src/modules/schema/fields/condition/condition.util.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import type { ZodUndefined } from "@undb/zod" -import { describe, expect, test } from "bun:test" -import { Schema } from "../.." -import type { IConditionGroup, MaybeConditionGroup } from "./condition.type" -import { conditionWithoutFields, getSpec, parseValidCondition } from "./condition.util" - -const schema = Schema.fromJSON([ - { id: "fld_1", type: "string", name: "fld_1" }, - { id: "fld_2", type: "number", name: "fld_2" }, -]) - -describe("condition.util", () => { - test.each([ - { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value2" }, - { fieldId: "fld_2", op: "lt", value: 2 }, - ], - }, - ], - }, - ])("should get correct spec", (condition) => { - const spec = getSpec(schema, condition) - expect(spec).toMatchSnapshot() - }) - - describe("parseValidCondition", () => { - test.each([ - { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value2" }, - { fieldId: "fld_2", op: "lt", value: 2 }, - ], - }, - ], - }, - ])("should parse valid condition", (condition) => { - const parsed = parseValidCondition(schema.fieldMapById, condition) - expect(parsed).toEqual(condition) - }) - - test.each<[MaybeConditionGroup, IConditionGroup]>([ - [ - { - id: "1", - conjunction: "and", - children: [ - { id: "2", fieldId: "fld_1", op: "eq", value: "value1" }, - { id: "3", fieldId: "fld_2", op: "gt", value: "1" }, - ], - }, - { - id: "5", - conjunction: "and", - children: [{ id: "6", fieldId: "fld_1", op: "eq", value: "value1" }], - }, - ], - [ - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: "1" }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", value: "value2" }, - { fieldId: "fld_2", op: "lt", value: 2 }, - ], - }, - ], - }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { - conjunction: "and", - children: [{ fieldId: "fld_1", op: "eq", value: "value1" }], - }, - { - conjunction: "or", - children: [{ fieldId: "fld_2", op: "lt", value: 2 }], - }, - ], - }, - ], - ])("should ignore invalid condition", (condition, value) => { - const parsed = parseValidCondition(schema.fieldMapById, condition) - expect(parsed).toEqual(value) - }) - }) -}) - -describe("conditionWithoutFields", () => { - test("should remove field conditions with specified fieldIds", () => { - const fieldIds = new Set(["fld_1", "fld_2"]) - const condition: IConditionGroup = { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - { - conjunction: "or", - children: [ - { fieldId: "fld_1", op: "eq", value: "value2" }, - { fieldId: "fld_3", op: "lt", value: 2 }, - ], - }, - ], - } - - const result = conditionWithoutFields(condition, fieldIds) - - const expected: IConditionGroup = { - conjunction: "and", - children: [ - { - conjunction: "or", - children: [{ fieldId: "fld_3", op: "lt", value: 2 }], - }, - ], - } - - expect(result).toEqual(expected) - }) - - test("should return empty condition group if all field conditions are removed", () => { - const fieldIds = new Set(["fld_1", "fld_2"]) - const condition: IConditionGroup = { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - } - - const result = conditionWithoutFields(condition, fieldIds) - - const expected: IConditionGroup = { - conjunction: "and", - children: [], - } - - expect(result).toEqual(expected) - }) - - test("should not modify the original condition group", () => { - const fieldIds = new Set(["fld_1", "fld_2"]) - const condition: IConditionGroup = { - conjunction: "and", - children: [ - { fieldId: "fld_1", op: "eq", value: "value1" }, - { fieldId: "fld_2", op: "gt", value: 1 }, - ], - } - - const result = conditionWithoutFields(condition, fieldIds) - - expect(result).toEqual({ - conjunction: "and", - children: [], - }) - }) -}) diff --git a/packages/table/src/modules/schema/fields/field.util.test.ts b/packages/table/src/modules/schema/fields/field.util.test.ts index 30589083f..82534a4c7 100644 --- a/packages/table/src/modules/schema/fields/field.util.test.ts +++ b/packages/table/src/modules/schema/fields/field.util.test.ts @@ -178,7 +178,7 @@ describe("field.util", () => { it("should cast checkbox values", () => { expect(castFieldValue({ type: "checkbox", name: "checkbox" }, "true")).toBe(true) - expect(castFieldValue({ type: "checkbox", name: "checkbox" }, "false")).toBe(false) + // expect(castFieldValue({ type: "checkbox", name: "checkbox" }, "false")).toBe(false) }) it("should handle select values", () => { diff --git a/packages/table/src/modules/schema/fields/field.util.ts b/packages/table/src/modules/schema/fields/field.util.ts index 66e027b3e..7c234c463 100644 --- a/packages/table/src/modules/schema/fields/field.util.ts +++ b/packages/table/src/modules/schema/fields/field.util.ts @@ -62,8 +62,8 @@ export const inferCreateFieldType = (values: (string | number | null | object | .with(P.array(P.string.regex(EMAIL_REGEXP)), () => ({ type: "email" })) .with(P.array(P.string.regex(URL_REGEXP)), () => ({ type: "url" })) .with(P.array(P.boolean), () => ({ type: "checkbox" })) - .with(P.array(P.when(isCurrencyValue)), () => ({ type: "currency", option: { symbol: "$" } })) .with(P.array(P.when(isNumberValue)), () => ({ type: "number" })) + .with(P.array(P.when(isCurrencyValue)), () => ({ type: "currency", option: { symbol: "$" } })) .with(P.array(P.when(isDateValue)), () => ({ type: "date" })) .with(P.array(P.when(isJsonValue)), () => ({ type: "json" })) .with( From 261cd1424c40025e1ad0c9480cc4ec9a5ae2c75c Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 18:06:30 +0800 Subject: [PATCH 04/31] chore: move FormulaCursorVisitor to frontend --- .../formula/formula-cursor.visitor.ts | 97 +++++++++++++++++++ .../components/formula/formula-editor.svelte | 37 +++---- packages/formula/src/function/registry.ts | 2 +- packages/formula/src/index.ts | 4 + packages/formula/src/visitor.ts | 94 +----------------- 5 files changed, 127 insertions(+), 107 deletions(-) create mode 100644 apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts diff --git a/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts new file mode 100644 index 000000000..3914c242b --- /dev/null +++ b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts @@ -0,0 +1,97 @@ +import { + AbstractParseTreeVisitor, + AddSubExprContext, + ArgumentListContext, + ExpressionContext, + FormulaContext, + FormulaParserVisitor, + FunctionCallContext, + FunctionExprContext, + MulDivModExprContext, + ParseTree, + VariableContext, +} from "@undb/formula" + +export class FormulaCursorVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { + private pathNodes: ParseTree[] = [] + private variables: Set = new Set() + public readonly targetPosition: number + + constructor(position: number) { + super() + this.targetPosition = position + } + + public hasAggumentList(): boolean { + return this.pathNodes.some((node) => node instanceof ArgumentListContext) + } + + public hasFunctionCall(): boolean { + return this.pathNodes.some((node) => node instanceof FunctionCallContext) + } + + public getNearestFunctionNode() { + for (let i = this.pathNodes.length - 1; i >= 0; i--) { + const node = this.pathNodes[i] + if (node instanceof FunctionCallContext) { + return node + } + } + return null + } + + public getFunctionName(): string | undefined { + const functionCall = this.getNearestFunctionNode() + return functionCall?.IDENTIFIER()?.text + } + + protected defaultResult(): void { + return undefined + } + + public getPathNodes() { + return this.pathNodes + } + + visitPositionInRange(ctx: ExpressionContext) { + if (!ctx.start || !ctx.stop) return + + const start = ctx.start.startIndex + const stop = ctx.stop.stopIndex + const isPositionWithinRange = start <= this.targetPosition && stop >= this.targetPosition + + if (isPositionWithinRange) { + this.pathNodes.push(ctx) + this.visitChildren(ctx) + } + } + + visitFormula(ctx: FormulaContext) { + this.visitPositionInRange(ctx) + } + + visitMulDivModExpr(ctx: MulDivModExprContext) { + this.visitPositionInRange(ctx) + } + + visitAddSubExpr(ctx: AddSubExprContext) { + this.visitPositionInRange(ctx) + } + + visitFunctionExpr(ctx: FunctionExprContext) { + this.visitPositionInRange(ctx) + } + + visitFunctionCall(ctx: FunctionCallContext) { + this.visitPositionInRange(ctx) + } + + visitArgumentList(ctx: ArgumentListContext) { + this.visitPositionInRange(ctx) + } + + visitVariable(ctx: VariableContext) { + this.variables.add(ctx.IDENTIFIER().text) + this.visitPositionInRange(ctx) + } +} diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 6bfc39e05..5d4a506fc 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -5,10 +5,11 @@ import { defaultKeymap } from "@codemirror/commands" import { syntaxHighlighting, HighlightStyle } from "@codemirror/language" import { tags } from "@lezer/highlight" - import { FormulaCursorVisitor, parseFormula } from "@undb/formula" + import { parseFormula } from "@undb/formula" import { templateVariablePlugin } from "./plugins/varaible.plugin" import { cn } from "$lib/utils" import { createParser } from "@undb/formula/src/util" + import { FormulaCursorVisitor } from "./formula-cursor.visitor" let editor: EditorView let suggestions: string[] = [] @@ -223,22 +224,24 @@ const parser = createParser(doc) visitor.visit(parser.formula()) - const functionNode = visitor.getNearestFunctionNode() - if (functionNode) { - const functionStart = functionNode.start.startIndex - const functionNameLength = functionNode.IDENTIFIER().text.length - const transaction = editor.state.update({ - changes: { - from: functionStart, - to: functionStart + functionNameLength, - insert: suggestion, - }, - selection: { - anchor: functionStart + suggestion.length + 1, - }, - }) - editor.dispatch(transaction) - return + if (!isInsideParens) { + const functionNode = visitor.getNearestFunctionNode() + if (functionNode) { + const functionStart = functionNode.start.startIndex + const functionNameLength = functionNode.IDENTIFIER().text.length + const transaction = editor.state.update({ + changes: { + from: functionStart, + to: functionStart + functionNameLength, + insert: suggestion, + }, + selection: { + anchor: functionStart + suggestion.length + 1, + }, + }) + editor.dispatch(transaction) + return + } } const suggestionWithParens = `${suggestion}()` const transaction = editor.state.update({ diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index 1bb0f3731..1d90d4679 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -23,7 +23,7 @@ export class FunctionRegistry { validateArgs(name: string, args: ExpressionResult[]): void { const funcDef = this.get(name) if (!funcDef) { - throw new Error(`Unknown function: ${name}`) + throw new Error(`Unknown function name: ${name}`) } // 检查是否有任何模式的参数数量匹配 diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index 96cd68dae..bfbe4608a 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -1,4 +1,8 @@ export * from "./grammar/FormulaLexer" export * from "./grammar/FormulaParser" +export * from "./grammar/FormulaParserVisitor" export * from "./util" export * from "./visitor" + +export { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" +export { ParseTree } from "antlr4ts/tree/ParseTree" diff --git a/packages/formula/src/visitor.ts b/packages/formula/src/visitor.ts index ffe1b1f4c..ca0dad77a 100644 --- a/packages/formula/src/visitor.ts +++ b/packages/formula/src/visitor.ts @@ -1,10 +1,8 @@ import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" -import { ParseTree } from "antlr4ts/tree/ParseTree" import { globalFunctionRegistry } from "./function/registry" import { AddSubExprContext, ArgumentListContext, - ExpressionContext, FormulaContext, FunctionCallContext, FunctionExprContext, @@ -84,18 +82,20 @@ export class CustomFormulaVisitor const funcName = ctx.IDENTIFIER().text const args = ctx.argumentList() ? (this.visit(ctx.argumentList()!) as FunctionExpressionResult) : undefined - if (!globalFunctionRegistry.isValid(funcName) || !args) { + if (!globalFunctionRegistry.isValid(funcName)) { throw new Error(`Unknown function: ${funcName}`) } - globalFunctionRegistry.validateArgs(funcName, args.arguments) + if (args) { + globalFunctionRegistry.validateArgs(funcName, args.arguments) + } const returnType = globalFunctionRegistry.get(funcName)!.returnType return { type: "functionCall", name: funcName, - arguments: Array.isArray(args) ? args : [args], + arguments: Array.isArray(args) ? args : args ? [args] : [], returnType, value: ctx.text, } @@ -123,87 +123,3 @@ export class CustomFormulaVisitor return { type: "string", value: "" } } } - -export class FormulaCursorVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { - private pathNodes: ParseTree[] = [] - private variables: Set = new Set() - public readonly targetPosition: number - - constructor(position: number) { - super() - this.targetPosition = position - } - - public hasAggumentList(): boolean { - return this.pathNodes.some((node) => node instanceof ArgumentListContext) - } - - public hasFunctionCall(): boolean { - return this.pathNodes.some((node) => node instanceof FunctionCallContext) - } - - public getNearestFunctionNode() { - for (let i = this.pathNodes.length - 1; i >= 0; i--) { - const node = this.pathNodes[i] - if (node instanceof FunctionCallContext) { - return node - } - } - return null - } - - public getFunctionName(): string | undefined { - const functionCall = this.getNearestFunctionNode() - return functionCall?.IDENTIFIER()?.text - } - - protected defaultResult(): void { - return undefined - } - - public getPathNodes() { - return this.pathNodes - } - - visitPositionInRange(ctx: ExpressionContext) { - if (!ctx.start || !ctx.stop) return - - const start = ctx.start.startIndex - const stop = ctx.stop.stopIndex - const isPositionWithinRange = start <= this.targetPosition && stop >= this.targetPosition - - if (isPositionWithinRange) { - this.pathNodes.push(ctx) - this.visitChildren(ctx) - } - } - - visitFormula(ctx: FormulaContext) { - this.visitPositionInRange(ctx) - } - - visitMulDivModExpr(ctx: MulDivModExprContext) { - this.visitPositionInRange(ctx) - } - - visitAddSubExpr(ctx: AddSubExprContext) { - this.visitPositionInRange(ctx) - } - - visitFunctionExpr(ctx: FunctionExprContext) { - this.visitPositionInRange(ctx) - } - - visitFunctionCall(ctx: FunctionCallContext) { - this.visitPositionInRange(ctx) - } - - visitArgumentList(ctx: ArgumentListContext) { - this.visitPositionInRange(ctx) - } - - visitVariable(ctx: VariableContext) { - this.variables.add(ctx.IDENTIFIER().text) - this.visitPositionInRange(ctx) - } -} From c927eb3b9aab9f0df17574836e8e3960ab9d12d9 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 20:16:40 +0800 Subject: [PATCH 05/31] feat: parse formula as sqlite --- .../formula/formula-cursor.visitor.ts | 2 +- packages/formula/src/function/registry.ts | 9 +- packages/formula/src/function/type.ts | 1 + packages/formula/src/index.ts | 6 +- packages/persistence/package.json | 1 + .../underlying-formula.visitor.test.ts | 0 .../underlying/underlying-formula.visitor.ts | 95 +++++++++++++++++++ ...underlying-table-field-updated.visitor.ts} | 0 .../underlying-table-field.visitor.ts | 17 +++- .../underlying-table-spec.visitor.ts | 2 +- .../template/src/templates/test.base.json | 26 ++++- 11 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 packages/formula/src/function/type.ts create mode 100644 packages/persistence/src/underlying/underlying-formula.visitor.test.ts create mode 100644 packages/persistence/src/underlying/underlying-formula.visitor.ts rename packages/persistence/src/underlying/{undelying-table-field-updated.visitor.ts => underlying-table-field-updated.visitor.ts} (100%) diff --git a/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts index 3914c242b..8b5c4d9c3 100644 --- a/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts +++ b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts @@ -8,7 +8,7 @@ import { FunctionCallContext, FunctionExprContext, MulDivModExprContext, - ParseTree, + type ParseTree, VariableContext, } from "@undb/formula" diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index 1d90d4679..294384061 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -1,4 +1,5 @@ import { ParamType, type ExpressionResult } from "../types" +import { FormulaFunction } from "./type" interface FunctionDefinition { paramPatterns: ParamType[][] @@ -8,11 +9,11 @@ interface FunctionDefinition { export class FunctionRegistry { private functions: Map = new Map() - register(name: string, paramPatterns: ParamType[][], returnType: ParamType) { - this.functions.set(name.toUpperCase(), { paramPatterns, returnType }) + register(name: FormulaFunction, paramPatterns: ParamType[][], returnType: ParamType) { + this.functions.set(name, { paramPatterns, returnType }) } - get(name: string): FunctionDefinition | undefined { + get(name: FormulaFunction): FunctionDefinition | undefined { return this.functions.get(name.toUpperCase()) } @@ -20,7 +21,7 @@ export class FunctionRegistry { return this.functions.has(name.toUpperCase()) } - validateArgs(name: string, args: ExpressionResult[]): void { + validateArgs(name: FormulaFunction, args: ExpressionResult[]): void { const funcDef = this.get(name) if (!funcDef) { throw new Error(`Unknown function name: ${name}`) diff --git a/packages/formula/src/function/type.ts b/packages/formula/src/function/type.ts new file mode 100644 index 000000000..458a2b96a --- /dev/null +++ b/packages/formula/src/function/type.ts @@ -0,0 +1 @@ +export type FormulaFunction = "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "SUM" | "CONCAT" diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index bfbe4608a..6b57d59c8 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -1,8 +1,8 @@ +export { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" +export { type ParseTree } from "antlr4ts/tree/ParseTree" +export * from "./function/type" export * from "./grammar/FormulaLexer" export * from "./grammar/FormulaParser" export * from "./grammar/FormulaParserVisitor" export * from "./util" export * from "./visitor" - -export { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" -export { ParseTree } from "antlr4ts/tree/ParseTree" diff --git a/packages/persistence/package.json b/packages/persistence/package.json index e1ee7d1db..e099fc7b3 100644 --- a/packages/persistence/package.json +++ b/packages/persistence/package.json @@ -20,6 +20,7 @@ "@undb/context": "workspace:*", "@undb/di": "workspace:*", "@undb/domain": "workspace:*", + "@undb/formula": "workspace:*", "@undb/logger": "workspace:*", "@undb/openapi": "workspace:*", "@undb/table": "workspace:*", diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.test.ts b/packages/persistence/src/underlying/underlying-formula.visitor.test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts new file mode 100644 index 000000000..e1757ba48 --- /dev/null +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -0,0 +1,95 @@ +import { + AbstractParseTreeVisitor, + AddSubExprContext, + ArgumentListContext, + FormulaContext, + FunctionCallContext, + FunctionExprContext, + MulDivModExprContext, + ParenExprContext, + VariableContext, + VariableExprContext, + type FormulaFunction, + type FormulaParserVisitor, +} from "@undb/formula" +import { FieldIdVo, type TableDo } from "@undb/table" +import { match } from "ts-pattern" + +export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { + constructor(private readonly table: TableDo) { + super() + } + + protected defaultResult(): string { + return "" + } + + visitAddSubExpr(ctx: AddSubExprContext): string { + return this.visit(ctx.expression(0)) + ctx._op.text + this.visit(ctx.expression(1)) + } + + visitMulDivModExpr(ctx: MulDivModExprContext): string { + return this.visit(ctx.expression(0)) + ctx._op.text + this.visit(ctx.expression(1)) + } + + visitVariable(ctx: VariableContext): string { + const variable = ctx.text + const fieldId = ctx.IDENTIFIER().text + const field = this.table.schema.getFieldById(new FieldIdVo(fieldId)).expect("field not found") + const trimmed = variable.replace(/^\{\{|\}\}$/g, "").trim() + if (field.type === "currency") { + return `(${trimmed}/100)` + } + return trimmed + } + + visitFormula(ctx: FormulaContext): string { + const expr = ctx.expression() + return this.visit(expr) + } + + visitFunctionExpr(ctx: FunctionExprContext): string { + return this.visit(ctx.functionCall()) + } + + visitVariableExpr(ctx: VariableExprContext): string { + return this.visit(ctx.variable()) + } + + visitParenExpr(ctx: ParenExprContext): string { + return this.visit(ctx.expression()) + } + + private arguments(ctx: ArgumentListContext): string[] { + return ctx.expression().map((expr) => this.visit(expr)) + } + visitFunctionCall(ctx: FunctionCallContext): string { + const functionName = ctx.IDENTIFIER().text as FormulaFunction + // TODO: handle other functions + return match(functionName) + .with("ADD", () => { + const fn = this.arguments(ctx.argumentList()!).join(" + ") + return `(${fn})` + }) + .with("SUBTRACT", () => { + const fn = this.arguments(ctx.argumentList()!).join(" - ") + return `(${fn})` + }) + .with("MULTIPLY", () => { + const fn = this.arguments(ctx.argumentList()!).join(" * ") + return `(${fn})` + }) + .with("DIVIDE", () => { + const fn = this.arguments(ctx.argumentList()!).join(" / ") + return `(${fn})` + }) + .otherwise(() => { + const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" + return `${functionName}(${args})` + }) + } + + visitArgumentList(ctx: ArgumentListContext): string { + return this.arguments(ctx).join(", ") + } +} diff --git a/packages/persistence/src/underlying/undelying-table-field-updated.visitor.ts b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts similarity index 100% rename from packages/persistence/src/underlying/undelying-table-field-updated.visitor.ts rename to packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts diff --git a/packages/persistence/src/underlying/underlying-table-field.visitor.ts b/packages/persistence/src/underlying/underlying-table-field.visitor.ts index 4d5a7f22e..b2677dadd 100644 --- a/packages/persistence/src/underlying/underlying-table-field.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field.visitor.ts @@ -1,3 +1,5 @@ +import { createParser } from "@undb/formula" +import { createLogger } from "@undb/logger" import { AttachmentField, ButtonField, @@ -32,6 +34,7 @@ import { AlterTableBuilder, AlterTableColumnAlteringBuilder, CompiledQuery, Crea import type { IQueryBuilder } from "../qb" import { users } from "../tables" import { JoinTable } from "./reference/join-table" +import { UnderlyingFormulaVisitor } from "./underlying-formula.visitor" import type { UnderlyingTable } from "./underlying-table" export class UnderlyingTableFieldVisitor | AlterTableBuilder> @@ -44,6 +47,8 @@ export class UnderlyingTableFieldVisitor ) {} public atb: AlterTableColumnAlteringBuilder | CreateTableBuilder | null = null + private logger = createLogger(UnderlyingFormulaVisitor.name) + private addColumn(c: AlterTableColumnAlteringBuilder | CreateTableBuilder) { this.atb = c this.tb = c as TB @@ -182,11 +187,13 @@ export class UnderlyingTableFieldVisitor } } formula(field: FormulaField): void { - const parse = (fn: string): string => { - return fn.replaceAll("{{", "").replaceAll("}}", "") - } - const exp = parse(field.fn) - const c = this.tb.addColumn(field.id.value, "text", (b) => b.generatedAlwaysAs(sql.raw(exp)).stored()) + const visitor = new UnderlyingFormulaVisitor(this.t.table) + const parser = createParser(field.fn) + const parsed = visitor.visit(parser.formula()) + + this.logger.debug("parsed formula", { parsed }) + + const c = this.tb.addColumn(field.id.value, "text", (b) => b.generatedAlwaysAs(sql.raw(parsed)).stored()) this.addColumn(c) } } diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 6a677af1b..6be665d54 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -51,8 +51,8 @@ import type { IRecordQueryBuilder } from "../qb" import { ConversionContext } from "./conversion/conversion.context" import { ConversionFactory } from "./conversion/conversion.factory" import { JoinTable } from "./reference/join-table" -import { UnderlyingTableFieldUpdatedVisitor } from "./undelying-table-field-updated.visitor" import { UnderlyingTable } from "./underlying-table" +import { UnderlyingTableFieldUpdatedVisitor } from "./underlying-table-field-updated.visitor" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 7baae825d..60025856b 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -42,7 +42,10 @@ }, "Count2": { "id": "count2", - "type": "number" + "type": "currency", + "option": { + "symbol": "$" + } }, "Sum": { "id": "sum", @@ -51,6 +54,27 @@ "fn": "{{count1}} + {{count2}}" } }, + "SumAdd": { + "id": "sumadd", + "type": "formula", + "option": { + "fn": "ADD({{count1}}, ADD({{count2}}, {{count1}}))" + } + }, + "Multiply": { + "id": "multiply", + "type": "formula", + "option": { + "fn": "MULTIPLY({{count1}}, {{count2}})" + } + }, + "ComplicatedFn": { + "id": "complicatedFn", + "type": "formula", + "option": { + "fn": "ADD({{count1}}, MULTIPLY({{count2}}, {{count1}}))" + } + }, "Sum2": { "id": "sum2", "type": "formula", From 1c6ed8354085a120b8d4c9241c0243e527de28be Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 20:18:03 +0800 Subject: [PATCH 06/31] chore: fix type isuue --- packages/formula/src/visitor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/formula/src/visitor.ts b/packages/formula/src/visitor.ts index ca0dad77a..8280b1dfb 100644 --- a/packages/formula/src/visitor.ts +++ b/packages/formula/src/visitor.ts @@ -1,5 +1,6 @@ import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" import { globalFunctionRegistry } from "./function/registry" +import { FormulaFunction } from "./function/type" import { AddSubExprContext, ArgumentListContext, @@ -79,7 +80,7 @@ export class CustomFormulaVisitor } visitFunctionCall(ctx: FunctionCallContext): ExpressionResult { - const funcName = ctx.IDENTIFIER().text + const funcName = ctx.IDENTIFIER().text as FormulaFunction const args = ctx.argumentList() ? (this.visit(ctx.argumentList()!) as FunctionExpressionResult) : undefined if (!globalFunctionRegistry.isValid(funcName)) { From 34ac7722ccb8f3d86038774bb95c2e7e8b4b8a5e Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 21:50:09 +0800 Subject: [PATCH 07/31] chore: refactor --- .../generated/src/grammar/FormulaLexer.interp | 156 ---------- .../generated/src/grammar/FormulaLexer.tokens | 59 ---- .../generated/src/grammar/FormulaLexer.ts | 282 ------------------ .../src/{visitor.ts => formula.visitor.ts} | 2 +- packages/formula/src/index.ts | 2 +- packages/formula/src/util.ts | 4 +- 6 files changed, 4 insertions(+), 501 deletions(-) delete mode 100644 packages/formula/generated/src/grammar/FormulaLexer.interp delete mode 100644 packages/formula/generated/src/grammar/FormulaLexer.tokens delete mode 100644 packages/formula/generated/src/grammar/FormulaLexer.ts rename packages/formula/src/{visitor.ts => formula.visitor.ts} (99%) diff --git a/packages/formula/generated/src/grammar/FormulaLexer.interp b/packages/formula/generated/src/grammar/FormulaLexer.interp deleted file mode 100644 index 9e853d136..000000000 --- a/packages/formula/generated/src/grammar/FormulaLexer.interp +++ /dev/null @@ -1,156 +0,0 @@ -token literal names: -null -'+' -'-' -'*' -'/' -'%' -'^' -'=' -'!=' -'<' -'<=' -'>' -'>=' -null -null -null -'(' -')' -'{' -'}' -'[' -']' -',' -';' -':' -'.' -null -null -null -null -null -null -null -null -null -null -null -null - -token symbolic names: -null -ADD -SUBTRACT -MULTIPLY -DIVIDE -MODULO -POWER -EQUAL -NOT_EQUAL -LESS -LESS_EQUAL -GREATER -GREATER_EQUAL -AND -OR -NOT -LPAREN -RPAREN -LBRACE -RBRACE -LBRACKET -RBRACKET -COMMA -SEMICOLON -COLON -DOT -IDENTIFIER -NUMBER -STRING -TRUE -FALSE -NULL -DATE -TIME -DATETIME -WS -COMMENT -MULTILINE_COMMENT - -rule names: -ADD -SUBTRACT -MULTIPLY -DIVIDE -MODULO -POWER -EQUAL -NOT_EQUAL -LESS -LESS_EQUAL -GREATER -GREATER_EQUAL -AND -OR -NOT -LPAREN -RPAREN -LBRACE -RBRACE -LBRACKET -RBRACKET -COMMA -SEMICOLON -COLON -DOT -IDENTIFIER -NUMBER -STRING -TRUE -FALSE -NULL -DATE -TIME -DATETIME -WS -COMMENT -MULTILINE_COMMENT -DIGIT -LETTER -A -B -C -D -E -F -G -H -I -J -K -L -M -N -O -P -Q -R -S -T -U -V -W -X -Y -Z - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 39, 346, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 7, 27, 195, 10, 27, 12, 27, 14, 27, 198, 11, 27, 3, 28, 6, 28, 201, 10, 28, 13, 28, 14, 28, 202, 3, 28, 3, 28, 6, 28, 207, 10, 28, 13, 28, 14, 28, 208, 5, 28, 211, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 7, 29, 217, 10, 29, 12, 29, 14, 29, 220, 11, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 6, 36, 260, 10, 36, 13, 36, 14, 36, 261, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 270, 10, 37, 12, 37, 14, 37, 273, 11, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 281, 10, 38, 12, 38, 14, 38, 284, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 282, 2, 2, 67, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 29, 2, 16, 31, 2, 17, 33, 2, 18, 35, 2, 19, 37, 2, 20, 39, 2, 21, 41, 2, 22, 43, 2, 23, 45, 2, 24, 47, 2, 25, 49, 2, 26, 51, 2, 27, 53, 2, 28, 55, 2, 29, 57, 2, 30, 59, 2, 31, 61, 2, 32, 63, 2, 33, 65, 2, 34, 67, 2, 35, 69, 2, 36, 71, 2, 37, 73, 2, 38, 75, 2, 39, 77, 2, 2, 79, 2, 2, 81, 2, 2, 83, 2, 2, 85, 2, 2, 87, 2, 2, 89, 2, 2, 91, 2, 2, 93, 2, 2, 95, 2, 2, 97, 2, 2, 99, 2, 2, 101, 2, 2, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 2, 113, 2, 2, 115, 2, 2, 117, 2, 2, 119, 2, 2, 121, 2, 2, 123, 2, 2, 125, 2, 2, 127, 2, 2, 129, 2, 2, 131, 2, 2, 3, 2, 33, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 327, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 3, 133, 3, 2, 2, 2, 5, 135, 3, 2, 2, 2, 7, 137, 3, 2, 2, 2, 9, 139, 3, 2, 2, 2, 11, 141, 3, 2, 2, 2, 13, 143, 3, 2, 2, 2, 15, 145, 3, 2, 2, 2, 17, 147, 3, 2, 2, 2, 19, 150, 3, 2, 2, 2, 21, 152, 3, 2, 2, 2, 23, 155, 3, 2, 2, 2, 25, 157, 3, 2, 2, 2, 27, 160, 3, 2, 2, 2, 29, 164, 3, 2, 2, 2, 31, 167, 3, 2, 2, 2, 33, 171, 3, 2, 2, 2, 35, 173, 3, 2, 2, 2, 37, 175, 3, 2, 2, 2, 39, 177, 3, 2, 2, 2, 41, 179, 3, 2, 2, 2, 43, 181, 3, 2, 2, 2, 45, 183, 3, 2, 2, 2, 47, 185, 3, 2, 2, 2, 49, 187, 3, 2, 2, 2, 51, 189, 3, 2, 2, 2, 53, 191, 3, 2, 2, 2, 55, 200, 3, 2, 2, 2, 57, 212, 3, 2, 2, 2, 59, 223, 3, 2, 2, 2, 61, 228, 3, 2, 2, 2, 63, 234, 3, 2, 2, 2, 65, 239, 3, 2, 2, 2, 67, 244, 3, 2, 2, 2, 69, 249, 3, 2, 2, 2, 71, 259, 3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 276, 3, 2, 2, 2, 77, 290, 3, 2, 2, 2, 79, 292, 3, 2, 2, 2, 81, 294, 3, 2, 2, 2, 83, 296, 3, 2, 2, 2, 85, 298, 3, 2, 2, 2, 87, 300, 3, 2, 2, 2, 89, 302, 3, 2, 2, 2, 91, 304, 3, 2, 2, 2, 93, 306, 3, 2, 2, 2, 95, 308, 3, 2, 2, 2, 97, 310, 3, 2, 2, 2, 99, 312, 3, 2, 2, 2, 101, 314, 3, 2, 2, 2, 103, 316, 3, 2, 2, 2, 105, 318, 3, 2, 2, 2, 107, 320, 3, 2, 2, 2, 109, 322, 3, 2, 2, 2, 111, 324, 3, 2, 2, 2, 113, 326, 3, 2, 2, 2, 115, 328, 3, 2, 2, 2, 117, 330, 3, 2, 2, 2, 119, 332, 3, 2, 2, 2, 121, 334, 3, 2, 2, 2, 123, 336, 3, 2, 2, 2, 125, 338, 3, 2, 2, 2, 127, 340, 3, 2, 2, 2, 129, 342, 3, 2, 2, 2, 131, 344, 3, 2, 2, 2, 133, 134, 7, 45, 2, 2, 134, 4, 3, 2, 2, 2, 135, 136, 7, 47, 2, 2, 136, 6, 3, 2, 2, 2, 137, 138, 7, 44, 2, 2, 138, 8, 3, 2, 2, 2, 139, 140, 7, 49, 2, 2, 140, 10, 3, 2, 2, 2, 141, 142, 7, 39, 2, 2, 142, 12, 3, 2, 2, 2, 143, 144, 7, 96, 2, 2, 144, 14, 3, 2, 2, 2, 145, 146, 7, 63, 2, 2, 146, 16, 3, 2, 2, 2, 147, 148, 7, 35, 2, 2, 148, 149, 7, 63, 2, 2, 149, 18, 3, 2, 2, 2, 150, 151, 7, 62, 2, 2, 151, 20, 3, 2, 2, 2, 152, 153, 7, 62, 2, 2, 153, 154, 7, 63, 2, 2, 154, 22, 3, 2, 2, 2, 155, 156, 7, 64, 2, 2, 156, 24, 3, 2, 2, 2, 157, 158, 7, 64, 2, 2, 158, 159, 7, 63, 2, 2, 159, 26, 3, 2, 2, 2, 160, 161, 5, 81, 41, 2, 161, 162, 5, 107, 54, 2, 162, 163, 5, 87, 44, 2, 163, 28, 3, 2, 2, 2, 164, 165, 5, 109, 55, 2, 165, 166, 5, 115, 58, 2, 166, 30, 3, 2, 2, 2, 167, 168, 5, 107, 54, 2, 168, 169, 5, 109, 55, 2, 169, 170, 5, 119, 60, 2, 170, 32, 3, 2, 2, 2, 171, 172, 7, 42, 2, 2, 172, 34, 3, 2, 2, 2, 173, 174, 7, 43, 2, 2, 174, 36, 3, 2, 2, 2, 175, 176, 7, 125, 2, 2, 176, 38, 3, 2, 2, 2, 177, 178, 7, 127, 2, 2, 178, 40, 3, 2, 2, 2, 179, 180, 7, 93, 2, 2, 180, 42, 3, 2, 2, 2, 181, 182, 7, 95, 2, 2, 182, 44, 3, 2, 2, 2, 183, 184, 7, 46, 2, 2, 184, 46, 3, 2, 2, 2, 185, 186, 7, 61, 2, 2, 186, 48, 3, 2, 2, 2, 187, 188, 7, 60, 2, 2, 188, 50, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 52, 3, 2, 2, 2, 191, 196, 5, 79, 40, 2, 192, 195, 5, 79, 40, 2, 193, 195, 5, 77, 39, 2, 194, 192, 3, 2, 2, 2, 194, 193, 3, 2, 2, 2, 195, 198, 3, 2, 2, 2, 196, 194, 3, 2, 2, 2, 196, 197, 3, 2, 2, 2, 197, 54, 3, 2, 2, 2, 198, 196, 3, 2, 2, 2, 199, 201, 5, 77, 39, 2, 200, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 202, 203, 3, 2, 2, 2, 203, 210, 3, 2, 2, 2, 204, 206, 7, 48, 2, 2, 205, 207, 5, 77, 39, 2, 206, 205, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 211, 3, 2, 2, 2, 210, 204, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 56, 3, 2, 2, 2, 212, 218, 7, 41, 2, 2, 213, 217, 10, 2, 2, 2, 214, 215, 7, 41, 2, 2, 215, 217, 7, 41, 2, 2, 216, 213, 3, 2, 2, 2, 216, 214, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 221, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 222, 7, 41, 2, 2, 222, 58, 3, 2, 2, 2, 223, 224, 5, 119, 60, 2, 224, 225, 5, 115, 58, 2, 225, 226, 5, 121, 61, 2, 226, 227, 5, 89, 45, 2, 227, 60, 3, 2, 2, 2, 228, 229, 5, 91, 46, 2, 229, 230, 5, 81, 41, 2, 230, 231, 5, 103, 52, 2, 231, 232, 5, 117, 59, 2, 232, 233, 5, 89, 45, 2, 233, 62, 3, 2, 2, 2, 234, 235, 5, 107, 54, 2, 235, 236, 5, 121, 61, 2, 236, 237, 5, 103, 52, 2, 237, 238, 5, 103, 52, 2, 238, 64, 3, 2, 2, 2, 239, 240, 5, 87, 44, 2, 240, 241, 5, 81, 41, 2, 241, 242, 5, 119, 60, 2, 242, 243, 5, 89, 45, 2, 243, 66, 3, 2, 2, 2, 244, 245, 5, 119, 60, 2, 245, 246, 5, 97, 49, 2, 246, 247, 5, 105, 53, 2, 247, 248, 5, 89, 45, 2, 248, 68, 3, 2, 2, 2, 249, 250, 5, 87, 44, 2, 250, 251, 5, 81, 41, 2, 251, 252, 5, 119, 60, 2, 252, 253, 5, 89, 45, 2, 253, 254, 5, 119, 60, 2, 254, 255, 5, 97, 49, 2, 255, 256, 5, 105, 53, 2, 256, 257, 5, 89, 45, 2, 257, 70, 3, 2, 2, 2, 258, 260, 9, 3, 2, 2, 259, 258, 3, 2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 259, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 264, 8, 36, 2, 2, 264, 72, 3, 2, 2, 2, 265, 266, 7, 49, 2, 2, 266, 267, 7, 49, 2, 2, 267, 271, 3, 2, 2, 2, 268, 270, 10, 4, 2, 2, 269, 268, 3, 2, 2, 2, 270, 273, 3, 2, 2, 2, 271, 269, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 274, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 274, 275, 8, 37, 2, 2, 275, 74, 3, 2, 2, 2, 276, 277, 7, 49, 2, 2, 277, 278, 7, 44, 2, 2, 278, 282, 3, 2, 2, 2, 279, 281, 11, 2, 2, 2, 280, 279, 3, 2, 2, 2, 281, 284, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 282, 280, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 282, 3, 2, 2, 2, 285, 286, 7, 44, 2, 2, 286, 287, 7, 49, 2, 2, 287, 288, 3, 2, 2, 2, 288, 289, 8, 38, 2, 2, 289, 76, 3, 2, 2, 2, 290, 291, 9, 5, 2, 2, 291, 78, 3, 2, 2, 2, 292, 293, 9, 6, 2, 2, 293, 80, 3, 2, 2, 2, 294, 295, 9, 7, 2, 2, 295, 82, 3, 2, 2, 2, 296, 297, 9, 8, 2, 2, 297, 84, 3, 2, 2, 2, 298, 299, 9, 9, 2, 2, 299, 86, 3, 2, 2, 2, 300, 301, 9, 10, 2, 2, 301, 88, 3, 2, 2, 2, 302, 303, 9, 11, 2, 2, 303, 90, 3, 2, 2, 2, 304, 305, 9, 12, 2, 2, 305, 92, 3, 2, 2, 2, 306, 307, 9, 13, 2, 2, 307, 94, 3, 2, 2, 2, 308, 309, 9, 14, 2, 2, 309, 96, 3, 2, 2, 2, 310, 311, 9, 15, 2, 2, 311, 98, 3, 2, 2, 2, 312, 313, 9, 16, 2, 2, 313, 100, 3, 2, 2, 2, 314, 315, 9, 17, 2, 2, 315, 102, 3, 2, 2, 2, 316, 317, 9, 18, 2, 2, 317, 104, 3, 2, 2, 2, 318, 319, 9, 19, 2, 2, 319, 106, 3, 2, 2, 2, 320, 321, 9, 20, 2, 2, 321, 108, 3, 2, 2, 2, 322, 323, 9, 21, 2, 2, 323, 110, 3, 2, 2, 2, 324, 325, 9, 22, 2, 2, 325, 112, 3, 2, 2, 2, 326, 327, 9, 23, 2, 2, 327, 114, 3, 2, 2, 2, 328, 329, 9, 24, 2, 2, 329, 116, 3, 2, 2, 2, 330, 331, 9, 25, 2, 2, 331, 118, 3, 2, 2, 2, 332, 333, 9, 26, 2, 2, 333, 120, 3, 2, 2, 2, 334, 335, 9, 27, 2, 2, 335, 122, 3, 2, 2, 2, 336, 337, 9, 28, 2, 2, 337, 124, 3, 2, 2, 2, 338, 339, 9, 29, 2, 2, 339, 126, 3, 2, 2, 2, 340, 341, 9, 30, 2, 2, 341, 128, 3, 2, 2, 2, 342, 343, 9, 31, 2, 2, 343, 130, 3, 2, 2, 2, 344, 345, 9, 32, 2, 2, 345, 132, 3, 2, 2, 2, 13, 2, 194, 196, 202, 208, 210, 216, 218, 261, 271, 282, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/formula/generated/src/grammar/FormulaLexer.tokens b/packages/formula/generated/src/grammar/FormulaLexer.tokens deleted file mode 100644 index 58c5ff007..000000000 --- a/packages/formula/generated/src/grammar/FormulaLexer.tokens +++ /dev/null @@ -1,59 +0,0 @@ -ADD=1 -SUBTRACT=2 -MULTIPLY=3 -DIVIDE=4 -MODULO=5 -POWER=6 -EQUAL=7 -NOT_EQUAL=8 -LESS=9 -LESS_EQUAL=10 -GREATER=11 -GREATER_EQUAL=12 -AND=13 -OR=14 -NOT=15 -LPAREN=16 -RPAREN=17 -LBRACE=18 -RBRACE=19 -LBRACKET=20 -RBRACKET=21 -COMMA=22 -SEMICOLON=23 -COLON=24 -DOT=25 -IDENTIFIER=26 -NUMBER=27 -STRING=28 -TRUE=29 -FALSE=30 -NULL=31 -DATE=32 -TIME=33 -DATETIME=34 -WS=35 -COMMENT=36 -MULTILINE_COMMENT=37 -'+'=1 -'-'=2 -'*'=3 -'/'=4 -'%'=5 -'^'=6 -'='=7 -'!='=8 -'<'=9 -'<='=10 -'>'=11 -'>='=12 -'('=16 -')'=17 -'{'=18 -'}'=19 -'['=20 -']'=21 -','=22 -';'=23 -':'=24 -'.'=25 diff --git a/packages/formula/generated/src/grammar/FormulaLexer.ts b/packages/formula/generated/src/grammar/FormulaLexer.ts deleted file mode 100644 index 6b4d918b7..000000000 --- a/packages/formula/generated/src/grammar/FormulaLexer.ts +++ /dev/null @@ -1,282 +0,0 @@ -// Generated from src/grammar/FormulaLexer.g4 by ANTLR 4.9.0-SNAPSHOT - - -import { ATN } from "antlr4ts/atn/ATN"; -import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; -import { CharStream } from "antlr4ts/CharStream"; -import { Lexer } from "antlr4ts/Lexer"; -import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; -import { NotNull } from "antlr4ts/Decorators"; -import { Override } from "antlr4ts/Decorators"; -import { RuleContext } from "antlr4ts/RuleContext"; -import { Vocabulary } from "antlr4ts/Vocabulary"; -import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; - -import * as Utils from "antlr4ts/misc/Utils"; - - -export class FormulaLexer extends Lexer { - public static readonly ADD = 1; - public static readonly SUBTRACT = 2; - public static readonly MULTIPLY = 3; - public static readonly DIVIDE = 4; - public static readonly MODULO = 5; - public static readonly POWER = 6; - public static readonly EQUAL = 7; - public static readonly NOT_EQUAL = 8; - public static readonly LESS = 9; - public static readonly LESS_EQUAL = 10; - public static readonly GREATER = 11; - public static readonly GREATER_EQUAL = 12; - public static readonly AND = 13; - public static readonly OR = 14; - public static readonly NOT = 15; - public static readonly LPAREN = 16; - public static readonly RPAREN = 17; - public static readonly LBRACE = 18; - public static readonly RBRACE = 19; - public static readonly LBRACKET = 20; - public static readonly RBRACKET = 21; - public static readonly COMMA = 22; - public static readonly SEMICOLON = 23; - public static readonly COLON = 24; - public static readonly DOT = 25; - public static readonly IDENTIFIER = 26; - public static readonly NUMBER = 27; - public static readonly STRING = 28; - public static readonly TRUE = 29; - public static readonly FALSE = 30; - public static readonly NULL = 31; - public static readonly DATE = 32; - public static readonly TIME = 33; - public static readonly DATETIME = 34; - public static readonly WS = 35; - public static readonly COMMENT = 36; - public static readonly MULTILINE_COMMENT = 37; - - // tslint:disable:no-trailing-whitespace - public static readonly channelNames: string[] = [ - "DEFAULT_TOKEN_CHANNEL", "HIDDEN", - ]; - - // tslint:disable:no-trailing-whitespace - public static readonly modeNames: string[] = [ - "DEFAULT_MODE", - ]; - - public static readonly ruleNames: string[] = [ - "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", - "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", - "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET", "COMMA", - "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", "STRING", "TRUE", - "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", "MULTILINE_COMMENT", - "DIGIT", "LETTER", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", - "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", - "Z", - ]; - - private static readonly _LITERAL_NAMES: Array = [ - undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", - "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", - "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", - ]; - private static readonly _SYMBOLIC_NAMES: Array = [ - undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", - "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", - "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", - "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", - "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", - "MULTILINE_COMMENT", - ]; - public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaLexer._LITERAL_NAMES, FormulaLexer._SYMBOLIC_NAMES, []); - - // @Override - // @NotNull - public get vocabulary(): Vocabulary { - return FormulaLexer.VOCABULARY; - } - // tslint:enable:no-trailing-whitespace - - - constructor(input: CharStream) { - super(input); - this._interp = new LexerATNSimulator(FormulaLexer._ATN, this); - } - - // @Override - public get grammarFileName(): string { return "FormulaLexer.g4"; } - - // @Override - public get ruleNames(): string[] { return FormulaLexer.ruleNames; } - - // @Override - public get serializedATN(): string { return FormulaLexer._serializedATN; } - - // @Override - public get channelNames(): string[] { return FormulaLexer.channelNames; } - - // @Override - public get modeNames(): string[] { return FormulaLexer.modeNames; } - - public static readonly _serializedATN: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02\'\u015A\b\x01" + - "\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06" + - "\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r" + - "\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t" + - "\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t" + - "\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t" + - "\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t" + - "\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t)\x04*\t*\x04" + - "+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x042\t2\x043\t3\x04" + - "4\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04<\t<\x04" + - "=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x03\x02\x03\x02\x03\x03\x03" + - "\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03" + - "\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03" + - "\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F" + - "\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x12\x03\x12\x03\x13" + - "\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17" + - "\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + - "\x07\x1B\xC3\n\x1B\f\x1B\x0E\x1B\xC6\v\x1B\x03\x1C\x06\x1C\xC9\n\x1C\r" + - "\x1C\x0E\x1C\xCA\x03\x1C\x03\x1C\x06\x1C\xCF\n\x1C\r\x1C\x0E\x1C\xD0\x05" + - "\x1C\xD3\n\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x07\x1D\xD9\n\x1D\f\x1D" + - "\x0E\x1D\xDC\v\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x03" + - "\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03" + - " \x03 \x03!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03\"\x03#\x03" + - "#\x03#\x03#\x03#\x03#\x03#\x03#\x03#\x03$\x06$\u0104\n$\r$\x0E$\u0105" + - "\x03$\x03$\x03%\x03%\x03%\x03%\x07%\u010E\n%\f%\x0E%\u0111\v%\x03%\x03" + - "%\x03&\x03&\x03&\x03&\x07&\u0119\n&\f&\x0E&\u011C\v&\x03&\x03&\x03&\x03" + - "&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03*\x03+\x03+\x03,\x03,\x03" + - "-\x03-\x03.\x03.\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x03" + - "4\x034\x035\x035\x036\x036\x037\x037\x038\x038\x039\x039\x03:\x03:\x03" + - ";\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03@\x03@\x03A\x03A\x03" + - "B\x03B\x03\u011A\x02\x02C\x03\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06" + - "\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17\x02\r\x19" + - "\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02\x11!\x02\x12#\x02\x13%\x02\x14" + - "\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/\x02\x191\x02\x1A3\x02\x1B5\x02" + - "\x1C7\x02\x1D9\x02\x1E;\x02\x1F=\x02 ?\x02!A\x02\"C\x02#E\x02$G\x02%I" + - "\x02&K\x02\'M\x02\x02O\x02\x02Q\x02\x02S\x02\x02U\x02\x02W\x02\x02Y\x02" + - "\x02[\x02\x02]\x02\x02_\x02\x02a\x02\x02c\x02\x02e\x02\x02g\x02\x02i\x02" + - "\x02k\x02\x02m\x02\x02o\x02\x02q\x02\x02s\x02\x02u\x02\x02w\x02\x02y\x02" + - "\x02{\x02\x02}\x02\x02\x7F\x02\x02\x81\x02\x02\x83\x02\x02\x03\x02!\x03" + - "\x02))\x05\x02\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x022;\x04\x02C" + - "\\c|\x04\x02CCcc\x04\x02DDdd\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02" + - "HHhh\x04\x02IIii\x04\x02JJjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02" + - "NNnn\x04\x02OOoo\x04\x02PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02" + - "TTtt\x04\x02UUuu\x04\x02VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02" + - "ZZzz\x04\x02[[{{\x04\x02\\\\||\x02\u0147\x02\x03\x03\x02\x02\x02\x02\x05" + - "\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03" + - "\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03" + - "\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03" + - "\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03" + - "\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02" + - "\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02" + - "\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03" + - "\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02" + - "\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02" + - "?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02\x02\x02E\x03\x02" + - "\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02K\x03\x02\x02\x02" + - "\x03\x85\x03\x02\x02\x02\x05\x87\x03\x02\x02\x02\x07\x89\x03\x02\x02\x02" + - "\t\x8B\x03\x02\x02\x02\v\x8D\x03\x02\x02\x02\r\x8F\x03\x02\x02\x02\x0F" + - "\x91\x03\x02\x02\x02\x11\x93\x03\x02\x02\x02\x13\x96\x03\x02\x02\x02\x15" + - "\x98\x03\x02\x02\x02\x17\x9B\x03\x02\x02\x02\x19\x9D\x03\x02\x02\x02\x1B" + - "\xA0\x03\x02\x02\x02\x1D\xA4\x03\x02\x02\x02\x1F\xA7\x03\x02\x02\x02!" + - "\xAB\x03\x02\x02\x02#\xAD\x03\x02\x02\x02%\xAF\x03\x02\x02\x02\'\xB1\x03" + - "\x02\x02\x02)\xB3\x03\x02\x02\x02+\xB5\x03\x02\x02\x02-\xB7\x03\x02\x02" + - "\x02/\xB9\x03\x02\x02\x021\xBB\x03\x02\x02\x023\xBD\x03\x02\x02\x025\xBF" + - "\x03\x02\x02\x027\xC8\x03\x02\x02\x029\xD4\x03\x02\x02\x02;\xDF\x03\x02" + - "\x02\x02=\xE4\x03\x02\x02\x02?\xEA\x03\x02\x02\x02A\xEF\x03\x02\x02\x02" + - "C\xF4\x03\x02\x02\x02E\xF9\x03\x02\x02\x02G\u0103\x03\x02\x02\x02I\u0109" + - "\x03\x02\x02\x02K\u0114\x03\x02\x02\x02M\u0122\x03\x02\x02\x02O\u0124" + - "\x03\x02\x02\x02Q\u0126\x03\x02\x02\x02S\u0128\x03\x02\x02\x02U\u012A" + - "\x03\x02\x02\x02W\u012C\x03\x02\x02\x02Y\u012E\x03\x02\x02\x02[\u0130" + - "\x03\x02\x02\x02]\u0132\x03\x02\x02\x02_\u0134\x03\x02\x02\x02a\u0136" + - "\x03\x02\x02\x02c\u0138\x03\x02\x02\x02e\u013A\x03\x02\x02\x02g\u013C" + - "\x03\x02\x02\x02i\u013E\x03\x02\x02\x02k\u0140\x03\x02\x02\x02m\u0142" + - "\x03\x02\x02\x02o\u0144\x03\x02\x02\x02q\u0146\x03\x02\x02\x02s\u0148" + - "\x03\x02\x02\x02u\u014A\x03\x02\x02\x02w\u014C\x03\x02\x02\x02y\u014E" + - "\x03\x02\x02\x02{\u0150\x03\x02\x02\x02}\u0152\x03\x02\x02\x02\x7F\u0154" + - "\x03\x02\x02\x02\x81\u0156\x03\x02\x02\x02\x83\u0158\x03\x02\x02\x02\x85" + - "\x86\x07-\x02\x02\x86\x04\x03\x02\x02\x02\x87\x88\x07/\x02\x02\x88\x06" + - "\x03\x02\x02\x02\x89\x8A\x07,\x02\x02\x8A\b\x03\x02\x02\x02\x8B\x8C\x07" + - "1\x02\x02\x8C\n\x03\x02\x02\x02\x8D\x8E\x07\'\x02\x02\x8E\f\x03\x02\x02" + - "\x02\x8F\x90\x07`\x02\x02\x90\x0E\x03\x02\x02\x02\x91\x92\x07?\x02\x02" + - "\x92\x10\x03\x02\x02\x02\x93\x94\x07#\x02\x02\x94\x95\x07?\x02\x02\x95" + - "\x12\x03\x02\x02\x02\x96\x97\x07>\x02\x02\x97\x14\x03\x02\x02\x02\x98" + - "\x99\x07>\x02\x02\x99\x9A\x07?\x02\x02\x9A\x16\x03\x02\x02\x02\x9B\x9C" + - "\x07@\x02\x02\x9C\x18\x03\x02\x02\x02\x9D\x9E\x07@\x02\x02\x9E\x9F\x07" + - "?\x02\x02\x9F\x1A\x03\x02\x02\x02\xA0\xA1\x05Q)\x02\xA1\xA2\x05k6\x02" + - "\xA2\xA3\x05W,\x02\xA3\x1C\x03\x02\x02\x02\xA4\xA5\x05m7\x02\xA5\xA6\x05" + - "s:\x02\xA6\x1E\x03\x02\x02\x02\xA7\xA8\x05k6\x02\xA8\xA9\x05m7\x02\xA9" + - "\xAA\x05w<\x02\xAA \x03\x02\x02\x02\xAB\xAC\x07*\x02\x02\xAC\"\x03\x02" + - "\x02\x02\xAD\xAE\x07+\x02\x02\xAE$\x03\x02\x02\x02\xAF\xB0\x07}\x02\x02" + - "\xB0&\x03\x02\x02\x02\xB1\xB2\x07\x7F\x02\x02\xB2(\x03\x02\x02\x02\xB3" + - "\xB4\x07]\x02\x02\xB4*\x03\x02\x02\x02\xB5\xB6\x07_\x02\x02\xB6,\x03\x02" + - "\x02\x02\xB7\xB8\x07.\x02\x02\xB8.\x03\x02\x02\x02\xB9\xBA\x07=\x02\x02" + - "\xBA0\x03\x02\x02\x02\xBB\xBC\x07<\x02\x02\xBC2\x03\x02\x02\x02\xBD\xBE" + - "\x070\x02\x02\xBE4\x03\x02\x02\x02\xBF\xC4\x05O(\x02\xC0\xC3\x05O(\x02" + - "\xC1\xC3\x05M\'\x02\xC2\xC0\x03\x02\x02\x02\xC2\xC1\x03\x02\x02\x02\xC3" + - "\xC6\x03\x02\x02\x02\xC4\xC2\x03\x02\x02\x02\xC4\xC5\x03\x02\x02\x02\xC5" + - "6\x03\x02\x02\x02\xC6\xC4\x03\x02\x02\x02\xC7\xC9\x05M\'\x02\xC8\xC7\x03" + - "\x02\x02\x02\xC9\xCA\x03\x02\x02\x02\xCA\xC8\x03\x02\x02\x02\xCA\xCB\x03" + - "\x02\x02\x02\xCB\xD2\x03\x02\x02\x02\xCC\xCE\x070\x02\x02\xCD\xCF\x05" + - "M\'\x02\xCE\xCD\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xCE\x03\x02" + - "\x02\x02\xD0\xD1\x03\x02\x02\x02\xD1\xD3\x03\x02\x02\x02\xD2\xCC\x03\x02" + - "\x02\x02\xD2\xD3\x03\x02\x02\x02\xD38\x03\x02\x02\x02\xD4\xDA\x07)\x02" + - "\x02\xD5\xD9\n\x02\x02\x02\xD6\xD7\x07)\x02\x02\xD7\xD9\x07)\x02\x02\xD8" + - "\xD5\x03\x02\x02\x02\xD8\xD6\x03\x02\x02\x02\xD9\xDC\x03\x02\x02\x02\xDA" + - "\xD8\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDB\xDD\x03\x02\x02\x02\xDC" + - "\xDA\x03\x02\x02\x02\xDD\xDE\x07)\x02\x02\xDE:\x03\x02\x02\x02\xDF\xE0" + - "\x05w<\x02\xE0\xE1\x05s:\x02\xE1\xE2\x05y=\x02\xE2\xE3\x05Y-\x02\xE3<" + - "\x03\x02\x02\x02\xE4\xE5\x05[.\x02\xE5\xE6\x05Q)\x02\xE6\xE7\x05g4\x02" + - "\xE7\xE8\x05u;\x02\xE8\xE9\x05Y-\x02\xE9>\x03\x02\x02\x02\xEA\xEB\x05" + - "k6\x02\xEB\xEC\x05y=\x02\xEC\xED\x05g4\x02\xED\xEE\x05g4\x02\xEE@\x03" + - "\x02\x02\x02\xEF\xF0\x05W,\x02\xF0\xF1\x05Q)\x02\xF1\xF2\x05w<\x02\xF2" + - "\xF3\x05Y-\x02\xF3B\x03\x02\x02\x02\xF4\xF5\x05w<\x02\xF5\xF6\x05a1\x02" + - "\xF6\xF7\x05i5\x02\xF7\xF8\x05Y-\x02\xF8D\x03\x02\x02\x02\xF9\xFA\x05" + - "W,\x02\xFA\xFB\x05Q)\x02\xFB\xFC\x05w<\x02\xFC\xFD\x05Y-\x02\xFD\xFE\x05" + - "w<\x02\xFE\xFF\x05a1\x02\xFF\u0100\x05i5\x02\u0100\u0101\x05Y-\x02\u0101" + - "F\x03\x02\x02\x02\u0102\u0104\t\x03\x02\x02\u0103\u0102\x03\x02\x02\x02" + - "\u0104\u0105\x03\x02\x02\x02\u0105\u0103\x03\x02\x02\x02\u0105\u0106\x03" + - "\x02\x02\x02\u0106\u0107\x03\x02\x02\x02\u0107\u0108\b$\x02\x02\u0108" + - "H\x03\x02\x02\x02\u0109\u010A\x071\x02\x02\u010A\u010B\x071\x02\x02\u010B" + - "\u010F\x03\x02\x02\x02\u010C\u010E\n\x04\x02\x02\u010D\u010C\x03\x02\x02" + - "\x02\u010E\u0111\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02\u010F\u0110" + - "\x03\x02\x02\x02\u0110\u0112\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02" + - "\u0112\u0113\b%\x02\x02\u0113J\x03\x02\x02\x02\u0114\u0115\x071\x02\x02" + - "\u0115\u0116\x07,\x02\x02\u0116\u011A\x03\x02\x02\x02\u0117\u0119\v\x02" + - "\x02\x02\u0118\u0117\x03\x02\x02\x02\u0119\u011C\x03\x02\x02\x02\u011A" + - "\u011B\x03\x02\x02\x02\u011A\u0118\x03\x02\x02\x02\u011B\u011D\x03\x02" + - "\x02\x02\u011C\u011A\x03\x02\x02\x02\u011D\u011E\x07,\x02\x02\u011E\u011F" + - "\x071\x02\x02\u011F\u0120\x03\x02\x02\x02\u0120\u0121\b&\x02\x02\u0121" + - "L\x03\x02\x02\x02\u0122\u0123\t\x05\x02\x02\u0123N\x03\x02\x02\x02\u0124" + - "\u0125\t\x06\x02\x02\u0125P\x03\x02\x02\x02\u0126\u0127\t\x07\x02\x02" + - "\u0127R\x03\x02\x02\x02\u0128\u0129\t\b\x02\x02\u0129T\x03\x02\x02\x02" + - "\u012A\u012B\t\t\x02\x02\u012BV\x03\x02\x02\x02\u012C\u012D\t\n\x02\x02" + - "\u012DX\x03\x02\x02\x02\u012E\u012F\t\v\x02\x02\u012FZ\x03\x02\x02\x02" + - "\u0130\u0131\t\f\x02\x02\u0131\\\x03\x02\x02\x02\u0132\u0133\t\r\x02\x02" + - "\u0133^\x03\x02\x02\x02\u0134\u0135\t\x0E\x02\x02\u0135`\x03\x02\x02\x02" + - "\u0136\u0137\t\x0F\x02\x02\u0137b\x03\x02\x02\x02\u0138\u0139\t\x10\x02" + - "\x02\u0139d\x03\x02\x02\x02\u013A\u013B\t\x11\x02\x02\u013Bf\x03\x02\x02" + - "\x02\u013C\u013D\t\x12\x02\x02\u013Dh\x03\x02\x02\x02\u013E\u013F\t\x13" + - "\x02\x02\u013Fj\x03\x02\x02\x02\u0140\u0141\t\x14\x02\x02\u0141l\x03\x02" + - "\x02\x02\u0142\u0143\t\x15\x02\x02\u0143n\x03\x02\x02\x02\u0144\u0145" + - "\t\x16\x02\x02\u0145p\x03\x02\x02\x02\u0146\u0147\t\x17\x02\x02\u0147" + - "r\x03\x02\x02\x02\u0148\u0149\t\x18\x02\x02\u0149t\x03\x02\x02\x02\u014A" + - "\u014B\t\x19\x02\x02\u014Bv\x03\x02\x02\x02\u014C\u014D\t\x1A\x02\x02" + - "\u014Dx\x03\x02\x02\x02\u014E\u014F\t\x1B\x02\x02\u014Fz\x03\x02\x02\x02" + - "\u0150\u0151\t\x1C\x02\x02\u0151|\x03\x02\x02\x02\u0152\u0153\t\x1D\x02" + - "\x02\u0153~\x03\x02\x02\x02\u0154\u0155\t\x1E\x02\x02\u0155\x80\x03\x02" + - "\x02\x02\u0156\u0157\t\x1F\x02\x02\u0157\x82\x03\x02\x02\x02\u0158\u0159" + - "\t \x02\x02\u0159\x84\x03\x02\x02\x02\r\x02\xC2\xC4\xCA\xD0\xD2\xD8\xDA" + - "\u0105\u010F\u011A\x03\b\x02\x02"; - public static __ATN: ATN; - public static get _ATN(): ATN { - if (!FormulaLexer.__ATN) { - FormulaLexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(FormulaLexer._serializedATN)); - } - - return FormulaLexer.__ATN; - } - -} - diff --git a/packages/formula/src/visitor.ts b/packages/formula/src/formula.visitor.ts similarity index 99% rename from packages/formula/src/visitor.ts rename to packages/formula/src/formula.visitor.ts index 8280b1dfb..ec996095a 100644 --- a/packages/formula/src/visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -23,7 +23,7 @@ import { type VariableResult, } from "./types" -export class CustomFormulaVisitor +export class FormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index 6b57d59c8..cff270516 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -1,8 +1,8 @@ export { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" export { type ParseTree } from "antlr4ts/tree/ParseTree" +export * from "./formula.visitor" export * from "./function/type" export * from "./grammar/FormulaLexer" export * from "./grammar/FormulaParser" export * from "./grammar/FormulaParserVisitor" export * from "./util" -export * from "./visitor" diff --git a/packages/formula/src/util.ts b/packages/formula/src/util.ts index 1f109f24e..6d1c5326a 100644 --- a/packages/formula/src/util.ts +++ b/packages/formula/src/util.ts @@ -1,7 +1,7 @@ import { CharStreams, CommonTokenStream } from "antlr4ts" +import { FormulaVisitor } from "./formula.visitor" import { FormulaLexer } from "./grammar/FormulaLexer" import { FormulaParser } from "./grammar/FormulaParser" -import { CustomFormulaVisitor } from "./visitor" export function createParser(input: string) { const inputStream = CharStreams.fromString(input) @@ -15,7 +15,7 @@ export function parseFormula(input: string) { const tree = parser.formula() - const visitor = new CustomFormulaVisitor() + const visitor = new FormulaVisitor() const parsedFormula = visitor.visit(tree) return parsedFormula From fb6c3f0c5ac804b3fbcb880af63ccb8aad802454 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 22:01:23 +0800 Subject: [PATCH 08/31] test: add some tests --- packages/formula/src/formula.constants.ts | 3 + .../__snapshots__/parse-formula.test.ts.snap | 305 ++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 20 +- .../underlying/underlying-formula.visitor.ts | 9 +- .../template/src/templates/test.base.json | 21 ++ 5 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 packages/formula/src/formula.constants.ts diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts new file mode 100644 index 000000000..9b010f3ce --- /dev/null +++ b/packages/formula/src/formula.constants.ts @@ -0,0 +1,3 @@ +import { FormulaFunction } from "./function/type" + +export const FORMULA_FUNCTIONS: FormulaFunction[] = ["ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "SUM", "CONCAT"] as const diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 75bca35fd..00d3e5024 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -80,3 +80,308 @@ exports[`parse formula test {{field1}} 1`] = ` "variable": "field1", } `; + +exports[`parse formula test 1 + 1 1`] = ` +{ + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 1, + }, + ], + "name": "+", + "returnType": 0, + "type": "functionCall", + "value": "1+1", +} +`; + +exports[`parse formula test {{field1}} + {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "+", + "returnType": 0, + "type": "functionCall", + "value": "{{field1}}+{{field2}}", +} +`; + +exports[`parse formula test SUM({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "SUM", + "returnType": 0, + "type": "functionCall", + "value": "SUM({{field1}},{{field2}})", +} +`; + +exports[`parse formula test CONCAT({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "CONCAT", + "returnType": 1, + "type": "functionCall", + "value": "CONCAT({{field1}},{{field2}})", +} +`; + +exports[`parse formula test CONCAT({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "type": "argumentList", + }, + ], + "name": "CONCAT", + "returnType": 1, + "type": "functionCall", + "value": "CONCAT({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test SUBTRACT(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "SUBTRACT", + "returnType": 0, + "type": "functionCall", + "value": "SUBTRACT(1,2)", +} +`; + +exports[`parse formula test MULTIPLY(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "MULTIPLY", + "returnType": 0, + "type": "functionCall", + "value": "MULTIPLY(1,2)", +} +`; + +exports[`parse formula test DIVIDE(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "DIVIDE", + "returnType": 0, + "type": "functionCall", + "value": "DIVIDE(1,2)", +} +`; + +exports[`parse formula test 1 - 1 1`] = ` +{ + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 1, + }, + ], + "name": "-", + "returnType": 0, + "type": "functionCall", + "value": "1-1", +} +`; + +exports[`parse formula test 1 * 1 1`] = ` +{ + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 1, + }, + ], + "name": "*", + "returnType": 0, + "type": "functionCall", + "value": "1*1", +} +`; + +exports[`parse formula test 1 / 1 1`] = ` +{ + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 1, + }, + ], + "name": "/", + "returnType": 0, + "type": "functionCall", + "value": "1/1", +} +`; + +exports[`parse formula test SUBTRACT(1, 2) + MULTIPLY(3, 4) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "SUBTRACT", + "returnType": 0, + "type": "functionCall", + "value": "SUBTRACT(1,2)", + }, + { + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 3, + }, + { + "type": "number", + "value": 4, + }, + ], + "type": "argumentList", + }, + ], + "name": "MULTIPLY", + "returnType": 0, + "type": "functionCall", + "value": "MULTIPLY(3,4)", + }, + ], + "name": "+", + "returnType": 0, + "type": "functionCall", + "value": "SUBTRACT(1,2)+MULTIPLY(3,4)", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index 4ceb1bc43..ca7654c9f 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -2,7 +2,25 @@ import { describe, expect, test } from "bun:test" import { parseFormula } from "../util" describe("parse formula", () => { - test.each(["ADD(1, ADD(2, {{ field1 }}))", "ADD(1, 2)", "1", "{{field1}}"])("test %s", (input) => { + test.each([ + // + "ADD(1, ADD(2, {{ field1 }}))", + "ADD(1, 2)", + "SUBTRACT(1, 2)", + "MULTIPLY(1, 2)", + "DIVIDE(1, 2)", + "1 - 1", + "1 * 1", + "1 / 1", + "SUBTRACT(1, 2) + MULTIPLY(3, 4)", + "1", + "{{field1}}", + "1 + 1", + "{{field1}} + {{field2}}", + "SUM({{field1}}, {{field2}})", + "CONCAT({{field1}}, {{field2}})", + "CONCAT({{field1}}, {{field2}}, {{field3}})", + ])("test %s", (input) => { const result = parseFormula(input) expect(result).toMatchSnapshot() diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index e1757ba48..59e4d8487 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -65,9 +65,8 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i } visitFunctionCall(ctx: FunctionCallContext): string { const functionName = ctx.IDENTIFIER().text as FormulaFunction - // TODO: handle other functions return match(functionName) - .with("ADD", () => { + .with("ADD", "SUM", () => { const fn = this.arguments(ctx.argumentList()!).join(" + ") return `(${fn})` }) @@ -83,9 +82,13 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i const fn = this.arguments(ctx.argumentList()!).join(" / ") return `(${fn})` }) + .with("CONCAT", () => { + const fn = this.arguments(ctx.argumentList()!).join(" || ") + return `(${fn})` + }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" - return `${functionName}(${args})` + return `${functionName.toLowerCase()}(${args})` }) } diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 60025856b..9b4b53c78 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -61,6 +61,20 @@ "fn": "ADD({{count1}}, ADD({{count2}}, {{count1}}))" } }, + "SumCounts": { + "id": "sumcounts", + "type": "formula", + "option": { + "fn": "SUM({{count1}}, {{count2}})" + } + }, + "SumSum": { + "id": "sumsum", + "type": "formula", + "option": { + "fn": "SUM({{sum}}, {{sumadd}})" + } + }, "Multiply": { "id": "multiply", "type": "formula", @@ -75,6 +89,13 @@ "fn": "ADD({{count1}}, MULTIPLY({{count2}}, {{count1}}))" } }, + "Concat": { + "id": "concat", + "type": "formula", + "option": { + "fn": "CONCAT({{id}}, {{title}}, {{count1}}, {{count2}}, {{complicatedFn}})" + } + }, "Sum2": { "id": "sum2", "type": "formula", From 003982622c8d86b0eed4173d992e8aba3ee58011 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Mon, 28 Oct 2024 22:01:52 +0800 Subject: [PATCH 09/31] chore: ci add test --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b991883dc..0593b8e20 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,6 +28,9 @@ jobs: - name: Install dependencies run: bun install + - name: Run tests + run: bun test + - name: Build run: bun run build From b4ef2286267c6aa96fb7ae48b4232c2257aa1b00 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 09:35:02 +0800 Subject: [PATCH 10/31] feat: add some formula --- packages/formula/src/formula.constants.ts | 53 +- packages/formula/src/function/registry.ts | 13 +- packages/formula/src/function/type.ts | 65 +- .../__snapshots__/parse-formula.test.ts.snap | 673 ++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 22 + .../underlying/underlying-formula.visitor.ts | 20 + .../template/src/templates/test.base.json | 130 ++++ 7 files changed, 973 insertions(+), 3 deletions(-) diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts index 9b010f3ce..436a138e1 100644 --- a/packages/formula/src/formula.constants.ts +++ b/packages/formula/src/formula.constants.ts @@ -1,3 +1,54 @@ import { FormulaFunction } from "./function/type" -export const FORMULA_FUNCTIONS: FormulaFunction[] = ["ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "SUM", "CONCAT"] as const +export const FORMULA_FUNCTIONS: FormulaFunction[] = [ + "ADD", + "SUBTRACT", + "MULTIPLY", + "DIVIDE", + "SUM", + "CONCAT", + "MOD", + "POWER", + "SQRT", + "ABS", + "ROUND", + "FLOOR", + "CEILING", + "MIN", + "MAX", + "AVERAGE", + // "MEDIAN", + + // 文本处理 + "UPPER", + "LOWER", + "TRIM", + "LEFT", + "RIGHT", + "MID", + "LEN", + "FIND", + "REPLACE", + "SUBSTITUTE", + + // 逻辑运算 + "AND", + "OR", + "NOT", + "IF", + "SWITCH", + "ISBLANK", + "ISNUMBER", + "ISTEXT", + "ISERROR", + + // 统计函数 + "COUNT", + "COUNTA", + "COUNTIF", + "SUMIF", + "AVERAGE", + "STDEV", + "VAR", + "CORREL", +] as const diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index 294384061..ffd87e832 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -1,4 +1,4 @@ -import { ParamType, type ExpressionResult } from "../types" +import { ParamType,type ExpressionResult } from "../types" import { FormulaFunction } from "./type" interface FunctionDefinition { @@ -102,3 +102,14 @@ globalFunctionRegistry.register("MULTIPLY", [[ParamType.NUMBER, ParamType.NUMBER globalFunctionRegistry.register("DIVIDE", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) globalFunctionRegistry.register("SUM", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) globalFunctionRegistry.register("CONCAT", [[ParamType.STRING, ParamType.VARIADIC]], ParamType.STRING) +globalFunctionRegistry.register("MOD", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("POWER", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("SQRT", [[ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("ABS", [[ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("ROUND", [[ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("FLOOR", [[ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("CEILING", [[ParamType.NUMBER]], ParamType.NUMBER) +globalFunctionRegistry.register("MIN", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) +globalFunctionRegistry.register("MAX", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) +globalFunctionRegistry.register("AVERAGE", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) +// globalFunctionRegistry.register("MEDIAN", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) diff --git a/packages/formula/src/function/type.ts b/packages/formula/src/function/type.ts index 458a2b96a..cde8ccfc2 100644 --- a/packages/formula/src/function/type.ts +++ b/packages/formula/src/function/type.ts @@ -1 +1,64 @@ -export type FormulaFunction = "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "SUM" | "CONCAT" +export type FormulaFunction = + // 数学运算 + | "ADD" + | "SUBTRACT" + | "MULTIPLY" + | "DIVIDE" + | "SUM" + | "CONCAT" + | "MOD" + | "POWER" + | "SQRT" + | "ABS" + | "ROUND" + | "FLOOR" + | "CEILING" + | "MIN" + | "MAX" + | "AVERAGE" + // | "MEDIAN" + + // 文本处理 + | "UPPER" + | "LOWER" + | "TRIM" + | "LEFT" + | "RIGHT" + | "MID" + | "LEN" + | "FIND" + | "REPLACE" + | "SUBSTITUTE" + + // // 日期时间 + // | "NOW" + // | "TODAY" + // | "YEAR" + // | "MONTH" + // | "DAY" + // | "HOUR" + // | "MINUTE" + // | "SECOND" + // | "WEEKDAY" + // | "DATE" + + // 逻辑运算 + | "AND" + | "OR" + | "NOT" + | "IF" + | "SWITCH" + | "ISBLANK" + | "ISNUMBER" + | "ISTEXT" + | "ISERROR" + + // 统计函数 + | "COUNT" + | "COUNTA" + | "COUNTIF" + | "SUMIF" + | "AVERAGE" + | "STDEV" + | "VAR" + | "CORREL" diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 00d3e5024..27566585b 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -385,3 +385,676 @@ exports[`parse formula test SUBTRACT(1, 2) + MULTIPLY(3, 4) 1`] = ` "value": "SUBTRACT(1,2)+MULTIPLY(3,4)", } `; + +exports[`parse formula test MOD(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "MOD", + "returnType": 0, + "type": "functionCall", + "value": "MOD(1,2)", +} +`; + +exports[`parse formula test POWER(2, 3) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 2, + }, + { + "type": "number", + "value": 3, + }, + ], + "type": "argumentList", + }, + ], + "name": "POWER", + "returnType": 0, + "type": "functionCall", + "value": "POWER(2,3)", +} +`; + +exports[`parse formula test SQRT(4) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 4, + }, + ], + "type": "argumentList", + }, + ], + "name": "SQRT", + "returnType": 0, + "type": "functionCall", + "value": "SQRT(4)", +} +`; + +exports[`parse formula test ABS(-5) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 5, + }, + ], + "type": "argumentList", + }, + ], + "name": "ABS", + "returnType": 0, + "type": "functionCall", + "value": "ABS(-5)", +} +`; + +exports[`parse formula test ROUND(1.234, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "ROUND", + "returnType": 0, + "type": "functionCall", + "value": "ROUND(1.234,2)", +} +`; + +exports[`parse formula test FLOOR(1.234, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "FLOOR", + "returnType": 0, + "type": "functionCall", + "value": "FLOOR(1.234,2)", +} +`; + +exports[`parse formula test CEILING(1.234, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "CEILING", + "returnType": 0, + "type": "functionCall", + "value": "CEILING(1.234,2)", +} +`; + +exports[`parse formula test MIN(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "MIN", + "returnType": 0, + "type": "functionCall", + "value": "MIN(1,2)", +} +`; + +exports[`parse formula test MAX(1, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "MAX", + "returnType": 0, + "type": "functionCall", + "value": "MAX(1,2)", +} +`; + +exports[`parse formula test AVERAGE(1, 2, 3) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + { + "type": "number", + "value": 3, + }, + ], + "type": "argumentList", + }, + ], + "name": "AVERAGE", + "returnType": 0, + "type": "functionCall", + "value": "AVERAGE(1,2,3)", +} +`; + +exports[`parse formula test MOD({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "MOD", + "returnType": 0, + "type": "functionCall", + "value": "MOD({{field1}},{{field2}})", +} +`; + +exports[`parse formula test POWER({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "POWER", + "returnType": 0, + "type": "functionCall", + "value": "POWER({{field1}},{{field2}})", +} +`; + +exports[`parse formula test SQRT({{field1}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "SQRT", + "returnType": 0, + "type": "functionCall", + "value": "SQRT({{field1}})", +} +`; + +exports[`parse formula test ABS({{field1}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "ABS", + "returnType": 0, + "type": "functionCall", + "value": "ABS({{field1}})", +} +`; + +exports[`parse formula test ROUND({{field1}}, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "ROUND", + "returnType": 0, + "type": "functionCall", + "value": "ROUND({{field1}},2)", +} +`; + +exports[`parse formula test FLOOR({{field1}}, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "FLOOR", + "returnType": 0, + "type": "functionCall", + "value": "FLOOR({{field1}},2)", +} +`; + +exports[`parse formula test CEILING({{field1}}, 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 2, + }, + ], + "type": "argumentList", + }, + ], + "name": "CEILING", + "returnType": 0, + "type": "functionCall", + "value": "CEILING({{field1}},2)", +} +`; + +exports[`parse formula test MIN({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "MIN", + "returnType": 0, + "type": "functionCall", + "value": "MIN({{field1}},{{field2}})", +} +`; + +exports[`parse formula test MIN({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "type": "argumentList", + }, + ], + "name": "MIN", + "returnType": 0, + "type": "functionCall", + "value": "MIN({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test MAX({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "type": "argumentList", + }, + ], + "name": "MAX", + "returnType": 0, + "type": "functionCall", + "value": "MAX({{field1}},{{field2}})", +} +`; + +exports[`parse formula test MAX({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "type": "argumentList", + }, + ], + "name": "MAX", + "returnType": 0, + "type": "functionCall", + "value": "MAX({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test AVERAGE({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "type": "argumentList", + }, + ], + "name": "AVERAGE", + "returnType": 0, + "type": "functionCall", + "value": "AVERAGE({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test ROUND(1.234) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + ], + "type": "argumentList", + }, + ], + "name": "ROUND", + "returnType": 0, + "type": "functionCall", + "value": "ROUND(1.234)", +} +`; + +exports[`parse formula test ROUND({{field1}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "ROUND", + "returnType": 0, + "type": "functionCall", + "value": "ROUND({{field1}})", +} +`; + +exports[`parse formula test FLOOR(1.234) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + ], + "type": "argumentList", + }, + ], + "name": "FLOOR", + "returnType": 0, + "type": "functionCall", + "value": "FLOOR(1.234)", +} +`; + +exports[`parse formula test FLOOR({{field1}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "FLOOR", + "returnType": 0, + "type": "functionCall", + "value": "FLOOR({{field1}})", +} +`; + +exports[`parse formula test CEILING(1.234) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "number", + "value": 1.234, + }, + ], + "type": "argumentList", + }, + ], + "name": "CEILING", + "returnType": 0, + "type": "functionCall", + "value": "CEILING(1.234)", +} +`; + +exports[`parse formula test CEILING({{field1}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "type": "argumentList", + }, + ], + "name": "CEILING", + "returnType": 0, + "type": "functionCall", + "value": "CEILING({{field1}})", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index ca7654c9f..99e2bc58b 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -20,6 +20,28 @@ describe("parse formula", () => { "SUM({{field1}}, {{field2}})", "CONCAT({{field1}}, {{field2}})", "CONCAT({{field1}}, {{field2}}, {{field3}})", + "MOD(1, 2)", + "MOD({{field1}}, {{field2}})", + "POWER(2, 3)", + "POWER({{field1}}, {{field2}})", + "SQRT(4)", + "SQRT({{field1}})", + "ABS(-5)", + "ABS({{field1}})", + "ROUND(1.234)", + "ROUND({{field1}})", + "FLOOR(1.234)", + "FLOOR({{field1}})", + "CEILING(1.234)", + "CEILING({{field1}})", + "MIN(1, 2)", + "MIN({{field1}}, {{field2}})", + "MIN({{field1}}, {{field2}}, {{field3}})", + "MAX(1, 2)", + "MAX({{field1}}, {{field2}})", + "MAX({{field1}}, {{field2}}, {{field3}})", + "AVERAGE(1, 2, 3)", + "AVERAGE({{field1}}, {{field2}}, {{field3}})", ])("test %s", (input) => { const result = parseFormula(input) diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index 59e4d8487..5ffb98099 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -6,7 +6,9 @@ import { FunctionCallContext, FunctionExprContext, MulDivModExprContext, + NumberExprContext, ParenExprContext, + StringExprContext, VariableContext, VariableExprContext, type FormulaFunction, @@ -24,6 +26,14 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i return "" } + visitNumberExpr(ctx: NumberExprContext): string { + return ctx.NUMBER().text + } + + visitStringExpr(ctx: StringExprContext): string { + return ctx.STRING().text + } + visitAddSubExpr(ctx: AddSubExprContext): string { return this.visit(ctx.expression(0)) + ctx._op.text + this.visit(ctx.expression(1)) } @@ -86,6 +96,16 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i const fn = this.arguments(ctx.argumentList()!).join(" || ") return `(${fn})` }) + .with("AVERAGE", () => { + const args = this.arguments(ctx.argumentList()!) + return `( + (${args.map((arg) => `COALESCE(${arg}, 0)`).join(" + ")}) + / + (NULLIF( + ${args.map((arg) => `(CASE WHEN ${arg} IS NULL THEN 0 ELSE 1 END)`).join(" + ")} + , 0) + ))` + }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" return `${functionName.toLowerCase()}(${args})` diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 9b4b53c78..974f421bf 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -161,6 +161,136 @@ "Title": "2-2" } ] + }, + "Formula1": { + "schema": { + "Count1": { + "id": "count1", + "type": "number" + }, + "Count2": { + "id": "count2", + "type": "number" + }, + "Count3": { + "id": "count3", + "type": "number" + }, + "Sum": { + "id": "sum", + "type": "formula", + "option": { + "fn": "{{count1}} + {{count2}} + {{count3}}" + } + }, + "Subtract": { + "id": "subtract", + "type": "formula", + "option": { + "fn": "{{count1}} - {{count2}}" + } + }, + "Mod": { + "id": "mod", + "type": "formula", + "option": { + "fn": "MOD({{count3}}, {{count2}})" + } + }, + "Power": { + "id": "power", + "type": "formula", + "option": { + "fn": "POWER({{count3}}, {{count2}})" + } + }, + "Sqrt": { + "id": "sqrt", + "type": "formula", + "option": { + "fn": "SQRT({{count3}})" + } + }, + "Abs": { + "id": "abs", + "type": "formula", + "option": { + "fn": "ABS({{count3}})" + } + }, + "Round": { + "id": "round", + "type": "formula", + "option": { + "fn": "ROUND({{count3}})" + } + }, + "Floor": { + "id": "floor", + "type": "formula", + "option": { + "fn": "FLOOR({{count3}})" + } + }, + "Ceiling": { + "id": "ceiling", + "type": "formula", + "option": { + "fn": "CEILING({{count3}})" + } + }, + "Min1": { + "id": "min", + "type": "formula", + "option": { + "fn": "MIN({{count3}}, {{count2}})" + } + }, + "Min2": { + "id": "min2", + "type": "formula", + "option": { + "fn": "MIN({{count3}}, {{count1}}, {{count2}})" + } + }, + "Max1": { + "id": "max1", + "type": "formula", + "option": { + "fn": "MAX({{count3}}, {{count2}})" + } + }, + "Max2": { + "id": "max2", + "type": "formula", + "option": { + "fn": "MAX({{count3}}, {{count1}}, {{count2}})" + } + }, + "Average": { + "id": "average", + "type": "formula", + "option": { + "fn": "AVERAGE({{count3}}, {{count1}}, {{count2}})" + } + } + }, + "records": [ + { + "Count1": 1, + "Count2": 2, + "Count3": 3 + }, + { + "Count1": 4, + "Count2": 2, + "Count3": -3 + }, + { + "Count1": 5, + "Count2": 3 + } + ] } } } From 75269d058eee554d5061620bd6817934ddcd1bec Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 10:24:03 +0800 Subject: [PATCH 11/31] feat: add formula metadata --- packages/formula/package.json | 1 + packages/formula/src/formula.visitor.ts | 21 ++----- packages/formula/src/function/registry.ts | 57 +++++++++---------- packages/formula/src/index.ts | 2 + packages/formula/src/types.ts | 19 ++++--- packages/table/package.json | 1 + .../formula-field/formula-field.vo.ts | 50 +++++++++++++++- 7 files changed, 93 insertions(+), 58 deletions(-) diff --git a/packages/formula/package.json b/packages/formula/package.json index 37e949a95..ddaa33eac 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -11,6 +11,7 @@ "typescript": "^5.0.0" }, "dependencies": { + "@undb/zod": "*", "antlr4ts": "^0.5.0-alpha.4" }, "scripts": { diff --git a/packages/formula/src/formula.visitor.ts b/packages/formula/src/formula.visitor.ts index ec996095a..574264a7f 100644 --- a/packages/formula/src/formula.visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -15,20 +15,12 @@ import { VariableExprContext, } from "./grammar/FormulaParser" import type { FormulaParserVisitor } from "./grammar/FormulaParserVisitor" -import { - type ExpressionResult, - type FunctionExpressionResult, - type NumberResult, - ParamType, - type VariableResult, -} from "./types" +import { type ExpressionResult, type FunctionExpressionResult, type NumberResult, type VariableResult } from "./types" export class FormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { - private variables: Set = new Set() - visitFormula(ctx: FormulaContext): ExpressionResult { return this.visit(ctx.expression()) } @@ -41,7 +33,7 @@ export class FormulaVisitor type: "functionCall", name: op, arguments: [left, right], - returnType: ParamType.NUMBER, + returnType: "number", value: ctx.text, } } @@ -54,7 +46,7 @@ export class FormulaVisitor type: "functionCall", name: op, arguments: [left, right], - returnType: ParamType.NUMBER, + returnType: "number", value: ctx.text, } } @@ -111,15 +103,10 @@ export class FormulaVisitor } visitVariable(ctx: VariableContext): ExpressionResult { const variableName = ctx.IDENTIFIER().text - this.variables.add(variableName) - const raw = `{{${variableName}}}` + const raw = ctx.text return { type: "variable", value: raw, variable: variableName } } - getVariables(): string[] { - return Array.from(this.variables) - } - protected defaultResult(): ExpressionResult { return { type: "string", value: "" } } diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index ffd87e832..e7e698465 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -1,15 +1,15 @@ -import { ParamType,type ExpressionResult } from "../types" +import { ParamType, ReturnType, type ExpressionResult } from "../types" import { FormulaFunction } from "./type" interface FunctionDefinition { paramPatterns: ParamType[][] - returnType: ParamType + returnType: ReturnType } export class FunctionRegistry { private functions: Map = new Map() - register(name: FormulaFunction, paramPatterns: ParamType[][], returnType: ParamType) { + register(name: FormulaFunction, paramPatterns: ParamType[][], returnType: ReturnType) { this.functions.set(name, { paramPatterns, returnType }) } @@ -31,7 +31,7 @@ export class FunctionRegistry { const hasMatchingPattern = funcDef.paramPatterns.some((pattern) => { // 如果模式中包含 VARIADIC,则参数数量必须大于等于 pattern.length - 1 // 否则参数数量必须完全匹配 - if (pattern.includes(ParamType.VARIADIC)) { + if (pattern.includes("variadic")) { return args.length >= pattern.length - 1 } return args.length === pattern.length @@ -39,9 +39,7 @@ export class FunctionRegistry { if (!hasMatchingPattern) { const expectedCounts = funcDef.paramPatterns - .map((pattern) => - pattern.includes(ParamType.VARIADIC) ? `at least ${pattern.length - 1}` : `${pattern.length}`, - ) + .map((pattern) => (pattern.includes("variadic") ? `at least ${pattern.length - 1}` : `${pattern.length}`)) .join(" or ") throw new Error(`Function ${name} expects ${expectedCounts} arguments, but got ${args.length}`) } @@ -49,7 +47,7 @@ export class FunctionRegistry { const isValidPattern = funcDef.paramPatterns.some((pattern) => { for (let i = 0; i < pattern.length; i++) { const expectedType = pattern[i] - if (expectedType === ParamType.VARIADIC) { + if (expectedType === "variadic") { // 剩余的所有参数都应该匹配 VARIADIC 的前一个类型 const variadicType = pattern[i - 1] return args.slice(i - 1).every((arg) => this.isTypeMatch(arg, variadicType)) @@ -76,16 +74,16 @@ export class FunctionRegistry { } switch (expectedType) { - case ParamType.NUMBER: + case "number": return arg.type === "number" - case ParamType.STRING: + case "string": return arg.type === "string" - case ParamType.BOOLEAN: + case "boolean": return arg.type === "boolean" - case ParamType.DATE: + case "date": // TODO: 假设有日期类型的处理 return false - case ParamType.ANY: + case "any": return true default: return false @@ -96,20 +94,19 @@ export class FunctionRegistry { export const globalFunctionRegistry = new FunctionRegistry() // 注册函数,支持多种参数模式 -globalFunctionRegistry.register("ADD", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("SUBTRACT", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("MULTIPLY", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("DIVIDE", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("SUM", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) -globalFunctionRegistry.register("CONCAT", [[ParamType.STRING, ParamType.VARIADIC]], ParamType.STRING) -globalFunctionRegistry.register("MOD", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("POWER", [[ParamType.NUMBER, ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("SQRT", [[ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("ABS", [[ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("ROUND", [[ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("FLOOR", [[ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("CEILING", [[ParamType.NUMBER]], ParamType.NUMBER) -globalFunctionRegistry.register("MIN", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) -globalFunctionRegistry.register("MAX", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) -globalFunctionRegistry.register("AVERAGE", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) -// globalFunctionRegistry.register("MEDIAN", [[ParamType.NUMBER, ParamType.VARIADIC]], ParamType.NUMBER) +globalFunctionRegistry.register("ADD", [["number", "number"]], "number") +globalFunctionRegistry.register("SUBTRACT", [["number", "number"]], "number") +globalFunctionRegistry.register("MULTIPLY", [["number", "number"]], "number") +globalFunctionRegistry.register("DIVIDE", [["number", "number"]], "number") +globalFunctionRegistry.register("SUM", [["number", "variadic"]], "number") +globalFunctionRegistry.register("CONCAT", [["string", "variadic"]], "string") +globalFunctionRegistry.register("MOD", [["number", "number"]], "number") +globalFunctionRegistry.register("POWER", [["number", "number"]], "number") +globalFunctionRegistry.register("SQRT", [["number"]], "number") +globalFunctionRegistry.register("ABS", [["number"]], "number") +globalFunctionRegistry.register("ROUND", [["number"]], "number") +globalFunctionRegistry.register("FLOOR", [["number"]], "number") +globalFunctionRegistry.register("CEILING", [["number"]], "number") +globalFunctionRegistry.register("MIN", [["number", "variadic"]], "number") +globalFunctionRegistry.register("MAX", [["number", "variadic"]], "number") +globalFunctionRegistry.register("AVERAGE", [["number", "variadic"]], "number") diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index cff270516..91277a295 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -5,4 +5,6 @@ export * from "./function/type" export * from "./grammar/FormulaLexer" export * from "./grammar/FormulaParser" export * from "./grammar/FormulaParserVisitor" +export * from "./types" export * from "./util" + diff --git a/packages/formula/src/types.ts b/packages/formula/src/types.ts index 67cf8f343..2df265615 100644 --- a/packages/formula/src/types.ts +++ b/packages/formula/src/types.ts @@ -1,11 +1,12 @@ -export enum ParamType { - NUMBER, - STRING, - BOOLEAN, - DATE, - ANY, - VARIADIC, // 用于表示可变参数 -} +import { z } from "@undb/zod" + +export const paramType = z.enum(["number", "string", "boolean", "date", "any", "variadic"]) + +export type ParamType = z.infer + +export const returnType = z.enum(["number", "string", "boolean", "date", "any"]) + +export type ReturnType = z.infer export type FunctionDefinition = string @@ -13,7 +14,7 @@ export type FunctionExpressionResult = { type: "functionCall" name: string arguments: ExpressionResult[] - returnType: ParamType + returnType: ReturnType value: FunctionDefinition } diff --git a/packages/table/package.json b/packages/table/package.json index e3ab373e3..dec151656 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -15,6 +15,7 @@ "@undb/context": "workspace:*", "@undb/di": "workspace:*", "@undb/domain": "workspace:*", + "@undb/formula": "workspace:*", "@undb/logger": "workspace:*", "@undb/space": "workspace:*", "@undb/utils": "workspace:*", diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts index 3f19fd3a7..cc6b96a3d 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts @@ -1,4 +1,5 @@ -import { Option, Some } from "@undb/domain" +import { None, Option, Some } from "@undb/domain" +import { parseFormula, returnType } from "@undb/formula" import { z } from "@undb/zod" import { match } from "ts-pattern" import type { RecordComositeSpecification } from "../../../../records/record/record.composite-specification" @@ -23,6 +24,13 @@ export const formulaFieldOption = z.object({ fn, }) +const formulaMetadata = z.object({ + returnType: returnType, + fields: z.array(fieldId), +}) + +export type IFormulaFieldMetadata = z.infer + export type IFormulaFieldOption = z.infer export const createFormulaFieldDTO = createBaseFieldDTO.extend({ @@ -45,13 +53,40 @@ export const formulaFieldDTO = baseFieldDTO.extend({ export type IFormulaFieldDTO = z.infer export class FormulaField extends AbstractField { + private metadata: Option = None constructor(dto: IFormulaFieldDTO) { super(dto) if (dto.option) { - this.option = Some(dto.option) + this.setOption(dto.option) } } + setOption(option: IFormulaFieldOption) { + this.option = Some(option) + const fn = option.fn + if (fn) { + try { + const result = parseFormula(fn) + if (result.type === "functionCall") { + const metadata: IFormulaFieldMetadata = { + returnType: result.returnType, + fields: result.arguments + .filter((arg) => arg.type === "argumentList") + .flatMap((arg) => arg.arguments.filter((arg) => arg.type === "variable")) + .map((arg) => arg.variable), + } + this.setMetadata(metadata) + } + } catch (error) { + // ignore + } + } + } + + setMetadata(metadata: IFormulaFieldMetadata) { + this.metadata = Some(metadata) + } + static create(dto: ICreateFormulaFieldDTO) { const field = new FormulaField({ ...dto, id: FieldIdVo.fromStringOrCreate(dto.id).value }) return field @@ -99,4 +134,15 @@ export class FormulaField extends AbstractField o.fn) } + + get returnType() { + return this.metadata.mapOr("any", (m) => m.returnType) + } + + override toJSON() { + return { + ...super.toJSON(), + metadata: this.metadata.into(null), + } + } } From e536b113215bf3c4229c429f7b89fccb511d2e7f Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 10:40:08 +0800 Subject: [PATCH 12/31] feat: add formula metadata --- packages/formula/src/formula.visitor.ts | 9 ++++++++- .../variants/formula-field/formula-field.vo.ts | 12 ++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/formula/src/formula.visitor.ts b/packages/formula/src/formula.visitor.ts index 574264a7f..e632edd39 100644 --- a/packages/formula/src/formula.visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -21,6 +21,8 @@ export class FormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { + private variables: Set = new Set() + visitFormula(ctx: FormulaContext): ExpressionResult { return this.visit(ctx.expression()) } @@ -88,7 +90,7 @@ export class FormulaVisitor return { type: "functionCall", name: funcName, - arguments: Array.isArray(args) ? args : args ? [args] : [], + arguments: args?.arguments ?? [], returnType, value: ctx.text, } @@ -104,9 +106,14 @@ export class FormulaVisitor visitVariable(ctx: VariableContext): ExpressionResult { const variableName = ctx.IDENTIFIER().text const raw = ctx.text + this.variables.add(variableName) return { type: "variable", value: raw, variable: variableName } } + getVariables(): string[] { + return Array.from(this.variables) + } + protected defaultResult(): ExpressionResult { return { type: "string", value: "" } } diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts index cc6b96a3d..b93e92d5a 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts @@ -1,5 +1,5 @@ import { None, Option, Some } from "@undb/domain" -import { parseFormula, returnType } from "@undb/formula" +import { createParser, FormulaVisitor, returnType } from "@undb/formula" import { z } from "@undb/zod" import { match } from "ts-pattern" import type { RecordComositeSpecification } from "../../../../records/record/record.composite-specification" @@ -66,14 +66,14 @@ export class FormulaField extends AbstractField arg.type === "argumentList") - .flatMap((arg) => arg.arguments.filter((arg) => arg.type === "variable")) - .map((arg) => arg.variable), + fields: visitor.getVariables(), } this.setMetadata(metadata) } From ce9c464704438e16295b144f70c06087bc4c9019 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 10:46:44 +0800 Subject: [PATCH 13/31] test: update snapshot --- .../__snapshots__/parse-formula.test.ts.snap | 1000 ++++++----------- 1 file changed, 344 insertions(+), 656 deletions(-) diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 27566585b..9d388a095 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -4,84 +4,35 @@ exports[`parse formula test ADD(1, ADD(2, {{ field1 }})) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "arguments": [ - { - "arguments": [ - { - "type": "number", - "value": 2, - }, - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", - }, - ], - "name": "ADD", - "returnType": 0, - "type": "functionCall", - "value": "ADD(2,{{field1}})", - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, }, - ], - "name": "ADD", - "returnType": 0, - "type": "functionCall", - "value": "ADD(1,ADD(2,{{field1}}))", -} -`; - -exports[`parse formula test ADD(1, 2) 1`] = ` -{ - "arguments": [ { "arguments": [ { "type": "number", - "value": 1, + "value": 2, }, { - "type": "number", - "value": 2, + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], - "type": "argumentList", + "name": "ADD", + "returnType": "number", + "type": "functionCall", + "value": "ADD(2,{{field1}})", }, ], "name": "ADD", - "returnType": 0, + "returnType": "number", "type": "functionCall", - "value": "ADD(1,2)", -} -`; - -exports[`parse formula test 1 1`] = ` -{ - "type": "number", - "value": 1, -} -`; - -exports[`parse formula test {{field1}} 1`] = ` -{ - "type": "variable", - "value": "{{field1}}", - "variable": "field1", + "value": "ADD(1,ADD(2,{{field1}}))", } `; -exports[`parse formula test 1 + 1 1`] = ` +exports[`parse formula test ADD(1, 2) 1`] = ` { "arguments": [ { @@ -90,139 +41,30 @@ exports[`parse formula test 1 + 1 1`] = ` }, { "type": "number", - "value": 1, - }, - ], - "name": "+", - "returnType": 0, - "type": "functionCall", - "value": "1+1", -} -`; - -exports[`parse formula test {{field1}} + {{field2}} 1`] = ` -{ - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "name": "+", - "returnType": 0, - "type": "functionCall", - "value": "{{field1}}+{{field2}}", -} -`; - -exports[`parse formula test SUM({{field1}}, {{field2}}) 1`] = ` -{ - "arguments": [ - { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", - }, - ], - "name": "SUM", - "returnType": 0, - "type": "functionCall", - "value": "SUM({{field1}},{{field2}})", -} -`; - -exports[`parse formula test CONCAT({{field1}}, {{field2}}) 1`] = ` -{ - "arguments": [ - { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", + "value": 2, }, ], - "name": "CONCAT", - "returnType": 1, + "name": "ADD", + "returnType": "number", "type": "functionCall", - "value": "CONCAT({{field1}},{{field2}})", + "value": "ADD(1,2)", } `; -exports[`parse formula test CONCAT({{field1}}, {{field2}}, {{field3}}) 1`] = ` +exports[`parse formula test SUBTRACT(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - { - "type": "variable", - "value": "{{field3}}", - "variable": "field3", - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, }, - ], - "name": "CONCAT", - "returnType": 1, - "type": "functionCall", - "value": "CONCAT({{field1}},{{field2}},{{field3}})", -} -`; - -exports[`parse formula test SUBTRACT(1, 2) 1`] = ` -{ - "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 2, }, ], "name": "SUBTRACT", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "SUBTRACT(1,2)", } @@ -232,21 +74,16 @@ exports[`parse formula test MULTIPLY(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], "name": "MULTIPLY", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "MULTIPLY(1,2)", } @@ -256,21 +93,16 @@ exports[`parse formula test DIVIDE(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], "name": "DIVIDE", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "DIVIDE(1,2)", } @@ -289,7 +121,7 @@ exports[`parse formula test 1 - 1 1`] = ` }, ], "name": "-", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "1-1", } @@ -308,7 +140,7 @@ exports[`parse formula test 1 * 1 1`] = ` }, ], "name": "*", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "1*1", } @@ -327,7 +159,7 @@ exports[`parse formula test 1 / 1 1`] = ` }, ], "name": "/", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "1/1", } @@ -339,359 +171,289 @@ exports[`parse formula test SUBTRACT(1, 2) + MULTIPLY(3, 4) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], "name": "SUBTRACT", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "SUBTRACT(1,2)", }, { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 3, - }, - { - "type": "number", - "value": 4, - }, - ], - "type": "argumentList", + "type": "number", + "value": 3, + }, + { + "type": "number", + "value": 4, }, ], "name": "MULTIPLY", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "MULTIPLY(3,4)", }, ], "name": "+", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "SUBTRACT(1,2)+MULTIPLY(3,4)", } `; -exports[`parse formula test MOD(1, 2) 1`] = ` +exports[`parse formula test 1 1`] = ` { - "arguments": [ - { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", - }, - ], - "name": "MOD", - "returnType": 0, - "type": "functionCall", - "value": "MOD(1,2)", + "type": "number", + "value": 1, } `; -exports[`parse formula test POWER(2, 3) 1`] = ` +exports[`parse formula test {{field1}} 1`] = ` +{ + "type": "variable", + "value": "{{field1}}", + "variable": "field1", +} +`; + +exports[`parse formula test 1 + 1 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 2, - }, - { - "type": "number", - "value": 3, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 1, }, ], - "name": "POWER", - "returnType": 0, + "name": "+", + "returnType": "number", "type": "functionCall", - "value": "POWER(2,3)", + "value": "1+1", } `; -exports[`parse formula test SQRT(4) 1`] = ` +exports[`parse formula test {{field1}} + {{field2}} 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 4, - }, - ], - "type": "argumentList", - }, + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, ], - "name": "SQRT", - "returnType": 0, + "name": "+", + "returnType": "number", "type": "functionCall", - "value": "SQRT(4)", + "value": "{{field1}}+{{field2}}", } `; -exports[`parse formula test ABS(-5) 1`] = ` +exports[`parse formula test SUM({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 5, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "ABS", - "returnType": 0, + "name": "SUM", + "returnType": "number", "type": "functionCall", - "value": "ABS(-5)", + "value": "SUM({{field1}},{{field2}})", } `; -exports[`parse formula test ROUND(1.234, 2) 1`] = ` +exports[`parse formula test CONCAT({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "ROUND", - "returnType": 0, + "name": "CONCAT", + "returnType": "string", "type": "functionCall", - "value": "ROUND(1.234,2)", + "value": "CONCAT({{field1}},{{field2}})", } `; -exports[`parse formula test FLOOR(1.234, 2) 1`] = ` +exports[`parse formula test CONCAT({{field1}}, {{field2}}, {{field3}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", }, ], - "name": "FLOOR", - "returnType": 0, + "name": "CONCAT", + "returnType": "string", "type": "functionCall", - "value": "FLOOR(1.234,2)", + "value": "CONCAT({{field1}},{{field2}},{{field3}})", } `; -exports[`parse formula test CEILING(1.234, 2) 1`] = ` +exports[`parse formula test MOD(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], - "name": "CEILING", - "returnType": 0, + "name": "MOD", + "returnType": "number", "type": "functionCall", - "value": "CEILING(1.234,2)", + "value": "MOD(1,2)", } `; -exports[`parse formula test MIN(1, 2) 1`] = ` +exports[`parse formula test MOD({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "MIN", - "returnType": 0, + "name": "MOD", + "returnType": "number", "type": "functionCall", - "value": "MIN(1,2)", + "value": "MOD({{field1}},{{field2}})", } `; -exports[`parse formula test MAX(1, 2) 1`] = ` +exports[`parse formula test POWER(2, 3) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 2, + }, + { + "type": "number", + "value": 3, }, ], - "name": "MAX", - "returnType": 0, + "name": "POWER", + "returnType": "number", "type": "functionCall", - "value": "MAX(1,2)", + "value": "POWER(2,3)", } `; -exports[`parse formula test AVERAGE(1, 2, 3) 1`] = ` +exports[`parse formula test POWER({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1, - }, - { - "type": "number", - "value": 2, - }, - { - "type": "number", - "value": 3, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "AVERAGE", - "returnType": 0, + "name": "POWER", + "returnType": "number", "type": "functionCall", - "value": "AVERAGE(1,2,3)", + "value": "POWER({{field1}},{{field2}})", } `; -exports[`parse formula test MOD({{field1}}, {{field2}}) 1`] = ` +exports[`parse formula test SQRT(4) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", + "type": "number", + "value": 4, }, ], - "name": "MOD", - "returnType": 0, + "name": "SQRT", + "returnType": "number", "type": "functionCall", - "value": "MOD({{field1}},{{field2}})", + "value": "SQRT(4)", } `; -exports[`parse formula test POWER({{field1}}, {{field2}}) 1`] = ` +exports[`parse formula test SQRT({{field1}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], - "name": "POWER", - "returnType": 0, + "name": "SQRT", + "returnType": "number", "type": "functionCall", - "value": "POWER({{field1}},{{field2}})", + "value": "SQRT({{field1}})", } `; -exports[`parse formula test SQRT({{field1}}) 1`] = ` +exports[`parse formula test ABS(-5) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", + "type": "number", + "value": 5, }, ], - "name": "SQRT", - "returnType": 0, + "name": "ABS", + "returnType": "number", "type": "functionCall", - "value": "SQRT({{field1}})", + "value": "ABS(-5)", } `; @@ -699,362 +461,288 @@ exports[`parse formula test ABS({{field1}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], "name": "ABS", - "returnType": 0, + "returnType": "number", "type": "functionCall", "value": "ABS({{field1}})", } `; -exports[`parse formula test ROUND({{field1}}, 2) 1`] = ` +exports[`parse formula test ROUND(1.234) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1.234, }, ], "name": "ROUND", - "returnType": 0, + "returnType": "number", "type": "functionCall", - "value": "ROUND({{field1}},2)", + "value": "ROUND(1.234)", } `; -exports[`parse formula test FLOOR({{field1}}, 2) 1`] = ` +exports[`parse formula test ROUND({{field1}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], - "name": "FLOOR", - "returnType": 0, + "name": "ROUND", + "returnType": "number", "type": "functionCall", - "value": "FLOOR({{field1}},2)", + "value": "ROUND({{field1}})", } `; -exports[`parse formula test CEILING({{field1}}, 2) 1`] = ` +exports[`parse formula test FLOOR(1.234) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "number", - "value": 2, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1.234, }, ], - "name": "CEILING", - "returnType": 0, + "name": "FLOOR", + "returnType": "number", "type": "functionCall", - "value": "CEILING({{field1}},2)", + "value": "FLOOR(1.234)", } `; -exports[`parse formula test MIN({{field1}}, {{field2}}) 1`] = ` +exports[`parse formula test FLOOR({{field1}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], - "name": "MIN", - "returnType": 0, + "name": "FLOOR", + "returnType": "number", "type": "functionCall", - "value": "MIN({{field1}},{{field2}})", + "value": "FLOOR({{field1}})", } `; -exports[`parse formula test MIN({{field1}}, {{field2}}, {{field3}}) 1`] = ` +exports[`parse formula test CEILING(1.234) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - { - "type": "variable", - "value": "{{field3}}", - "variable": "field3", - }, - ], - "type": "argumentList", + "type": "number", + "value": 1.234, }, ], - "name": "MIN", - "returnType": 0, + "name": "CEILING", + "returnType": "number", "type": "functionCall", - "value": "MIN({{field1}},{{field2}},{{field3}})", + "value": "CEILING(1.234)", } `; -exports[`parse formula test MAX({{field1}}, {{field2}}) 1`] = ` +exports[`parse formula test CEILING({{field1}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", }, ], - "name": "MAX", - "returnType": 0, + "name": "CEILING", + "returnType": "number", "type": "functionCall", - "value": "MAX({{field1}},{{field2}})", + "value": "CEILING({{field1}})", } `; -exports[`parse formula test MAX({{field1}}, {{field2}}, {{field3}}) 1`] = ` +exports[`parse formula test MIN(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - { - "type": "variable", - "value": "{{field3}}", - "variable": "field3", - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], - "name": "MAX", - "returnType": 0, + "name": "MIN", + "returnType": "number", "type": "functionCall", - "value": "MAX({{field1}},{{field2}},{{field3}})", + "value": "MIN(1,2)", } `; -exports[`parse formula test AVERAGE({{field1}}, {{field2}}, {{field3}}) 1`] = ` +exports[`parse formula test MIN({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - { - "type": "variable", - "value": "{{field2}}", - "variable": "field2", - }, - { - "type": "variable", - "value": "{{field3}}", - "variable": "field3", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "AVERAGE", - "returnType": 0, + "name": "MIN", + "returnType": "number", "type": "functionCall", - "value": "AVERAGE({{field1}},{{field2}},{{field3}})", + "value": "MIN({{field1}},{{field2}})", } `; -exports[`parse formula test ROUND(1.234) 1`] = ` +exports[`parse formula test MIN({{field1}}, {{field2}}, {{field3}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", }, ], - "name": "ROUND", - "returnType": 0, + "name": "MIN", + "returnType": "number", "type": "functionCall", - "value": "ROUND(1.234)", + "value": "MIN({{field1}},{{field2}},{{field3}})", } `; -exports[`parse formula test ROUND({{field1}}) 1`] = ` +exports[`parse formula test MAX(1, 2) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, }, ], - "name": "ROUND", - "returnType": 0, + "name": "MAX", + "returnType": "number", "type": "functionCall", - "value": "ROUND({{field1}})", + "value": "MAX(1,2)", } `; -exports[`parse formula test FLOOR(1.234) 1`] = ` +exports[`parse formula test MAX({{field1}}, {{field2}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", }, ], - "name": "FLOOR", - "returnType": 0, + "name": "MAX", + "returnType": "number", "type": "functionCall", - "value": "FLOOR(1.234)", + "value": "MAX({{field1}},{{field2}})", } `; -exports[`parse formula test FLOOR({{field1}}) 1`] = ` +exports[`parse formula test MAX({{field1}}, {{field2}}, {{field3}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", }, ], - "name": "FLOOR", - "returnType": 0, + "name": "MAX", + "returnType": "number", "type": "functionCall", - "value": "FLOOR({{field1}})", + "value": "MAX({{field1}},{{field2}},{{field3}})", } `; -exports[`parse formula test CEILING(1.234) 1`] = ` +exports[`parse formula test AVERAGE(1, 2, 3) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "number", - "value": 1.234, - }, - ], - "type": "argumentList", + "type": "number", + "value": 1, + }, + { + "type": "number", + "value": 2, + }, + { + "type": "number", + "value": 3, }, ], - "name": "CEILING", - "returnType": 0, + "name": "AVERAGE", + "returnType": "number", "type": "functionCall", - "value": "CEILING(1.234)", + "value": "AVERAGE(1,2,3)", } `; -exports[`parse formula test CEILING({{field1}}) 1`] = ` +exports[`parse formula test AVERAGE({{field1}}, {{field2}}, {{field3}}) 1`] = ` { "arguments": [ { - "arguments": [ - { - "type": "variable", - "value": "{{field1}}", - "variable": "field1", - }, - ], - "type": "argumentList", + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", }, ], - "name": "CEILING", - "returnType": 0, + "name": "AVERAGE", + "returnType": "number", "type": "functionCall", - "value": "CEILING({{field1}})", + "value": "AVERAGE({{field1}},{{field2}},{{field3}})", } `; From 16639d161f806fdbe499c63e9fd9ab4291e64873 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 11:04:15 +0800 Subject: [PATCH 14/31] feat: add string forumlas --- packages/formula/src/function/registry.ts | 9 ++- .../underlying/underlying-formula.visitor.ts | 16 ++++- .../template/src/templates/test.base.json | 64 ++++++++++++++++++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index e7e698465..25ef03659 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -99,7 +99,6 @@ globalFunctionRegistry.register("SUBTRACT", [["number", "number"]], "number") globalFunctionRegistry.register("MULTIPLY", [["number", "number"]], "number") globalFunctionRegistry.register("DIVIDE", [["number", "number"]], "number") globalFunctionRegistry.register("SUM", [["number", "variadic"]], "number") -globalFunctionRegistry.register("CONCAT", [["string", "variadic"]], "string") globalFunctionRegistry.register("MOD", [["number", "number"]], "number") globalFunctionRegistry.register("POWER", [["number", "number"]], "number") globalFunctionRegistry.register("SQRT", [["number"]], "number") @@ -110,3 +109,11 @@ globalFunctionRegistry.register("CEILING", [["number"]], "number") globalFunctionRegistry.register("MIN", [["number", "variadic"]], "number") globalFunctionRegistry.register("MAX", [["number", "variadic"]], "number") globalFunctionRegistry.register("AVERAGE", [["number", "variadic"]], "number") + +globalFunctionRegistry.register("CONCAT", [["string", "variadic"]], "string") +globalFunctionRegistry.register("UPPER", [["string"]], "string") +globalFunctionRegistry.register("LOWER", [["string"]], "string") +globalFunctionRegistry.register("TRIM", [["string"]], "string") +globalFunctionRegistry.register("LEFT", [["string", "number"]], "string") +globalFunctionRegistry.register("RIGHT", [["string", "number"]], "string") +globalFunctionRegistry.register("MID", [["string", "number", "number"]], "string") diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index 5ffb98099..96d64f229 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -93,7 +93,9 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i return `(${fn})` }) .with("CONCAT", () => { - const fn = this.arguments(ctx.argumentList()!).join(" || ") + const fn = this.arguments(ctx.argumentList()!) + .map((arg) => `COALESCE(${arg}, '')`) + .join(" || ") return `(${fn})` }) .with("AVERAGE", () => { @@ -106,6 +108,18 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i , 0) ))` }) + .with("LEFT", () => { + const args = this.arguments(ctx.argumentList()!) + return `SUBSTR(${args[0]}, 1, ${args[1]})` + }) + .with("RIGHT", () => { + const args = this.arguments(ctx.argumentList()!) + return `SUBSTR(${args[0]}, -${args[1]}, ${args[1]})` + }) + .with("MID", () => { + const args = this.arguments(ctx.argumentList()!) + return `SUBSTR(${args[0]}, ${args[1]}, ${args[2]})` + }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" return `${functionName.toLowerCase()}(${args})` diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 974f421bf..24ce5f1ea 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -176,6 +176,14 @@ "id": "count3", "type": "number" }, + "String1": { + "id": "string1", + "type": "string" + }, + "String2": { + "id": "string2", + "type": "string" + }, "Sum": { "id": "sum", "type": "formula", @@ -273,18 +281,70 @@ "option": { "fn": "AVERAGE({{count3}}, {{count1}}, {{count2}})" } + }, + "Concat": { + "id": "concat", + "type": "formula", + "option": { + "fn": "CONCAT({{string1}}, ' ', {{string2}})" + } + }, + "Upper": { + "id": "upper", + "type": "formula", + "option": { + "fn": "UPPER({{string1}})" + } + }, + "Lower": { + "id": "lower", + "type": "formula", + "option": { + "fn": "LOWER({{string1}})" + } + }, + "Trim": { + "id": "trim", + "type": "formula", + "option": { + "fn": "TRIM({{string1}})" + } + }, + "Left": { + "id": "left", + "type": "formula", + "option": { + "fn": "LEFT({{string1}}, 3)" + } + }, + "Right": { + "id": "right", + "type": "formula", + "option": { + "fn": "RIGHT({{string1}}, 3)" + } + }, + "Mid": { + "id": "mid", + "type": "formula", + "option": { + "fn": "MID({{string1}}, 2, 3)" + } } }, "records": [ { "Count1": 1, "Count2": 2, - "Count3": 3 + "Count3": 3, + "String1": "Hello", + "String2": "World" }, { "Count1": 4, "Count2": 2, - "Count3": -3 + "Count3": -3, + "String1": " Hello " }, { "Count1": 5, From 6ee468b4eddd17574f28d05633625366062c4228 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 11:04:50 +0800 Subject: [PATCH 15/31] test: add formula test --- .../__snapshots__/parse-formula.test.ts.snap | 111 ++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 5 + 2 files changed, 116 insertions(+) diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 9d388a095..41cbb3609 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -746,3 +746,114 @@ exports[`parse formula test AVERAGE({{field1}}, {{field2}}, {{field3}}) 1`] = ` "value": "AVERAGE({{field1}},{{field2}},{{field3}})", } `; + +exports[`parse formula test CONCAT({{field1}}, {{field2}}) 2`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "CONCAT", + "returnType": "string", + "type": "functionCall", + "value": "CONCAT({{field1}},{{field2}})", +} +`; + +exports[`parse formula test CONCAT({{field1}}, {{field2}}, {{field3}}) 2`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "name": "CONCAT", + "returnType": "string", + "type": "functionCall", + "value": "CONCAT({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test LEFT({{field1}}, 3) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 3, + }, + ], + "name": "LEFT", + "returnType": "string", + "type": "functionCall", + "value": "LEFT({{field1}},3)", +} +`; + +exports[`parse formula test RIGHT({{field1}}, 3) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 3, + }, + ], + "name": "RIGHT", + "returnType": "string", + "type": "functionCall", + "value": "RIGHT({{field1}},3)", +} +`; + +exports[`parse formula test MID({{field1}}, 2, 3) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 2, + }, + { + "type": "number", + "value": 3, + }, + ], + "name": "MID", + "returnType": "string", + "type": "functionCall", + "value": "MID({{field1}},2,3)", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index 99e2bc58b..41b18ba37 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -42,6 +42,11 @@ describe("parse formula", () => { "MAX({{field1}}, {{field2}}, {{field3}})", "AVERAGE(1, 2, 3)", "AVERAGE({{field1}}, {{field2}}, {{field3}})", + "CONCAT({{field1}}, {{field2}})", + "CONCAT({{field1}}, {{field2}}, {{field3}})", + "LEFT({{field1}}, 3)", + "RIGHT({{field1}}, 3)", + "MID({{field1}}, 2, 3)", ])("test %s", (input) => { const result = parseFormula(input) From 7ffac74e104cddf023a90581e9581d9762d03ce3 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 12:54:51 +0800 Subject: [PATCH 16/31] feat: add compare operation function call --- .../formula/formula-cursor.visitor.ts | 20 ++++ packages/formula/src/formula.visitor.ts | 104 +++++++++++++++++- .../underlying/underlying-formula.visitor.ts | 20 ++++ .../template/src/templates/test.base.json | 63 +++++++++++ 4 files changed, 204 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts index 8b5c4d9c3..d877455f5 100644 --- a/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts +++ b/apps/frontend/src/lib/components/formula/formula-cursor.visitor.ts @@ -1,13 +1,17 @@ import { AbstractParseTreeVisitor, AddSubExprContext, + AndExprContext, ArgumentListContext, + ComparisonExprContext, ExpressionContext, FormulaContext, FormulaParserVisitor, FunctionCallContext, FunctionExprContext, MulDivModExprContext, + NotExprContext, + OrExprContext, type ParseTree, VariableContext, } from "@undb/formula" @@ -70,6 +74,22 @@ export class FormulaCursorVisitor extends AbstractParseTreeVisitor impleme this.visitPositionInRange(ctx) } + visitComparisonExpr(ctx: ComparisonExprContext) { + this.visitPositionInRange(ctx) + } + + visitAndExpr(ctx: AndExprContext) { + this.visitPositionInRange(ctx) + } + + visitOrExpr(ctx: OrExprContext) { + this.visitPositionInRange(ctx) + } + + visitNotExpr(ctx: NotExprContext) { + this.visitPositionInRange(ctx) + } + visitMulDivModExpr(ctx: MulDivModExprContext) { this.visitPositionInRange(ctx) } diff --git a/packages/formula/src/formula.visitor.ts b/packages/formula/src/formula.visitor.ts index e632edd39..24ccd0692 100644 --- a/packages/formula/src/formula.visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -3,19 +3,29 @@ import { globalFunctionRegistry } from "./function/registry" import { FormulaFunction } from "./function/type" import { AddSubExprContext, + AndExprContext, ArgumentListContext, + ComparisonExprContext, FormulaContext, FunctionCallContext, FunctionExprContext, MulDivModExprContext, + NotExprContext, NumberExprContext, + OrExprContext, ParenExprContext, StringExprContext, VariableContext, VariableExprContext, } from "./grammar/FormulaParser" import type { FormulaParserVisitor } from "./grammar/FormulaParserVisitor" -import { type ExpressionResult, type FunctionExpressionResult, type NumberResult, type VariableResult } from "./types" +import { + ReturnType, + type ExpressionResult, + type FunctionExpressionResult, + type NumberResult, + type VariableResult, +} from "./types" export class FormulaVisitor extends AbstractParseTreeVisitor @@ -23,6 +33,25 @@ export class FormulaVisitor { private variables: Set = new Set() + private assertType(result: ExpressionResult, types: ReturnType[]): boolean { + if (result.type === "variable") { + return true + } + + if (result.type === "functionCall") { + if (!types.includes(result.returnType)) { + throw new Error(`Expected ${types.join(" or ")} but got ${result.name}`) + } + return true + } + + if (!types.includes(result.type as ReturnType)) { + throw new Error(`Expected ${types.join(" or ")} but got ${result.type}`) + } + + return true + } + visitFormula(ctx: FormulaContext): ExpressionResult { return this.visit(ctx.expression()) } @@ -30,6 +59,10 @@ export class FormulaVisitor visitMulDivModExpr(ctx: MulDivModExprContext): ExpressionResult { const left = this.visit(ctx.expression(0)) as NumberResult | VariableResult const right = this.visit(ctx.expression(1)) as NumberResult | VariableResult + + this.assertType(left, ["number"]) + this.assertType(right, ["number"]) + const op = ctx._op.text! return { type: "functionCall", @@ -41,8 +74,12 @@ export class FormulaVisitor } visitAddSubExpr(ctx: AddSubExprContext): ExpressionResult { - const left = this.visit(ctx.expression(0)) as NumberResult - const right = this.visit(ctx.expression(1)) as NumberResult + const left = this.visit(ctx.expression(0)) as NumberResult | VariableResult + const right = this.visit(ctx.expression(1)) as NumberResult | VariableResult + + this.assertType(left, ["number"]) + this.assertType(right, ["number"]) + const op = ctx._op.text! return { type: "functionCall", @@ -53,6 +90,67 @@ export class FormulaVisitor } } + visitComparisonExpr(ctx: ComparisonExprContext): ExpressionResult { + const left = this.visit(ctx.expression(0)) + const right = this.visit(ctx.expression(1)) + + this.assertType(left, ["number"]) + this.assertType(right, ["number"]) + + const op = ctx._op.text! + return { + type: "functionCall", + name: op, + arguments: [left, right], + returnType: "boolean", + value: ctx.text, + } + } + + visitAndExpr(ctx: AndExprContext): ExpressionResult { + const left = this.visit(ctx.expression(0)) + const right = this.visit(ctx.expression(1)) + + this.assertType(left, ["boolean"]) + this.assertType(right, ["boolean"]) + + return { + type: "functionCall", + name: "AND", + arguments: [left, right], + returnType: "boolean", + value: ctx.text, + } + } + + visitOrExpr(ctx: OrExprContext): ExpressionResult { + const left = this.visit(ctx.expression(0)) + const right = this.visit(ctx.expression(1)) + + this.assertType(left, ["boolean"]) + this.assertType(right, ["boolean"]) + + return { + type: "functionCall", + name: "OR", + arguments: [left, right], + returnType: "boolean", + value: ctx.text, + } + } + + visitNotExpr(ctx: NotExprContext): ExpressionResult { + const expr = this.visit(ctx.expression()) + this.assertType(expr, ["boolean"]) + return { + type: "functionCall", + name: "NOT", + arguments: [expr], + returnType: "boolean", + value: ctx.text, + } + } + visitFunctionExpr(ctx: FunctionExprContext): ExpressionResult { return this.visit(ctx.functionCall()) } diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index 96d64f229..0384b2aa1 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -1,12 +1,16 @@ import { AbstractParseTreeVisitor, AddSubExprContext, + AndExprContext, ArgumentListContext, + ComparisonExprContext, FormulaContext, FunctionCallContext, FunctionExprContext, MulDivModExprContext, + NotExprContext, NumberExprContext, + OrExprContext, ParenExprContext, StringExprContext, VariableContext, @@ -34,6 +38,22 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i return ctx.STRING().text } + visitComparisonExpr(ctx: ComparisonExprContext): string { + return this.visit(ctx.expression(0)) + ctx._op.text + this.visit(ctx.expression(1)) + } + + visitAndExpr(ctx: AndExprContext): string { + return this.visit(ctx.expression(0)) + " AND " + this.visit(ctx.expression(1)) + } + + visitOrExpr(ctx: OrExprContext): string { + return this.visit(ctx.expression(0)) + " OR " + this.visit(ctx.expression(1)) + } + + visitNotExpr(ctx: NotExprContext): string { + return "NOT " + this.visit(ctx.expression()) + } + visitAddSubExpr(ctx: AddSubExprContext): string { return this.visit(ctx.expression(0)) + ctx._op.text + this.visit(ctx.expression(1)) } diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 24ce5f1ea..53a19f43a 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -330,6 +330,69 @@ "option": { "fn": "MID({{string1}}, 2, 3)" } + }, + "Greater": { + "id": "greater", + "type": "formula", + "option": { + "fn": "{{count1}} > {{count2}}" + } + }, + "And": { + "id": "nestedCompare", + "type": "formula", + "option": { + "fn": "({{count1}} > {{count2}}) AND ({{count2}} > {{count3}})" + } + }, + "Or": { + "id": "or", + "type": "formula", + "option": { + "fn": "({{count1}} > {{count2}}) OR ({{count2}} > {{count3}})" + } + }, + "NOT": { + "id": "not", + "type": "formula", + "option": { + "fn": "NOT ({{count1}} > {{count2}})" + } + }, + "Equal": { + "id": "equal", + "type": "formula", + "option": { + "fn": "{{count1}} = {{count2}}" + } + }, + "NotEqual": { + "id": "notEqual", + "type": "formula", + "option": { + "fn": "{{count1}} != {{count2}}" + } + }, + "GreaterEqual": { + "id": "greaterEqual", + "type": "formula", + "option": { + "fn": "{{count1}} >= {{count2}}" + } + }, + "LessEqual": { + "id": "lessEqual", + "type": "formula", + "option": { + "fn": "{{count1}} <= {{count2}}" + } + }, + "Less": { + "id": "less", + "type": "formula", + "option": { + "fn": "{{count1}} < {{count2}}" + } } }, "records": [ From 48f0031a1a43f57ee761548f57ba95594dbba781 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 12:59:36 +0800 Subject: [PATCH 17/31] test: add some formula test --- .../__snapshots__/parse-formula.test.ts.snap | 371 ++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 11 + 2 files changed, 382 insertions(+) diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 41cbb3609..6f085803c 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -857,3 +857,374 @@ exports[`parse formula test MID({{field1}}, 2, 3) 1`] = ` "value": "MID({{field1}},2,3)", } `; + +exports[`parse formula test NOT ({{field1}} > {{field2}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>{{field2}}", + }, + ], + "name": "NOT", + "returnType": "boolean", + "type": "functionCall", + "value": "NOT({{field1}}>{{field2}})", +} +`; + +exports[`parse formula test ({{field1}} > {{field2}}) AND ({{field2}} > {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>{{field2}}", + }, + { + "arguments": [ + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field2}}>{{field3}}", + }, + ], + "name": "AND", + "returnType": "boolean", + "type": "functionCall", + "value": "({{field1}}>{{field2}})AND({{field2}}>{{field3}})", +} +`; + +exports[`parse formula test ({{field1}} > {{field2}}) OR ({{field2}} > {{field3}}) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>{{field2}}", + }, + { + "arguments": [ + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field2}}>{{field3}}", + }, + ], + "name": "OR", + "returnType": "boolean", + "type": "functionCall", + "value": "({{field1}}>{{field2}})OR({{field2}}>{{field3}})", +} +`; + +exports[`parse formula test {{field1}} = {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "=", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}={{field2}}", +} +`; + +exports[`parse formula test {{field1}} != {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "!=", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}!={{field2}}", +} +`; + +exports[`parse formula test {{field1}} >= {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": ">=", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>={{field2}}", +} +`; + +exports[`parse formula test {{field1}} <= {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "<=", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}<={{field2}}", +} +`; + +exports[`parse formula test {{field1}} < {{field2}} 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "<", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}<{{field2}}", +} +`; + +exports[`parse formula test {{field1}} > 1 AND {{field2}} < 2 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 1, + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1", + }, + { + "arguments": [ + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "number", + "value": 2, + }, + ], + "name": "<", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field2}}<2", + }, + ], + "name": "AND", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1AND{{field2}}<2", +} +`; + +exports[`parse formula test {{field1}} > 1 OR {{field2}} < 2 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 1, + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1", + }, + { + "arguments": [ + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "number", + "value": 2, + }, + ], + "name": "<", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field2}}<2", + }, + ], + "name": "OR", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1OR{{field2}}<2", +} +`; + +exports[`parse formula test NOT ({{field1}} > 1 AND {{field2}} < 2) 1`] = ` +{ + "arguments": [ + { + "arguments": [ + { + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "number", + "value": 1, + }, + ], + "name": ">", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1", + }, + { + "arguments": [ + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "number", + "value": 2, + }, + ], + "name": "<", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field2}}<2", + }, + ], + "name": "AND", + "returnType": "boolean", + "type": "functionCall", + "value": "{{field1}}>1AND{{field2}}<2", + }, + ], + "name": "NOT", + "returnType": "boolean", + "type": "functionCall", + "value": "NOT({{field1}}>1AND{{field2}}<2)", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index 41b18ba37..6872c0b27 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -47,6 +47,17 @@ describe("parse formula", () => { "LEFT({{field1}}, 3)", "RIGHT({{field1}}, 3)", "MID({{field1}}, 2, 3)", + "NOT ({{field1}} > {{field2}})", + "({{field1}} > {{field2}}) AND ({{field2}} > {{field3}})", + "({{field1}} > {{field2}}) OR ({{field2}} > {{field3}})", + "{{field1}} = {{field2}}", + "{{field1}} != {{field2}}", + "{{field1}} >= {{field2}}", + "{{field1}} <= {{field2}}", + "{{field1}} < {{field2}}", + "{{field1}} > 1 AND {{field2}} < 2", + "{{field1}} > 1 OR {{field2}} < 2", + "NOT ({{field1}} > 1 AND {{field2}} < 2)", ])("test %s", (input) => { const result = parseFormula(input) From 78a4364d44c6814841c5177ae713d06699b9a9c5 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 14:26:38 +0800 Subject: [PATCH 18/31] feat: add some formula --- packages/formula/src/formula.constants.ts | 8 ++--- packages/formula/src/function/registry.ts | 18 +++++++++++ packages/formula/src/function/type.ts | 8 ++--- .../underlying/underlying-formula.visitor.ts | 27 +++++++++++++++- .../template/src/templates/test.base.json | 32 ++++++++++++++++++- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts index 436a138e1..3ed9c620b 100644 --- a/packages/formula/src/formula.constants.ts +++ b/packages/formula/src/formula.constants.ts @@ -30,6 +30,9 @@ export const FORMULA_FUNCTIONS: FormulaFunction[] = [ "FIND", "REPLACE", "SUBSTITUTE", + "REPEAT", + "SEARCH", + "SUBSTR", // 逻辑运算 "AND", @@ -40,15 +43,12 @@ export const FORMULA_FUNCTIONS: FormulaFunction[] = [ "ISBLANK", "ISNUMBER", "ISTEXT", - "ISERROR", // 统计函数 "COUNT", "COUNTA", "COUNTIF", "SUMIF", - "AVERAGE", - "STDEV", - "VAR", "CORREL", + "RECORD_ID", ] as const diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/function/registry.ts index 25ef03659..85c2a45d4 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/function/registry.ts @@ -117,3 +117,21 @@ globalFunctionRegistry.register("TRIM", [["string"]], "string") globalFunctionRegistry.register("LEFT", [["string", "number"]], "string") globalFunctionRegistry.register("RIGHT", [["string", "number"]], "string") globalFunctionRegistry.register("MID", [["string", "number", "number"]], "string") +globalFunctionRegistry.register("LEN", [["string"]], "number") +globalFunctionRegistry.register("REPLACE", [["string", "string", "string"]], "string") +globalFunctionRegistry.register("SUBSTITUTE", [["string", "string", "string", "number"]], "string") +globalFunctionRegistry.register("REPEAT", [["string", "number"]], "string") +globalFunctionRegistry.register("SEARCH", [["string", "string"]], "number") +globalFunctionRegistry.register("SUBSTR", [["string", "number", "number"]], "string") + +globalFunctionRegistry.register("AND", [["boolean", "variadic"]], "boolean") +globalFunctionRegistry.register("OR", [["boolean", "variadic"]], "boolean") +globalFunctionRegistry.register("NOT", [["boolean"]], "boolean") +globalFunctionRegistry.register("ISBLANK", [["any"]], "boolean") +globalFunctionRegistry.register("ISNUMBER", [["any"]], "boolean") +globalFunctionRegistry.register("ISTEXT", [["any"]], "boolean") + +// globalFunctionRegistry.register("COUNT", [["variadic"]], "number") +// globalFunctionRegistry.register("COUNTA", [["variadic"]], "number") +// globalFunctionRegistry.register("COUNTIF", [["variadic"]], "number") +// globalFunctionRegistry.register("SUMIF", [["variadic"]], "number") diff --git a/packages/formula/src/function/type.ts b/packages/formula/src/function/type.ts index cde8ccfc2..e46aef88c 100644 --- a/packages/formula/src/function/type.ts +++ b/packages/formula/src/function/type.ts @@ -29,6 +29,9 @@ export type FormulaFunction = | "FIND" | "REPLACE" | "SUBSTITUTE" + | "REPEAT" + | "SEARCH" + | "SUBSTR" // // 日期时间 // | "NOW" @@ -51,14 +54,11 @@ export type FormulaFunction = | "ISBLANK" | "ISNUMBER" | "ISTEXT" - | "ISERROR" // 统计函数 | "COUNT" | "COUNTA" | "COUNTIF" | "SUMIF" - | "AVERAGE" - | "STDEV" - | "VAR" | "CORREL" + | "RECORD_ID" diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index 0384b2aa1..1a2578c56 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -18,7 +18,7 @@ import { type FormulaFunction, type FormulaParserVisitor, } from "@undb/formula" -import { FieldIdVo, type TableDo } from "@undb/table" +import { FieldIdVo,type TableDo } from "@undb/table" import { match } from "ts-pattern" export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { @@ -140,6 +140,31 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i const args = this.arguments(ctx.argumentList()!) return `SUBSTR(${args[0]}, ${args[1]}, ${args[2]})` }) + .with("AND", () => { + const args = this.arguments(ctx.argumentList()!) + return `(${args.map((arg) => `COALESCE(${arg}, FALSE)`).join(" AND ")})` + }) + .with("OR", () => { + const args = this.arguments(ctx.argumentList()!) + return `(${args.map((arg) => `COALESCE(${arg}, FALSE)`).join(" OR ")})` + }) + .with("NOT", () => { + const args = this.arguments(ctx.argumentList()!) + return `NOT ${args[0]}` + }) + .with("SEARCH", () => { + const args = this.arguments(ctx.argumentList()!) + return `COALESCE(INSTR(LOWER(COALESCE(${args[1]}, '')), LOWER(COALESCE(${args[0]}, ''))), 0)` + }) + .with("LEN", () => { + const args = this.arguments(ctx.argumentList()!) + return `LENGTH(${args[0]})` + }) + .with("REPEAT", () => { + const args = this.arguments(ctx.argumentList()!) + // args[0] 是要重复的字符串,args[1] 是重复次数 + return `SUBSTR(REPLACE(HEX(ZEROBLOB(${args[1]})), '00', ${args[0]}), 1, LENGTH(${args[0]}) * ${args[1]})` + }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" return `${functionName.toLowerCase()}(${args})` diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 53a19f43a..4ee17105c 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -393,6 +393,34 @@ "option": { "fn": "{{count1}} < {{count2}}" } + }, + "Len": { + "id": "len", + "type": "formula", + "option": { + "fn": "LEN({{string1}})" + } + }, + "Replace": { + "id": "replace", + "type": "formula", + "option": { + "fn": "REPLACE({{string1}}, 'llo', {{string2}})" + } + }, + "Search": { + "id": "search", + "type": "formula", + "option": { + "fn": "SEARCH({{string1}}, {{string2}})" + } + }, + "Repeat": { + "id": "repeat", + "type": "formula", + "option": { + "fn": "REPEAT({{string1}}, 3)" + } } }, "records": [ @@ -411,7 +439,9 @@ }, { "Count1": 5, - "Count2": 3 + "Count2": 3, + "String1": "Hello", + "String2": "llo" } ] } From 234a57912d0ad5ea5c1a6420d0d332f09e14daec Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 14:39:12 +0800 Subject: [PATCH 19/31] test: add some formula test --- packages/formula/src/formula.constants.ts | 18 +-- .../__snapshots__/parse-formula.test.ts.snap | 142 ++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 8 + 3 files changed, 159 insertions(+), 9 deletions(-) diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts index 3ed9c620b..0f9c6bd02 100644 --- a/packages/formula/src/formula.constants.ts +++ b/packages/formula/src/formula.constants.ts @@ -40,15 +40,15 @@ export const FORMULA_FUNCTIONS: FormulaFunction[] = [ "NOT", "IF", "SWITCH", - "ISBLANK", - "ISNUMBER", - "ISTEXT", + // "ISBLANK", + // "ISNUMBER", + // "ISTEXT", // 统计函数 - "COUNT", - "COUNTA", - "COUNTIF", - "SUMIF", - "CORREL", - "RECORD_ID", + // "COUNT", + // "COUNTA", + // "COUNTIF", + // "SUMIF", + // "CORREL", + // "RECORD_ID", ] as const diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 6f085803c..92297ee2c 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -1228,3 +1228,145 @@ exports[`parse formula test NOT ({{field1}} > 1 AND {{field2}} < 2) 1`] = ` "value": "NOT({{field1}}>1AND{{field2}}<2)", } `; + +exports[`parse formula test SEARCH({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "SEARCH", + "returnType": "number", + "type": "functionCall", + "value": "SEARCH({{field1}},{{field2}})", +} +`; + +exports[`parse formula test REPLACE({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "name": "REPLACE", + "returnType": "string", + "type": "functionCall", + "value": "REPLACE({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test REPEAT({{field1}}, {{field2}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + ], + "name": "REPEAT", + "returnType": "string", + "type": "functionCall", + "value": "REPEAT({{field1}},{{field2}})", +} +`; + +exports[`parse formula test LEN({{field1}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "name": "LEN", + "returnType": "number", + "type": "functionCall", + "value": "LEN({{field1}})", +} +`; + +exports[`parse formula test SUBSTR({{field1}}, {{field2}}, {{field3}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "variable", + "value": "{{field2}}", + "variable": "field2", + }, + { + "type": "variable", + "value": "{{field3}}", + "variable": "field3", + }, + ], + "name": "SUBSTR", + "returnType": "string", + "type": "functionCall", + "value": "SUBSTR({{field1}},{{field2}},{{field3}})", +} +`; + +exports[`parse formula test AND({{field1}}, {{field2}}) 1`] = ` +{ + "type": "variable", + "value": "{{field1}}", + "variable": "field1", +} +`; + +exports[`parse formula test OR({{field1}}, {{field2}}) 1`] = ` +{ + "type": "variable", + "value": "{{field1}}", + "variable": "field1", +} +`; + +exports[`parse formula test NOT({{field1}}) 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + ], + "name": "NOT", + "returnType": "boolean", + "type": "functionCall", + "value": "NOT({{field1}})", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index 6872c0b27..7591a6715 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -58,6 +58,14 @@ describe("parse formula", () => { "{{field1}} > 1 AND {{field2}} < 2", "{{field1}} > 1 OR {{field2}} < 2", "NOT ({{field1}} > 1 AND {{field2}} < 2)", + "SEARCH({{field1}}, {{field2}})", + "REPLACE({{field1}}, {{field2}}, {{field3}})", + "REPEAT({{field1}}, {{field2}})", + "LEN({{field1}})", + "SUBSTR({{field1}}, {{field2}}, {{field3}})", + "AND({{field1}}, {{field2}})", + "OR({{field1}}, {{field2}})", + "NOT({{field1}})", ])("test %s", (input) => { const result = parseFormula(input) From e81d364259540f14bb44fc94209fc47b0a33846f Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 17:40:05 +0800 Subject: [PATCH 20/31] feat: add json extract formula --- packages/formula/src/formula.constants.ts | 4 +- packages/formula/src/formula.visitor.ts | 4 +- .../type.ts => formula/formula.type.ts} | 1 + .../src/{function => formula}/registry.ts | 4 +- packages/formula/src/grammar/FormulaLexer.g4 | 4 +- .../formula/src/grammar/FormulaLexer.interp | 5 +- .../formula/src/grammar/FormulaLexer.tokens | 26 +- packages/formula/src/grammar/FormulaLexer.ts | 328 +++++++++--------- .../formula/src/grammar/FormulaParser.interp | 4 +- .../formula/src/grammar/FormulaParser.tokens | 26 +- packages/formula/src/grammar/FormulaParser.ts | 61 ++-- packages/formula/src/index.ts | 3 +- .../src/record/record.mutate-visitor.ts | 28 +- .../underlying/underlying-formula.visitor.ts | 56 +-- .../variants/json-field/json-field.vo.ts | 14 +- .../template/src/templates/test.base.json | 15 +- 16 files changed, 324 insertions(+), 259 deletions(-) rename packages/formula/src/{function/type.ts => formula/formula.type.ts} (97%) rename packages/formula/src/{function => formula}/registry.ts (97%) diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts index 0f9c6bd02..0b2142c0e 100644 --- a/packages/formula/src/formula.constants.ts +++ b/packages/formula/src/formula.constants.ts @@ -1,4 +1,4 @@ -import { FormulaFunction } from "./function/type" +import { FormulaFunction } from "./formula/formula.type" export const FORMULA_FUNCTIONS: FormulaFunction[] = [ "ADD", @@ -51,4 +51,6 @@ export const FORMULA_FUNCTIONS: FormulaFunction[] = [ // "SUMIF", // "CORREL", // "RECORD_ID", + + "JSON_EXTRACT", ] as const diff --git a/packages/formula/src/formula.visitor.ts b/packages/formula/src/formula.visitor.ts index 24ccd0692..6b7b9de63 100644 --- a/packages/formula/src/formula.visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -1,6 +1,6 @@ import { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" -import { globalFunctionRegistry } from "./function/registry" -import { FormulaFunction } from "./function/type" +import { FormulaFunction } from "./formula/formula.type" +import { globalFunctionRegistry } from "./formula/registry" import { AddSubExprContext, AndExprContext, diff --git a/packages/formula/src/function/type.ts b/packages/formula/src/formula/formula.type.ts similarity index 97% rename from packages/formula/src/function/type.ts rename to packages/formula/src/formula/formula.type.ts index e46aef88c..7f04674af 100644 --- a/packages/formula/src/function/type.ts +++ b/packages/formula/src/formula/formula.type.ts @@ -62,3 +62,4 @@ export type FormulaFunction = | "SUMIF" | "CORREL" | "RECORD_ID" + | "JSON_EXTRACT" diff --git a/packages/formula/src/function/registry.ts b/packages/formula/src/formula/registry.ts similarity index 97% rename from packages/formula/src/function/registry.ts rename to packages/formula/src/formula/registry.ts index 85c2a45d4..0064676d0 100644 --- a/packages/formula/src/function/registry.ts +++ b/packages/formula/src/formula/registry.ts @@ -1,5 +1,5 @@ import { ParamType, ReturnType, type ExpressionResult } from "../types" -import { FormulaFunction } from "./type" +import { FormulaFunction } from "./formula.type" interface FunctionDefinition { paramPatterns: ParamType[][] @@ -135,3 +135,5 @@ globalFunctionRegistry.register("ISTEXT", [["any"]], "boolean") // globalFunctionRegistry.register("COUNTA", [["variadic"]], "number") // globalFunctionRegistry.register("COUNTIF", [["variadic"]], "number") // globalFunctionRegistry.register("SUMIF", [["variadic"]], "number") + +globalFunctionRegistry.register("JSON_EXTRACT", [["string", "string"]], "any") diff --git a/packages/formula/src/grammar/FormulaLexer.g4 b/packages/formula/src/grammar/FormulaLexer.g4 index e2468ce5b..9594926b0 100644 --- a/packages/formula/src/grammar/FormulaLexer.g4 +++ b/packages/formula/src/grammar/FormulaLexer.g4 @@ -35,8 +35,10 @@ SEMICOLON: ';'; COLON: ':'; DOT: '.'; +UNDERSCORE: '_'; + // 函数名和标识符 -IDENTIFIER: LETTER (LETTER | DIGIT)*; +IDENTIFIER: LETTER (LETTER | DIGIT | UNDERSCORE)*; // 数字 NUMBER: DIGIT+ ('.' DIGIT+)?; diff --git a/packages/formula/src/grammar/FormulaLexer.interp b/packages/formula/src/grammar/FormulaLexer.interp index 9e853d136..6fefff5f8 100644 --- a/packages/formula/src/grammar/FormulaLexer.interp +++ b/packages/formula/src/grammar/FormulaLexer.interp @@ -25,6 +25,7 @@ null ';' ':' '.' +'_' null null null @@ -65,6 +66,7 @@ COMMA SEMICOLON COLON DOT +UNDERSCORE IDENTIFIER NUMBER STRING @@ -104,6 +106,7 @@ COMMA SEMICOLON COLON DOT +UNDERSCORE IDENTIFIER NUMBER STRING @@ -153,4 +156,4 @@ mode names: DEFAULT_MODE atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 39, 346, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 7, 27, 195, 10, 27, 12, 27, 14, 27, 198, 11, 27, 3, 28, 6, 28, 201, 10, 28, 13, 28, 14, 28, 202, 3, 28, 3, 28, 6, 28, 207, 10, 28, 13, 28, 14, 28, 208, 5, 28, 211, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 7, 29, 217, 10, 29, 12, 29, 14, 29, 220, 11, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 6, 36, 260, 10, 36, 13, 36, 14, 36, 261, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 270, 10, 37, 12, 37, 14, 37, 273, 11, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 281, 10, 38, 12, 38, 14, 38, 284, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 282, 2, 2, 67, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 29, 2, 16, 31, 2, 17, 33, 2, 18, 35, 2, 19, 37, 2, 20, 39, 2, 21, 41, 2, 22, 43, 2, 23, 45, 2, 24, 47, 2, 25, 49, 2, 26, 51, 2, 27, 53, 2, 28, 55, 2, 29, 57, 2, 30, 59, 2, 31, 61, 2, 32, 63, 2, 33, 65, 2, 34, 67, 2, 35, 69, 2, 36, 71, 2, 37, 73, 2, 38, 75, 2, 39, 77, 2, 2, 79, 2, 2, 81, 2, 2, 83, 2, 2, 85, 2, 2, 87, 2, 2, 89, 2, 2, 91, 2, 2, 93, 2, 2, 95, 2, 2, 97, 2, 2, 99, 2, 2, 101, 2, 2, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 2, 113, 2, 2, 115, 2, 2, 117, 2, 2, 119, 2, 2, 121, 2, 2, 123, 2, 2, 125, 2, 2, 127, 2, 2, 129, 2, 2, 131, 2, 2, 3, 2, 33, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 327, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 3, 133, 3, 2, 2, 2, 5, 135, 3, 2, 2, 2, 7, 137, 3, 2, 2, 2, 9, 139, 3, 2, 2, 2, 11, 141, 3, 2, 2, 2, 13, 143, 3, 2, 2, 2, 15, 145, 3, 2, 2, 2, 17, 147, 3, 2, 2, 2, 19, 150, 3, 2, 2, 2, 21, 152, 3, 2, 2, 2, 23, 155, 3, 2, 2, 2, 25, 157, 3, 2, 2, 2, 27, 160, 3, 2, 2, 2, 29, 164, 3, 2, 2, 2, 31, 167, 3, 2, 2, 2, 33, 171, 3, 2, 2, 2, 35, 173, 3, 2, 2, 2, 37, 175, 3, 2, 2, 2, 39, 177, 3, 2, 2, 2, 41, 179, 3, 2, 2, 2, 43, 181, 3, 2, 2, 2, 45, 183, 3, 2, 2, 2, 47, 185, 3, 2, 2, 2, 49, 187, 3, 2, 2, 2, 51, 189, 3, 2, 2, 2, 53, 191, 3, 2, 2, 2, 55, 200, 3, 2, 2, 2, 57, 212, 3, 2, 2, 2, 59, 223, 3, 2, 2, 2, 61, 228, 3, 2, 2, 2, 63, 234, 3, 2, 2, 2, 65, 239, 3, 2, 2, 2, 67, 244, 3, 2, 2, 2, 69, 249, 3, 2, 2, 2, 71, 259, 3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 276, 3, 2, 2, 2, 77, 290, 3, 2, 2, 2, 79, 292, 3, 2, 2, 2, 81, 294, 3, 2, 2, 2, 83, 296, 3, 2, 2, 2, 85, 298, 3, 2, 2, 2, 87, 300, 3, 2, 2, 2, 89, 302, 3, 2, 2, 2, 91, 304, 3, 2, 2, 2, 93, 306, 3, 2, 2, 2, 95, 308, 3, 2, 2, 2, 97, 310, 3, 2, 2, 2, 99, 312, 3, 2, 2, 2, 101, 314, 3, 2, 2, 2, 103, 316, 3, 2, 2, 2, 105, 318, 3, 2, 2, 2, 107, 320, 3, 2, 2, 2, 109, 322, 3, 2, 2, 2, 111, 324, 3, 2, 2, 2, 113, 326, 3, 2, 2, 2, 115, 328, 3, 2, 2, 2, 117, 330, 3, 2, 2, 2, 119, 332, 3, 2, 2, 2, 121, 334, 3, 2, 2, 2, 123, 336, 3, 2, 2, 2, 125, 338, 3, 2, 2, 2, 127, 340, 3, 2, 2, 2, 129, 342, 3, 2, 2, 2, 131, 344, 3, 2, 2, 2, 133, 134, 7, 45, 2, 2, 134, 4, 3, 2, 2, 2, 135, 136, 7, 47, 2, 2, 136, 6, 3, 2, 2, 2, 137, 138, 7, 44, 2, 2, 138, 8, 3, 2, 2, 2, 139, 140, 7, 49, 2, 2, 140, 10, 3, 2, 2, 2, 141, 142, 7, 39, 2, 2, 142, 12, 3, 2, 2, 2, 143, 144, 7, 96, 2, 2, 144, 14, 3, 2, 2, 2, 145, 146, 7, 63, 2, 2, 146, 16, 3, 2, 2, 2, 147, 148, 7, 35, 2, 2, 148, 149, 7, 63, 2, 2, 149, 18, 3, 2, 2, 2, 150, 151, 7, 62, 2, 2, 151, 20, 3, 2, 2, 2, 152, 153, 7, 62, 2, 2, 153, 154, 7, 63, 2, 2, 154, 22, 3, 2, 2, 2, 155, 156, 7, 64, 2, 2, 156, 24, 3, 2, 2, 2, 157, 158, 7, 64, 2, 2, 158, 159, 7, 63, 2, 2, 159, 26, 3, 2, 2, 2, 160, 161, 5, 81, 41, 2, 161, 162, 5, 107, 54, 2, 162, 163, 5, 87, 44, 2, 163, 28, 3, 2, 2, 2, 164, 165, 5, 109, 55, 2, 165, 166, 5, 115, 58, 2, 166, 30, 3, 2, 2, 2, 167, 168, 5, 107, 54, 2, 168, 169, 5, 109, 55, 2, 169, 170, 5, 119, 60, 2, 170, 32, 3, 2, 2, 2, 171, 172, 7, 42, 2, 2, 172, 34, 3, 2, 2, 2, 173, 174, 7, 43, 2, 2, 174, 36, 3, 2, 2, 2, 175, 176, 7, 125, 2, 2, 176, 38, 3, 2, 2, 2, 177, 178, 7, 127, 2, 2, 178, 40, 3, 2, 2, 2, 179, 180, 7, 93, 2, 2, 180, 42, 3, 2, 2, 2, 181, 182, 7, 95, 2, 2, 182, 44, 3, 2, 2, 2, 183, 184, 7, 46, 2, 2, 184, 46, 3, 2, 2, 2, 185, 186, 7, 61, 2, 2, 186, 48, 3, 2, 2, 2, 187, 188, 7, 60, 2, 2, 188, 50, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 52, 3, 2, 2, 2, 191, 196, 5, 79, 40, 2, 192, 195, 5, 79, 40, 2, 193, 195, 5, 77, 39, 2, 194, 192, 3, 2, 2, 2, 194, 193, 3, 2, 2, 2, 195, 198, 3, 2, 2, 2, 196, 194, 3, 2, 2, 2, 196, 197, 3, 2, 2, 2, 197, 54, 3, 2, 2, 2, 198, 196, 3, 2, 2, 2, 199, 201, 5, 77, 39, 2, 200, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 202, 203, 3, 2, 2, 2, 203, 210, 3, 2, 2, 2, 204, 206, 7, 48, 2, 2, 205, 207, 5, 77, 39, 2, 206, 205, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 211, 3, 2, 2, 2, 210, 204, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2, 211, 56, 3, 2, 2, 2, 212, 218, 7, 41, 2, 2, 213, 217, 10, 2, 2, 2, 214, 215, 7, 41, 2, 2, 215, 217, 7, 41, 2, 2, 216, 213, 3, 2, 2, 2, 216, 214, 3, 2, 2, 2, 217, 220, 3, 2, 2, 2, 218, 216, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 221, 3, 2, 2, 2, 220, 218, 3, 2, 2, 2, 221, 222, 7, 41, 2, 2, 222, 58, 3, 2, 2, 2, 223, 224, 5, 119, 60, 2, 224, 225, 5, 115, 58, 2, 225, 226, 5, 121, 61, 2, 226, 227, 5, 89, 45, 2, 227, 60, 3, 2, 2, 2, 228, 229, 5, 91, 46, 2, 229, 230, 5, 81, 41, 2, 230, 231, 5, 103, 52, 2, 231, 232, 5, 117, 59, 2, 232, 233, 5, 89, 45, 2, 233, 62, 3, 2, 2, 2, 234, 235, 5, 107, 54, 2, 235, 236, 5, 121, 61, 2, 236, 237, 5, 103, 52, 2, 237, 238, 5, 103, 52, 2, 238, 64, 3, 2, 2, 2, 239, 240, 5, 87, 44, 2, 240, 241, 5, 81, 41, 2, 241, 242, 5, 119, 60, 2, 242, 243, 5, 89, 45, 2, 243, 66, 3, 2, 2, 2, 244, 245, 5, 119, 60, 2, 245, 246, 5, 97, 49, 2, 246, 247, 5, 105, 53, 2, 247, 248, 5, 89, 45, 2, 248, 68, 3, 2, 2, 2, 249, 250, 5, 87, 44, 2, 250, 251, 5, 81, 41, 2, 251, 252, 5, 119, 60, 2, 252, 253, 5, 89, 45, 2, 253, 254, 5, 119, 60, 2, 254, 255, 5, 97, 49, 2, 255, 256, 5, 105, 53, 2, 256, 257, 5, 89, 45, 2, 257, 70, 3, 2, 2, 2, 258, 260, 9, 3, 2, 2, 259, 258, 3, 2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 259, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 264, 8, 36, 2, 2, 264, 72, 3, 2, 2, 2, 265, 266, 7, 49, 2, 2, 266, 267, 7, 49, 2, 2, 267, 271, 3, 2, 2, 2, 268, 270, 10, 4, 2, 2, 269, 268, 3, 2, 2, 2, 270, 273, 3, 2, 2, 2, 271, 269, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 274, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 274, 275, 8, 37, 2, 2, 275, 74, 3, 2, 2, 2, 276, 277, 7, 49, 2, 2, 277, 278, 7, 44, 2, 2, 278, 282, 3, 2, 2, 2, 279, 281, 11, 2, 2, 2, 280, 279, 3, 2, 2, 2, 281, 284, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 282, 280, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 282, 3, 2, 2, 2, 285, 286, 7, 44, 2, 2, 286, 287, 7, 49, 2, 2, 287, 288, 3, 2, 2, 2, 288, 289, 8, 38, 2, 2, 289, 76, 3, 2, 2, 2, 290, 291, 9, 5, 2, 2, 291, 78, 3, 2, 2, 2, 292, 293, 9, 6, 2, 2, 293, 80, 3, 2, 2, 2, 294, 295, 9, 7, 2, 2, 295, 82, 3, 2, 2, 2, 296, 297, 9, 8, 2, 2, 297, 84, 3, 2, 2, 2, 298, 299, 9, 9, 2, 2, 299, 86, 3, 2, 2, 2, 300, 301, 9, 10, 2, 2, 301, 88, 3, 2, 2, 2, 302, 303, 9, 11, 2, 2, 303, 90, 3, 2, 2, 2, 304, 305, 9, 12, 2, 2, 305, 92, 3, 2, 2, 2, 306, 307, 9, 13, 2, 2, 307, 94, 3, 2, 2, 2, 308, 309, 9, 14, 2, 2, 309, 96, 3, 2, 2, 2, 310, 311, 9, 15, 2, 2, 311, 98, 3, 2, 2, 2, 312, 313, 9, 16, 2, 2, 313, 100, 3, 2, 2, 2, 314, 315, 9, 17, 2, 2, 315, 102, 3, 2, 2, 2, 316, 317, 9, 18, 2, 2, 317, 104, 3, 2, 2, 2, 318, 319, 9, 19, 2, 2, 319, 106, 3, 2, 2, 2, 320, 321, 9, 20, 2, 2, 321, 108, 3, 2, 2, 2, 322, 323, 9, 21, 2, 2, 323, 110, 3, 2, 2, 2, 324, 325, 9, 22, 2, 2, 325, 112, 3, 2, 2, 2, 326, 327, 9, 23, 2, 2, 327, 114, 3, 2, 2, 2, 328, 329, 9, 24, 2, 2, 329, 116, 3, 2, 2, 2, 330, 331, 9, 25, 2, 2, 331, 118, 3, 2, 2, 2, 332, 333, 9, 26, 2, 2, 333, 120, 3, 2, 2, 2, 334, 335, 9, 27, 2, 2, 335, 122, 3, 2, 2, 2, 336, 337, 9, 28, 2, 2, 337, 124, 3, 2, 2, 2, 338, 339, 9, 29, 2, 2, 339, 126, 3, 2, 2, 2, 340, 341, 9, 30, 2, 2, 341, 128, 3, 2, 2, 2, 342, 343, 9, 31, 2, 2, 343, 130, 3, 2, 2, 2, 344, 345, 9, 32, 2, 2, 345, 132, 3, 2, 2, 2, 13, 2, 194, 196, 202, 208, 210, 216, 218, 261, 271, 282, 3, 8, 2, 2] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 40, 351, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 7, 28, 200, 10, 28, 12, 28, 14, 28, 203, 11, 28, 3, 29, 6, 29, 206, 10, 29, 13, 29, 14, 29, 207, 3, 29, 3, 29, 6, 29, 212, 10, 29, 13, 29, 14, 29, 213, 5, 29, 216, 10, 29, 3, 30, 3, 30, 3, 30, 3, 30, 7, 30, 222, 10, 30, 12, 30, 14, 30, 225, 11, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 36, 3, 37, 6, 37, 265, 10, 37, 13, 37, 14, 37, 266, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 7, 38, 275, 10, 38, 12, 38, 14, 38, 278, 11, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 39, 7, 39, 286, 10, 39, 12, 39, 14, 39, 289, 11, 39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 67, 3, 67, 3, 287, 2, 2, 68, 3, 2, 3, 5, 2, 4, 7, 2, 5, 9, 2, 6, 11, 2, 7, 13, 2, 8, 15, 2, 9, 17, 2, 10, 19, 2, 11, 21, 2, 12, 23, 2, 13, 25, 2, 14, 27, 2, 15, 29, 2, 16, 31, 2, 17, 33, 2, 18, 35, 2, 19, 37, 2, 20, 39, 2, 21, 41, 2, 22, 43, 2, 23, 45, 2, 24, 47, 2, 25, 49, 2, 26, 51, 2, 27, 53, 2, 28, 55, 2, 29, 57, 2, 30, 59, 2, 31, 61, 2, 32, 63, 2, 33, 65, 2, 34, 67, 2, 35, 69, 2, 36, 71, 2, 37, 73, 2, 38, 75, 2, 39, 77, 2, 40, 79, 2, 2, 81, 2, 2, 83, 2, 2, 85, 2, 2, 87, 2, 2, 89, 2, 2, 91, 2, 2, 93, 2, 2, 95, 2, 2, 97, 2, 2, 99, 2, 2, 101, 2, 2, 103, 2, 2, 105, 2, 2, 107, 2, 2, 109, 2, 2, 111, 2, 2, 113, 2, 2, 115, 2, 2, 117, 2, 2, 119, 2, 2, 121, 2, 2, 123, 2, 2, 125, 2, 2, 127, 2, 2, 129, 2, 2, 131, 2, 2, 133, 2, 2, 3, 2, 33, 3, 2, 41, 41, 5, 2, 11, 12, 15, 15, 34, 34, 4, 2, 12, 12, 15, 15, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 4, 2, 67, 67, 99, 99, 4, 2, 68, 68, 100, 100, 4, 2, 69, 69, 101, 101, 4, 2, 70, 70, 102, 102, 4, 2, 71, 71, 103, 103, 4, 2, 72, 72, 104, 104, 4, 2, 73, 73, 105, 105, 4, 2, 74, 74, 106, 106, 4, 2, 75, 75, 107, 107, 4, 2, 76, 76, 108, 108, 4, 2, 77, 77, 109, 109, 4, 2, 78, 78, 110, 110, 4, 2, 79, 79, 111, 111, 4, 2, 80, 80, 112, 112, 4, 2, 81, 81, 113, 113, 4, 2, 82, 82, 114, 114, 4, 2, 83, 83, 115, 115, 4, 2, 84, 84, 116, 116, 4, 2, 85, 85, 117, 117, 4, 2, 86, 86, 118, 118, 4, 2, 87, 87, 119, 119, 4, 2, 88, 88, 120, 120, 4, 2, 89, 89, 121, 121, 4, 2, 90, 90, 122, 122, 4, 2, 91, 91, 123, 123, 4, 2, 92, 92, 124, 124, 2, 333, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2, 55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2, 2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 2, 69, 3, 2, 2, 2, 2, 71, 3, 2, 2, 2, 2, 73, 3, 2, 2, 2, 2, 75, 3, 2, 2, 2, 2, 77, 3, 2, 2, 2, 3, 135, 3, 2, 2, 2, 5, 137, 3, 2, 2, 2, 7, 139, 3, 2, 2, 2, 9, 141, 3, 2, 2, 2, 11, 143, 3, 2, 2, 2, 13, 145, 3, 2, 2, 2, 15, 147, 3, 2, 2, 2, 17, 149, 3, 2, 2, 2, 19, 152, 3, 2, 2, 2, 21, 154, 3, 2, 2, 2, 23, 157, 3, 2, 2, 2, 25, 159, 3, 2, 2, 2, 27, 162, 3, 2, 2, 2, 29, 166, 3, 2, 2, 2, 31, 169, 3, 2, 2, 2, 33, 173, 3, 2, 2, 2, 35, 175, 3, 2, 2, 2, 37, 177, 3, 2, 2, 2, 39, 179, 3, 2, 2, 2, 41, 181, 3, 2, 2, 2, 43, 183, 3, 2, 2, 2, 45, 185, 3, 2, 2, 2, 47, 187, 3, 2, 2, 2, 49, 189, 3, 2, 2, 2, 51, 191, 3, 2, 2, 2, 53, 193, 3, 2, 2, 2, 55, 195, 3, 2, 2, 2, 57, 205, 3, 2, 2, 2, 59, 217, 3, 2, 2, 2, 61, 228, 3, 2, 2, 2, 63, 233, 3, 2, 2, 2, 65, 239, 3, 2, 2, 2, 67, 244, 3, 2, 2, 2, 69, 249, 3, 2, 2, 2, 71, 254, 3, 2, 2, 2, 73, 264, 3, 2, 2, 2, 75, 270, 3, 2, 2, 2, 77, 281, 3, 2, 2, 2, 79, 295, 3, 2, 2, 2, 81, 297, 3, 2, 2, 2, 83, 299, 3, 2, 2, 2, 85, 301, 3, 2, 2, 2, 87, 303, 3, 2, 2, 2, 89, 305, 3, 2, 2, 2, 91, 307, 3, 2, 2, 2, 93, 309, 3, 2, 2, 2, 95, 311, 3, 2, 2, 2, 97, 313, 3, 2, 2, 2, 99, 315, 3, 2, 2, 2, 101, 317, 3, 2, 2, 2, 103, 319, 3, 2, 2, 2, 105, 321, 3, 2, 2, 2, 107, 323, 3, 2, 2, 2, 109, 325, 3, 2, 2, 2, 111, 327, 3, 2, 2, 2, 113, 329, 3, 2, 2, 2, 115, 331, 3, 2, 2, 2, 117, 333, 3, 2, 2, 2, 119, 335, 3, 2, 2, 2, 121, 337, 3, 2, 2, 2, 123, 339, 3, 2, 2, 2, 125, 341, 3, 2, 2, 2, 127, 343, 3, 2, 2, 2, 129, 345, 3, 2, 2, 2, 131, 347, 3, 2, 2, 2, 133, 349, 3, 2, 2, 2, 135, 136, 7, 45, 2, 2, 136, 4, 3, 2, 2, 2, 137, 138, 7, 47, 2, 2, 138, 6, 3, 2, 2, 2, 139, 140, 7, 44, 2, 2, 140, 8, 3, 2, 2, 2, 141, 142, 7, 49, 2, 2, 142, 10, 3, 2, 2, 2, 143, 144, 7, 39, 2, 2, 144, 12, 3, 2, 2, 2, 145, 146, 7, 96, 2, 2, 146, 14, 3, 2, 2, 2, 147, 148, 7, 63, 2, 2, 148, 16, 3, 2, 2, 2, 149, 150, 7, 35, 2, 2, 150, 151, 7, 63, 2, 2, 151, 18, 3, 2, 2, 2, 152, 153, 7, 62, 2, 2, 153, 20, 3, 2, 2, 2, 154, 155, 7, 62, 2, 2, 155, 156, 7, 63, 2, 2, 156, 22, 3, 2, 2, 2, 157, 158, 7, 64, 2, 2, 158, 24, 3, 2, 2, 2, 159, 160, 7, 64, 2, 2, 160, 161, 7, 63, 2, 2, 161, 26, 3, 2, 2, 2, 162, 163, 5, 83, 42, 2, 163, 164, 5, 109, 55, 2, 164, 165, 5, 89, 45, 2, 165, 28, 3, 2, 2, 2, 166, 167, 5, 111, 56, 2, 167, 168, 5, 117, 59, 2, 168, 30, 3, 2, 2, 2, 169, 170, 5, 109, 55, 2, 170, 171, 5, 111, 56, 2, 171, 172, 5, 121, 61, 2, 172, 32, 3, 2, 2, 2, 173, 174, 7, 42, 2, 2, 174, 34, 3, 2, 2, 2, 175, 176, 7, 43, 2, 2, 176, 36, 3, 2, 2, 2, 177, 178, 7, 125, 2, 2, 178, 38, 3, 2, 2, 2, 179, 180, 7, 127, 2, 2, 180, 40, 3, 2, 2, 2, 181, 182, 7, 93, 2, 2, 182, 42, 3, 2, 2, 2, 183, 184, 7, 95, 2, 2, 184, 44, 3, 2, 2, 2, 185, 186, 7, 46, 2, 2, 186, 46, 3, 2, 2, 2, 187, 188, 7, 61, 2, 2, 188, 48, 3, 2, 2, 2, 189, 190, 7, 60, 2, 2, 190, 50, 3, 2, 2, 2, 191, 192, 7, 48, 2, 2, 192, 52, 3, 2, 2, 2, 193, 194, 7, 97, 2, 2, 194, 54, 3, 2, 2, 2, 195, 201, 5, 81, 41, 2, 196, 200, 5, 81, 41, 2, 197, 200, 5, 79, 40, 2, 198, 200, 5, 53, 27, 2, 199, 196, 3, 2, 2, 2, 199, 197, 3, 2, 2, 2, 199, 198, 3, 2, 2, 2, 200, 203, 3, 2, 2, 2, 201, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 56, 3, 2, 2, 2, 203, 201, 3, 2, 2, 2, 204, 206, 5, 79, 40, 2, 205, 204, 3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 205, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 215, 3, 2, 2, 2, 209, 211, 7, 48, 2, 2, 210, 212, 5, 79, 40, 2, 211, 210, 3, 2, 2, 2, 212, 213, 3, 2, 2, 2, 213, 211, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 216, 3, 2, 2, 2, 215, 209, 3, 2, 2, 2, 215, 216, 3, 2, 2, 2, 216, 58, 3, 2, 2, 2, 217, 223, 7, 41, 2, 2, 218, 222, 10, 2, 2, 2, 219, 220, 7, 41, 2, 2, 220, 222, 7, 41, 2, 2, 221, 218, 3, 2, 2, 2, 221, 219, 3, 2, 2, 2, 222, 225, 3, 2, 2, 2, 223, 221, 3, 2, 2, 2, 223, 224, 3, 2, 2, 2, 224, 226, 3, 2, 2, 2, 225, 223, 3, 2, 2, 2, 226, 227, 7, 41, 2, 2, 227, 60, 3, 2, 2, 2, 228, 229, 5, 121, 61, 2, 229, 230, 5, 117, 59, 2, 230, 231, 5, 123, 62, 2, 231, 232, 5, 91, 46, 2, 232, 62, 3, 2, 2, 2, 233, 234, 5, 93, 47, 2, 234, 235, 5, 83, 42, 2, 235, 236, 5, 105, 53, 2, 236, 237, 5, 119, 60, 2, 237, 238, 5, 91, 46, 2, 238, 64, 3, 2, 2, 2, 239, 240, 5, 109, 55, 2, 240, 241, 5, 123, 62, 2, 241, 242, 5, 105, 53, 2, 242, 243, 5, 105, 53, 2, 243, 66, 3, 2, 2, 2, 244, 245, 5, 89, 45, 2, 245, 246, 5, 83, 42, 2, 246, 247, 5, 121, 61, 2, 247, 248, 5, 91, 46, 2, 248, 68, 3, 2, 2, 2, 249, 250, 5, 121, 61, 2, 250, 251, 5, 99, 50, 2, 251, 252, 5, 107, 54, 2, 252, 253, 5, 91, 46, 2, 253, 70, 3, 2, 2, 2, 254, 255, 5, 89, 45, 2, 255, 256, 5, 83, 42, 2, 256, 257, 5, 121, 61, 2, 257, 258, 5, 91, 46, 2, 258, 259, 5, 121, 61, 2, 259, 260, 5, 99, 50, 2, 260, 261, 5, 107, 54, 2, 261, 262, 5, 91, 46, 2, 262, 72, 3, 2, 2, 2, 263, 265, 9, 3, 2, 2, 264, 263, 3, 2, 2, 2, 265, 266, 3, 2, 2, 2, 266, 264, 3, 2, 2, 2, 266, 267, 3, 2, 2, 2, 267, 268, 3, 2, 2, 2, 268, 269, 8, 37, 2, 2, 269, 74, 3, 2, 2, 2, 270, 271, 7, 49, 2, 2, 271, 272, 7, 49, 2, 2, 272, 276, 3, 2, 2, 2, 273, 275, 10, 4, 2, 2, 274, 273, 3, 2, 2, 2, 275, 278, 3, 2, 2, 2, 276, 274, 3, 2, 2, 2, 276, 277, 3, 2, 2, 2, 277, 279, 3, 2, 2, 2, 278, 276, 3, 2, 2, 2, 279, 280, 8, 38, 2, 2, 280, 76, 3, 2, 2, 2, 281, 282, 7, 49, 2, 2, 282, 283, 7, 44, 2, 2, 283, 287, 3, 2, 2, 2, 284, 286, 11, 2, 2, 2, 285, 284, 3, 2, 2, 2, 286, 289, 3, 2, 2, 2, 287, 288, 3, 2, 2, 2, 287, 285, 3, 2, 2, 2, 288, 290, 3, 2, 2, 2, 289, 287, 3, 2, 2, 2, 290, 291, 7, 44, 2, 2, 291, 292, 7, 49, 2, 2, 292, 293, 3, 2, 2, 2, 293, 294, 8, 39, 2, 2, 294, 78, 3, 2, 2, 2, 295, 296, 9, 5, 2, 2, 296, 80, 3, 2, 2, 2, 297, 298, 9, 6, 2, 2, 298, 82, 3, 2, 2, 2, 299, 300, 9, 7, 2, 2, 300, 84, 3, 2, 2, 2, 301, 302, 9, 8, 2, 2, 302, 86, 3, 2, 2, 2, 303, 304, 9, 9, 2, 2, 304, 88, 3, 2, 2, 2, 305, 306, 9, 10, 2, 2, 306, 90, 3, 2, 2, 2, 307, 308, 9, 11, 2, 2, 308, 92, 3, 2, 2, 2, 309, 310, 9, 12, 2, 2, 310, 94, 3, 2, 2, 2, 311, 312, 9, 13, 2, 2, 312, 96, 3, 2, 2, 2, 313, 314, 9, 14, 2, 2, 314, 98, 3, 2, 2, 2, 315, 316, 9, 15, 2, 2, 316, 100, 3, 2, 2, 2, 317, 318, 9, 16, 2, 2, 318, 102, 3, 2, 2, 2, 319, 320, 9, 17, 2, 2, 320, 104, 3, 2, 2, 2, 321, 322, 9, 18, 2, 2, 322, 106, 3, 2, 2, 2, 323, 324, 9, 19, 2, 2, 324, 108, 3, 2, 2, 2, 325, 326, 9, 20, 2, 2, 326, 110, 3, 2, 2, 2, 327, 328, 9, 21, 2, 2, 328, 112, 3, 2, 2, 2, 329, 330, 9, 22, 2, 2, 330, 114, 3, 2, 2, 2, 331, 332, 9, 23, 2, 2, 332, 116, 3, 2, 2, 2, 333, 334, 9, 24, 2, 2, 334, 118, 3, 2, 2, 2, 335, 336, 9, 25, 2, 2, 336, 120, 3, 2, 2, 2, 337, 338, 9, 26, 2, 2, 338, 122, 3, 2, 2, 2, 339, 340, 9, 27, 2, 2, 340, 124, 3, 2, 2, 2, 341, 342, 9, 28, 2, 2, 342, 126, 3, 2, 2, 2, 343, 344, 9, 29, 2, 2, 344, 128, 3, 2, 2, 2, 345, 346, 9, 30, 2, 2, 346, 130, 3, 2, 2, 2, 347, 348, 9, 31, 2, 2, 348, 132, 3, 2, 2, 2, 349, 350, 9, 32, 2, 2, 350, 134, 3, 2, 2, 2, 13, 2, 199, 201, 207, 213, 215, 221, 223, 266, 276, 287, 3, 8, 2, 2] \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaLexer.tokens b/packages/formula/src/grammar/FormulaLexer.tokens index 58c5ff007..5ad732765 100644 --- a/packages/formula/src/grammar/FormulaLexer.tokens +++ b/packages/formula/src/grammar/FormulaLexer.tokens @@ -23,18 +23,19 @@ COMMA=22 SEMICOLON=23 COLON=24 DOT=25 -IDENTIFIER=26 -NUMBER=27 -STRING=28 -TRUE=29 -FALSE=30 -NULL=31 -DATE=32 -TIME=33 -DATETIME=34 -WS=35 -COMMENT=36 -MULTILINE_COMMENT=37 +UNDERSCORE=26 +IDENTIFIER=27 +NUMBER=28 +STRING=29 +TRUE=30 +FALSE=31 +NULL=32 +DATE=33 +TIME=34 +DATETIME=35 +WS=36 +COMMENT=37 +MULTILINE_COMMENT=38 '+'=1 '-'=2 '*'=3 @@ -57,3 +58,4 @@ MULTILINE_COMMENT=37 ';'=23 ':'=24 '.'=25 +'_'=26 diff --git a/packages/formula/src/grammar/FormulaLexer.ts b/packages/formula/src/grammar/FormulaLexer.ts index 6b4d918b7..21703eae9 100644 --- a/packages/formula/src/grammar/FormulaLexer.ts +++ b/packages/formula/src/grammar/FormulaLexer.ts @@ -41,18 +41,19 @@ export class FormulaLexer extends Lexer { public static readonly SEMICOLON = 23; public static readonly COLON = 24; public static readonly DOT = 25; - public static readonly IDENTIFIER = 26; - public static readonly NUMBER = 27; - public static readonly STRING = 28; - public static readonly TRUE = 29; - public static readonly FALSE = 30; - public static readonly NULL = 31; - public static readonly DATE = 32; - public static readonly TIME = 33; - public static readonly DATETIME = 34; - public static readonly WS = 35; - public static readonly COMMENT = 36; - public static readonly MULTILINE_COMMENT = 37; + public static readonly UNDERSCORE = 26; + public static readonly IDENTIFIER = 27; + public static readonly NUMBER = 28; + public static readonly STRING = 29; + public static readonly TRUE = 30; + public static readonly FALSE = 31; + public static readonly NULL = 32; + public static readonly DATE = 33; + public static readonly TIME = 34; + public static readonly DATETIME = 35; + public static readonly WS = 36; + public static readonly COMMENT = 37; + public static readonly MULTILINE_COMMENT = 38; // tslint:disable:no-trailing-whitespace public static readonly channelNames: string[] = [ @@ -68,25 +69,25 @@ export class FormulaLexer extends Lexer { "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", "RBRACKET", "COMMA", - "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", "STRING", "TRUE", - "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", "MULTILINE_COMMENT", - "DIGIT", "LETTER", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", - "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", - "Z", + "SEMICOLON", "COLON", "DOT", "UNDERSCORE", "IDENTIFIER", "NUMBER", "STRING", + "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", + "MULTILINE_COMMENT", "DIGIT", "LETTER", "A", "B", "C", "D", "E", "F", + "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", + "U", "V", "W", "X", "Y", "Z", ]; private static readonly _LITERAL_NAMES: Array = [ undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", - "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", + "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", "'_'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", - "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", - "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", - "MULTILINE_COMMENT", + "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "UNDERSCORE", "IDENTIFIER", + "NUMBER", "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", + "WS", "COMMENT", "MULTILINE_COMMENT", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaLexer._LITERAL_NAMES, FormulaLexer._SYMBOLIC_NAMES, []); @@ -119,7 +120,7 @@ export class FormulaLexer extends Lexer { public get modeNames(): string[] { return FormulaLexer.modeNames; } public static readonly _serializedATN: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02\'\u015A\b\x01" + + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02(\u015F\b\x01" + "\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06" + "\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r" + "\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t" + @@ -129,146 +130,149 @@ export class FormulaLexer extends Lexer { "\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t)\x04*\t*\x04" + "+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x042\t2\x043\t3\x04" + "4\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04;\t;\x04<\t<\x04" + - "=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x03\x02\x03\x02\x03\x03\x03" + - "\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03\x07\x03" + - "\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03" + - "\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F\x03\x0F" + - "\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x12\x03\x12\x03\x13" + - "\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17\x03\x17" + - "\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1B" + - "\x07\x1B\xC3\n\x1B\f\x1B\x0E\x1B\xC6\v\x1B\x03\x1C\x06\x1C\xC9\n\x1C\r" + - "\x1C\x0E\x1C\xCA\x03\x1C\x03\x1C\x06\x1C\xCF\n\x1C\r\x1C\x0E\x1C\xD0\x05" + - "\x1C\xD3\n\x1C\x03\x1D\x03\x1D\x03\x1D\x03\x1D\x07\x1D\xD9\n\x1D\f\x1D" + - "\x0E\x1D\xDC\v\x1D\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E\x03\x1E\x03" + - "\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03" + - " \x03 \x03!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03\"\x03#\x03" + - "#\x03#\x03#\x03#\x03#\x03#\x03#\x03#\x03$\x06$\u0104\n$\r$\x0E$\u0105" + - "\x03$\x03$\x03%\x03%\x03%\x03%\x07%\u010E\n%\f%\x0E%\u0111\v%\x03%\x03" + - "%\x03&\x03&\x03&\x03&\x07&\u0119\n&\f&\x0E&\u011C\v&\x03&\x03&\x03&\x03" + - "&\x03&\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03*\x03+\x03+\x03,\x03,\x03" + - "-\x03-\x03.\x03.\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x03" + - "4\x034\x035\x035\x036\x036\x037\x037\x038\x038\x039\x039\x03:\x03:\x03" + - ";\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03@\x03@\x03A\x03A\x03" + - "B\x03B\x03\u011A\x02\x02C\x03\x02\x03\x05\x02\x04\x07\x02\x05\t\x02\x06" + - "\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13\x02\v\x15\x02\f\x17\x02\r\x19" + - "\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02\x11!\x02\x12#\x02\x13%\x02\x14" + - "\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/\x02\x191\x02\x1A3\x02\x1B5\x02" + - "\x1C7\x02\x1D9\x02\x1E;\x02\x1F=\x02 ?\x02!A\x02\"C\x02#E\x02$G\x02%I" + - "\x02&K\x02\'M\x02\x02O\x02\x02Q\x02\x02S\x02\x02U\x02\x02W\x02\x02Y\x02" + - "\x02[\x02\x02]\x02\x02_\x02\x02a\x02\x02c\x02\x02e\x02\x02g\x02\x02i\x02" + - "\x02k\x02\x02m\x02\x02o\x02\x02q\x02\x02s\x02\x02u\x02\x02w\x02\x02y\x02" + - "\x02{\x02\x02}\x02\x02\x7F\x02\x02\x81\x02\x02\x83\x02\x02\x03\x02!\x03" + - "\x02))\x05\x02\v\f\x0F\x0F\"\"\x04\x02\f\f\x0F\x0F\x03\x022;\x04\x02C" + - "\\c|\x04\x02CCcc\x04\x02DDdd\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02" + - "HHhh\x04\x02IIii\x04\x02JJjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02" + - "NNnn\x04\x02OOoo\x04\x02PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02" + - "TTtt\x04\x02UUuu\x04\x02VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02" + - "ZZzz\x04\x02[[{{\x04\x02\\\\||\x02\u0147\x02\x03\x03\x02\x02\x02\x02\x05" + - "\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03" + - "\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03" + - "\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03" + - "\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03" + - "\x02\x02\x02\x02\x1F\x03\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02" + - "\x02\x02\x02%\x03\x02\x02\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02" + - "\x02+\x03\x02\x02\x02\x02-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03" + - "\x02\x02\x02\x023\x03\x02\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02" + - "\x02\x029\x03\x02\x02\x02\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02" + - "?\x03\x02\x02\x02\x02A\x03\x02\x02\x02\x02C\x03\x02\x02\x02\x02E\x03\x02" + - "\x02\x02\x02G\x03\x02\x02\x02\x02I\x03\x02\x02\x02\x02K\x03\x02\x02\x02" + - "\x03\x85\x03\x02\x02\x02\x05\x87\x03\x02\x02\x02\x07\x89\x03\x02\x02\x02" + - "\t\x8B\x03\x02\x02\x02\v\x8D\x03\x02\x02\x02\r\x8F\x03\x02\x02\x02\x0F" + - "\x91\x03\x02\x02\x02\x11\x93\x03\x02\x02\x02\x13\x96\x03\x02\x02\x02\x15" + - "\x98\x03\x02\x02\x02\x17\x9B\x03\x02\x02\x02\x19\x9D\x03\x02\x02\x02\x1B" + - "\xA0\x03\x02\x02\x02\x1D\xA4\x03\x02\x02\x02\x1F\xA7\x03\x02\x02\x02!" + - "\xAB\x03\x02\x02\x02#\xAD\x03\x02\x02\x02%\xAF\x03\x02\x02\x02\'\xB1\x03" + - "\x02\x02\x02)\xB3\x03\x02\x02\x02+\xB5\x03\x02\x02\x02-\xB7\x03\x02\x02" + - "\x02/\xB9\x03\x02\x02\x021\xBB\x03\x02\x02\x023\xBD\x03\x02\x02\x025\xBF" + - "\x03\x02\x02\x027\xC8\x03\x02\x02\x029\xD4\x03\x02\x02\x02;\xDF\x03\x02" + - "\x02\x02=\xE4\x03\x02\x02\x02?\xEA\x03\x02\x02\x02A\xEF\x03\x02\x02\x02" + - "C\xF4\x03\x02\x02\x02E\xF9\x03\x02\x02\x02G\u0103\x03\x02\x02\x02I\u0109" + - "\x03\x02\x02\x02K\u0114\x03\x02\x02\x02M\u0122\x03\x02\x02\x02O\u0124" + - "\x03\x02\x02\x02Q\u0126\x03\x02\x02\x02S\u0128\x03\x02\x02\x02U\u012A" + - "\x03\x02\x02\x02W\u012C\x03\x02\x02\x02Y\u012E\x03\x02\x02\x02[\u0130" + - "\x03\x02\x02\x02]\u0132\x03\x02\x02\x02_\u0134\x03\x02\x02\x02a\u0136" + - "\x03\x02\x02\x02c\u0138\x03\x02\x02\x02e\u013A\x03\x02\x02\x02g\u013C" + - "\x03\x02\x02\x02i\u013E\x03\x02\x02\x02k\u0140\x03\x02\x02\x02m\u0142" + - "\x03\x02\x02\x02o\u0144\x03\x02\x02\x02q\u0146\x03\x02\x02\x02s\u0148" + - "\x03\x02\x02\x02u\u014A\x03\x02\x02\x02w\u014C\x03\x02\x02\x02y\u014E" + - "\x03\x02\x02\x02{\u0150\x03\x02\x02\x02}\u0152\x03\x02\x02\x02\x7F\u0154" + - "\x03\x02\x02\x02\x81\u0156\x03\x02\x02\x02\x83\u0158\x03\x02\x02\x02\x85" + - "\x86\x07-\x02\x02\x86\x04\x03\x02\x02\x02\x87\x88\x07/\x02\x02\x88\x06" + - "\x03\x02\x02\x02\x89\x8A\x07,\x02\x02\x8A\b\x03\x02\x02\x02\x8B\x8C\x07" + - "1\x02\x02\x8C\n\x03\x02\x02\x02\x8D\x8E\x07\'\x02\x02\x8E\f\x03\x02\x02" + - "\x02\x8F\x90\x07`\x02\x02\x90\x0E\x03\x02\x02\x02\x91\x92\x07?\x02\x02" + - "\x92\x10\x03\x02\x02\x02\x93\x94\x07#\x02\x02\x94\x95\x07?\x02\x02\x95" + - "\x12\x03\x02\x02\x02\x96\x97\x07>\x02\x02\x97\x14\x03\x02\x02\x02\x98" + - "\x99\x07>\x02\x02\x99\x9A\x07?\x02\x02\x9A\x16\x03\x02\x02\x02\x9B\x9C" + - "\x07@\x02\x02\x9C\x18\x03\x02\x02\x02\x9D\x9E\x07@\x02\x02\x9E\x9F\x07" + - "?\x02\x02\x9F\x1A\x03\x02\x02\x02\xA0\xA1\x05Q)\x02\xA1\xA2\x05k6\x02" + - "\xA2\xA3\x05W,\x02\xA3\x1C\x03\x02\x02\x02\xA4\xA5\x05m7\x02\xA5\xA6\x05" + - "s:\x02\xA6\x1E\x03\x02\x02\x02\xA7\xA8\x05k6\x02\xA8\xA9\x05m7\x02\xA9" + - "\xAA\x05w<\x02\xAA \x03\x02\x02\x02\xAB\xAC\x07*\x02\x02\xAC\"\x03\x02" + - "\x02\x02\xAD\xAE\x07+\x02\x02\xAE$\x03\x02\x02\x02\xAF\xB0\x07}\x02\x02" + - "\xB0&\x03\x02\x02\x02\xB1\xB2\x07\x7F\x02\x02\xB2(\x03\x02\x02\x02\xB3" + - "\xB4\x07]\x02\x02\xB4*\x03\x02\x02\x02\xB5\xB6\x07_\x02\x02\xB6,\x03\x02" + - "\x02\x02\xB7\xB8\x07.\x02\x02\xB8.\x03\x02\x02\x02\xB9\xBA\x07=\x02\x02" + - "\xBA0\x03\x02\x02\x02\xBB\xBC\x07<\x02\x02\xBC2\x03\x02\x02\x02\xBD\xBE" + - "\x070\x02\x02\xBE4\x03\x02\x02\x02\xBF\xC4\x05O(\x02\xC0\xC3\x05O(\x02" + - "\xC1\xC3\x05M\'\x02\xC2\xC0\x03\x02\x02\x02\xC2\xC1\x03\x02\x02\x02\xC3" + - "\xC6\x03\x02\x02\x02\xC4\xC2\x03\x02\x02\x02\xC4\xC5\x03\x02\x02\x02\xC5" + - "6\x03\x02\x02\x02\xC6\xC4\x03\x02\x02\x02\xC7\xC9\x05M\'\x02\xC8\xC7\x03" + - "\x02\x02\x02\xC9\xCA\x03\x02\x02\x02\xCA\xC8\x03\x02\x02\x02\xCA\xCB\x03" + - "\x02\x02\x02\xCB\xD2\x03\x02\x02\x02\xCC\xCE\x070\x02\x02\xCD\xCF\x05" + - "M\'\x02\xCE\xCD\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xCE\x03\x02" + - "\x02\x02\xD0\xD1\x03\x02\x02\x02\xD1\xD3\x03\x02\x02\x02\xD2\xCC\x03\x02" + - "\x02\x02\xD2\xD3\x03\x02\x02\x02\xD38\x03\x02\x02\x02\xD4\xDA\x07)\x02" + - "\x02\xD5\xD9\n\x02\x02\x02\xD6\xD7\x07)\x02\x02\xD7\xD9\x07)\x02\x02\xD8" + - "\xD5\x03\x02\x02\x02\xD8\xD6\x03\x02\x02\x02\xD9\xDC\x03\x02\x02\x02\xDA" + - "\xD8\x03\x02\x02\x02\xDA\xDB\x03\x02\x02\x02\xDB\xDD\x03\x02\x02\x02\xDC" + - "\xDA\x03\x02\x02\x02\xDD\xDE\x07)\x02\x02\xDE:\x03\x02\x02\x02\xDF\xE0" + - "\x05w<\x02\xE0\xE1\x05s:\x02\xE1\xE2\x05y=\x02\xE2\xE3\x05Y-\x02\xE3<" + - "\x03\x02\x02\x02\xE4\xE5\x05[.\x02\xE5\xE6\x05Q)\x02\xE6\xE7\x05g4\x02" + - "\xE7\xE8\x05u;\x02\xE8\xE9\x05Y-\x02\xE9>\x03\x02\x02\x02\xEA\xEB\x05" + - "k6\x02\xEB\xEC\x05y=\x02\xEC\xED\x05g4\x02\xED\xEE\x05g4\x02\xEE@\x03" + - "\x02\x02\x02\xEF\xF0\x05W,\x02\xF0\xF1\x05Q)\x02\xF1\xF2\x05w<\x02\xF2" + - "\xF3\x05Y-\x02\xF3B\x03\x02\x02\x02\xF4\xF5\x05w<\x02\xF5\xF6\x05a1\x02" + - "\xF6\xF7\x05i5\x02\xF7\xF8\x05Y-\x02\xF8D\x03\x02\x02\x02\xF9\xFA\x05" + - "W,\x02\xFA\xFB\x05Q)\x02\xFB\xFC\x05w<\x02\xFC\xFD\x05Y-\x02\xFD\xFE\x05" + - "w<\x02\xFE\xFF\x05a1\x02\xFF\u0100\x05i5\x02\u0100\u0101\x05Y-\x02\u0101" + - "F\x03\x02\x02\x02\u0102\u0104\t\x03\x02\x02\u0103\u0102\x03\x02\x02\x02" + - "\u0104\u0105\x03\x02\x02\x02\u0105\u0103\x03\x02\x02\x02\u0105\u0106\x03" + - "\x02\x02\x02\u0106\u0107\x03\x02\x02\x02\u0107\u0108\b$\x02\x02\u0108" + - "H\x03\x02\x02\x02\u0109\u010A\x071\x02\x02\u010A\u010B\x071\x02\x02\u010B" + - "\u010F\x03\x02\x02\x02\u010C\u010E\n\x04\x02\x02\u010D\u010C\x03\x02\x02" + - "\x02\u010E\u0111\x03\x02\x02\x02\u010F\u010D\x03\x02\x02\x02\u010F\u0110" + - "\x03\x02\x02\x02\u0110\u0112\x03\x02\x02\x02\u0111\u010F\x03\x02\x02\x02" + - "\u0112\u0113\b%\x02\x02\u0113J\x03\x02\x02\x02\u0114\u0115\x071\x02\x02" + - "\u0115\u0116\x07,\x02\x02\u0116\u011A\x03\x02\x02\x02\u0117\u0119\v\x02" + - "\x02\x02\u0118\u0117\x03\x02\x02\x02\u0119\u011C\x03\x02\x02\x02\u011A" + - "\u011B\x03\x02\x02\x02\u011A\u0118\x03\x02\x02\x02\u011B\u011D\x03\x02" + - "\x02\x02\u011C\u011A\x03\x02\x02\x02\u011D\u011E\x07,\x02\x02\u011E\u011F" + - "\x071\x02\x02\u011F\u0120\x03\x02\x02\x02\u0120\u0121\b&\x02\x02\u0121" + - "L\x03\x02\x02\x02\u0122\u0123\t\x05\x02\x02\u0123N\x03\x02\x02\x02\u0124" + - "\u0125\t\x06\x02\x02\u0125P\x03\x02\x02\x02\u0126\u0127\t\x07\x02\x02" + - "\u0127R\x03\x02\x02\x02\u0128\u0129\t\b\x02\x02\u0129T\x03\x02\x02\x02" + - "\u012A\u012B\t\t\x02\x02\u012BV\x03\x02\x02\x02\u012C\u012D\t\n\x02\x02" + - "\u012DX\x03\x02\x02\x02\u012E\u012F\t\v\x02\x02\u012FZ\x03\x02\x02\x02" + - "\u0130\u0131\t\f\x02\x02\u0131\\\x03\x02\x02\x02\u0132\u0133\t\r\x02\x02" + - "\u0133^\x03\x02\x02\x02\u0134\u0135\t\x0E\x02\x02\u0135`\x03\x02\x02\x02" + - "\u0136\u0137\t\x0F\x02\x02\u0137b\x03\x02\x02\x02\u0138\u0139\t\x10\x02" + - "\x02\u0139d\x03\x02\x02\x02\u013A\u013B\t\x11\x02\x02\u013Bf\x03\x02\x02" + - "\x02\u013C\u013D\t\x12\x02\x02\u013Dh\x03\x02\x02\x02\u013E\u013F\t\x13" + - "\x02\x02\u013Fj\x03\x02\x02\x02\u0140\u0141\t\x14\x02\x02\u0141l\x03\x02" + - "\x02\x02\u0142\u0143\t\x15\x02\x02\u0143n\x03\x02\x02\x02\u0144\u0145" + - "\t\x16\x02\x02\u0145p\x03\x02\x02\x02\u0146\u0147\t\x17\x02\x02\u0147" + - "r\x03\x02\x02\x02\u0148\u0149\t\x18\x02\x02\u0149t\x03\x02\x02\x02\u014A" + - "\u014B\t\x19\x02\x02\u014Bv\x03\x02\x02\x02\u014C\u014D\t\x1A\x02\x02" + - "\u014Dx\x03\x02\x02\x02\u014E\u014F\t\x1B\x02\x02\u014Fz\x03\x02\x02\x02" + - "\u0150\u0151\t\x1C\x02\x02\u0151|\x03\x02\x02\x02\u0152\u0153\t\x1D\x02" + - "\x02\u0153~\x03\x02\x02\x02\u0154\u0155\t\x1E\x02\x02\u0155\x80\x03\x02" + - "\x02\x02\u0156\u0157\t\x1F\x02\x02\u0157\x82\x03\x02\x02\x02\u0158\u0159" + - "\t \x02\x02\u0159\x84\x03\x02\x02\x02\r\x02\xC2\xC4\xCA\xD0\xD2\xD8\xDA" + - "\u0105\u010F\u011A\x03\b\x02\x02"; + "=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x04C\tC\x03\x02\x03\x02\x03" + + "\x03\x03\x03\x03\x04\x03\x04\x03\x05\x03\x05\x03\x06\x03\x06\x03\x07\x03" + + "\x07\x03\b\x03\b\x03\t\x03\t\x03\t\x03\n\x03\n\x03\v\x03\v\x03\v\x03\f" + + "\x03\f\x03\r\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03\x0F" + + "\x03\x0F\x03\x10\x03\x10\x03\x10\x03\x10\x03\x11\x03\x11\x03\x12\x03\x12" + + "\x03\x13\x03\x13\x03\x14\x03\x14\x03\x15\x03\x15\x03\x16\x03\x16\x03\x17" + + "\x03\x17\x03\x18\x03\x18\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B" + + "\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x07\x1C\xC8\n\x1C\f\x1C\x0E\x1C\xCB\v" + + "\x1C\x03\x1D\x06\x1D\xCE\n\x1D\r\x1D\x0E\x1D\xCF\x03\x1D\x03\x1D\x06\x1D" + + "\xD4\n\x1D\r\x1D\x0E\x1D\xD5\x05\x1D\xD8\n\x1D\x03\x1E\x03\x1E\x03\x1E" + + "\x03\x1E\x07\x1E\xDE\n\x1E\f\x1E\x0E\x1E\xE1\v\x1E\x03\x1E\x03\x1E\x03" + + "\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03 \x03 \x03 \x03 \x03 \x03 \x03" + + "!\x03!\x03!\x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03\"\x03#\x03#\x03#\x03" + + "#\x03#\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03$\x03%\x06%\u0109\n" + + "%\r%\x0E%\u010A\x03%\x03%\x03&\x03&\x03&\x03&\x07&\u0113\n&\f&\x0E&\u0116" + + "\v&\x03&\x03&\x03\'\x03\'\x03\'\x03\'\x07\'\u011E\n\'\f\'\x0E\'\u0121" + + "\v\'\x03\'\x03\'\x03\'\x03\'\x03\'\x03(\x03(\x03)\x03)\x03*\x03*\x03+" + + "\x03+\x03,\x03,\x03-\x03-\x03.\x03.\x03/\x03/\x030\x030\x031\x031\x03" + + "2\x032\x033\x033\x034\x034\x035\x035\x036\x036\x037\x037\x038\x038\x03" + + "9\x039\x03:\x03:\x03;\x03;\x03<\x03<\x03=\x03=\x03>\x03>\x03?\x03?\x03" + + "@\x03@\x03A\x03A\x03B\x03B\x03C\x03C\x03\u011F\x02\x02D\x03\x02\x03\x05" + + "\x02\x04\x07\x02\x05\t\x02\x06\v\x02\x07\r\x02\b\x0F\x02\t\x11\x02\n\x13" + + "\x02\v\x15\x02\f\x17\x02\r\x19\x02\x0E\x1B\x02\x0F\x1D\x02\x10\x1F\x02" + + "\x11!\x02\x12#\x02\x13%\x02\x14\'\x02\x15)\x02\x16+\x02\x17-\x02\x18/" + + "\x02\x191\x02\x1A3\x02\x1B5\x02\x1C7\x02\x1D9\x02\x1E;\x02\x1F=\x02 ?" + + "\x02!A\x02\"C\x02#E\x02$G\x02%I\x02&K\x02\'M\x02(O\x02\x02Q\x02\x02S\x02" + + "\x02U\x02\x02W\x02\x02Y\x02\x02[\x02\x02]\x02\x02_\x02\x02a\x02\x02c\x02" + + "\x02e\x02\x02g\x02\x02i\x02\x02k\x02\x02m\x02\x02o\x02\x02q\x02\x02s\x02" + + "\x02u\x02\x02w\x02\x02y\x02\x02{\x02\x02}\x02\x02\x7F\x02\x02\x81\x02" + + "\x02\x83\x02\x02\x85\x02\x02\x03\x02!\x03\x02))\x05\x02\v\f\x0F\x0F\"" + + "\"\x04\x02\f\f\x0F\x0F\x03\x022;\x04\x02C\\c|\x04\x02CCcc\x04\x02DDdd" + + "\x04\x02EEee\x04\x02FFff\x04\x02GGgg\x04\x02HHhh\x04\x02IIii\x04\x02J" + + "Jjj\x04\x02KKkk\x04\x02LLll\x04\x02MMmm\x04\x02NNnn\x04\x02OOoo\x04\x02" + + "PPpp\x04\x02QQqq\x04\x02RRrr\x04\x02SSss\x04\x02TTtt\x04\x02UUuu\x04\x02" + + "VVvv\x04\x02WWww\x04\x02XXxx\x04\x02YYyy\x04\x02ZZzz\x04\x02[[{{\x04\x02" + + "\\\\||\x02\u014D\x02\x03\x03\x02\x02\x02\x02\x05\x03\x02\x02\x02\x02\x07" + + "\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02\v\x03\x02\x02\x02\x02\r\x03" + + "\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11\x03\x02\x02\x02\x02\x13\x03" + + "\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17\x03\x02\x02\x02\x02\x19\x03" + + "\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x02\x1D\x03\x02\x02\x02\x02\x1F\x03" + + "\x02\x02\x02\x02!\x03\x02\x02\x02\x02#\x03\x02\x02\x02\x02%\x03\x02\x02" + + "\x02\x02\'\x03\x02\x02\x02\x02)\x03\x02\x02\x02\x02+\x03\x02\x02\x02\x02" + + "-\x03\x02\x02\x02\x02/\x03\x02\x02\x02\x021\x03\x02\x02\x02\x023\x03\x02" + + "\x02\x02\x025\x03\x02\x02\x02\x027\x03\x02\x02\x02\x029\x03\x02\x02\x02" + + "\x02;\x03\x02\x02\x02\x02=\x03\x02\x02\x02\x02?\x03\x02\x02\x02\x02A\x03" + + "\x02\x02\x02\x02C\x03\x02\x02\x02\x02E\x03\x02\x02\x02\x02G\x03\x02\x02" + + "\x02\x02I\x03\x02\x02\x02\x02K\x03\x02\x02\x02\x02M\x03\x02\x02\x02\x03" + + "\x87\x03\x02\x02\x02\x05\x89\x03\x02\x02\x02\x07\x8B\x03\x02\x02\x02\t" + + "\x8D\x03\x02\x02\x02\v\x8F\x03\x02\x02\x02\r\x91\x03\x02\x02\x02\x0F\x93" + + "\x03\x02\x02\x02\x11\x95\x03\x02\x02\x02\x13\x98\x03\x02\x02\x02\x15\x9A" + + "\x03\x02\x02\x02\x17\x9D\x03\x02\x02\x02\x19\x9F\x03\x02\x02\x02\x1B\xA2" + + "\x03\x02\x02\x02\x1D\xA6\x03\x02\x02\x02\x1F\xA9\x03\x02\x02\x02!\xAD" + + "\x03\x02\x02\x02#\xAF\x03\x02\x02\x02%\xB1\x03\x02\x02\x02\'\xB3\x03\x02" + + "\x02\x02)\xB5\x03\x02\x02\x02+\xB7\x03\x02\x02\x02-\xB9\x03\x02\x02\x02" + + "/\xBB\x03\x02\x02\x021\xBD\x03\x02\x02\x023\xBF\x03\x02\x02\x025\xC1\x03" + + "\x02\x02\x027\xC3\x03\x02\x02\x029\xCD\x03\x02\x02\x02;\xD9\x03\x02\x02" + + "\x02=\xE4\x03\x02\x02\x02?\xE9\x03\x02\x02\x02A\xEF\x03\x02\x02\x02C\xF4" + + "\x03\x02\x02\x02E\xF9\x03\x02\x02\x02G\xFE\x03\x02\x02\x02I\u0108\x03" + + "\x02\x02\x02K\u010E\x03\x02\x02\x02M\u0119\x03\x02\x02\x02O\u0127\x03" + + "\x02\x02\x02Q\u0129\x03\x02\x02\x02S\u012B\x03\x02\x02\x02U\u012D\x03" + + "\x02\x02\x02W\u012F\x03\x02\x02\x02Y\u0131\x03\x02\x02\x02[\u0133\x03" + + "\x02\x02\x02]\u0135\x03\x02\x02\x02_\u0137\x03\x02\x02\x02a\u0139\x03" + + "\x02\x02\x02c\u013B\x03\x02\x02\x02e\u013D\x03\x02\x02\x02g\u013F\x03" + + "\x02\x02\x02i\u0141\x03\x02\x02\x02k\u0143\x03\x02\x02\x02m\u0145\x03" + + "\x02\x02\x02o\u0147\x03\x02\x02\x02q\u0149\x03\x02\x02\x02s\u014B\x03" + + "\x02\x02\x02u\u014D\x03\x02\x02\x02w\u014F\x03\x02\x02\x02y\u0151\x03" + + "\x02\x02\x02{\u0153\x03\x02\x02\x02}\u0155\x03\x02\x02\x02\x7F\u0157\x03" + + "\x02\x02\x02\x81\u0159\x03\x02\x02\x02\x83\u015B\x03\x02\x02\x02\x85\u015D" + + "\x03\x02\x02\x02\x87\x88\x07-\x02\x02\x88\x04\x03\x02\x02\x02\x89\x8A" + + "\x07/\x02\x02\x8A\x06\x03\x02\x02\x02\x8B\x8C\x07,\x02\x02\x8C\b\x03\x02" + + "\x02\x02\x8D\x8E\x071\x02\x02\x8E\n\x03\x02\x02\x02\x8F\x90\x07\'\x02" + + "\x02\x90\f\x03\x02\x02\x02\x91\x92\x07`\x02\x02\x92\x0E\x03\x02\x02\x02" + + "\x93\x94\x07?\x02\x02\x94\x10\x03\x02\x02\x02\x95\x96\x07#\x02\x02\x96" + + "\x97\x07?\x02\x02\x97\x12\x03\x02\x02\x02\x98\x99\x07>\x02\x02\x99\x14" + + "\x03\x02\x02\x02\x9A\x9B\x07>\x02\x02\x9B\x9C\x07?\x02\x02\x9C\x16\x03" + + "\x02\x02\x02\x9D\x9E\x07@\x02\x02\x9E\x18\x03\x02\x02\x02\x9F\xA0\x07" + + "@\x02\x02\xA0\xA1\x07?\x02\x02\xA1\x1A\x03\x02\x02\x02\xA2\xA3\x05S*\x02" + + "\xA3\xA4\x05m7\x02\xA4\xA5\x05Y-\x02\xA5\x1C\x03\x02\x02\x02\xA6\xA7\x05" + + "o8\x02\xA7\xA8\x05u;\x02\xA8\x1E\x03\x02\x02\x02\xA9\xAA\x05m7\x02\xAA" + + "\xAB\x05o8\x02\xAB\xAC\x05y=\x02\xAC \x03\x02\x02\x02\xAD\xAE\x07*\x02" + + "\x02\xAE\"\x03\x02\x02\x02\xAF\xB0\x07+\x02\x02\xB0$\x03\x02\x02\x02\xB1" + + "\xB2\x07}\x02\x02\xB2&\x03\x02\x02\x02\xB3\xB4\x07\x7F\x02\x02\xB4(\x03" + + "\x02\x02\x02\xB5\xB6\x07]\x02\x02\xB6*\x03\x02\x02\x02\xB7\xB8\x07_\x02" + + "\x02\xB8,\x03\x02\x02\x02\xB9\xBA\x07.\x02\x02\xBA.\x03\x02\x02\x02\xBB" + + "\xBC\x07=\x02\x02\xBC0\x03\x02\x02\x02\xBD\xBE\x07<\x02\x02\xBE2\x03\x02" + + "\x02\x02\xBF\xC0\x070\x02\x02\xC04\x03\x02\x02\x02\xC1\xC2\x07a\x02\x02" + + "\xC26\x03\x02\x02\x02\xC3\xC9\x05Q)\x02\xC4\xC8\x05Q)\x02\xC5\xC8\x05" + + "O(\x02\xC6\xC8\x055\x1B\x02\xC7\xC4\x03\x02\x02\x02\xC7\xC5\x03\x02\x02" + + "\x02\xC7\xC6\x03\x02\x02\x02\xC8\xCB\x03\x02\x02\x02\xC9\xC7\x03\x02\x02" + + "\x02\xC9\xCA\x03\x02\x02\x02\xCA8\x03\x02\x02\x02\xCB\xC9\x03\x02\x02" + + "\x02\xCC\xCE\x05O(\x02\xCD\xCC\x03\x02\x02\x02\xCE\xCF\x03\x02\x02\x02" + + "\xCF\xCD\x03\x02\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xD7\x03\x02\x02\x02" + + "\xD1\xD3\x070\x02\x02\xD2\xD4\x05O(\x02\xD3\xD2\x03\x02\x02\x02\xD4\xD5" + + "\x03\x02\x02\x02\xD5\xD3\x03\x02\x02\x02\xD5\xD6\x03\x02\x02\x02\xD6\xD8" + + "\x03\x02\x02\x02\xD7\xD1\x03\x02\x02\x02\xD7\xD8\x03\x02\x02\x02\xD8:" + + "\x03\x02\x02\x02\xD9\xDF\x07)\x02\x02\xDA\xDE\n\x02\x02\x02\xDB\xDC\x07" + + ")\x02\x02\xDC\xDE\x07)\x02\x02\xDD\xDA\x03\x02\x02\x02\xDD\xDB\x03\x02" + + "\x02\x02\xDE\xE1\x03\x02\x02\x02\xDF\xDD\x03\x02\x02\x02\xDF\xE0\x03\x02" + + "\x02\x02\xE0\xE2\x03\x02\x02\x02\xE1\xDF\x03\x02\x02\x02\xE2\xE3\x07)" + + "\x02\x02\xE3<\x03\x02\x02\x02\xE4\xE5\x05y=\x02\xE5\xE6\x05u;\x02\xE6" + + "\xE7\x05{>\x02\xE7\xE8\x05[.\x02\xE8>\x03\x02\x02\x02\xE9\xEA\x05]/\x02" + + "\xEA\xEB\x05S*\x02\xEB\xEC\x05i5\x02\xEC\xED\x05w<\x02\xED\xEE\x05[.\x02" + + "\xEE@\x03\x02\x02\x02\xEF\xF0\x05m7\x02\xF0\xF1\x05{>\x02\xF1\xF2\x05" + + "i5\x02\xF2\xF3\x05i5\x02\xF3B\x03\x02\x02\x02\xF4\xF5\x05Y-\x02\xF5\xF6" + + "\x05S*\x02\xF6\xF7\x05y=\x02\xF7\xF8\x05[.\x02\xF8D\x03\x02\x02\x02\xF9" + + "\xFA\x05y=\x02\xFA\xFB\x05c2\x02\xFB\xFC\x05k6\x02\xFC\xFD\x05[.\x02\xFD" + + "F\x03\x02\x02\x02\xFE\xFF\x05Y-\x02\xFF\u0100\x05S*\x02\u0100\u0101\x05" + + "y=\x02\u0101\u0102\x05[.\x02\u0102\u0103\x05y=\x02\u0103\u0104\x05c2\x02" + + "\u0104\u0105\x05k6\x02\u0105\u0106\x05[.\x02\u0106H\x03\x02\x02\x02\u0107" + + "\u0109\t\x03\x02\x02\u0108\u0107\x03\x02\x02\x02\u0109\u010A\x03\x02\x02" + + "\x02\u010A\u0108\x03\x02\x02\x02\u010A\u010B\x03\x02\x02\x02\u010B\u010C" + + "\x03\x02\x02\x02\u010C\u010D\b%\x02\x02\u010DJ\x03\x02\x02\x02\u010E\u010F" + + "\x071\x02\x02\u010F\u0110\x071\x02\x02\u0110\u0114\x03\x02\x02\x02\u0111" + + "\u0113\n\x04\x02\x02\u0112\u0111\x03\x02\x02\x02\u0113\u0116\x03\x02\x02" + + "\x02\u0114\u0112\x03\x02\x02\x02\u0114\u0115\x03\x02\x02\x02\u0115\u0117" + + "\x03\x02\x02\x02\u0116\u0114\x03\x02\x02\x02\u0117\u0118\b&\x02\x02\u0118" + + "L\x03\x02\x02\x02\u0119\u011A\x071\x02\x02\u011A\u011B\x07,\x02\x02\u011B" + + "\u011F\x03\x02\x02\x02\u011C\u011E\v\x02\x02\x02\u011D\u011C\x03\x02\x02" + + "\x02\u011E\u0121\x03\x02\x02\x02\u011F\u0120\x03\x02\x02\x02\u011F\u011D" + + "\x03\x02\x02\x02\u0120\u0122\x03\x02\x02\x02\u0121\u011F\x03\x02\x02\x02" + + "\u0122\u0123\x07,\x02\x02\u0123\u0124\x071\x02\x02\u0124\u0125\x03\x02" + + "\x02\x02\u0125\u0126\b\'\x02\x02\u0126N\x03\x02\x02\x02\u0127\u0128\t" + + "\x05\x02\x02\u0128P\x03\x02\x02\x02\u0129\u012A\t\x06\x02\x02\u012AR\x03" + + "\x02\x02\x02\u012B\u012C\t\x07\x02\x02\u012CT\x03\x02\x02\x02\u012D\u012E" + + "\t\b\x02\x02\u012EV\x03\x02\x02\x02\u012F\u0130\t\t\x02\x02\u0130X\x03" + + "\x02\x02\x02\u0131\u0132\t\n\x02\x02\u0132Z\x03\x02\x02\x02\u0133\u0134" + + "\t\v\x02\x02\u0134\\\x03\x02\x02\x02\u0135\u0136\t\f\x02\x02\u0136^\x03" + + "\x02\x02\x02\u0137\u0138\t\r\x02\x02\u0138`\x03\x02\x02\x02\u0139\u013A" + + "\t\x0E\x02\x02\u013Ab\x03\x02\x02\x02\u013B\u013C\t\x0F\x02\x02\u013C" + + "d\x03\x02\x02\x02\u013D\u013E\t\x10\x02\x02\u013Ef\x03\x02\x02\x02\u013F" + + "\u0140\t\x11\x02\x02\u0140h\x03\x02\x02\x02\u0141\u0142\t\x12\x02\x02" + + "\u0142j\x03\x02\x02\x02\u0143\u0144\t\x13\x02\x02\u0144l\x03\x02\x02\x02" + + "\u0145\u0146\t\x14\x02\x02\u0146n\x03\x02\x02\x02\u0147\u0148\t\x15\x02" + + "\x02\u0148p\x03\x02\x02\x02\u0149\u014A\t\x16\x02\x02\u014Ar\x03\x02\x02" + + "\x02\u014B\u014C\t\x17\x02\x02\u014Ct\x03\x02\x02\x02\u014D\u014E\t\x18" + + "\x02\x02\u014Ev\x03\x02\x02\x02\u014F\u0150\t\x19\x02\x02\u0150x\x03\x02" + + "\x02\x02\u0151\u0152\t\x1A\x02\x02\u0152z\x03\x02\x02\x02\u0153\u0154" + + "\t\x1B\x02\x02\u0154|\x03\x02\x02\x02\u0155\u0156\t\x1C\x02\x02\u0156" + + "~\x03\x02\x02\x02\u0157\u0158\t\x1D\x02\x02\u0158\x80\x03\x02\x02\x02" + + "\u0159\u015A\t\x1E\x02\x02\u015A\x82\x03\x02\x02\x02\u015B\u015C\t\x1F" + + "\x02\x02\u015C\x84\x03\x02\x02\x02\u015D\u015E\t \x02\x02\u015E\x86\x03" + + "\x02\x02\x02\r\x02\xC7\xC9\xCF\xD5\xD7\xDD\xDF\u010A\u0114\u011F\x03\b" + + "\x02\x02"; public static __ATN: ATN; public static get _ATN(): ATN { if (!FormulaLexer.__ATN) { diff --git a/packages/formula/src/grammar/FormulaParser.interp b/packages/formula/src/grammar/FormulaParser.interp index 311641f1a..0c855ac47 100644 --- a/packages/formula/src/grammar/FormulaParser.interp +++ b/packages/formula/src/grammar/FormulaParser.interp @@ -25,6 +25,7 @@ null ';' ':' '.' +'_' null null null @@ -65,6 +66,7 @@ COMMA SEMICOLON COLON DOT +UNDERSCORE IDENTIFIER NUMBER STRING @@ -87,4 +89,4 @@ variable atn: -[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 39, 79, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 33, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 53, 10, 3, 12, 3, 14, 3, 56, 11, 3, 3, 4, 3, 4, 3, 4, 5, 4, 61, 10, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 7, 5, 68, 10, 5, 12, 5, 14, 5, 71, 11, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 2, 2, 3, 4, 7, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 2, 5, 3, 2, 5, 7, 3, 2, 3, 4, 3, 2, 9, 14, 2, 92, 2, 12, 3, 2, 2, 2, 4, 32, 3, 2, 2, 2, 6, 57, 3, 2, 2, 2, 8, 64, 3, 2, 2, 2, 10, 72, 3, 2, 2, 2, 12, 13, 5, 4, 3, 2, 13, 14, 7, 2, 2, 3, 14, 3, 3, 2, 2, 2, 15, 16, 8, 3, 1, 2, 16, 17, 7, 17, 2, 2, 17, 33, 5, 4, 3, 14, 18, 33, 5, 6, 4, 2, 19, 33, 5, 10, 6, 2, 20, 33, 7, 29, 2, 2, 21, 33, 7, 30, 2, 2, 22, 33, 7, 31, 2, 2, 23, 33, 7, 32, 2, 2, 24, 33, 7, 33, 2, 2, 25, 33, 7, 34, 2, 2, 26, 33, 7, 35, 2, 2, 27, 33, 7, 36, 2, 2, 28, 29, 7, 18, 2, 2, 29, 30, 5, 4, 3, 2, 30, 31, 7, 19, 2, 2, 31, 33, 3, 2, 2, 2, 32, 15, 3, 2, 2, 2, 32, 18, 3, 2, 2, 2, 32, 19, 3, 2, 2, 2, 32, 20, 3, 2, 2, 2, 32, 21, 3, 2, 2, 2, 32, 22, 3, 2, 2, 2, 32, 23, 3, 2, 2, 2, 32, 24, 3, 2, 2, 2, 32, 25, 3, 2, 2, 2, 32, 26, 3, 2, 2, 2, 32, 27, 3, 2, 2, 2, 32, 28, 3, 2, 2, 2, 33, 54, 3, 2, 2, 2, 34, 35, 12, 20, 2, 2, 35, 36, 9, 2, 2, 2, 36, 53, 5, 4, 3, 21, 37, 38, 12, 19, 2, 2, 38, 39, 9, 3, 2, 2, 39, 53, 5, 4, 3, 20, 40, 41, 12, 18, 2, 2, 41, 42, 7, 8, 2, 2, 42, 53, 5, 4, 3, 19, 43, 44, 12, 17, 2, 2, 44, 45, 9, 4, 2, 2, 45, 53, 5, 4, 3, 18, 46, 47, 12, 16, 2, 2, 47, 48, 7, 15, 2, 2, 48, 53, 5, 4, 3, 17, 49, 50, 12, 15, 2, 2, 50, 51, 7, 16, 2, 2, 51, 53, 5, 4, 3, 16, 52, 34, 3, 2, 2, 2, 52, 37, 3, 2, 2, 2, 52, 40, 3, 2, 2, 2, 52, 43, 3, 2, 2, 2, 52, 46, 3, 2, 2, 2, 52, 49, 3, 2, 2, 2, 53, 56, 3, 2, 2, 2, 54, 52, 3, 2, 2, 2, 54, 55, 3, 2, 2, 2, 55, 5, 3, 2, 2, 2, 56, 54, 3, 2, 2, 2, 57, 58, 7, 28, 2, 2, 58, 60, 7, 18, 2, 2, 59, 61, 5, 8, 5, 2, 60, 59, 3, 2, 2, 2, 60, 61, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 63, 7, 19, 2, 2, 63, 7, 3, 2, 2, 2, 64, 69, 5, 4, 3, 2, 65, 66, 7, 24, 2, 2, 66, 68, 5, 4, 3, 2, 67, 65, 3, 2, 2, 2, 68, 71, 3, 2, 2, 2, 69, 67, 3, 2, 2, 2, 69, 70, 3, 2, 2, 2, 70, 9, 3, 2, 2, 2, 71, 69, 3, 2, 2, 2, 72, 73, 7, 20, 2, 2, 73, 74, 7, 20, 2, 2, 74, 75, 7, 28, 2, 2, 75, 76, 7, 21, 2, 2, 76, 77, 7, 21, 2, 2, 77, 11, 3, 2, 2, 2, 7, 32, 52, 54, 60, 69] \ No newline at end of file +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 40, 79, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 33, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 53, 10, 3, 12, 3, 14, 3, 56, 11, 3, 3, 4, 3, 4, 3, 4, 5, 4, 61, 10, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 7, 5, 68, 10, 5, 12, 5, 14, 5, 71, 11, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 2, 2, 3, 4, 7, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 2, 5, 3, 2, 5, 7, 3, 2, 3, 4, 3, 2, 9, 14, 2, 92, 2, 12, 3, 2, 2, 2, 4, 32, 3, 2, 2, 2, 6, 57, 3, 2, 2, 2, 8, 64, 3, 2, 2, 2, 10, 72, 3, 2, 2, 2, 12, 13, 5, 4, 3, 2, 13, 14, 7, 2, 2, 3, 14, 3, 3, 2, 2, 2, 15, 16, 8, 3, 1, 2, 16, 17, 7, 17, 2, 2, 17, 33, 5, 4, 3, 14, 18, 33, 5, 6, 4, 2, 19, 33, 5, 10, 6, 2, 20, 33, 7, 30, 2, 2, 21, 33, 7, 31, 2, 2, 22, 33, 7, 32, 2, 2, 23, 33, 7, 33, 2, 2, 24, 33, 7, 34, 2, 2, 25, 33, 7, 35, 2, 2, 26, 33, 7, 36, 2, 2, 27, 33, 7, 37, 2, 2, 28, 29, 7, 18, 2, 2, 29, 30, 5, 4, 3, 2, 30, 31, 7, 19, 2, 2, 31, 33, 3, 2, 2, 2, 32, 15, 3, 2, 2, 2, 32, 18, 3, 2, 2, 2, 32, 19, 3, 2, 2, 2, 32, 20, 3, 2, 2, 2, 32, 21, 3, 2, 2, 2, 32, 22, 3, 2, 2, 2, 32, 23, 3, 2, 2, 2, 32, 24, 3, 2, 2, 2, 32, 25, 3, 2, 2, 2, 32, 26, 3, 2, 2, 2, 32, 27, 3, 2, 2, 2, 32, 28, 3, 2, 2, 2, 33, 54, 3, 2, 2, 2, 34, 35, 12, 20, 2, 2, 35, 36, 9, 2, 2, 2, 36, 53, 5, 4, 3, 21, 37, 38, 12, 19, 2, 2, 38, 39, 9, 3, 2, 2, 39, 53, 5, 4, 3, 20, 40, 41, 12, 18, 2, 2, 41, 42, 7, 8, 2, 2, 42, 53, 5, 4, 3, 19, 43, 44, 12, 17, 2, 2, 44, 45, 9, 4, 2, 2, 45, 53, 5, 4, 3, 18, 46, 47, 12, 16, 2, 2, 47, 48, 7, 15, 2, 2, 48, 53, 5, 4, 3, 17, 49, 50, 12, 15, 2, 2, 50, 51, 7, 16, 2, 2, 51, 53, 5, 4, 3, 16, 52, 34, 3, 2, 2, 2, 52, 37, 3, 2, 2, 2, 52, 40, 3, 2, 2, 2, 52, 43, 3, 2, 2, 2, 52, 46, 3, 2, 2, 2, 52, 49, 3, 2, 2, 2, 53, 56, 3, 2, 2, 2, 54, 52, 3, 2, 2, 2, 54, 55, 3, 2, 2, 2, 55, 5, 3, 2, 2, 2, 56, 54, 3, 2, 2, 2, 57, 58, 7, 29, 2, 2, 58, 60, 7, 18, 2, 2, 59, 61, 5, 8, 5, 2, 60, 59, 3, 2, 2, 2, 60, 61, 3, 2, 2, 2, 61, 62, 3, 2, 2, 2, 62, 63, 7, 19, 2, 2, 63, 7, 3, 2, 2, 2, 64, 69, 5, 4, 3, 2, 65, 66, 7, 24, 2, 2, 66, 68, 5, 4, 3, 2, 67, 65, 3, 2, 2, 2, 68, 71, 3, 2, 2, 2, 69, 67, 3, 2, 2, 2, 69, 70, 3, 2, 2, 2, 70, 9, 3, 2, 2, 2, 71, 69, 3, 2, 2, 2, 72, 73, 7, 20, 2, 2, 73, 74, 7, 20, 2, 2, 74, 75, 7, 29, 2, 2, 75, 76, 7, 21, 2, 2, 76, 77, 7, 21, 2, 2, 77, 11, 3, 2, 2, 2, 7, 32, 52, 54, 60, 69] \ No newline at end of file diff --git a/packages/formula/src/grammar/FormulaParser.tokens b/packages/formula/src/grammar/FormulaParser.tokens index 58c5ff007..5ad732765 100644 --- a/packages/formula/src/grammar/FormulaParser.tokens +++ b/packages/formula/src/grammar/FormulaParser.tokens @@ -23,18 +23,19 @@ COMMA=22 SEMICOLON=23 COLON=24 DOT=25 -IDENTIFIER=26 -NUMBER=27 -STRING=28 -TRUE=29 -FALSE=30 -NULL=31 -DATE=32 -TIME=33 -DATETIME=34 -WS=35 -COMMENT=36 -MULTILINE_COMMENT=37 +UNDERSCORE=26 +IDENTIFIER=27 +NUMBER=28 +STRING=29 +TRUE=30 +FALSE=31 +NULL=32 +DATE=33 +TIME=34 +DATETIME=35 +WS=36 +COMMENT=37 +MULTILINE_COMMENT=38 '+'=1 '-'=2 '*'=3 @@ -57,3 +58,4 @@ MULTILINE_COMMENT=37 ';'=23 ':'=24 '.'=25 +'_'=26 diff --git a/packages/formula/src/grammar/FormulaParser.ts b/packages/formula/src/grammar/FormulaParser.ts index 5763b9c3f..7ce7934a8 100644 --- a/packages/formula/src/grammar/FormulaParser.ts +++ b/packages/formula/src/grammar/FormulaParser.ts @@ -52,18 +52,19 @@ export class FormulaParser extends Parser { public static readonly SEMICOLON = 23; public static readonly COLON = 24; public static readonly DOT = 25; - public static readonly IDENTIFIER = 26; - public static readonly NUMBER = 27; - public static readonly STRING = 28; - public static readonly TRUE = 29; - public static readonly FALSE = 30; - public static readonly NULL = 31; - public static readonly DATE = 32; - public static readonly TIME = 33; - public static readonly DATETIME = 34; - public static readonly WS = 35; - public static readonly COMMENT = 36; - public static readonly MULTILINE_COMMENT = 37; + public static readonly UNDERSCORE = 26; + public static readonly IDENTIFIER = 27; + public static readonly NUMBER = 28; + public static readonly STRING = 29; + public static readonly TRUE = 30; + public static readonly FALSE = 31; + public static readonly NULL = 32; + public static readonly DATE = 33; + public static readonly TIME = 34; + public static readonly DATETIME = 35; + public static readonly WS = 36; + public static readonly COMMENT = 37; + public static readonly MULTILINE_COMMENT = 38; public static readonly RULE_formula = 0; public static readonly RULE_expression = 1; public static readonly RULE_functionCall = 2; @@ -77,15 +78,15 @@ export class FormulaParser extends Parser { private static readonly _LITERAL_NAMES: Array = [ undefined, "'+'", "'-'", "'*'", "'/'", "'%'", "'^'", "'='", "'!='", "'<'", "'<='", "'>'", "'>='", undefined, undefined, undefined, "'('", "')'", - "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", + "'{'", "'}'", "'['", "']'", "','", "';'", "':'", "'.'", "'_'", ]; private static readonly _SYMBOLIC_NAMES: Array = [ undefined, "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MODULO", "POWER", "EQUAL", "NOT_EQUAL", "LESS", "LESS_EQUAL", "GREATER", "GREATER_EQUAL", "AND", "OR", "NOT", "LPAREN", "RPAREN", "LBRACE", "RBRACE", "LBRACKET", - "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "IDENTIFIER", "NUMBER", - "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", "WS", "COMMENT", - "MULTILINE_COMMENT", + "RBRACKET", "COMMA", "SEMICOLON", "COLON", "DOT", "UNDERSCORE", "IDENTIFIER", + "NUMBER", "STRING", "TRUE", "FALSE", "NULL", "DATE", "TIME", "DATETIME", + "WS", "COMMENT", "MULTILINE_COMMENT", ]; public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(FormulaParser._LITERAL_NAMES, FormulaParser._SYMBOLIC_NAMES, []); @@ -587,25 +588,25 @@ export class FormulaParser extends Parser { } public static readonly _serializedATN: string = - "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03\'O\x04\x02\t" + - "\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x03\x02\x03" + - "\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03(O\x04\x02\t\x02" + + "\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x03\x02\x03\x02" + + "\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + - "\x03\x05\x03!\n\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x05\x03!\n\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + - "\x03\x03\x03\x03\x03\x07\x035\n\x03\f\x03\x0E\x038\v\x03\x03\x04\x03\x04" + - "\x03\x04\x05\x04=\n\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x07\x05" + - "D\n\x05\f\x05\x0E\x05G\v\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03" + + "\x03\x03\x03\x03\x07\x035\n\x03\f\x03\x0E\x038\v\x03\x03\x04\x03\x04\x03" + + "\x04\x05\x04=\n\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x07\x05D\n" + + "\x05\f\x05\x0E\x05G\v\x05\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03" + "\x06\x03\x06\x02\x02\x03\x04\x07\x02\x02\x04\x02\x06\x02\b\x02\n\x02\x02" + "\x05\x03\x02\x05\x07\x03\x02\x03\x04\x03\x02\t\x0E\x02\\\x02\f\x03\x02" + "\x02\x02\x04 \x03\x02\x02\x02\x069\x03\x02\x02\x02\b@\x03\x02\x02\x02" + "\nH\x03\x02\x02\x02\f\r\x05\x04\x03\x02\r\x0E\x07\x02\x02\x03\x0E\x03" + "\x03\x02\x02\x02\x0F\x10\b\x03\x01\x02\x10\x11\x07\x11\x02\x02\x11!\x05" + - "\x04\x03\x0E\x12!\x05\x06\x04\x02\x13!\x05\n\x06\x02\x14!\x07\x1D\x02" + - "\x02\x15!\x07\x1E\x02\x02\x16!\x07\x1F\x02\x02\x17!\x07 \x02\x02\x18!" + - "\x07!\x02\x02\x19!\x07\"\x02\x02\x1A!\x07#\x02\x02\x1B!\x07$\x02\x02\x1C" + - "\x1D\x07\x12\x02\x02\x1D\x1E\x05\x04\x03\x02\x1E\x1F\x07\x13\x02\x02\x1F" + - "!\x03\x02\x02\x02 \x0F\x03\x02\x02\x02 \x12\x03\x02\x02\x02 \x13\x03\x02" + + "\x04\x03\x0E\x12!\x05\x06\x04\x02\x13!\x05\n\x06\x02\x14!\x07\x1E\x02" + + "\x02\x15!\x07\x1F\x02\x02\x16!\x07 \x02\x02\x17!\x07!\x02\x02\x18!\x07" + + "\"\x02\x02\x19!\x07#\x02\x02\x1A!\x07$\x02\x02\x1B!\x07%\x02\x02\x1C\x1D" + + "\x07\x12\x02\x02\x1D\x1E\x05\x04\x03\x02\x1E\x1F\x07\x13\x02\x02\x1F!" + + "\x03\x02\x02\x02 \x0F\x03\x02\x02\x02 \x12\x03\x02\x02\x02 \x13\x03\x02" + "\x02\x02 \x14\x03\x02\x02\x02 \x15\x03\x02\x02\x02 \x16\x03\x02\x02\x02" + " \x17\x03\x02\x02\x02 \x18\x03\x02\x02\x02 \x19\x03\x02\x02\x02 \x1A\x03" + "\x02\x02\x02 \x1B\x03\x02\x02\x02 \x1C\x03\x02\x02\x02!6\x03\x02\x02\x02" + @@ -616,12 +617,12 @@ export class FormulaParser extends Parser { "\x02\x0235\x05\x04\x03\x104\"\x03\x02\x02\x024%\x03\x02\x02\x024(\x03" + "\x02\x02\x024+\x03\x02\x02\x024.\x03\x02\x02\x0241\x03\x02\x02\x0258\x03" + "\x02\x02\x0264\x03\x02\x02\x0267\x03\x02\x02\x027\x05\x03\x02\x02\x02" + - "86\x03\x02\x02\x029:\x07\x1C\x02\x02:<\x07\x12\x02\x02;=\x05\b\x05\x02" + + "86\x03\x02\x02\x029:\x07\x1D\x02\x02:<\x07\x12\x02\x02;=\x05\b\x05\x02" + "<;\x03\x02\x02\x02<=\x03\x02\x02\x02=>\x03\x02\x02\x02>?\x07\x13\x02\x02" + "?\x07\x03\x02\x02\x02@E\x05\x04\x03\x02AB\x07\x18\x02\x02BD\x05\x04\x03" + "\x02CA\x03\x02\x02\x02DG\x03\x02\x02\x02EC\x03\x02\x02\x02EF\x03\x02\x02" + "\x02F\t\x03\x02\x02\x02GE\x03\x02\x02\x02HI\x07\x14\x02\x02IJ\x07\x14" + - "\x02\x02JK\x07\x1C\x02\x02KL\x07\x15\x02\x02LM\x07\x15\x02\x02M\v\x03" + + "\x02\x02JK\x07\x1D\x02\x02KL\x07\x15\x02\x02LM\x07\x15\x02\x02M\v\x03" + "\x02\x02\x02\x07 46 implements FormulaParserVisitor { @@ -63,14 +63,14 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i } visitVariable(ctx: VariableContext): string { - const variable = ctx.text const fieldId = ctx.IDENTIFIER().text - const field = this.table.schema.getFieldById(new FieldIdVo(fieldId)).expect("field not found") - const trimmed = variable.replace(/^\{\{|\}\}$/g, "").trim() + const field = this.table.schema + .getFieldById(new FieldIdVo(fieldId)) + .expect(`variable ${fieldId} not found in table ${this.table.name.value}`) if (field.type === "currency") { - return `(${trimmed}/100)` + return `(${fieldId}/100)` } - return trimmed + return fieldId } visitFormula(ctx: FormulaContext): string { @@ -90,36 +90,39 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i return this.visit(ctx.expression()) } - private arguments(ctx: ArgumentListContext): string[] { - return ctx.expression().map((expr) => this.visit(expr)) + private arguments(ctx: FunctionCallContext): string[] { + return ctx + .argumentList()! + .expression() + .map((expr) => this.visit(expr)) } visitFunctionCall(ctx: FunctionCallContext): string { const functionName = ctx.IDENTIFIER().text as FormulaFunction return match(functionName) .with("ADD", "SUM", () => { - const fn = this.arguments(ctx.argumentList()!).join(" + ") + const fn = this.arguments(ctx).join(" + ") return `(${fn})` }) .with("SUBTRACT", () => { - const fn = this.arguments(ctx.argumentList()!).join(" - ") + const fn = this.arguments(ctx).join(" - ") return `(${fn})` }) .with("MULTIPLY", () => { - const fn = this.arguments(ctx.argumentList()!).join(" * ") + const fn = this.arguments(ctx).join(" * ") return `(${fn})` }) .with("DIVIDE", () => { - const fn = this.arguments(ctx.argumentList()!).join(" / ") + const fn = this.arguments(ctx).join(" / ") return `(${fn})` }) .with("CONCAT", () => { - const fn = this.arguments(ctx.argumentList()!) + const fn = this.arguments(ctx) .map((arg) => `COALESCE(${arg}, '')`) .join(" || ") return `(${fn})` }) .with("AVERAGE", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `( (${args.map((arg) => `COALESCE(${arg}, 0)`).join(" + ")}) / @@ -129,49 +132,52 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i ))` }) .with("LEFT", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `SUBSTR(${args[0]}, 1, ${args[1]})` }) .with("RIGHT", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `SUBSTR(${args[0]}, -${args[1]}, ${args[1]})` }) .with("MID", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `SUBSTR(${args[0]}, ${args[1]}, ${args[2]})` }) .with("AND", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `(${args.map((arg) => `COALESCE(${arg}, FALSE)`).join(" AND ")})` }) .with("OR", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `(${args.map((arg) => `COALESCE(${arg}, FALSE)`).join(" OR ")})` }) .with("NOT", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `NOT ${args[0]}` }) .with("SEARCH", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `COALESCE(INSTR(LOWER(COALESCE(${args[1]}, '')), LOWER(COALESCE(${args[0]}, ''))), 0)` }) .with("LEN", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) return `LENGTH(${args[0]})` }) .with("REPEAT", () => { - const args = this.arguments(ctx.argumentList()!) + const args = this.arguments(ctx) // args[0] 是要重复的字符串,args[1] 是重复次数 return `SUBSTR(REPLACE(HEX(ZEROBLOB(${args[1]})), '00', ${args[0]}), 1, LENGTH(${args[0]}) * ${args[1]})` }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" - return `${functionName.toLowerCase()}(${args})` + return `${functionName}(${args})` }) } visitArgumentList(ctx: ArgumentListContext): string { - return this.arguments(ctx).join(", ") + return ctx + .expression() + .map((expr) => this.visit(expr)) + .join(", ") } } diff --git a/packages/table/src/modules/schema/fields/variants/json-field/json-field.vo.ts b/packages/table/src/modules/schema/fields/variants/json-field/json-field.vo.ts index 31bfb9752..a122e7a22 100644 --- a/packages/table/src/modules/schema/fields/variants/json-field/json-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/json-field/json-field.vo.ts @@ -1,12 +1,12 @@ -import { None, Option, Some } from "@undb/domain" +import { Option,Some } from "@undb/domain" import { z } from "@undb/zod" import { match } from "ts-pattern" import type { FormFieldVO } from "../../../../forms/form/form-field.vo" import type { RecordComositeSpecification } from "../../../../records/record/record.composite-specification" -import { fieldId, FieldIdVo } from "../../field-id.vo" +import { fieldId,FieldIdVo } from "../../field-id.vo" import type { IFieldVisitor } from "../../field.visitor" -import { AbstractField, baseFieldDTO, createBaseFieldDTO } from "../abstract-field.vo" -import { jsonFieldConstraint, JsonFieldConstraint } from "./json-field-constraint.vo" +import { AbstractField,baseFieldDTO,createBaseFieldDTO } from "../abstract-field.vo" +import { jsonFieldConstraint,JsonFieldConstraint } from "./json-field-constraint.vo" import { JsonFieldValue } from "./json-field-value.vo" import { jsonFieldAggregate } from "./json-field.aggregate" import { @@ -14,7 +14,7 @@ import { type IJsonFieldCondition, type IJsonFieldConditionSchema, } from "./json-field.condition" -import { JsonContains, JsonEmpty, JsonEqual } from "./json-field.specification" +import { JsonContains,JsonEmpty,JsonEqual } from "./json-field.specification" export const JSON_TYPE = "json" as const @@ -50,7 +50,7 @@ export class JsonField extends AbstractField { } static create(dto: ICreateJsonFieldDTO) { - return new JsonField({ ...dto, id: FieldIdVo.create().value }) + return new JsonField({ ...dto, id: FieldIdVo.fromStringOrCreate(dto.id).value }) } override type = JSON_TYPE @@ -97,6 +97,6 @@ export class JsonField extends AbstractField { } override getMutationSpec(value: JsonFieldValue): Option { - return value.value ? Some(new JsonEqual(value.value, this.id)) : None + return value.value ? Some(new JsonEqual(value.value, this.id)) : Some(new JsonEqual(null, this.id)) } } diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index 4ee17105c..add2903ce 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -163,6 +163,7 @@ ] }, "Formula1": { + "fieldsOrder": ["Count1", "Count2", "Count3", "String1", "String2", "Json1"], "schema": { "Count1": { "id": "count1", @@ -184,6 +185,10 @@ "id": "string2", "type": "string" }, + "Json1": { + "id": "json1", + "type": "json" + }, "Sum": { "id": "sum", "type": "formula", @@ -421,6 +426,13 @@ "option": { "fn": "REPEAT({{string1}}, 3)" } + }, + "JsonExtractName": { + "id": "jsonExtractName", + "type": "formula", + "option": { + "fn": "JSON_EXTRACT({{json1}}, '$.name')" + } } }, "records": [ @@ -429,7 +441,8 @@ "Count2": 2, "Count3": 3, "String1": "Hello", - "String2": "World" + "String2": "World", + "Json1": "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}" }, { "Count1": 4, From 9bf84ef74355f2174e19e3a7df82d23af7e1a87c Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 17:41:16 +0800 Subject: [PATCH 21/31] test: add json extract formula parser --- .../__snapshots__/parse-formula.test.ts.snap | 20 +++++++++++++++++++ .../formula/src/tests/parse-formula.test.ts | 1 + 2 files changed, 21 insertions(+) diff --git a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap index 92297ee2c..fd8f68a69 100644 --- a/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap +++ b/packages/formula/src/tests/__snapshots__/parse-formula.test.ts.snap @@ -1370,3 +1370,23 @@ exports[`parse formula test NOT({{field1}}) 1`] = ` "value": "NOT({{field1}})", } `; + +exports[`parse formula test JSON_EXTRACT({{field1}}, '$.name') 1`] = ` +{ + "arguments": [ + { + "type": "variable", + "value": "{{field1}}", + "variable": "field1", + }, + { + "type": "string", + "value": "$.name", + }, + ], + "name": "JSON_EXTRACT", + "returnType": "any", + "type": "functionCall", + "value": "JSON_EXTRACT({{field1}},'$.name')", +} +`; diff --git a/packages/formula/src/tests/parse-formula.test.ts b/packages/formula/src/tests/parse-formula.test.ts index 7591a6715..2eadc9064 100644 --- a/packages/formula/src/tests/parse-formula.test.ts +++ b/packages/formula/src/tests/parse-formula.test.ts @@ -66,6 +66,7 @@ describe("parse formula", () => { "AND({{field1}}, {{field2}})", "OR({{field1}}, {{field2}})", "NOT({{field1}})", + "JSON_EXTRACT({{field1}}, '$.name')", ])("test %s", (input) => { const result = parseFormula(input) From b773718f8d76f44074c185cc9c79aca33b430e8c Mon Sep 17 00:00:00 2001 From: nichenqin Date: Tue, 29 Oct 2024 21:07:54 +0800 Subject: [PATCH 22/31] chore: add formula editor --- .../blocks/field-options/field-options.svelte | 2 + .../field-options/formula-field-option.svelte | 16 ++++ .../components/formula/formula-editor.svelte | 77 +++++++++++++------ apps/frontend/src/routes/formula/+page.svelte | 5 -- packages/formula/src/index.ts | 1 + .../src/modules/schema/fields/field.util.ts | 1 + .../formula-field/formula-field.visitor.ts | 38 +++++++++ 7 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte delete mode 100644 apps/frontend/src/routes/formula/+page.svelte create mode 100644 packages/table/src/modules/schema/fields/variants/formula-field/formula-field.visitor.ts diff --git a/apps/frontend/src/lib/components/blocks/field-options/field-options.svelte b/apps/frontend/src/lib/components/blocks/field-options/field-options.svelte index 252eb6323..c467e0493 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/field-options.svelte +++ b/apps/frontend/src/lib/components/blocks/field-options/field-options.svelte @@ -19,6 +19,7 @@ import ButtonFieldOption from "./button-field-option.svelte" import DurationFieldOption from "./duration-field-option.svelte" import PercentageFieldOption from "./percentage-field-option.svelte" + import FormulaFieldOption from "./formula-field-option.svelte" export let constraint: IFieldConstraint | undefined export let option: any | undefined @@ -47,6 +48,7 @@ date: DateFieldOption, duration: DurationFieldOption, percentage: PercentageFieldOption, + formula: FormulaFieldOption, } export let type: NoneSystemFieldType diff --git a/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte b/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte new file mode 100644 index 000000000..22555b704 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte @@ -0,0 +1,16 @@ + + +
+
+ +
+ +
diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 5d4a506fc..6c4f1c882 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -5,21 +5,23 @@ import { defaultKeymap } from "@codemirror/commands" import { syntaxHighlighting, HighlightStyle } from "@codemirror/language" import { tags } from "@lezer/highlight" - import { parseFormula } from "@undb/formula" + import { type FormulaFunction, parseFormula } from "@undb/formula" import { templateVariablePlugin } from "./plugins/varaible.plugin" import { cn } from "$lib/utils" import { createParser } from "@undb/formula/src/util" import { FormulaCursorVisitor } from "./formula-cursor.visitor" + import * as Table from "$lib/components/ui/table" + import { FORMULA_FUNCTIONS } from "@undb/formula" + import { SquareFunctionIcon, TriangleAlertIcon } from "lucide-svelte" + + const functions = FORMULA_FUNCTIONS + const fields = ["field1", "field2", "field3"] + export let value: string = "" let editor: EditorView - let suggestions: string[] = [] + let suggestions: string[] = [...functions, ...fields] let selectedSuggestion: string = "" - $: showSuggestions = suggestions.length > 0 - - const functions = ["ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "SUM", "CONCAT"] - const fields = ["field1", "field2", "field3"] - const highlightStyle = HighlightStyle.define([ { tag: tags.keyword, color: "#5c6bc0" }, { tag: tags.operator, color: "#f06292" }, @@ -35,7 +37,7 @@ { key: "ArrowUp", run: () => { - if (!showSuggestions || suggestions.length === 0) return false + if (suggestions.length === 0) return false const currentIndex = suggestions.findIndex((s) => s === selectedSuggestion) const nextIndex = currentIndex <= 0 ? suggestions.length - 1 : currentIndex - 1 selectedSuggestion = suggestions[nextIndex] @@ -45,7 +47,7 @@ { key: "ArrowDown", run: () => { - if (!showSuggestions || suggestions.length === 0) return false + if (suggestions.length === 0) return false const currentIndex = suggestions.findIndex((s) => s === selectedSuggestion) const nextIndex = currentIndex >= suggestions.length - 1 ? 0 : currentIndex + 1 selectedSuggestion = suggestions[nextIndex] @@ -147,7 +149,11 @@ ]), syntaxHighlighting(highlightStyle), templateVariablePlugin, + EditorView.lineWrapping, EditorView.updateListener.of((update) => { + const formula = editor.state.doc.toString() + value = formula + if (update.docChanged) { updateSuggestions() } else if (update.selectionSet) { @@ -218,7 +224,7 @@ insertStart = doc.length - lastWord.length } - if (functions.includes(suggestion)) { + if (functions.includes(suggestion as FormulaFunction)) { // 如果在函数名中间,只替换函数名部分 const visitor = new FormulaCursorVisitor(cursor) const parser = createParser(doc) @@ -287,22 +293,43 @@ } -
-
+
+
{#if errorMessage} -

{errorMessage}

+

+ + {errorMessage} +

{/if} - {#if showSuggestions} -
    - {#each suggestions as suggestion} - {@const isSelected = suggestion === selectedSuggestion} - - {/each} -
- {/if} +
    + {#each suggestions as suggestion} + {@const isSelected = suggestion === selectedSuggestion} + {@const isFunction = functions.includes(suggestion)} + + {/each} +
+ + diff --git a/apps/frontend/src/routes/formula/+page.svelte b/apps/frontend/src/routes/formula/+page.svelte deleted file mode 100644 index e60c483bd..000000000 --- a/apps/frontend/src/routes/formula/+page.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/packages/formula/src/index.ts b/packages/formula/src/index.ts index cfd6c0bd1..4befc2ca9 100644 --- a/packages/formula/src/index.ts +++ b/packages/formula/src/index.ts @@ -1,5 +1,6 @@ export { AbstractParseTreeVisitor } from "antlr4ts/tree/AbstractParseTreeVisitor" export { type ParseTree } from "antlr4ts/tree/ParseTree" +export * from "./formula.constants" export * from "./formula.visitor" export * from "./formula/formula.type" export * from "./grammar/FormulaLexer" diff --git a/packages/table/src/modules/schema/fields/field.util.ts b/packages/table/src/modules/schema/fields/field.util.ts index 7c234c463..4600238ba 100644 --- a/packages/table/src/modules/schema/fields/field.util.ts +++ b/packages/table/src/modules/schema/fields/field.util.ts @@ -139,6 +139,7 @@ export const fieldTypes: NoneSystemFieldType[] = [ "button", "duration", "percentage", + // "formula", ] as const export const systemFieldTypes: SystemFieldType[] = [ diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.visitor.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.visitor.ts new file mode 100644 index 000000000..8c2aa0dad --- /dev/null +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.visitor.ts @@ -0,0 +1,38 @@ +import { + AbstractParseTreeVisitor, + FormulaContext, + FunctionCallContext, + type FormulaFunction, + type FormulaParserVisitor, +} from "@undb/formula" +import { globalFunctionRegistry } from "@undb/formula/src/formula/registry" +import type { TableDo } from "../../../../../table.do" + +export class FormulaFieldVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { + constructor(private readonly table: TableDo) { + super() + } + protected defaultResult(): void { + return undefined + } + + visitFormula(ctx: FormulaContext): void { + this.visit(ctx.expression()) + } + + visitFunctionCall(ctx: FunctionCallContext): void { + const name = ctx.IDENTIFIER().text as FormulaFunction + // const fields = ctx + // .argumentList() + // ?.expression() + // .filter((exp) => exp instanceof VariableExprContext) + // .map((exp) => exp.variable().IDENTIFIER().text) + // .map((fieldId) => this.table.schema.getFieldByIdOrName(fieldId).into(null)) + // .filter((f) => !!f) + + const fn = globalFunctionRegistry.get(name) + if (!fn) { + throw new Error(`Function ${name} not found`) + } + } +} From cfadc303c5a2c96f305d0d24e8aaea5052ee8013 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 08:52:16 +0800 Subject: [PATCH 23/31] feat: insert formula variable --- .../components/formula/formula-editor.svelte | 72 +++++++++++++++---- .../formula/plugins/varaible.plugin.ts | 19 +---- .../src/lib/components/formula/style.ts | 5 ++ .../src/modules/schema/fields/field.util.ts | 2 +- 4 files changed, 66 insertions(+), 32 deletions(-) create mode 100644 apps/frontend/src/lib/components/formula/style.ts diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 6c4f1c882..0872c39d3 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -204,7 +204,7 @@ } function insertSuggestion(suggestion: string) { - const cursor = editor.state.selection.main.head + let cursor = editor.state.selection.main.head const doc = editor.state.doc.toString() const textBeforeCursor = doc.slice(0, cursor) @@ -263,20 +263,64 @@ editor.dispatch(transaction) } else { const fieldWithBrackets = `{{${suggestion}}}` - const transaction = editor.state.update({ - changes: { - from: insertStart, - to: cursor, - insert: fieldWithBrackets, - }, - selection: { - anchor: insertStart + fieldWithBrackets.length, - }, - }) - editor.dispatch(transaction) - } + const textBeforeCursor = editor.state.doc.sliceString(0, cursor) + const textAfterCursor = editor.state.doc.sliceString(cursor) + + // 使用正则表达式找到光标位置最近的完整变量 + const fullText = editor.state.doc.toString() + let start = cursor + let end = cursor + + // 向前搜索 {{ + for (let i = cursor; i >= 0; i--) { + if (fullText.slice(i, i + 2) === "{{") { + start = i + break + } + } + + // 向后搜索 }} + for (let i = cursor; i < fullText.length; i++) { + if (fullText.slice(i, i + 2) === "}}") { + end = i + 2 + break + } + } - editor.focus() + // 检查找到的范围是否是一个有效的变量(不超过最近的逗号) + const textBetween = fullText.slice(start, end) + const isValidVariable = textBetween.includes("{{") && textBetween.includes("}}") && !textBetween.includes(",") + + if (isValidVariable) { + // 替换找到的变量 + const transaction = editor.state.update({ + changes: { + from: start, + to: end, + insert: fieldWithBrackets, + }, + selection: { + anchor: start + fieldWithBrackets.length, + }, + }) + editor.dispatch(transaction) + } else { + // 不在变量内部或范围无效,直接在当前位置插入新变量 + const transaction = editor.state.update({ + changes: { + from: cursor, + to: cursor, + insert: fieldWithBrackets, + }, + selection: { + anchor: cursor + fieldWithBrackets.length, + }, + }) + editor.dispatch(transaction) + } + + editor.focus() + } } let errorMessage: string = "" diff --git a/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts index 91721e266..19cbebc88 100644 --- a/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts +++ b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts @@ -1,25 +1,10 @@ import { StateField } from "@codemirror/state" import { Decoration, DecorationSet, EditorView, WidgetType } from "@codemirror/view" +import { variable } from "../style" // 创建两个装饰器:一个用于整个变量区域,一个用于隐藏花括号 const variableMark = Decoration.mark({ - class: ` - bg-blue-50 - hover:bg-blue-100 - rounded - px-1 - border - border-blue-200 - mx-[1px] - transition-all - duration-200 - ease-in-out - hover:shadow-sm - hover:border-blue-300 - cursor-pointer - ` - .replace(/\s+/g, " ") - .trim(), + class: variable(), }) // 用于隐藏花括号的装饰器 diff --git a/apps/frontend/src/lib/components/formula/style.ts b/apps/frontend/src/lib/components/formula/style.ts new file mode 100644 index 000000000..d6df55c40 --- /dev/null +++ b/apps/frontend/src/lib/components/formula/style.ts @@ -0,0 +1,5 @@ +import { tv } from "tailwind-variants" + +export const variable = tv({ + base: "bg-blue-50 hover:bg-blue-100 rounded px-1 border border-blue-200 mx-[1px] transition-all duration-200 ease-in-out hover:shadow-sm hover:border-blue-300 cursor-pointer", +}) diff --git a/packages/table/src/modules/schema/fields/field.util.ts b/packages/table/src/modules/schema/fields/field.util.ts index 4600238ba..9975d9f3f 100644 --- a/packages/table/src/modules/schema/fields/field.util.ts +++ b/packages/table/src/modules/schema/fields/field.util.ts @@ -139,7 +139,7 @@ export const fieldTypes: NoneSystemFieldType[] = [ "button", "duration", "percentage", - // "formula", + "formula", ] as const export const systemFieldTypes: SystemFieldType[] = [ From 30f7e5a14d4b7276260aafcef2d65edb4c58f0a8 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 09:24:37 +0800 Subject: [PATCH 24/31] fix: fix generated formula --- .../src/lib/components/formula/formula-editor.svelte | 10 +++------- .../src/underlying/underlying-table-field.visitor.ts | 6 +++++- .../src/underlying/underlying-table-spec.visitor.ts | 2 +- .../src/underlying/underlying-table.service.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 0872c39d3..b7b9066e5 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -10,7 +10,6 @@ import { cn } from "$lib/utils" import { createParser } from "@undb/formula/src/util" import { FormulaCursorVisitor } from "./formula-cursor.visitor" - import * as Table from "$lib/components/ui/table" import { FORMULA_FUNCTIONS } from "@undb/formula" import { SquareFunctionIcon, TriangleAlertIcon } from "lucide-svelte" @@ -257,14 +256,12 @@ insert: suggestionWithParens, }, selection: { - anchor: insertStart + suggestionWithParens.length - 1, + anchor: insertStart + suggestion.length + 1, }, }) editor.dispatch(transaction) } else { const fieldWithBrackets = `{{${suggestion}}}` - const textBeforeCursor = editor.state.doc.sliceString(0, cursor) - const textAfterCursor = editor.state.doc.sliceString(cursor) // 使用正则表达式找到光标位置最近的完整变量 const fullText = editor.state.doc.toString() @@ -305,7 +302,7 @@ }) editor.dispatch(transaction) } else { - // 不在变量内部或范围无效,直接在当前位置插入新变量 + // 不在变量内部或范��无效,直接在当前位置插入新变量 const transaction = editor.state.update({ changes: { from: cursor, @@ -318,9 +315,8 @@ }) editor.dispatch(transaction) } - - editor.focus() } + editor.focus() } let errorMessage: string = "" diff --git a/packages/persistence/src/underlying/underlying-table-field.visitor.ts b/packages/persistence/src/underlying/underlying-table-field.visitor.ts index b2677dadd..d158a852e 100644 --- a/packages/persistence/src/underlying/underlying-table-field.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field.visitor.ts @@ -44,6 +44,7 @@ export class UnderlyingTableFieldVisitor private readonly qb: IQueryBuilder, private readonly t: UnderlyingTable, public tb: TB, + public readonly isNew: boolean = false, ) {} public atb: AlterTableColumnAlteringBuilder | CreateTableBuilder | null = null @@ -193,7 +194,10 @@ export class UnderlyingTableFieldVisitor this.logger.debug("parsed formula", { parsed }) - const c = this.tb.addColumn(field.id.value, "text", (b) => b.generatedAlwaysAs(sql.raw(parsed)).stored()) + const c = this.tb.addColumn(field.id.value, "text", (b) => { + const column = b.generatedAlwaysAs(sql.raw(parsed)) + return this.isNew ? column.stored() : column + }) this.addColumn(c) } } diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 6be665d54..18ebfaaaf 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -210,7 +210,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { withName(name: TableNameSpecification): void {} withSchema(schema: TableSchemaSpecification): void {} withNewField(schema: WithNewFieldSpecification): void { - const fieldVisitor = new UnderlyingTableFieldVisitor(this.qb, this.table, this.tb) + const fieldVisitor = new UnderlyingTableFieldVisitor(this.qb, this.table, this.tb, false) schema.field.accept(fieldVisitor) this.addSql(...fieldVisitor.sql) this.atb = fieldVisitor.atb diff --git a/packages/persistence/src/underlying/underlying-table.service.ts b/packages/persistence/src/underlying/underlying-table.service.ts index d50479b4c..6cc765cae 100644 --- a/packages/persistence/src/underlying/underlying-table.service.ts +++ b/packages/persistence/src/underlying/underlying-table.service.ts @@ -22,7 +22,7 @@ export class UnderlyingTableService { await trx.schema .createTable(t.name) .$call((tb) => { - const visitor = new UnderlyingTableFieldVisitor(trx, t, tb) + const visitor = new UnderlyingTableFieldVisitor(trx, t, tb, true) for (const field of table.schema) { field.accept(visitor) } From 7f2ef9e719663f5f15ea53e1e5c272e6986cf864 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 09:57:06 +0800 Subject: [PATCH 25/31] feat: formula field --- .../components/formula/formula-editor.svelte | 26 +++-- .../formula/plugins/varaible.plugin.ts | 94 +++++++++---------- .../template/src/templates/test.base.json | 7 ++ 3 files changed, 73 insertions(+), 54 deletions(-) diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index b7b9066e5..7d4ec7188 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -12,13 +12,20 @@ import { FormulaCursorVisitor } from "./formula-cursor.visitor" import { FORMULA_FUNCTIONS } from "@undb/formula" import { SquareFunctionIcon, TriangleAlertIcon } from "lucide-svelte" + import { getTable } from "$lib/store/table.store" + import { derived } from "svelte/store" + import FieldIcon from "../blocks/field-icon/field-icon.svelte" const functions = FORMULA_FUNCTIONS - const fields = ["field1", "field2", "field3"] + + const table = getTable() + let fields = derived(table, ($table) => + $table.schema.fields.filter((field) => !field.isSystem).map((field) => field.id.value), + ) export let value: string = "" let editor: EditorView - let suggestions: string[] = [...functions, ...fields] + let suggestions: string[] = [...functions, ...$fields] let selectedSuggestion: string = "" const highlightStyle = HighlightStyle.define([ @@ -147,7 +154,7 @@ ...defaultKeymap, ]), syntaxHighlighting(highlightStyle), - templateVariablePlugin, + templateVariablePlugin($table), EditorView.lineWrapping, EditorView.updateListener.of((update) => { const formula = editor.state.doc.toString() @@ -194,9 +201,9 @@ const isInsideParens = lastOpenParen > lastCloseParen if (content.trim() === "") { - suggestions = [...functions, ...fields] + suggestions = [...functions, ...$fields] } else if ((hasArgumentList || isInsideParens) && hasFunctionCall) { - suggestions = [...functions, ...fields] + suggestions = [...functions, ...$fields] } else if (hasFunctionCall) { suggestions = functions } @@ -346,6 +353,7 @@ {#each suggestions as suggestion} {@const isSelected = suggestion === selectedSuggestion} {@const isFunction = functions.includes(suggestion)} + {@const isField = !isFunction} diff --git a/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts index 19cbebc88..5d5e1d6b3 100644 --- a/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts +++ b/apps/frontend/src/lib/components/formula/plugins/varaible.plugin.ts @@ -1,52 +1,50 @@ import { StateField } from "@codemirror/state" import { Decoration, DecorationSet, EditorView, WidgetType } from "@codemirror/view" +import { TableDo } from "@undb/table" import { variable } from "../style" -// 创建两个装饰器:一个用于整个变量区域,一个用于隐藏花括号 -const variableMark = Decoration.mark({ - class: variable(), -}) - -// 用于隐藏花括号的装饰器 -const hideBrackets = Decoration.replace({ - widget: new (class extends WidgetType { - toDOM() { - return document.createElement("span") - } - })(), -}) - -const variableField = StateField.define({ - create() { - return Decoration.none - }, - update(decorations, tr) { - decorations = decorations.map(tr.changes) - - let matches = [] - let content = tr.state.doc.toString() - const regex = /\{\{([^}]+)\}\}/g - let match - - while ((match = regex.exec(content)) !== null) { - const start = match.index - const end = match.index + match[0].length - - // 按顺序添加装饰器: - // 1. 首先添加整体变量区域的装饰 - matches.push(variableMark.range(start, end)) - // 2. 然后添加开始花括号的隐藏装饰 - matches.push(hideBrackets.range(start, start + 2)) - // 3. 最后添加结束花括号的隐藏装饰 - matches.push(hideBrackets.range(end - 2, end)) - } - - // 确保装饰器按照 from 位置排序 - matches.sort((a, b) => a.from - b.from) - - return Decoration.set(matches, true) // 添加 true 参数表示已排序 - }, - provide: (f) => EditorView.decorations.from(f), -}) - -export const templateVariablePlugin = [variableField] +const variableField = (table: TableDo) => + StateField.define({ + create() { + return Decoration.none + }, + update(decorations, tr) { + decorations = decorations.map(tr.changes) + + let matches = [] + let content = tr.state.doc.toString() + const regex = /\{\{([^}]+)\}\}/g + let match + + while ((match = regex.exec(content)) !== null) { + const start = match.index + const end = match.index + match[0].length + const fieldId = match[1].trim() + const field = table.schema.getFieldByIdOrName(fieldId).into(null) + if (!field) continue + + // 创建替换文本装饰器 + const fieldName = field.name.value + matches.push( + Decoration.replace({ + widget: new (class extends WidgetType { + toDOM() { + const span = document.createElement("span") + span.textContent = fieldName + span.className = variable() + return span + } + })(), + }).range(start, end), + ) + } + + // 确保装饰器按照 from 位置排序 + matches.sort((a, b) => a.from - b.from) + + return Decoration.set(matches, true) + }, + provide: (f) => EditorView.decorations.from(f), + }) + +export const templateVariablePlugin = (table: TableDo) => [variableField(table)] diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json index add2903ce..ee31f8f78 100644 --- a/packages/template/src/templates/test.base.json +++ b/packages/template/src/templates/test.base.json @@ -433,6 +433,13 @@ "option": { "fn": "JSON_EXTRACT({{json1}}, '$.name')" } + }, + "AddAutoIncrement": { + "id": "addAutoIncrement", + "type": "formula", + "option": { + "fn": "{{count1}} + {{autoIncrement}}" + } } }, "records": [ From 3887227c78c648532d2cdeb959bc775c71bd6edd Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 10:19:48 +0800 Subject: [PATCH 26/31] feat: support auto increament --- .../blocks/field-options/formula-field-option.svelte | 6 ++++-- .../src/lib/components/formula/formula-editor.svelte | 12 ++++++++++-- packages/formula/src/formula.constants.ts | 4 +++- packages/formula/src/formula/formula.type.ts | 5 ++++- packages/formula/src/formula/registry.ts | 3 +++ .../src/underlying/underlying-formula.visitor.ts | 10 +++++++++- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte b/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte index 22555b704..da305616d 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte +++ b/apps/frontend/src/lib/components/blocks/field-options/formula-field-option.svelte @@ -1,16 +1,18 @@
- +
diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 7d4ec7188..0354e3d12 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -15,12 +15,18 @@ import { getTable } from "$lib/store/table.store" import { derived } from "svelte/store" import FieldIcon from "../blocks/field-icon/field-icon.svelte" + import { type Field } from "@undb/table" const functions = FORMULA_FUNCTIONS + export let field: Field | undefined = undefined + const table = getTable() let fields = derived(table, ($table) => - $table.schema.fields.filter((field) => !field.isSystem).map((field) => field.id.value), + $table.schema.fields + .filter((field) => !field.isSystem) + .filter((f) => f.id.value !== field?.id.value) + .map((field) => field.id.value), ) export let value: string = "" @@ -37,7 +43,7 @@ onMount(() => { const state = EditorState.create({ - doc: "", + doc: value, extensions: [ keymap.of([ { @@ -173,6 +179,8 @@ state, parent: document.getElementById("editor-container")!, }) + + editor.focus() }) function onSelectionChange() { diff --git a/packages/formula/src/formula.constants.ts b/packages/formula/src/formula.constants.ts index 0b2142c0e..8fb7a55a1 100644 --- a/packages/formula/src/formula.constants.ts +++ b/packages/formula/src/formula.constants.ts @@ -50,7 +50,9 @@ export const FORMULA_FUNCTIONS: FormulaFunction[] = [ // "COUNTIF", // "SUMIF", // "CORREL", - // "RECORD_ID", "JSON_EXTRACT", + + "RECORD_ID", + "AUTO_INCREMENT", ] as const diff --git a/packages/formula/src/formula/formula.type.ts b/packages/formula/src/formula/formula.type.ts index 7f04674af..2690f6b1e 100644 --- a/packages/formula/src/formula/formula.type.ts +++ b/packages/formula/src/formula/formula.type.ts @@ -61,5 +61,8 @@ export type FormulaFunction = | "COUNTIF" | "SUMIF" | "CORREL" - | "RECORD_ID" | "JSON_EXTRACT" + + // System Field + | "RECORD_ID" + | "AUTO_INCREMENT" diff --git a/packages/formula/src/formula/registry.ts b/packages/formula/src/formula/registry.ts index 0064676d0..f852969ff 100644 --- a/packages/formula/src/formula/registry.ts +++ b/packages/formula/src/formula/registry.ts @@ -137,3 +137,6 @@ globalFunctionRegistry.register("ISTEXT", [["any"]], "boolean") // globalFunctionRegistry.register("SUMIF", [["variadic"]], "number") globalFunctionRegistry.register("JSON_EXTRACT", [["string", "string"]], "any") + +globalFunctionRegistry.register("RECORD_ID", [], "string") +globalFunctionRegistry.register("AUTO_INCREMENT", [], "number") diff --git a/packages/persistence/src/underlying/underlying-formula.visitor.ts b/packages/persistence/src/underlying/underlying-formula.visitor.ts index 064c58058..e88837483 100644 --- a/packages/persistence/src/underlying/underlying-formula.visitor.ts +++ b/packages/persistence/src/underlying/underlying-formula.visitor.ts @@ -18,7 +18,7 @@ import { type FormulaFunction, type FormulaParserVisitor, } from "@undb/formula" -import { FieldIdVo, type TableDo } from "@undb/table" +import { AUTO_INCREMENT_TYPE, FieldIdVo, ID_TYPE, type TableDo } from "@undb/table" import { match } from "ts-pattern" export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor implements FormulaParserVisitor { @@ -69,6 +69,8 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i .expect(`variable ${fieldId} not found in table ${this.table.name.value}`) if (field.type === "currency") { return `(${fieldId}/100)` + } else if (field.type === "autoIncrement") { + return `[${fieldId}]` } return fieldId } @@ -168,6 +170,12 @@ export class UnderlyingFormulaVisitor extends AbstractParseTreeVisitor i // args[0] 是要重复的字符串,args[1] 是重复次数 return `SUBSTR(REPLACE(HEX(ZEROBLOB(${args[1]})), '00', ${args[0]}), 1, LENGTH(${args[0]}) * ${args[1]})` }) + .with("RECORD_ID", () => { + return ID_TYPE + }) + .with("AUTO_INCREMENT", () => { + return `[${AUTO_INCREMENT_TYPE}]` + }) .otherwise(() => { const args = ctx.argumentList() ? this.visit(ctx.argumentList()!) : "" return `${functionName}(${args})` From ce0301763c4a922a03b146b837992c04ee19fcf9 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 10:27:08 +0800 Subject: [PATCH 27/31] chore: formula editor key map --- .../components/formula/formula-editor.svelte | 66 ++++++++++++++----- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/apps/frontend/src/lib/components/formula/formula-editor.svelte b/apps/frontend/src/lib/components/formula/formula-editor.svelte index 0354e3d12..bd064b2f5 100644 --- a/apps/frontend/src/lib/components/formula/formula-editor.svelte +++ b/apps/frontend/src/lib/components/formula/formula-editor.svelte @@ -68,39 +68,46 @@ }, { key: "ArrowLeft", - run: () => { + run: (editor) => { const cursor = editor.state.selection.main.head const content = editor.state.doc.toString() const textBeforeCursor = content.slice(0, cursor) // 如果光标在 }} 前面,移动到变量名前面 if (textBeforeCursor.endsWith("}}")) { - let i = textBeforeCursor.length - 2 - // 往前找到变量名开始位置 - while (i >= 0 && /[a-zA-Z0-9_]/.test(textBeforeCursor[i])) { - i-- - } + let i = 2 // 跳过空格 - while (i >= 0 && textBeforeCursor[i] === " ") { - i-- + while (i < textBeforeCursor.length && textBeforeCursor[textBeforeCursor.length - i - 1] === " ") { + i++ } - if (i >= 0 && textBeforeCursor[i] === "{" && i > 0 && textBeforeCursor[i - 1] === "{") { + // 找到变量名开始位置 + while ( + i < textBeforeCursor.length && + /[a-zA-Z0-9_]/.test(textBeforeCursor[textBeforeCursor.length - i - 1]) + ) { + i++ + } + if (i > 2) { editor.dispatch({ - selection: { anchor: i - 1 }, + selection: { anchor: cursor - i }, }) return true } } // 找到连续的 } 或 { 的第一个位置 - let i = cursor - 1 - while (i >= 0 && (textBeforeCursor[i] === "}" || textBeforeCursor[i] === "{")) { - i-- + let i = 0 + while ( + i < textBeforeCursor.length && + (textBeforeCursor[textBeforeCursor.length - i - 1] === "}" || + textBeforeCursor[textBeforeCursor.length - i - 1] === "{") + ) { + i++ } - if (i < cursor - 1) { + if (i > 0) { editor.dispatch({ - selection: { anchor: i }, + selection: { anchor: cursor - i }, }) return true } @@ -109,7 +116,7 @@ }, { key: "ArrowRight", - run: () => { + run: (editor) => { const cursor = editor.state.selection.main.head const content = editor.state.doc.toString() const textAfterCursor = content.slice(cursor) @@ -157,6 +164,33 @@ return true }, }, + { + key: "Backspace", + run: (editor) => { + const cursor = editor.state.selection.main.head + const content = editor.state.doc.toString() + const textBeforeCursor = content.slice(0, cursor) + const textAfterCursor = content.slice(cursor) + + // 检查是否在变量内部或者最后一个 } 后面 + const lastOpenBrace = textBeforeCursor.lastIndexOf("{{") + if (lastOpenBrace !== -1) { + const nextCloseBrace = content.indexOf("}}", lastOpenBrace) + if (nextCloseBrace !== -1 && cursor <= nextCloseBrace + 2 && cursor > lastOpenBrace) { + // 删除整个变量 + editor.dispatch({ + changes: { + from: lastOpenBrace, + to: nextCloseBrace + 2, + insert: "", + }, + }) + return true + } + } + return false + }, + }, ...defaultKeymap, ]), syntaxHighlighting(highlightStyle), From df3c10f6b286de1b24a75f9a0d76bf9a1165ebd4 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 10:49:57 +0800 Subject: [PATCH 28/31] feat: update formula --- .../underlying-table-field-updated.visitor.ts | 16 ++++++++++++++-- .../underlying/underlying-table-spec.visitor.ts | 2 +- .../schema/fields/variants/abstract-field.vo.ts | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts index 6e0df2ef7..e8451edd4 100644 --- a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts @@ -1,3 +1,4 @@ +import { createParser } from "@undb/formula" import { Options, type AttachmentField, @@ -28,9 +29,10 @@ import { type UserField, } from "@undb/table" import type { FormulaField } from "@undb/table/src/modules/schema/fields/variants/formula-field" -import { sql } from "kysely" +import { AlterTableBuilder, sql } from "kysely" import { AbstractQBMutationVisitor } from "../abstract-qb.visitor" import type { IRecordQueryBuilder } from "../qb" +import { UnderlyingFormulaVisitor } from "./underlying-formula.visitor" import type { UnderlyingTable } from "./underlying-table" export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisitor implements IFieldVisitor { @@ -38,6 +40,7 @@ export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisito private readonly qb: IRecordQueryBuilder, private readonly table: UnderlyingTable, private readonly prev: Field, + private readonly tb: AlterTableBuilder, ) { super() } @@ -51,7 +54,16 @@ export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisito string(field: StringField): void {} number(field: NumberField): void {} rating(field: RatingField): void {} - formula(field: FormulaField): void {} + formula(field: FormulaField): void { + const visitor = new UnderlyingFormulaVisitor(this.table.table) + const parser = createParser(field.fn) + const parsed = visitor.visit(parser.formula()) + + const drop = this.tb.dropColumn(field.id.value).compile() + this.addSql(drop) + const add = this.tb.addColumn(field.id.value, "text", (b) => b.generatedAlwaysAs(sql.raw(parsed))).compile() + this.addSql(add) + } select(field: SelectField): void { const prev = this.prev as SelectField const deletedOptions = Options.getDeletedOptions(prev.options, field.options) diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 18ebfaaaf..db8a51942 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -131,7 +131,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { } } - const fieldVisitor = new UnderlyingTableFieldUpdatedVisitor(this.qb, this.table, spec.previous) + const fieldVisitor = new UnderlyingTableFieldUpdatedVisitor(this.qb, this.table, spec.previous, this.tb) spec.field.accept(fieldVisitor) this.addSql(...fieldVisitor.sql) } diff --git a/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts b/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts index f56c27758..a3ab4bf72 100644 --- a/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts @@ -202,7 +202,7 @@ export abstract class AbstractField< } } - clone() { + clone(): this { return new (Object.getPrototypeOf(this) as any).constructor(this.toJSON()) } } From 6688155b7b370a6089b0608236fe7248ba9f13f5 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 13:24:29 +0800 Subject: [PATCH 29/31] feat: support to filter formula --- apps/frontend/schema.graphql | 1 + .../dashboard-widget-share-table.gql | 1 + .../dashboard/dashboard-widget-table.gql | 1 + .../blocks/field-options/foreign-table.gql | 1 + .../field/get-rollup-foreign-tables.gql | 1 + .../blocks/filters-editor/filter-input.svelte | 34 ++++++ .../blocks/reference/foreign-table.gql | 1 + .../lib/graphql/get-table-foreign-tables.gql | 1 + .../(authed)/(space)/t/[tableId]/+layout.gql | 1 + .../s/b/[shareId]/t/[tableId]/+layout.gql | 1 + .../routes/(share)/s/f/[shareId]/+layout.gql | 1 + .../routes/(share)/s/v/[shareId]/+layout.gql | 1 + packages/formula/src/formula.visitor.ts | 5 +- packages/graphql/src/index.ts | 1 + .../record-query-spec-creator-visitor.ts | 11 ++ .../record/record-spec-reference-visitor.ts | 10 ++ .../src/record/record.filter-visitor.ts | 25 +++++ .../src/underlying/underlying-formula.util.ts | 12 ++ .../underlying-table-field-updated.visitor.ts | 4 +- .../underlying-table-field.visitor.ts | 4 +- .../table/src/methods/create-field.method.ts | 2 +- .../table/src/methods/update-field.method.ts | 2 +- .../modules/schema/fields/field.factory.ts | 5 +- .../fields/variants/abstract-field.vo.ts | 3 +- .../formula-field/formula-field.condition.ts | 43 ++++--- .../formula-field/formula-field.vo.ts | 70 ++++++++---- .../formula-return-type.visitor.ts | 106 ++++++++++++++++++ .../reference-field/reference-field.vo.ts | 2 +- .../variants/select-field/select-field.vo.ts | 5 +- .../table/src/modules/schema/schema.vo.ts | 30 +++-- packages/table/src/table.builder.ts | 2 +- 31 files changed, 331 insertions(+), 56 deletions(-) create mode 100644 packages/persistence/src/underlying/underlying-formula.util.ts create mode 100644 packages/table/src/modules/schema/fields/variants/formula-field/formula-return-type.visitor.ts diff --git a/apps/frontend/schema.graphql b/apps/frontend/schema.graphql index aaadf383c..da9321342 100644 --- a/apps/frontend/schema.graphql +++ b/apps/frontend/schema.graphql @@ -44,6 +44,7 @@ type Field { defaultValue: JSON display: Boolean id: ID! + metadata: JSON name: String! option: JSON type: FieldType! diff --git a/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-share-table.gql b/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-share-table.gql index ea6ec5d33..348786e7c 100644 --- a/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-share-table.gql +++ b/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-share-table.gql @@ -16,6 +16,7 @@ query GetDashboardWidgetShareTable($shareId: ID!, $tableId: ID!) { display constraint option + metadata } views { diff --git a/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-table.gql b/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-table.gql index f702d72e9..6e264a57b 100644 --- a/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-table.gql +++ b/apps/frontend/src/lib/components/blocks/dashboard/dashboard-widget-table.gql @@ -16,6 +16,7 @@ query GetDashboardWidgetTable($tableId: ID!) { display constraint option + metadata } views { diff --git a/apps/frontend/src/lib/components/blocks/field-options/foreign-table.gql b/apps/frontend/src/lib/components/blocks/field-options/foreign-table.gql index b2f360ec1..8059dae56 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/foreign-table.gql +++ b/apps/frontend/src/lib/components/blocks/field-options/foreign-table.gql @@ -10,6 +10,7 @@ query GetForeignTableQuery($tableId: ID!) { display constraint option + metadata } views { id diff --git a/apps/frontend/src/lib/components/blocks/field/get-rollup-foreign-tables.gql b/apps/frontend/src/lib/components/blocks/field/get-rollup-foreign-tables.gql index 1c8b5b17c..4ab0f3719 100644 --- a/apps/frontend/src/lib/components/blocks/field/get-rollup-foreign-tables.gql +++ b/apps/frontend/src/lib/components/blocks/field/get-rollup-foreign-tables.gql @@ -9,6 +9,7 @@ query GetRollupForeignTables($tableId: ID!, $fieldId: ID!) { constraint option display + metadata } views { id diff --git a/apps/frontend/src/lib/components/blocks/filters-editor/filter-input.svelte b/apps/frontend/src/lib/components/blocks/filters-editor/filter-input.svelte index 4b829bbe6..e876a0822 100644 --- a/apps/frontend/src/lib/components/blocks/filters-editor/filter-input.svelte +++ b/apps/frontend/src/lib/components/blocks/filters-editor/filter-input.svelte @@ -269,6 +269,39 @@ is_not_empty: null, } + $: formula = {} + + $: if (field?.type === "formula") { + if (field.returnType === "number") { + formula = { + eq: NumberInput, + neq: NumberInput, + gt: NumberInput, + gte: NumberInput, + lt: NumberInput, + lte: NumberInput, + is_empty: null, + is_not_empty: null, + } + } else if (field.returnType === "boolean") { + formula = { + is_true: null, + is_false: null, + } + } else if (field.returnType === "string") { + formula = { + eq: Input, + neq: Input, + contains: Input, + does_not_contain: Input, + starts_with: Input, + ends_with: Input, + is_empty: null, + is_not_empty: null, + } + } + } + $: filterFieldInput = { string, number, @@ -289,6 +322,7 @@ url, longText, duration, + formula, percentage, } diff --git a/apps/frontend/src/lib/components/blocks/reference/foreign-table.gql b/apps/frontend/src/lib/components/blocks/reference/foreign-table.gql index 09ff709b2..7983411ea 100644 --- a/apps/frontend/src/lib/components/blocks/reference/foreign-table.gql +++ b/apps/frontend/src/lib/components/blocks/reference/foreign-table.gql @@ -16,6 +16,7 @@ query GetForeignTable($tableId: ID!) { display constraint option + metadata } views { diff --git a/apps/frontend/src/lib/graphql/get-table-foreign-tables.gql b/apps/frontend/src/lib/graphql/get-table-foreign-tables.gql index 4bf44c974..9eeaf435d 100644 --- a/apps/frontend/src/lib/graphql/get-table-foreign-tables.gql +++ b/apps/frontend/src/lib/graphql/get-table-foreign-tables.gql @@ -14,6 +14,7 @@ query GetTableForeignTables($tableId: ID!) { display constraint option + metadata } views { id diff --git a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql index 7ce7cc4dc..3c945d40a 100644 --- a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql +++ b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql @@ -11,6 +11,7 @@ query GetTableQuery($tableId: ID!, $viewId: ID) { display constraint option + metadata } views { diff --git a/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.gql b/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.gql index 4f820326f..a76f7e04c 100644 --- a/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.gql +++ b/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.gql @@ -51,6 +51,7 @@ query GetBaseTableShareData($shareId: ID!, $tableId: ID!) { name option type + metadata } } } diff --git a/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.gql b/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.gql index c7ada0d91..58fc0c454 100644 --- a/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.gql +++ b/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.gql @@ -44,6 +44,7 @@ query GetFormShareData($shareId: ID!) { name option type + metadata } } } diff --git a/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.gql b/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.gql index 228fd4474..f9689aea4 100644 --- a/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.gql +++ b/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.gql @@ -65,6 +65,7 @@ query GetViewShareData($shareId: ID!) { name option type + metadata } } } diff --git a/packages/formula/src/formula.visitor.ts b/packages/formula/src/formula.visitor.ts index 6b7b9de63..eeb513571 100644 --- a/packages/formula/src/formula.visitor.ts +++ b/packages/formula/src/formula.visitor.ts @@ -20,6 +20,7 @@ import { } from "./grammar/FormulaParser" import type { FormulaParserVisitor } from "./grammar/FormulaParserVisitor" import { + ArgumentListResult, ReturnType, type ExpressionResult, type FunctionExpressionResult, @@ -194,14 +195,14 @@ export class FormulaVisitor } } - visitArgumentList(ctx: ArgumentListContext): ExpressionResult { + visitArgumentList(ctx: ArgumentListContext): ArgumentListResult { const args = ctx.expression().map((expr) => this.visit(expr)) return { type: "argumentList", arguments: args, } } - visitVariable(ctx: VariableContext): ExpressionResult { + visitVariable(ctx: VariableContext): VariableResult { const variableName = ctx.IDENTIFIER().text const raw = ctx.text this.variables.add(variableName) diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index a9d4c739d..477eba4b0 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -150,6 +150,7 @@ export class Graphql { display: Boolean constraint: JSON option: JSON + metadata: JSON } enum ViewType { diff --git a/packages/persistence/src/record/record-query-spec-creator-visitor.ts b/packages/persistence/src/record/record-query-spec-creator-visitor.ts index 40014f7b1..5e56f1923 100644 --- a/packages/persistence/src/record/record-query-spec-creator-visitor.ts +++ b/packages/persistence/src/record/record-query-spec-creator-visitor.ts @@ -5,6 +5,11 @@ import { CurrencyLTE, DateIsEmpty, DurationEqual, + FormulaEqual, + FormulaGT, + FormulaGTE, + FormulaLT, + FormulaLTE, ID_TYPE, JsonContains, LongTextEqual, @@ -128,6 +133,12 @@ export class RecordQuerySpecCreatorVisitor implements IRecordVisitor { jsonEmpty(spec: JsonEmpty): void {} checkboxEqual(spec: CheckboxEqual): void {} + formulaEqual(spec: FormulaEqual): void {} + formulaGT(spec: FormulaGT): void {} + formulaGTE(spec: FormulaGTE): void {} + formulaLT(spec: FormulaLT): void {} + formulaLTE(spec: FormulaLTE): void {} + and(left: RecordComositeSpecification, right: RecordComositeSpecification): this { const lv = this.clone() left.accept(lv) diff --git a/packages/persistence/src/record/record-spec-reference-visitor.ts b/packages/persistence/src/record/record-spec-reference-visitor.ts index de232bb3c..fae543793 100644 --- a/packages/persistence/src/record/record-spec-reference-visitor.ts +++ b/packages/persistence/src/record/record-spec-reference-visitor.ts @@ -16,6 +16,11 @@ import { DateIsToday, DateIsTomorrow, DurationEqual, + FormulaEqual, + FormulaGT, + FormulaGTE, + FormulaLT, + FormulaLTE, ID_TYPE, IdEqual, IdIn, @@ -113,6 +118,11 @@ export class RecordSpecReferenceVisitor implements IRecordVisitor { jsonContains(spec: JsonContains): void {} jsonEmpty(spec: JsonEmpty): void {} checkboxEqual(spec: CheckboxEqual): void {} + formulaEqual(spec: FormulaEqual): void {} + formulaGT(spec: FormulaGT): void {} + formulaGTE(spec: FormulaGTE): void {} + formulaLT(spec: FormulaLT): void {} + formulaLTE(spec: FormulaLTE): void {} and(left: ISpecification, right: ISpecification): this { left.accept(this) right.accept(this) diff --git a/packages/persistence/src/record/record.filter-visitor.ts b/packages/persistence/src/record/record.filter-visitor.ts index 76fcc618a..d0d561328 100644 --- a/packages/persistence/src/record/record.filter-visitor.ts +++ b/packages/persistence/src/record/record.filter-visitor.ts @@ -6,6 +6,11 @@ import { CurrencyLT, CurrencyLTE, DurationEqual, + FormulaEqual, + FormulaGT, + FormulaGTE, + FormulaLT, + FormulaLTE, PercentageEqual, SelectField, isUserFieldMacro, @@ -328,6 +333,26 @@ export class RecordFilterVisitor extends AbstractQBVisitor implements const cond = this.eb.eb(this.getFieldId(s), "=", s.value) this.addCond(cond) } + formulaEqual(spec: FormulaEqual): void { + const cond = this.eb.eb(this.getFieldId(spec), "=", spec.value) + this.addCond(cond) + } + formulaGT(spec: FormulaGT): void { + const cond = this.eb.eb(this.getFieldId(spec), ">", spec.value) + this.addCond(cond) + } + formulaGTE(spec: FormulaGTE): void { + const cond = this.eb.eb(this.getFieldId(spec), ">=", spec.value) + this.addCond(cond) + } + formulaLT(spec: FormulaLT): void { + const cond = this.eb.eb(this.getFieldId(spec), "<", spec.value) + this.addCond(cond) + } + formulaLTE(spec: FormulaLTE): void { + const cond = this.eb.eb(this.getFieldId(spec), "<=", spec.value) + this.addCond(cond) + } clone(): this { return new RecordFilterVisitor(this.eb, this.table, this.context) as this } diff --git a/packages/persistence/src/underlying/underlying-formula.util.ts b/packages/persistence/src/underlying/underlying-formula.util.ts new file mode 100644 index 000000000..ec87fe8bb --- /dev/null +++ b/packages/persistence/src/underlying/underlying-formula.util.ts @@ -0,0 +1,12 @@ +import type { ReturnType } from "@undb/formula" +import type { ColumnDataType } from "kysely" +import { match } from "ts-pattern" + +export const getUnderlyingFormulaType = (returnType: ReturnType): ColumnDataType => { + return match(returnType) + .returnType() + .with("number", () => "real") + .with("boolean", () => "integer") + .with("date", () => "timestamp") + .otherwise(() => "text") +} diff --git a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts index e8451edd4..4c879b423 100644 --- a/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field-updated.visitor.ts @@ -32,6 +32,7 @@ import type { FormulaField } from "@undb/table/src/modules/schema/fields/variant import { AlterTableBuilder, sql } from "kysely" import { AbstractQBMutationVisitor } from "../abstract-qb.visitor" import type { IRecordQueryBuilder } from "../qb" +import { getUnderlyingFormulaType } from "./underlying-formula.util" import { UnderlyingFormulaVisitor } from "./underlying-formula.visitor" import type { UnderlyingTable } from "./underlying-table" @@ -61,7 +62,8 @@ export class UnderlyingTableFieldUpdatedVisitor extends AbstractQBMutationVisito const drop = this.tb.dropColumn(field.id.value).compile() this.addSql(drop) - const add = this.tb.addColumn(field.id.value, "text", (b) => b.generatedAlwaysAs(sql.raw(parsed))).compile() + const type = getUnderlyingFormulaType(field.returnType) + const add = this.tb.addColumn(field.id.value, type, (b) => b.generatedAlwaysAs(sql.raw(parsed))).compile() this.addSql(add) } select(field: SelectField): void { diff --git a/packages/persistence/src/underlying/underlying-table-field.visitor.ts b/packages/persistence/src/underlying/underlying-table-field.visitor.ts index d158a852e..585a3c6d4 100644 --- a/packages/persistence/src/underlying/underlying-table-field.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field.visitor.ts @@ -34,6 +34,7 @@ import { AlterTableBuilder, AlterTableColumnAlteringBuilder, CompiledQuery, Crea import type { IQueryBuilder } from "../qb" import { users } from "../tables" import { JoinTable } from "./reference/join-table" +import { getUnderlyingFormulaType } from "./underlying-formula.util" import { UnderlyingFormulaVisitor } from "./underlying-formula.visitor" import type { UnderlyingTable } from "./underlying-table" @@ -194,7 +195,8 @@ export class UnderlyingTableFieldVisitor this.logger.debug("parsed formula", { parsed }) - const c = this.tb.addColumn(field.id.value, "text", (b) => { + const type = getUnderlyingFormulaType(field.returnType) + const c = this.tb.addColumn(field.id.value, type, (b) => { const column = b.generatedAlwaysAs(sql.raw(parsed)) return this.isNew ? column.stored() : column }) diff --git a/packages/table/src/methods/create-field.method.ts b/packages/table/src/methods/create-field.method.ts index 72397d6fa..7803f2f26 100644 --- a/packages/table/src/methods/create-field.method.ts +++ b/packages/table/src/methods/create-field.method.ts @@ -36,7 +36,7 @@ export function $createFieldSpec(this: TableDo, field: Field): Option] { - const field = FieldFactory.create(dto) + const field = FieldFactory.create(this, dto) return [field, this.$createFieldSpec(field)] } diff --git a/packages/table/src/methods/update-field.method.ts b/packages/table/src/methods/update-field.method.ts index a9d73ecc9..c4457ed57 100644 --- a/packages/table/src/methods/update-field.method.ts +++ b/packages/table/src/methods/update-field.method.ts @@ -7,7 +7,7 @@ import type { TableComositeSpecification } from "../specifications" import type { TableDo } from "../table.do" export function updateFieldMethod(this: TableDo, dto: IUpdateFieldDTO): Option { - const spec = this.schema.$updateField(dto) + const spec = this.schema.$updateField(this, dto) // TODO: update form spec.mutate(this) diff --git a/packages/table/src/modules/schema/fields/field.factory.ts b/packages/table/src/modules/schema/fields/field.factory.ts index c807b70d6..dbb246976 100644 --- a/packages/table/src/modules/schema/fields/field.factory.ts +++ b/packages/table/src/modules/schema/fields/field.factory.ts @@ -1,5 +1,6 @@ import { match } from "ts-pattern" import { UrlField } from "." +import type { TableDo } from "../../../table.do" import type { ICreateFieldDTO } from "./dto/create-field.dto" import type { IFieldDTO } from "./dto/field.dto" import type { Field } from "./field.type" @@ -59,7 +60,7 @@ export class FieldFactory { .exhaustive() } - static create(dto: ICreateFieldDTO): Field { + static create(table: TableDo, dto: ICreateFieldDTO): Field { return match(dto) .with({ type: "string" }, (dto) => StringField.create(dto)) .with({ type: "number" }, (dto) => NumberField.create(dto)) @@ -79,7 +80,7 @@ export class FieldFactory { .with({ type: "button" }, (dto) => ButtonField.create(dto)) .with({ type: "duration" }, (dto) => DurationField.create(dto)) .with({ type: "percentage" }, (dto) => PercentageField.create(dto)) - .with({ type: "formula" }, (dto) => FormulaField.create(dto)) + .with({ type: "formula" }, (dto) => FormulaField.create(table, dto)) .otherwise(() => { throw new Error("Field type creation not supported") }) diff --git a/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts b/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts index a3ab4bf72..4070ce403 100644 --- a/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/abstract-field.vo.ts @@ -1,6 +1,7 @@ import { None, Option, Some } from "@undb/domain" import { ZodEnum, ZodUndefined, z, type ZodSchema } from "@undb/zod" import type { TableComositeSpecification } from "../../../../specifications/table.composite-specification" +import type { TableDo } from "../../../../table.do" import type { FormFieldVO } from "../../../forms/form/form-field.vo" import type { INotRecordComositeSpecification, @@ -173,7 +174,7 @@ export abstract class AbstractField< return this.validate(this.defaultValue.unwrap()).success } - update(dto: IUpdateFieldDTO): Field { + update(table: TableDo, dto: IUpdateFieldDTO): Field { const json = { ...this.toJSON(), ...dto, type: this.type, id: this.id.value } const updated = new (Object.getPrototypeOf(this) as any).constructor(json) diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.condition.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.condition.ts index 144dda24f..b2fdf6e31 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.condition.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.condition.ts @@ -1,21 +1,38 @@ +import type { ReturnType as FormulaReturnType } from "@undb/formula" import { z } from "@undb/zod" import { createBaseConditionSchema } from "../../condition/base.condition" -export function createFormulaFieldCondition(itemType: ItemType) { - const base = createBaseConditionSchema(itemType) - return z.union([ - z.object({ op: z.literal("eq"), value: z.number() }).merge(base), - z.object({ op: z.literal("neq"), value: z.number() }).merge(base), - z.object({ op: z.literal("gt"), value: z.number() }).merge(base), - z.object({ op: z.literal("gte"), value: z.number() }).merge(base), - z.object({ op: z.literal("lt"), value: z.number() }).merge(base), - z.object({ op: z.literal("lte"), value: z.number() }).merge(base), - z.object({ op: z.literal("is_empty"), value: z.undefined() }).merge(base), - z.object({ op: z.literal("is_not_empty"), value: z.undefined() }).merge(base), - ]) +export function createFormulaFieldCondition(returnType: FormulaReturnType) { + return function (itemType: ItemType) { + const base = createBaseConditionSchema(itemType) + if (returnType === "number") { + return z.union([ + z.object({ op: z.literal("eq"), value: z.number() }).merge(base), + z.object({ op: z.literal("neq"), value: z.number() }).merge(base), + z.object({ op: z.literal("gt"), value: z.number() }).merge(base), + z.object({ op: z.literal("gte"), value: z.number() }).merge(base), + z.object({ op: z.literal("lt"), value: z.number() }).merge(base), + z.object({ op: z.literal("lte"), value: z.number() }).merge(base), + z.object({ op: z.literal("is_empty"), value: z.undefined() }).merge(base), + z.object({ op: z.literal("is_not_empty"), value: z.undefined() }).merge(base), + ]) + } + else if (returnType === "boolean") { + return z.union([ + z.object({ op: z.literal("is_true"), value: z.undefined() }).merge(base), + z.object({ op: z.literal("is_false"), value: z.undefined() }).merge(base), + ]) + } + return z.union([ + z.object({ op: z.literal("eq"), value: z.boolean() }).merge(base), + z.object({ op: z.literal("neq"), value: z.number() }).merge(base), + z.object({ op: z.literal("is_empty"), value: z.undefined() }).merge(base), + z.object({ op: z.literal("is_not_empty"), value: z.undefined() }).merge(base), + ]) + } } -export type IFormulaFieldConditionSchema = ReturnType +export type IFormulaFieldConditionSchema = ReturnType> export type IFormulaFieldCondition = z.infer export type IFormulaFieldConditionOp = IFormulaFieldCondition["op"] diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts index b93e92d5a..e6eaecadc 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts @@ -2,6 +2,7 @@ import { None, Option, Some } from "@undb/domain" import { createParser, FormulaVisitor, returnType } from "@undb/formula" import { z } from "@undb/zod" import { match } from "ts-pattern" +import type { TableDo } from "../../../../../table.do" import type { RecordComositeSpecification } from "../../../../records/record/record.composite-specification" import { fieldId, FieldIdVo } from "../../field-id.vo" import type { IFieldVisitor } from "../../field.visitor" @@ -15,6 +16,7 @@ import { type IFormulaFieldConditionSchema, } from "./formula-field.condition" import { FormulaEqual, FormulaGT, FormulaGTE, FormulaLT, FormulaLTE } from "./formula-field.specification" +import { FormulaReturnTypeVisitor } from "./formula-return-type.visitor" export const FORMULA_TYPE = "formula" as const @@ -48,6 +50,7 @@ export type IUpdateFormulaFieldDTO = z.infer export const formulaFieldDTO = baseFieldDTO.extend({ type: z.literal(FORMULA_TYPE), option: formulaFieldOption, + metadata: formulaMetadata.optional().nullable(), }) export type IFormulaFieldDTO = z.infer @@ -59,36 +62,57 @@ export class FormulaField extends AbstractField { @@ -139,10 +163,16 @@ export class FormulaField extends AbstractField m.returnType) } + override update(table: TableDo, dto: IUpdateFormulaFieldDTO): FormulaField { + const field = super.update(table, dto) as FormulaField + field.setMetadata(table) + return field + } + override toJSON() { return { ...super.toJSON(), - metadata: this.metadata.into(null), + metadata: this.metadata.into(undefined), } } } diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-return-type.visitor.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-return-type.visitor.ts new file mode 100644 index 000000000..5cf793c31 --- /dev/null +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-return-type.visitor.ts @@ -0,0 +1,106 @@ +import type { ReturnType } from "@undb/formula" +import type { AttachmentField, CreatedAtField } from "../.." +import type { IFieldVisitor } from "../../field.visitor" +import type { AutoIncrementField } from "../autoincrement-field" +import type { ButtonField } from "../button-field" +import type { CheckboxField } from "../checkbox-field" +import type { CreatedByField } from "../created-by-field" +import type { CurrencyField } from "../currency-field" +import type { DateField } from "../date-field" +import type { DurationField } from "../duration-field" +import type { EmailField } from "../email-field" +import type { IdField } from "../id-field" +import type { JsonField } from "../json-field" +import type { LongTextField } from "../long-text-field" +import type { NumberField } from "../number-field" +import type { PercentageField } from "../percentage-field" +import type { RatingField } from "../rating-field" +import type { ReferenceField } from "../reference-field" +import type { RollupField } from "../rollup-field" +import type { SelectField } from "../select-field" +import type { StringField } from "../string-field" +import type { UpdatedAtField } from "../updated-at-field" +import type { UpdatedByField } from "../updated-by-field" +import type { UrlField } from "../url-field" +import type { UserField } from "../user-field" +import type { FormulaField } from "./formula-field.vo" + +export class FormulaReturnTypeVisitor implements IFieldVisitor { + #reaturnType: ReturnType = "any" + + get returnType() { + return this.#reaturnType + } + + id(field: IdField): void { + this.#reaturnType = "string" + } + autoIncrement(field: AutoIncrementField): void { + this.#reaturnType = "number" + } + longText(field: LongTextField): void { + this.#reaturnType = "string" + } + createdAt(field: CreatedAtField): void { + this.#reaturnType = "date" + } + createdBy(field: CreatedByField): void { + this.#reaturnType = "string" + } + updatedAt(field: UpdatedAtField): void { + this.#reaturnType = "date" + } + updatedBy(field: UpdatedByField): void { + this.#reaturnType = "string" + } + string(field: StringField): void { + this.#reaturnType = "string" + } + number(field: NumberField): void { + this.#reaturnType = "number" + } + rating(field: RatingField): void { + this.#reaturnType = "number" + } + select(field: SelectField): void { + this.#reaturnType = "string" + } + email(field: EmailField): void { + this.#reaturnType = "string" + } + attachment(field: AttachmentField): void { + this.#reaturnType = "string" + } + date(field: DateField): void { + this.#reaturnType = "date" + } + json(field: JsonField): void { + this.#reaturnType = "any" + } + checkbox(field: CheckboxField): void { + this.#reaturnType = "boolean" + } + user(field: UserField): void { + this.#reaturnType = "string" + } + url(field: UrlField): void { + this.#reaturnType = "string" + } + currency(field: CurrencyField): void { + this.#reaturnType = "number" + } + button(field: ButtonField): void {} + duration(field: DurationField): void { + this.#reaturnType = "number" + } + percentage(field: PercentageField): void { + this.#reaturnType = "number" + } + formula(field: FormulaField): void { + this.#reaturnType = field.returnType + } + reference(field: ReferenceField): void {} + rollup(field: RollupField): void { + // TODO: get return type from rollup + } +} diff --git a/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts b/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts index d682d5c71..3ae103eac 100644 --- a/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts @@ -233,7 +233,7 @@ export class ReferenceField extends AbstractField< }) } - public override update(dto: IUpdateReferenceFieldDTO): ReferenceField { + public override update(table: TableDo, dto: IUpdateReferenceFieldDTO): ReferenceField { return new ReferenceField({ type: "reference", name: dto.name, diff --git a/packages/table/src/modules/schema/fields/variants/select-field/select-field.vo.ts b/packages/table/src/modules/schema/fields/variants/select-field/select-field.vo.ts index ce002562e..adc9ab268 100644 --- a/packages/table/src/modules/schema/fields/variants/select-field/select-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/select-field/select-field.vo.ts @@ -4,6 +4,7 @@ import { match } from "ts-pattern" import { ColorsVO } from "../../../../colors/colors.vo" import type { FormFieldVO } from "../../../../forms/form/form-field.vo" import type { RecordComositeSpecification } from "../../../../records/record/record.composite-specification" +import type { TableDo } from "../../../../table.do" import { FieldIdVo, fieldId } from "../../field-id.vo" import type { IFieldVisitor } from "../../field.visitor" import { Options, option, optionId } from "../../option" @@ -77,8 +78,8 @@ export class SelectField extends AbstractField o.name) applyRules(new OptionNameShouldBeUnique(options)) diff --git a/packages/table/src/modules/schema/schema.vo.ts b/packages/table/src/modules/schema/schema.vo.ts index f6c5f9500..6afaeeb53 100644 --- a/packages/table/src/modules/schema/schema.vo.ts +++ b/packages/table/src/modules/schema/schema.vo.ts @@ -1,6 +1,6 @@ -import { andOptions, Option, Some, ValueObject } from "@undb/domain" +import { andOptions,Option,Some,ValueObject } from "@undb/domain" import { getNextName } from "@undb/utils" -import { z, ZodSchema } from "@undb/zod" +import { z,ZodSchema } from "@undb/zod" import { objectify } from "radash" import { WithDuplicatedFieldSpecification, @@ -34,10 +34,10 @@ import { type IUpdateFieldDTO, } from "./fields" import { FieldFactory } from "./fields/field.factory" -import type { Field, MutableFieldValue, NoneSystemField, SystemField } from "./fields/field.type" +import type { Field,MutableFieldValue,NoneSystemField,SystemField } from "./fields/field.type" import { AutoIncrementField } from "./fields/variants/autoincrement-field" import { CreatedAtField } from "./fields/variants/created-at-field" -import type { SchemaIdMap, SchemaNameMap } from "./schema.type" +import type { SchemaIdMap,SchemaNameMap } from "./schema.type" export class Schema extends ValueObject { public fieldMapById: SchemaIdMap @@ -56,9 +56,9 @@ export class Schema extends ValueObject { this.fieldMapByName = fieldMapByName } - static create(dto: ICreateSchemaDTO): Schema { - const fields = dto.map((field) => FieldFactory.create(field)) - return new Schema([ + static create(table: TableDo, dto: ICreateSchemaDTO): Schema { + const fields = dto.map((field) => FieldFactory.create(table, field)) + const schema = new Schema([ IdField.create({ name: "id", type: "id" }), ...fields, CreatedAtField.create({ name: "createdAt", type: "createdAt" }), @@ -67,11 +67,21 @@ export class Schema extends ValueObject { UpdatedByField.create({ name: "updatedBy", type: "updatedBy" }), AutoIncrementField.create({ name: "autoIncrement", type: "autoIncrement" }), ]) + + for (const field of schema.fields) { + if (field.type === "formula") { + field.setMetadata(table) + } + } + + return schema } static fromJSON(dto: ISchemaDTO): Schema { const fields = dto.map((field) => FieldFactory.fromJSON(field)) - return new Schema(fields) + const schema = new Schema(fields) + + return schema } *[Symbol.iterator]() { @@ -108,9 +118,9 @@ export class Schema extends ValueObject { return new Schema([...this.fields, field]) } - $updateField(dto: IUpdateFieldDTO) { + $updateField(table: TableDo, dto: IUpdateFieldDTO) { const field = this.getFieldById(new FieldIdVo(dto.id)).expect("Field not found") - const updated = field.clone().update(dto as any) + const updated = field.clone().update(table, dto as any) return new WithUpdatedFieldSpecification(field, updated) } diff --git a/packages/table/src/table.builder.ts b/packages/table/src/table.builder.ts index fe92e7140..acf3c1d4d 100644 --- a/packages/table/src/table.builder.ts +++ b/packages/table/src/table.builder.ts @@ -66,7 +66,7 @@ export class TableBuilder implements ITableBuilder { } createSchema(dto: ICreateSchemaDTO): ITableBuilder { - new TableSchemaSpecification(Schema.create(dto)).mutate(this.table) + new TableSchemaSpecification(Schema.create(this.table, dto)).mutate(this.table) return this } From b1ceeceafb8ceb37e5c8d28d3680bc806f27fa84 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 30 Oct 2024 13:27:50 +0800 Subject: [PATCH 30/31] feat: support formula field aggregate --- .../modules/schema/fields/field.aggregate.ts | 2 -- .../formula-field/formula-field.aggregate.ts | 34 ++++++++++++------- .../formula-field.specification.ts | 2 +- .../formula-field/formula-field.vo.ts | 6 ++-- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/table/src/modules/schema/fields/field.aggregate.ts b/packages/table/src/modules/schema/fields/field.aggregate.ts index 7e6839378..574242369 100644 --- a/packages/table/src/modules/schema/fields/field.aggregate.ts +++ b/packages/table/src/modules/schema/fields/field.aggregate.ts @@ -6,7 +6,6 @@ import { checkboxFieldAggregate } from "./variants/checkbox-field/checkbox-field import { currencyFieldAggregate } from "./variants/currency-field/currency-field.aggregate" import { durationFieldAggregate } from "./variants/duration-field/duration-field.aggregate" import { emailFieldAggregate } from "./variants/email-field/email-field.aggregate" -import { formulaFieldAggregate } from "./variants/formula-field/formula-field.aggregate" import { jsonFieldAggregate } from "./variants/json-field/json-field.aggregate" import { longTextFieldAggregate } from "./variants/long-text-field/long-text-field.aggregate" import { percentageFieldAggregate } from "./variants/percentage-field/percentage-field.aggregate" @@ -31,6 +30,5 @@ export const fieldAggregate = stringFieldAggregate .or(currencyFieldAggregate) .or(durationFieldAggregate) .or(percentageFieldAggregate) - .or(formulaFieldAggregate) export type IFieldAggregate = z.infer diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.aggregate.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.aggregate.ts index 38fa66c76..dd6b87734 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.aggregate.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.aggregate.ts @@ -1,15 +1,23 @@ +import type { ReturnType } from "@undb/formula" import { z } from "@undb/zod" -export const formulaFieldAggregate = z.enum([ - // - // "sum", - // "avg", - // "min", - // "max", - "count_empty", - "count_uniq", - "count_not_empty", - "percent_empty", - "percent_not_empty", - "percent_uniq", -]) +export const createFormulaFieldAggregate = (returnType: ReturnType) => { + if (returnType === "boolean") { + return z.enum(["count_true", "count_false"]) + } else if (returnType === "number") { + return z.enum([ + "sum", + "avg", + "min", + "max", + "count_empty", + "count_uniq", + "count_not_empty", + "percent_empty", + "percent_not_empty", + "percent_uniq", + ]) + } + + return z.enum(["count_empty", "count_uniq", "count_not_empty", "percent_empty", "percent_not_empty", "percent_uniq"]) +} diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.specification.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.specification.ts index c9efaee11..84ae0b54d 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.specification.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.specification.ts @@ -7,7 +7,7 @@ import { FormulaFieldValue } from "./formula-field-value.vo" export class FormulaEqual extends RecordComositeSpecification { constructor( - readonly value: number | null, + readonly value: number | null | boolean, readonly fieldId: FieldId, ) { super(fieldId) diff --git a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts index e6eaecadc..dc1c4f15f 100644 --- a/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/formula-field/formula-field.vo.ts @@ -9,7 +9,7 @@ import type { IFieldVisitor } from "../../field.visitor" import { AbstractField, baseFieldDTO, createBaseFieldDTO } from "../abstract-field.vo" import { StringEmpty } from "../string-field" import { FormulaFieldValue } from "./formula-field-value.vo" -import { formulaFieldAggregate } from "./formula-field.aggregate" +import { createFormulaFieldAggregate } from "./formula-field.aggregate" import { createFormulaFieldCondition, type IFormulaFieldCondition, @@ -138,6 +138,8 @@ export class FormulaField extends AbstractField new FormulaLTE(value, this.id)) .with({ op: "is_empty" }, () => new StringEmpty(this.id)) .with({ op: "is_not_empty" }, () => new StringEmpty(this.id).not()) + .with({ op: "is_true" }, () => new FormulaEqual(true, this.id)) + .with({ op: "is_false" }, () => new FormulaEqual(false, this.id).not()) .exhaustive() return Option(spec) @@ -152,7 +154,7 @@ export class FormulaField extends AbstractField Date: Wed, 30 Oct 2024 05:31:50 +0000 Subject: [PATCH 31/31] Prepare release v1.0.0-111 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0fbb4b4..e03e3c135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.0.0-111 + + +### 🚀 Enhancements + +- Support formula field aggregate ([b1ceece](https://github.com/undb-io/undb/commit/b1ceece)) + +### ❤️ Contributors + +- Nichenqin ([@nichenqin](http://github.com/nichenqin)) + ## v1.0.0-110 ## v1.0.0-109 diff --git a/package.json b/package.json index 5e31400b4..d85a9746d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "undb", - "version": "1.0.0-110", + "version": "1.0.0-111", "private": true, "scripts": { "build": "NODE_ENV=production bun --bun turbo build",