From fa6a37b6ca11920fcbc543394a375aace64353cf Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Mon, 5 Nov 2018 23:40:47 +0300 Subject: [PATCH 01/20] pycalc distribution archives implementation 1 --- final_task/pycalc-1.0.0-py3-none-any.whl | Bin 0 -> 13276 bytes final_task/pycalc-1.0.0.tar.gz | Bin 0 -> 8894 bytes final_task/setup.py | 11 +++++++++++ 3 files changed, 11 insertions(+) create mode 100644 final_task/pycalc-1.0.0-py3-none-any.whl create mode 100644 final_task/pycalc-1.0.0.tar.gz diff --git a/final_task/pycalc-1.0.0-py3-none-any.whl b/final_task/pycalc-1.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..0dd80cd9fad93895bc05f70e17c48be736a2778e GIT binary patch literal 13276 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 VM}5K{RY3>OXj{7{|9-mXKEMDZB}!JZM3pUY z?$h1V-P6<4SX_;RXzcl&Zl`Hd{&4~S zM|~*nba(i__2(7IgGrDF-akm1g-JYg{Epvv9n8tlVYJcEa)3x0zbxnTAiaX@WlEg4 zSNSZ7opAz*@x+P3m^fH#83lQgHvXQ>iMI%*(AsaWe|r7qpRXIwmQW)dI?oWG&huH2 z#^HEI;*SB(^2j_0qoD&3&%1F4^3vF+CAxo`Vpa!wYy2FJNt|izeowM-8ZL4=RFU`= zhH;ssbCvQu3bHJ`3<(V3ZJJEeU_KAysq=FXPnR%IXE+p-g13I>*r@SlLDC?{^1obV zIhkXbpTl_h#d#IRdE>#K?lIPXM)KvNv$(pA^?zKw{*MQW|M!l&xc*ND{%-x>#dDb^ zbBE$RPm(Be!ucXebC&W=AaEz=rNJC(p$)$tZqLa^p+zEj3E;BN5!q`c2;Mtrl^2Ra; z8gDcnI#Ut@e~Y+mX>buU5AvCKPbdQfeG!YNqbyH>8D@oE^2E6eVL+mA<{Ubg%b1Q1 z-$_1hq@yGTmK{#xkt&AV{=@rr^T26-@aX^V_vxQQ_TvNn^Ih{Cs^^~*T0JY)Kjek? zp)M8#QL_M;WJ#%~C#SUJQ}*Wx`$7LdWl7(j(iYU%X|-USWHXwNZ|o%tVth#fZML2SSkhz}Pbh+8 zG6kvri4-yIDO7tB*PcSQC$a4*bbAWlo`_<9!N&I_)06awj z9z{W2XOcENlQ=v@9UdbOd{c<@1Vwm?Bs@hEbzLfA@f5Xqid;O6UOYuG9;X=o+LLef zy$R~^bn@{O<~ZU!MM0iIo1`JHf{65qv{ ze($mB?>*kYdrvg+-V=?y_e3+GhK}Op{DMe!I%%8jmrHn}3?M%!SAXAu|Nid-{Zm*j zmhzrf!&7xGQc&rMoK%Yq+Z7yo9Wo!1$67O&byH)og1vI)%6XLJu!4OW%u}*X!8k8d zUhgtn&dc3NcE@?D-FAC7GyTpql6GT%)bs-&wX zT$OB9qE$*(kJE+62&1wyPA|H2mBLlZRzWjTvU&n*oNTZGK`I`n6_Xn&#@aBIce+WS zdjz^epgKR({J*23h34#+8&phNwcV)PBCs6-+aS>W0o@+3-2vSkVC$;NA|+!oA@P_r z)GM`7wVFex3zb~{_X+>|RQx^_3F75cynHKO+VIz>v&O##)BqTUPLD!@{}nIa@xLG7 zZ=XR0UjCp%2hl;&aO{xZm*J-%BFLy==P0?h;`$?Abfg!>nGt9WcZeNA7YqFI)9FqKI_yJQl6;t-X|up(^Bg@M~B#c?CkD5qd8erNQZOgN>;q$uY!BU7fi zwO2|{su~fLrRotuV={^SE~rf=#e~6E@NEsgkQk;}?vRMgff&%V5IFgA0c$MCvSb_v zIhi=0!+hqDU_5g^k}D^K>JC#^kj#Q~x|~x&x=d0ax-=!(B8ewxUllzneL&%n3X7rB zg2h0|0Drq&N0=2eX0A{c^bGjzN^PQ$fi-L}r7}k^3%^~dfj~x2%jmgMB`H*)1EOL} z%fR0i-Acix^&rw*uFB{wF<2Bq!69MmKuAHYG*5E52BhV{Pgm$iX7bqOijtoEWl_;7 zld5LXx$g?yO3T3CuAysbnT||#t?PhEVZAj>aD|elffRpZI+K%H4VvI`HBGAkKP%?O zM7UOzCb+sfr?oHAT&dz2nUGM?4HNT5;h0e*6Z_f#Xo9O0MXgdK=ZB(SuaiveFlG9K z-UKG_7W_pqXWJ~B*81XBbj;MEVilUoDlvVjSis|r0@n^N zozVyn0!Jg~^wepNM)M$yN2BJDov^{SId3&@&7|CP|Ac4kf41|VbP=nw(=D6-F8+*ZjbT zpXyNG0=|AT5?ulZxJZNas-a~-l2bSYVk+5zW2$JNFtQOrH;Ou2F5^4U@iUwQiIAT_ zk%q{IY%mlM=NwqBi(kKknKmLfD(sOh4 z2pEBE3K$a@FL+i~g7Y-|L}I44qvMr82b;4FBvj66Di$sPYG?>AkU3^SbM_tB17t0C zSQ2=7dNs7}{gMS}2Fb#Qc`G1*mau4n2rQ-JoQ^p7?Q(M%E+k#oS&J(B0(&7YGAy4nQpvj?S zmrMz)8`mA8;dVSDfPYYsI7#8&XrtK(Bb?ZPje{6_y&z5+g#6eRNFik#ydc89NV>_a zE77Q7Dp)KC=r9)QIHM3dN5CM?du}B{U(i2D!$Ibd4UzhvKbjq+cK|hU*PI-QN|bFb z)k?~SdY0mRs%PDERLy+*3@V|?7KUw2Sc|W@3U0g-s_DX55vYq&4V;}X7;8ZzbS$!h z(yb%j<0B40SqAC`B5zU}Ctq@;Y#?(g(}Lo1*t^D@YB5wn^L9e;DcQTV2$WQRE&NgI z3J4j{*^$%#=MQDm5zB#&#oZ7NbZM+4@!qva{3g8ro>D&Ucef)2q=9VXDQ!~_-0cFX zk_-2Gm5?a2$f)w{8RA*Y}KT7hM_+8b}E?0lKVywpLV!$!$svL`Gid%DHM zz6slS3s%;+JI)V)OY$X{FCsE@-p6|k*Ie%^5}~mg3*kDJPGrf z=em_g^->(m(1GHj3)Z1wRDfR1ZCs%&hoz1;(Pz;r}NQcE5XZ1{E zv$C?0{|Zx+&Hf`c#M`7cG*A&S zUu>exrkSyjika}=pjPr0eiaEUKbTDBz}2Z|o+$E4BtWJphZ2uxg8*V_)EOa5$s!8I zs)Of7s;sTaFx8HB;SncE*AC9HDM#7lvYa;4%vCa*B{yx#;@HCTAR{dSyN*av_Re1= za%45uNy!q4wmd3e`EFHtql`ei8ZDAcclfhd`<|t`%M5B{P$#6yK2&ssr=Z#lFH$TX zUHz{<7SMjv5$Xu^s#v?{_4khs2Op02_fHNFAAR>&>0k=mr)qL7S>H7aW~QPbD|f_n zSt(7|q&gasUz)%8eHlb8(au?ux@5Jb?&C?#XkV&Fhc%A7+F9E`It(3iLmm8oq8y4I zNw1>E&GVYE$+;a+1=QULx3DSO7`JjGcU|0ie%tGLfL`zD&>MX40K*=j*n9MycR!Gf z;?C4@+a{j9Bc<~W)o{iBvwuEkYVs(mIfqtLOTV_7Kz_h4p;`w#T9E>CkT94zz8qlNA8B zxn6Dr?x#GCb-t*Z6*e&;7K$c#7a<|NZ=Ya)MFEbWpK|!e+IE&*?B_xCA6x;MY-F8b&uqtBFqJz2O94mMt&%jX!1tsX%K+ zK2h5T6wmn?7u`(-lSxQ_Fxvz-C`_D-D+hXY=t*2ko!YTL-KNU(yk6Vux7+O_*k9e# zjg;Cjx8;VJ=iUGrVaB!YY8-r@kPMxddBG+(Sviw1!^kSjFq?_PiqErfJfp6rLOZ8s z8R*x@OL7An(uM1+*`X5zeYh|8^f|p{xF$+Yn@t4^IpoZA!!rD%tZq@VqB!eQ$dq~V zl@%1mh(h;*_KeH!>)F% zt7Z@$b!b>O2n;AV#j|rI+*t5Q7 zEoRffJw#!}I4jjXYUcwLeTBNG%zRRv=R?;uJW!DKrK=AwPz%s~{id_7INt=YB~X?o z%l0d!a>a&9u&$_aU{1%ku9u#q?+Q#r$#fK?(;7s8O3@@uPPzeJTDSo5NO?O{)=&dv z4FVC^8+!JF#+`~gq^N?6`=V8d;dj^B=AwU_=nymBE+)$3r#oMw;BP1|=+#5jhbGrp z(nVZQRG`Ra+KFtlbooN4BN4d-2F7prVoZnH)rX}Wt+ZzOP02W!qKa3^xjv{fnl>{$ zDQq&2C>X9yktmLFbB&_df>14>+m0oBExn>x2~e)96S|^NDNF8|KIlt*Q2CyTlhXB2 zwJn{GBj(0xUKt~mxBzbO2^#F-ZI~6<(27cYd`{Aj4tF>$7?d;!^I$o25tv4sUZqQs!EU+`BtEx^D+6Kr%! zw>Deolps^T4vp5Rs@FO3AN3yLzoWt9?h!kNx1_GSE22B2e#bxZ2akKlgZ|*r^|K#!D(J^z|D5q}JYCYyG8kQED+E^!shgn(cQh6KeUz+1u z%_0$g)q=G#eo`rbL~$v7f^iN+5?v`Tb=zHBTu4)@wk>lZt2pa2mXL9CmTxIU^rybm z43H$WeR^y%${MG0?WXT423qZUUMsA8ztinL?sf-*Zuh7UgzInNoT-2!S#9)hk|v`` z_$fpnS8SI=HttTbeAB$`VHHm{G;}SR^H%}2MPxD0o6<}-2PkfjL7y9i@#vC!Onk0@ z>ospbZ47--e;E9kYK+%^sZOLPerTR6yvW&DlwsBM2!nH=T3mGpdtLO>hRl0RY6O~A zZN>7LuPJCaKt&p8jg;*S%U66>ki<37V@_mKvO!i=K`xu!BE7~U^%S$bS8Mdz7il+N zz5I}7#13gH5K%bExh6Mk-%lW$lhZ-CDvpoTDloOR3%c`Pb^lAZv~S}I(2DzC#{>VQ z82{(kAMEb`+{5@kdGL|?tRxuxG+APRMsaj#J5{<-JRrFp)j|OsIOU+fGBQxX?rseY zR5I6(g9Qr-dr4h&;%U{4GJa47ng~^vlFLF+o2r09D}o6XN2JRd%H*Z6vBnMhl7mL2 zZ|*f6kCxD5Wvd9R_J~1xupqODHP*5X7Xk>%YN|n@u!kf$N#`Lm3)9H!!aS=2dKwm$ z;=OWjsCi@n&$rH6|j6Iu7_%UMT0V3Ki zC`*X0jHARP4W}4I0qq@Cr;5dpS8TYr(+c7#X=#XbSKtxVG#X5^1R2?GPc1!y zQ>&%-)nlExET#x=`LsZFZmoq3Mzp%gA|cZ}qgLBL$5PFMX4T;F5m+t|Y`Zt)eOK0m zsYBb;s*8M`w==ArnuTV2^ib8^xEiH(vZ5l3{UK@F@Pn|}u92K;g|aTesAZ1xCDSDc z&HdSOu}Jvt_=2oK$(p=E3bE|d3-Zc{(PEIhTU-pRj44(MRam&2at>0F;DS;Fx|>*d zOSE0vhh(s>v;9tR8MT_OZ9UXM*ve8--3#L-Y1m^gmH7`}zcmB9jZv*jIi7#6g}9OE zt`OkG7Tt=Ph72iJDh7hFm1?XRIJZh0x9Z}IlT>J_=3-{AayhdIFv80(VBCO8E{z|Q zBggFNv14>w6TcXkixUq#%j(ur+!QyzC3!kzTe$41bCJzjBhki})YiljEaaG5&|Yi~n*j_y6<*j5J0Vv)2mU zfd%Q9(cs>TZE% z7Qi{9T|k>Q{?upAIxn7c3WdpH6HwO`s>}j%C?$Jo$qMs$oE053AW?KIes5e2r9?2y9oznDmBO$4qixCb;(4w@zq4{B_I(IK4GC zlOB&5X;b7NPk74}a@tL|%5FeI`q9GfK{;tnJ3+gk5vnM;#^Bq{(r!_W{Z1!UcSmR4wGOBRa{ zg&po`co0|=L3*rDT5rE-rLkkD+*s4o;#sTq8WjZFHHo!rEy#(qdr<68y8uwQlxK-M z&kAH4NMC%WNiOF`895Dr8U<6P-(;}&0h**54)RV18S|BshjZ98C37x}omr5*8rCc)4kFGPTOm^&eHiE=fDJ#wp>syDC(%W}{M!Zu5A>mYpJH)tuMWMkwYC z5(5T$p6Bm-1GFZ!JP}ibkLHzXZ|LkD=yiEx)=T5|Vw+tVUm1G%h4gH~dfdju$>8e; zA-36n*q?1h|6OVSIqK{2f2se+aeuJ0|J=#*@LT6_nWcvpVGMZBXGz>|JaoK$4_5Vr zE~npq{lR;LDb*2PgLJwGQrI5z-`Q2Rt|jMB6xfyST&P1Jovdy`dQOuwhh?ZkU=}@% zI+Tj@U>eeZtEhPN&+4pUH>1pfwSOuh-g(BGeanWlNR#PllR+E!%!Fj)G^8IcfYc>y zmEKYuvk*?Gu!S&6?Z&~H*B;0sjG!Ylp5d}^p3j0b4nY{jAL(bGoELAOy&5_%K{|qn zV3rM-D(`%~K5%$HCQ$(FA7_;Y^EuGtfz#{yk2>-!fB}v-!{qBPe@U zemy`Cgh`m}Fa-;S@nVTzC4d~len4RkH2P;mH8+01bp;w}I{k!>9;^*q@wp&Q1sWmw zR2LmB_`fxpi?t~1r7a@R(9hMtu`nN$pjZUyc!qG|dKqUPmaHr9?Hb3Ij{QVq}q zx>Vo*+T=@^w|wz20tndsVhT6j2!8W_> zzR`&sAP+R@jDEqQ*f;YyM7~~q`%yVouXFqHhNyZd3`p8tK+5z1f?;a={&{1an|np) zaKqJn%}aX^)zvHevfa!-{W_oGxY;5oE5rI1JB&Kron!hfegAcP%Ku9J-|rpu`UU-e zaMC^5>Hqi9|6B6tFun}Gptxc(M0X?;+ecA? zC0PY8Ht^yxXv%?cMl5WWR=sIx$2V_%}8 zZaJ?PIp3Z-B{$l_X~%e(rl3!%V?XWegg0}(;#|>af}T>EN%Z`su(sP(E4k7QRc9*V zn8WPZa-ht>P&$*QJEPJV3#$|Z8(531bML{%DQ|Jqid|KX+Q!AK9|D#HQy4xynCsSj z#`t>Et(|iHArm9^R`q~aofc~D-G#d1+|Z2g-p(Yf=q zD3;C)B%E)%zthtd9X8fA&JES_S&x6$m)EPS^UEeKA}> zLT?l8ZFC}kBlv#1gYR`c9tzGnR#`NxyEj(HrL61uR`^u?B(F^kwLFLphq_qgPBu%H z(FAXjz?@h3<gSQ= zfRE^kn&zn+B5WCSl4z94)rO8TKvIH-Enz&Bnxoh#STSj1`IaX59ZB`AnL5?dLfPde z-3^v&<#)WR;}crju{y-)!`;X&=DJt6lo>6sUd61~Q1YS;RSLG48{E^nji8@=_Aha` zB65^;!6_VOh+`W|)@ElnD?auuze~pI#YX2YchDZ8oYQq{j)^4dUMRGN6cd<0I+pBUd<)p~q z;;x)#okd?`KvhQ(_x!~#o%~CFWBu=_Qvd4?dM76SLI3OacKY94JQ;|b1*rH9uKWK6Ith&%uB8ce zMoTJ$h-_5t92&(d1!e;1ds-UvJJNn>US=v?k517jQtaEGe)L|x{^3odhLlTr6M_Z` z5|6J&Q5b*BtcCEV&(&y=gedH61@dGuipVDt-E_I!pY<=GxsCl_-TL>B3;h42@B6#; ze;3ayk_VF@54?Ynlsa!ae#dXT232#&_hbz%2Z*Hc%W^)4Z5w2-zE9Bjdom~9BA7yJ zzrFtH^_zdbZaiB;jdbXgZw~M*4-JLU&;f|&-59U9rLj*-bpJNREIb=;{2Y!!UDDe9 zUfp$2iEm*TXlGL?&rur=FGB)Dc&qLeI6uQSV~K|h!=acIy!AWBMvXVrbcyAExyo`f z$1*>M@$!rF3K+wm&~#M0{44?-2hxH|m}Lw`Kj?6r)>N|9)Zp z_j%VgS7xz53CI1J#!ut0I-Cg|uJ9*A<%brAJ#FXdkobPMVF<$K1 zsfMs)6o38o&x}Fi%jtIeSh@a>iu?aT_jnio;cnJHF#8cQ`}0N~Oh*^XFq**Hr#~im zi4W?%6%NXZP%$-Lrdk&+gehyJz?8o Date: Sat, 10 Nov 2018 18:14:21 +0300 Subject: [PATCH 02/20] update CI script and hometask Signed-off-by: Aliaksei Buziuma --- final_task/README.md | 10 +++++----- pycalc_checker.py | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/final_task/README.md b/final_task/README.md index d05c634..0de8450 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -27,14 +27,14 @@ error explanation **with "ERROR: " prefix** and exit with non-zero exit code: ```shell $ pycalc '15(25+1' ERROR: brackets are not balanced -$ pycalc 'sin(-Pi/4)**1.5' -ERROR: negative number cannot be raised to a fractional power +$ pycalc 'func' +ERROR: unknown function 'func' ``` ### Mathematical operations calculator must support * Arithmetic (`+`, `-`, `*`, `/`, `//`, `%`, `^`) (`^` is a power). * Comparison (`<`, `<=`, `==`, `!=`, `>=`, `>`). -* 3 built-in python functions: `abs`, `pow`, `round`. +* 2 built-in python functions: `abs` and `round`. * All functions and constants from standard python module `math` (trigonometry, logarithms, etc.). @@ -89,9 +89,9 @@ These requirements are not mandatory for implementation, but you can get more po ```shell $ pycalc 'sin(pi/2)' 1.0 - $ pycalc -m my_module 'sin(pi/2)' + $ pycalc 'sin(pi/2)' -m my_module 42 - $ pycalc -m time 'time()/3600/24/365' + $ pycalc 'time()/3600/24/365' -m time 48.80147332327218 ``` diff --git a/pycalc_checker.py b/pycalc_checker.py index f1525cd..4009851 100644 --- a/pycalc_checker.py +++ b/pycalc_checker.py @@ -33,6 +33,9 @@ "log10(100)": log10(100), "sin(pi/2)*111*6": sin(pi/2)*111*6, "2*sin(pi/2)": 2*sin(pi/2), + "pow(2, 3)": pow(2, 3), + "abs(-5)": abs(-5), + "round(123.4567890)": round(123.4567890), } ASSOCIATIVE = { From 712dfab92d386a01a6c844f92e0e4991ef890a23 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Mon, 5 Nov 2018 23:56:40 +0300 Subject: [PATCH 03/20] pycalc implementation2 --- final_task/pycalc-1.0.0-py3-none-any.whl | Bin 13276 -> 0 bytes final_task/pycalc-1.0.0.tar.gz | Bin 8894 -> 0 bytes final_task/pycalc/UnitTests.py | 267 +++++++++++++++++++++++ final_task/pycalc/__init__.py | 0 final_task/pycalc/addmultsigns.py | 77 +++++++ final_task/pycalc/constsreplacer.py | 31 +++ final_task/pycalc/pycalc.py | 66 ++++++ final_task/pycalc/pycalclib.py | 94 ++++++++ final_task/pycalc/rpn.py | 116 ++++++++++ final_task/pycalc/rpncalculator.py | 89 ++++++++ final_task/pycalc/tokenizer.py | 99 +++++++++ 11 files changed, 839 insertions(+) delete mode 100644 final_task/pycalc-1.0.0-py3-none-any.whl delete mode 100644 final_task/pycalc-1.0.0.tar.gz create mode 100644 final_task/pycalc/UnitTests.py create mode 100644 final_task/pycalc/__init__.py create mode 100644 final_task/pycalc/addmultsigns.py create mode 100644 final_task/pycalc/constsreplacer.py create mode 100644 final_task/pycalc/pycalc.py create mode 100644 final_task/pycalc/pycalclib.py create mode 100644 final_task/pycalc/rpn.py create mode 100644 final_task/pycalc/rpncalculator.py create mode 100644 final_task/pycalc/tokenizer.py diff --git a/final_task/pycalc-1.0.0-py3-none-any.whl b/final_task/pycalc-1.0.0-py3-none-any.whl deleted file mode 100644 index 0dd80cd9fad93895bc05f70e17c48be736a2778e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13276 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 VM}5K{RY3>OXj{7{|9-mXKEMDZB}!JZM3pUY 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', '<', '>=', '<=', '!=', '==']) + self.assertEqual(error_msg, None) + + def test_extract_pos_constants(self): + """Are positive constants extracted properly?""" + user_expr = 'e+pi-tau/inf*nan' + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['e', '+', 'pi', '-', 'tau', '/', 'inf', '*', 'nan']) + self.assertEqual(error_msg, None) + + def test_extract_neg_constants(self): + """Are negative constants extracted properly?""" + user_expr = '-e+-pi--tau/-inf*-nan' + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['-e', '-', 'pi', '+', 'tau', '/', '-inf', '*', '-nan']) + self.assertEqual(error_msg, None) + + def test_extract_brackets(self): + """Are brackets extracted properly?""" + user_expr = '()' + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['(', ')']) + self.assertEqual(error_msg, None) + + def test_extract_comma(self): + """Is comma extracted?""" + user_expr = 'pow(2,3)' + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['pow', '(', '2', ',', '3', ')']) + self.assertEqual(error_msg, None) + + def test_extract_functions(self): + """Are functions extracted properly?""" + user_expr = "round(sin(2)-asin(1))-abs(exp(3))" + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['round', '(', 'sin', '(', '2', ')', '-', 'asin', '(', '1', ')', ')', + '-', 'abs', '(', 'exp', '(', '3', ')', ')']) + self.assertEqual(error_msg, None) + + def test_consider_sub_signs_method(self): + """Are several subtraction and addition signs replaced by one integrated sign?""" + user_expr = '-1---2+-3+++4+-2' + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(tokens, ['-1.0', '-', '2', '-', '3', '+', '4', '-', '2']) + self.assertEqual(error_msg, None) + + def test_is_number_method(self): + """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" + tokens = ['.3', '-0.3', '7', 'tan'] + tokenizer = Tokenizer(user_expr='') + is_numbers = [] + for token in tokens: + is_numbers.append(tokenizer.is_number(token)) + self.assertEqual(is_numbers, [True, True, True, False]) + + def test_extract_tokens_error_msg(self): + """Is error_message created?""" + user_expr = "2+shikaka(3)" + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + self.assertEqual(error_msg, 'ERROR: invalid syntax') + + +# Tests of 'Multsignsadder' class from 'addmultsigns' module +class MultsignsadderTestCase(unittest.TestCase): + """Tests for Multsignsadder class""" + + def test_is_number_method(self): + """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" + tokens = ['2.3', '-0.6', '5', 'sin', 'exp'] + mult_signs_adder = Multsignsadder(tokens) + is_numbers = [] + for token in tokens: + is_numbers.append(mult_signs_adder.is_number(token)) + self.assertEqual(is_numbers, [True, True, True, False, False]) + + def test_addmultsigns_add_mult_signs(self): + """Are multiplication signs added to where they implicit were to be in expression?""" + tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', '-', '9', '(', '1', '+', '10', ')'] + mult_signs_adder = Multsignsadder(tokens) + extd_tokens = mult_signs_adder.addmultsigns() + self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '-', + '9', '*', '(', '1', '+', '10', ')']) + + def test_addmultsigns_dont_add_mult_signs(self): + """Aren't multiplication signs added if it's not needed?""" + tokens = ['2', '+', '3', '*', '5'] + mult_signs_adder = Multsignsadder(tokens) + extd_tokens = mult_signs_adder.addmultsigns() + self.assertEqual(extd_tokens, ['2', '+', '3', '*', '5']) + + def test_consider_neg_funcs_method(self): + """Are negative functions tokens replaced by '-1*function' tokens?""" + tokens = ['2', '*', '-sin', '(', '2', ')'] + mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder.consider_neg_functions(mult_signs_adder.tokens) + self.assertEqual(mult_signs_adder.tokens, ['2', '*', '-1', '*', 'sin', '(', '2', ')']) + + def test_consider_log_args_method(self): + """Is 'e' added as a base for log functions if last was entered with one argument?""" + tokens = ['log', '(', '33', ')'] + mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder.consider_log_args(mult_signs_adder.tokens) + self.assertEqual(mult_signs_adder.tokens, ['log', '(', '33', ',', 'e', ')']) + + +# Tests of 'RPN class' from 'rpn' module +class RPNTestCase(unittest.TestCase): + """Tests for RPN class""" + + def test_is_left_associative_method(self): + """Are left associative operators recognized?""" + tokens = ['^', '**', '+', '/'] + rpn = RPN(tokens) + is_left_associative = [] + for token in tokens: + is_left_associative.append(rpn.is_left_associative(token)) + self.assertEqual(is_left_associative, [False, False, True, True]) + + def test_is_number_method(self): + """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" + tokens = ['1.3', '-0.5', '/', '%', '9'] + rpn = RPN(tokens) + is_numbers = [] + for token in tokens: + is_numbers.append(rpn.is_number(token)) + self.assertEqual(is_numbers, [True, True, False, False, True]) + + def test_convert2rpn_method(self): + """Does 'convert2rpn' method work correctly?""" + tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] + rpn = RPN(tokens) + result, error_msg = rpn.convert2rpn() + self.assertEqual(result, ['-pi', '2.23', 'round', '*', '5', '//', '2', '3', 'pow', '*']) + self.assertEqual(error_msg, None) + + def test_convert2rpn_method_error_msg(self): + """Is error_message created?""" + tokens = ['(', '2', '+', '3', ')', ')'] + rpn = RPN(tokens) + result, error_msg = rpn.convert2rpn() + self.assertEqual(error_msg, 'ERROR: brackets are not balanced') + + +# Tests of 'Constsreplacer' class from 'constsreplacer' module +class ConstsreplacerTestCase(unittest.TestCase): + """Tests for Constsreplacer class""" + + def test_replace_constants_method(self): + """Are constants replaced and not constants aren't replaced?""" + tokens = ['e', '-e', 'pi', '-pi', 'tau', '-tau', '2', 'cos', 'inf', '-nan', '+'] + constsreplacer = Constsreplacer(tokens) + replaced_tokens = constsreplacer.replace_constants() + self.assertEqual(replaced_tokens, ['2.718281828459045', '-2.718281828459045', + '3.141592653589793', '-3.141592653589793', + '6.283185307179586', '-6.283185307179586', + '2', 'cos', 'inf', '-nan', '+']) + + +# Tests of 'RPNcalculator' class from 'rpncalculator' module +class RPNcalculatorTestCase(unittest.TestCase): + """Tests for RPNcalculator class""" + + def test_evaluate_method_result(self): + """Does 'evaluate' method actually evaluate RPN math expression and give out correct result?""" + rpn_tokens = ['2', 'sqrt', '3', '/', '3.14', '*', 'tan'] + rpncalculator = RPNcalculator(rpn_tokens) + result, error_msg = rpncalculator.evaluate() + self.assertEqual(result, 11.009005500434151) + self.assertEqual(error_msg, None) + + def test_evaluate_method_error_msg_zero_division(self): + """Is 'division by zero' error message created?""" + rpn_tokens = ['2', '0', '/'] + rpncalculator = RPNcalculator(rpn_tokens) + result, error_msg = rpncalculator.evaluate() + self.assertEqual(error_msg, 'ERROR: float division by zero') + + def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): + """Is 'negative number cannot be raised to a fractional power' error message created?""" + rpn_tokens = [['-2', '0.5', '**'], ['-2', '0.5', '^']] + error_msgs = [] + for rpn_tokens_list in rpn_tokens: + rpncalculator = RPNcalculator(rpn_tokens_list) + error_msgs.append(rpncalculator.evaluate()[1]) + for error_msg in error_msgs: + self.assertEqual(error_msg, 'ERROR: negative number cannot be raised to a fractional power') + + def test_evaluate_method_error_msg_neg_num_sqrt(self): + """Is 'root can't be extracted from a negative number' error message created?""" + rpn_tokens = ['-2', 'sqrt'] + rpncalculator = RPNcalculator(rpn_tokens) + result, error_msg = rpncalculator.evaluate() + self.assertEqual(error_msg, "ERROR: a root can't be extracted from a negative number") + + def test_evaluate_method_error_msg_invalid_syntax(self): + """Is 'invalid syntax' error message created?""" + rpn_tokens = ['2', '+'] + rpncalculator = RPNcalculator(rpn_tokens) + result, error_msg = rpncalculator.evaluate() + self.assertEqual(error_msg, "ERROR: invalid syntax") + + +if __name__ == '__main__': + unittest.main() diff --git a/final_task/pycalc/__init__.py b/final_task/pycalc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py new file mode 100644 index 0000000..1858d87 --- /dev/null +++ b/final_task/pycalc/addmultsigns.py @@ -0,0 +1,77 @@ +"""This module contains a class that allows to take into account implicit multiplication signs""" + +# import from pycalc self library +from pycalclib import constants, functions, negative_functions + + +class Multsignsadder(): + """A model of mult_signs_adder capable of adding implicit multiplications signs in list of tokens""" + + def __init__(self, tokens): + """Initialize mult_signs_adder""" + self.tokens = tokens + self.extended_tokens = [] + self.constants = constants + self.functions = functions + self.negative_functions = negative_functions + + def is_number(self, token): + """Determines whether token is a number""" + try: + float(token) + return True + except ValueError: + return False + + def consider_neg_functions(self, tokens): + """Replaces negative functions tokens by '-1*function' tokens""" + index = 0 + while index != len(tokens)-1: + if tokens[index] in self.negative_functions: + tokens[index] = tokens[index][1:] # remove '-' sign + tokens.insert(index, '-1') + tokens.insert(index+1, '*') + index += 3 + index += 1 + + def consider_log_args(self, tokens): + """Adds 'e' as a base for log function explicitly if last was originally entered with one argument""" + for index in range(len(tokens)): + if tokens[index] == 'log': + for index2 in range(index+1, len(tokens)): + if ((tokens[index2] == ')' and index2 == len(tokens)-1) + or (tokens[index2] == ')' and index2 != len(tokens)-1 and tokens[index2+1] != ',')): + log_args = tokens[index+2:index2] + if ',' not in log_args: + tokens.insert(index2, ',') + tokens.insert(index2+1, 'e') + break + + def addmultsigns(self): + """Adds implicit multiplication signs in list of math tokens to where they are supposed to be""" + for index in range(len(self.tokens)-1): + self.extended_tokens.append(self.tokens[index]) + if (self.is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants) + or (self.tokens[index+1] in self.functions) + or (self.tokens[index+1] == '('))): + self.extended_tokens.append('*') + continue + elif self.tokens[index] == ')' and self.tokens[index+1] == '(': + self.extended_tokens.append('*') + continue + self.extended_tokens.append(self.tokens[-1]) + + self.consider_neg_functions(self.extended_tokens) + self.consider_log_args(self.extended_tokens) + + return self.extended_tokens + + +if __name__ == '__main__': + print("""This module contains class that allows to insert multiplications signs to where they where supposed + to be in a list with math tokens. For example: \n""") + test_tokens = ['-0.1', 'tan', '+', '23', '*', '-sin', '(', '3', ')', '/', '.12', 'e'] + mult_signs_adder = Multsignsadder(test_tokens) + extended_tokens = mult_signs_adder.addmultsigns() + print('Original tokens: ', test_tokens) + print('Extended tokens: ', extended_tokens) diff --git a/final_task/pycalc/constsreplacer.py b/final_task/pycalc/constsreplacer.py new file mode 100644 index 0000000..6a93d3f --- /dev/null +++ b/final_task/pycalc/constsreplacer.py @@ -0,0 +1,31 @@ +"""This module contains a class that allows to replace constants by their numeric equivalents""" + +# import from pyalc self library +from pycalclib import constants_numeric_equivalents + + +class Constsreplacer(): + """A model of constants replacer capable of replacing constants (from math module) by their numeric equivalents""" + + def __init__(self, rpn_tokens): + """Initialize constsreplacer""" + self.rpn_tokens = rpn_tokens + self.constants_numeric_equivalents = constants_numeric_equivalents + + def replace_constants(self): + """Replaces tokens which are math module constants by their numeric equivalent""" + for index in range(len(self.rpn_tokens)): + if self.rpn_tokens[index] in self.constants_numeric_equivalents.keys(): + self.rpn_tokens[index] = str(self.constants_numeric_equivalents[self.rpn_tokens[index]]) + + return self.rpn_tokens + + +if __name__ == '__main__': + print("""This module contains class that allows to replace tokens which are math module constants by their + numeric equivalents. For example: \n""") + test_tokens = ['2', '*', 'nan', '-', '-inf', '+', '-tau', '*', '-pi', '+', 'e'] + print('RPN tokens with constants: ', test_tokens) + constsreplacer = Constsreplacer(test_tokens) + rpn_tokens = constsreplacer.replace_constants() + print('RPN tokens after replacement of constants: ', rpn_tokens) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py new file mode 100644 index 0000000..8d411d0 --- /dev/null +++ b/final_task/pycalc/pycalc.py @@ -0,0 +1,66 @@ +#! /usr/bin/python3 +# -*- coding: UTF-8 -*- + +# general import +import argparse +import sys +# import from pycalc self library +from tokenizer import Tokenizer +from addmultsigns import Multsignsadder +from rpn import RPN +from constsreplacer import Constsreplacer +from rpncalculator import RPNcalculator + + +def createparser(): + """Creates parser with one positional argument 'expression' to parse user's mathematical expression""" + parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', + epilog="""Anton Charnichenka for EPAM: Introduction to Python + and Golang programming, 2018.""") + parser.add_argument('expression', nargs=argparse.REMAINDER, help="""mathematical expression string to evaluate; + implicit multiplication is supported""") + + return parser + + +# main +if __name__ == "__main__": + parser = createparser() + main_input, spare_input = parser.parse_known_args(sys.argv[1:]) + # get user's expression + if main_input.expression: + user_expr = main_input.expression[0] + else: + user_expr = spare_input[0] + + # calculation chain + # tokenize user's expression string + tokenizer = Tokenizer(user_expr) + tokens, error_msg = tokenizer.extract_tokens() + if error_msg: + print(error_msg) + sys.exit(1) + elif not tokens: + print('ERROR: no expression was entered') + sys.exit(1) + # add implicit multiplication signs to the list of extracted tokens + mult_signs_adder = Multsignsadder(tokens) + tokens = mult_signs_adder.addmultsigns() + # transform extracted tokens into RPN + rpn = RPN(tokens) + rpn_tokens, error_msg = rpn.convert2rpn() + if error_msg: + print(error_msg) + sys.exit(1) + # replace constants with their numeric equivalents + constsreplacer = Constsreplacer(rpn_tokens) + rpn_tokens = constsreplacer.replace_constants() + # evaluate user's expression + rpncalculator = RPNcalculator(rpn_tokens) + result, error_msg = rpncalculator.evaluate() + if error_msg: + print(error_msg) + sys.exit(1) + else: + print(result) + sys.exit(0) diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py new file mode 100644 index 0000000..78ac9bf --- /dev/null +++ b/final_task/pycalc/pycalclib.py @@ -0,0 +1,94 @@ +"""This module contains lists and dictionaries of data that are used in other pycalc modules """ + +# general import +import math +import operator + + +# r_strings that are used to find operators / functions / etc +r_one_sign_operators = ['^\+', '^-', '^\*', '^/', '^\^', '^%'] +r_two_signs_operators = ['^//', '^\*\*'] +r_comparison_operators = ['^<=', '^>=', '^<', '^>', '^==', '^!='] +r_functions = ['^acosh', '^acos', '^asinh', '^asin', '^atan2', '^atanh', '^atan', '^ceil', '^copysign', '^cosh', + '^cos', '^degrees', '^erfc', '^erf', '^expm1', '^exp', '^fabs', '^factorial', '^floor', '^fmod', + '^gamma', '^gcd', '^hypot', '^isfinite', '^isinf', '^isnan', '^ldexp', '^lgamma', '^log10', '^log1p', + '^log2', '^log', '^pow', '^radians', '^sinh', '^sin', '^sqrt', '^tanh', '^tan', '^trunc', + '^abs', '^round'] +r_negative_functions = ['^-acosh', '^-acos', '^-asinh', '^-asin', '^-atan2', '^-atanh', '^-atan', '^-ceil', + '^-copysign', '^-cosh', '^-cos', '^-degrees', '^-erfc', '^-erf', '^-expm1', '^-exp', + '^-fabs', '^-factorial', '^-floor', '^-fmod', '^-gamma', '^-gcd', '^-hypot', '^-isfinite', + '^-isinf', '^-isnan', '^-ldexp', '^-lgamma', '^-log10', '^-log1p', '^-log2', '^-log', + '^-pow', '^-radians', '^-sinh', '^-sin', '-^sqrt', '^-tanh', '^-tan', '^-trunc', + '^-abs', '^-round'] +r_constants = ['^e', '^pi', '^tau', '^inf', '^nan'] +r_negative_constants = ['^\-e', '^\-pi', '^\-tau', '^\-inf', '^\-nan'] +r_int_numbers = ['^\d+'] +r_negative_int_numbers = ['^\-\d+'] +r_float_numbers = ['^\d+\.\d+|^\.\d+'] +r_negative_float_numbers = ['^\-\d+\.\d+|^\-\.\d+'] +r_brackets = ['^\(', '^\)'] +r_comma = ['^,'] +r_space = ['^\s'] +# all r_strings together +r_strings = r_brackets + r_two_signs_operators + r_one_sign_operators + r_negative_functions + r_functions + \ + r_comparison_operators + r_negative_float_numbers + r_negative_int_numbers + r_negative_constants + \ + r_float_numbers + r_int_numbers + r_constants + r_space + r_comma # the order matters + + +# acceptable constants and functions +constants = ['e', 'pi', 'tau', 'inf', 'nan'] +negative_constants = ['-e', '-pi', '-tau', '-inf', '-nan'] +functions = ['acosh', 'acos', 'asinh', 'asin', 'atan2', 'atanh', 'atan', 'ceil', 'copysign', 'cosh', 'cos', + 'degrees', 'erfc', 'erf', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', + 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log10', + 'log1p', 'log2', 'log', 'modf', 'pow', 'radians', 'sinh', 'sin', 'sqrt', 'tanh', 'tan', 'trunc', + 'round', 'abs'] + +negative_functions = ['-acosh', '-acos', '-asinh', '-asin', '-atan2', '-atanh', '-atan', '-ceil', '-copysign', + '-cosh', '-cos', '-degrees', '-erfc', '-erf', '-exp', '-expm1', '-fabs', '-factorial', + '-floor', '-fmod', '-frexp', '-fsum', '-gamma', '-gcd', '-hypot', '-isclose', '-isfinite', + '-isinf', '-isnan', '-ldexp', '-lgamma', '-log10', '-log1p', '-log2', '-log', '-modf', + '-pow', '-radians', '-sinh', '-sin', '-sqrt', '-tanh', '-tan', '-trunc', '-round', '-abs'] + + +# acceptable operators +operators = ['+', '-', '*', '/', '//', '%', '^', '**'] + + +# acceptable comparison operators +comparison_operators = ['<=', '>=', '<', '>', '==', '!='] + + +# operators precedence +precedence = {'(': 0, ')': 0, '<': 0, '>': 0, '<=': 0, '>=': 0, '==': 0, '!=': 0, '+': 1, '-': 1, + '*': 2, '/': 2, '//': 2, '%': 2, '^': 3, '**': 3} + + +# numeric equivalents of constants +constants_numeric_equivalents = {'e': math.e, '-e': -math.e, 'pi': math.pi, '-pi': -math.pi, 'tau': math.tau, + '-tau': -math.tau} + + +# operator's actions +operators_dict = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, '^': operator.pow, + '**': operator.pow, '//': operator.floordiv, '%': operator.mod, '<': operator.lt, '>': operator.gt, + '<=': operator.le, '>=': operator.ge, '==': operator.eq, '!=': operator.ne} + + +# function's actions +# first element in a tuple associated with each key is a number of arguments for corresponding function +functions_dict = {'acos': (1, math.acos), 'acosh': (1, math.acosh), 'asin': (1, math.asin), 'asinh': (1, math.asinh), + 'atan': (1, math.atan), 'atan2': (2, math.atan2), 'atanh': (1, math.atanh), 'ceil': (1, math.ceil), + 'copysign': (2, math.copysign), 'cos': (1, math.cos), 'cosh': (1, math.cosh), + 'degrees': (1, math.degrees), 'erf': (1, math.erf), 'erfc': (1, math.erfc), 'exp': (1, math.exp), + 'expm1': (1, math.expm1), 'fabs': (1, math.fabs), 'factorial': (1, math. factorial), + 'floor': (1, math.floor), 'fmod': (2, math.fmod), 'gamma': (1, math.gamma), 'gcd': (2, math.gcd), + 'hypot': (2, math.hypot), 'isfinite': (1, math.isfinite), 'isinf': (1, math.isinf), + 'isnan': (1, math.isnan), 'ldexp': (2, math.ldexp), 'lgamma': (1, math.lgamma), 'log': (2, math.log), + 'log10': (1, math.log10), 'log1p': (1, math.log1p), 'log2': (1, math.log2), 'pow': (2, math.pow), + 'radians': (1, math.radians), 'sin': (1, math.sin), 'sinh': (1, math.sinh), 'sqrt': (1, math.sqrt), + 'tan': (1, math.tan), 'tanh': (1, math.tanh), 'trunc': (1, math.trunc), 'abs': (1, lambda x: abs(x)), + 'round': (1, lambda x: round(x)), '-abs': (1, lambda x: -abs(x))} + +if __name__ == '__main__': + print('This module contains lists and dictionaries of data that are used in other pycalc modules') diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py new file mode 100644 index 0000000..926b613 --- /dev/null +++ b/final_task/pycalc/rpn.py @@ -0,0 +1,116 @@ +"""This module contains a class that allows to transform infix notation math tokens into RPN""" + +# import from pycalc self library +from pycalclib import constants, negative_constants, operators, comparison_operators, precedence +from pycalclib import functions, negative_functions + + +class RPN(): + """A model of rpn capable of converting infix to postfix (RPN) notation""" + + def __init__(self, tokens): + """Initialize rpn""" + self.tokens = tokens + self.operators_stack = [] + self.output_queue = [] + self.error_msg = None + self.constants = constants + self.negative_constants = negative_constants + self.operators = operators + self.comparison_operators = comparison_operators + self.precedence = precedence + self.functions = functions + self.negative_functions = negative_functions + + def is_left_associative(self, operator): + """Determines whether operator is left associative""" + if operator in ['^', '**']: + return False + else: + return True + + def is_number(self, token): + """Determines whether token is a number""" + try: + float(token) + return True + except ValueError: + return False + + def convert2rpn(self): + """Converts list of tokens in infix notation into RPN""" + counter = 0 + while counter != (len(self.tokens)): + current_token = self.tokens[counter] + if self.is_number(current_token) or (current_token in (self.constants + self.negative_constants)): + self.output_queue.append(current_token) + counter += 1 + elif current_token in self.functions or current_token in self.negative_functions: + self.operators_stack.append(current_token) + counter += 1 + elif current_token in self.operators or current_token in self.comparison_operators: + if len(self.operators_stack) == 0: + self.operators_stack.append(current_token) + counter += 1 + else: + while (len(self.operators_stack) != 0 + and ((self.operators_stack[-1] in self.functions + or self.operators_stack[-1] in self.negative_functions) + or (self.precedence[self.operators_stack[-1]] > self.precedence[current_token]) + or (self.precedence[self.operators_stack[-1]] == self.precedence[current_token] + and self.is_left_associative(self.operators_stack[-1]))) + and (self.operators_stack[-1] != "(")): + self.output_queue.append(self.operators_stack.pop()) + self.operators_stack.append(current_token) + counter += 1 + elif current_token == '(': + self.operators_stack.append(current_token) + counter += 1 + elif current_token in [')', ',']: + if len(self.operators_stack) == 0 and len(self.output_queue) == 0: + if current_token == ')': + self.error_msg = "ERROR: brackets are not balanced" + elif current_token == ',': + self.error_msg = "ERROR: incorrect usage of ','" + break + elif len(self.operators_stack) == 0 and len(self.output_queue) != 0: + if current_token == ')': + self.error_msg = "ERROR: brackets are not balanced" + elif current_token == ',': + self.error_msg = "ERROR: incorrect usage of ','" + break + else: + while len(self.operators_stack) != 0: + if self.operators_stack[-1] != '(': + self.output_queue.append(self.operators_stack.pop()) + else: + if current_token == ')': + self.operators_stack.pop() # it should be '(' + counter += 1 + break + else: + self.error_msg = "ERROR: brackets are not balanced" + break + if not self.error_msg: + # if there are tokens left in operators_stack consistently add them to output_queue + while self.operators_stack: + remaining_operator = self.operators_stack.pop() + if remaining_operator not in ['(', ')']: + self.output_queue.append(remaining_operator) + else: + self.error_msg = 'ERROR: brackets are not balanced' + + return self.output_queue, self.error_msg + + +if __name__ == '__main__': + print("""This module contains class that allows to transform a list of math tokens in infix notation into list of +tokens in RPN. For example: \n""") + test_tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] + print("Infix_tokens: ", test_tokens) + rpn = RPN(test_tokens) + rpn_tokens, error_msg = rpn.convert2rpn() + if not error_msg: + print('RPN tokens: ', rpn_tokens) + else: + print(error_msg) diff --git a/final_task/pycalc/rpncalculator.py b/final_task/pycalc/rpncalculator.py new file mode 100644 index 0000000..1f9d6c8 --- /dev/null +++ b/final_task/pycalc/rpncalculator.py @@ -0,0 +1,89 @@ +"""This module contains a class that allows to evaluate math expression in RPN""" + +# import from pycalc self library +from pycalclib import operators_dict, functions_dict + + +class RPNcalculator(): + """A model of RPN math expression evaluator""" + + def __init__(self, rpn_tokens): + """Initialize RPNcalculator object""" + + self.rpn_tokens = rpn_tokens + self.error_msg = None + self.operators_dict = operators_dict + self.functions_dict = functions_dict + self.stack = [] + + def evaluate(self): + """Evaluates math expression given in a form of RPN tokens""" + for token in self.rpn_tokens: + if token in self.operators_dict.keys(): + try: + op2, op1 = self.stack.pop(), self.stack.pop() + except IndexError: + self.error_msg = 'ERROR: invalid syntax' + break + if token in ['^', '**'] and (float(op1) < 0 and (not float(op2).is_integer())): # check pow operation + self.error_msg = 'ERROR: negative number cannot be raised to a fractional power' + break + try: + self.stack.append(self.operators_dict[token](op1, op2)) + except Exception as e: + self.error_msg = 'ERROR: {}'.format(e) + break + elif token in self.functions_dict.keys(): + if self.functions_dict[token][0] == 1: + try: + op1 = self.stack.pop() + except IndexError: + self.error_msg = 'ERROR: invalid syntax' + break + if token == 'sqrt' and float(op1) < 0: # check sqrt operation + self.error_msg = "ERROR: a root can't be extracted from a negative number" + break + try: + self.stack.append(self.functions_dict[token][1](op1)) + except Exception: + self.error_msg = 'ERROR: incorrect use of {} function'.format(token) + break + elif self.functions_dict[token][0] == 2: + try: + op2, op1 = self.stack.pop(), self.stack.pop() + except IndexError: + self.error_msg = 'ERROR: invalid syntax' + break + if token == 'pow' and (float(op1) < 0 and (not float(op2).is_integer())): # check pow function + self.error_msg = 'ERROR: negative number cannot be raised to a fractional power' + break + try: + self.stack.append(self.functions_dict[token][1](op1, op2)) + except Exception: + self.error_msg = 'ERROR: incorrect use of {} function'.format(token) + break + else: + self.stack.append(float(token)) + + # final check of result and error message: + if not self.error_msg and len(self.stack) == 1: + result = self.stack.pop() + elif not self.error_msg and len(self.stack) > 1: + result = None + self.error_msg = 'ERROR: invalid syntax' + else: + result = None + + return result, self.error_msg + + +if __name__ == "__main__": + print("This module contains class that allows to evaluate math expression in form of RPN tokens. For example: \n") + test_rpn = ['2', '3', '4', '*', '-', '2', '5', '/', '-'] + print('RPN tokens math expression: ', test_rpn) + rpncalculator = RPNcalculator(test_rpn) + result, error_msg = rpncalculator.evaluate() + if not error_msg: + print('Result: ', result) + else: + print(error_msg) diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py new file mode 100644 index 0000000..839b9d3 --- /dev/null +++ b/final_task/pycalc/tokenizer.py @@ -0,0 +1,99 @@ +"""This module contains a class that allows to find and extract tokens from the user's mathematical expression""" + +# general import +import re +# import from pycalc self library +from pycalclib import r_strings, operators, constants + + +class Tokenizer(): + """A model of tokenizer capable of finding and extracting tokens from string math expression""" + def __init__(self, user_expr): + """Initialize tokenizer""" + self.user_expr = user_expr + self.r_strings = r_strings + self.operators = operators + self.constants = constants + self.error_msg = None + self.tokens = [] + + def is_number(self, token): + """Determines whether token is a number""" + try: + float(token) + return True + except ValueError: + return False + + def consider_sub_signs(self, tokens): + """Considers and replaces several subtraction signs when they follow each other""" + index = 0 + while True: + if tokens[index] == '-' and (tokens[index+1] == '-' or tokens[index+1] == '+'): + tokens.pop(index), tokens.pop(index) + tokens.insert(index, '+') + elif tokens[index] == '+' and tokens[index+1] == '-': + tokens.pop(index), tokens.pop(index) + tokens.insert(index, '-') + elif tokens[index] == '+' and tokens[index+1] == '+': + tokens.pop(index), tokens.pop(index) + tokens.insert(index, '+') + else: + index += 1 + if index < len(tokens)-2: + continue + else: + break + + def check_first_tokens(self, tokens): + """Check whether first two tokens are a negative number (negative constant) + and replaces them by negative number if so""" + if tokens[0] == '-' and (self.is_number(tokens[1]) or tokens[1] in self.constants): + if self.is_number(tokens[1]): + first_neg_token = str(float(tokens[1])*-1) + else: + first_neg_token = '-{}'.format(tokens[1]) + tokens.pop(0), tokens.pop(0) + tokens.insert(0, first_neg_token) + + def extract_tokens(self): + """Extracts tokens from string math expression""" + got_token = False # flag that switches to True every time some token has been found + + while len(self.user_expr) != 0: + for r_string in self.r_strings: + search_result = re.search(r''.join(r_string), self.user_expr) + if search_result is not None: + if (search_result.group(0) == '-' and len(self.tokens) != 0 + and self.tokens[-1] in (['('] + self.operators[2:])): + continue + self.user_expr = self.user_expr[search_result.end():] + if search_result.group(0) != ' ': + self.tokens.append(search_result.group(0)) + got_token = True + break + else: + continue + if got_token is False: # is True when an unknown sign / signs has been found + self.error_msg = "ERROR: invalid syntax" + break + got_token = False # if one of acceptable sign/signs has been found, switch flag back to False for + # the next entrance to the 'for' loop + + if len(self.tokens) >= 2: + self.consider_sub_signs(self.tokens) + self.check_first_tokens(self.tokens) + + return self.tokens, self.error_msg + + +if __name__ == '__main__': + print("This module contains class that allows to extract tokens from math strings. For example: \n") + test_string = '1---1*-5-sin(-3)' + print("Math string: ", test_string) + tokenizer = Tokenizer(test_string) + tokens, error_msg = tokenizer.extract_tokens() + if not error_msg: + print('Extracted tokes: ', tokens) + else: + print(error_msg) From 57f69baa3c244f349597734f49f336a6aba7b4ca Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 00:15:32 +0300 Subject: [PATCH 04/20] implementation3 --- final_task/pycalc/pycalc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 8d411d0..2478744 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -5,11 +5,11 @@ import argparse import sys # import from pycalc self library -from tokenizer import Tokenizer -from addmultsigns import Multsignsadder -from rpn import RPN -from constsreplacer import Constsreplacer -from rpncalculator import RPNcalculator +from .tokenizer import Tokenizer +from .addmultsigns import Multsignsadder +from .rpn import RPN +from .constsreplacer import Constsreplacer +from .rpncalculator import RPNcalculator def createparser(): From ce7661d94431ba98677ba228c4ed9f32f52d7d37 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 00:23:08 +0300 Subject: [PATCH 05/20] pycalc implementation 4 --- final_task/pycalc/addmultsigns.py | 2 +- final_task/pycalc/constsreplacer.py | 2 +- final_task/pycalc/rpn.py | 4 ++-- final_task/pycalc/rpncalculator.py | 2 +- final_task/pycalc/tokenizer.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 1858d87..46f9d01 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -1,7 +1,7 @@ """This module contains a class that allows to take into account implicit multiplication signs""" # import from pycalc self library -from pycalclib import constants, functions, negative_functions +from .pycalclib import constants, functions, negative_functions class Multsignsadder(): diff --git a/final_task/pycalc/constsreplacer.py b/final_task/pycalc/constsreplacer.py index 6a93d3f..467b7d7 100644 --- a/final_task/pycalc/constsreplacer.py +++ b/final_task/pycalc/constsreplacer.py @@ -1,7 +1,7 @@ """This module contains a class that allows to replace constants by their numeric equivalents""" # import from pyalc self library -from pycalclib import constants_numeric_equivalents +from .pycalclib import constants_numeric_equivalents class Constsreplacer(): diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index 926b613..efb32ac 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -1,8 +1,8 @@ """This module contains a class that allows to transform infix notation math tokens into RPN""" # import from pycalc self library -from pycalclib import constants, negative_constants, operators, comparison_operators, precedence -from pycalclib import functions, negative_functions +from .pycalclib import constants, negative_constants, operators, comparison_operators, precedence +from .pycalclib import functions, negative_functions class RPN(): diff --git a/final_task/pycalc/rpncalculator.py b/final_task/pycalc/rpncalculator.py index 1f9d6c8..65ea1d7 100644 --- a/final_task/pycalc/rpncalculator.py +++ b/final_task/pycalc/rpncalculator.py @@ -1,7 +1,7 @@ """This module contains a class that allows to evaluate math expression in RPN""" # import from pycalc self library -from pycalclib import operators_dict, functions_dict +from .pycalclib import operators_dict, functions_dict class RPNcalculator(): diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index 839b9d3..4a56286 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -3,7 +3,7 @@ # general import import re # import from pycalc self library -from pycalclib import r_strings, operators, constants +from .pycalclib import r_strings, operators, constants class Tokenizer(): From 0e5a5fa2784a9036a0d1dfbef7dc13032df0b453 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 00:31:43 +0300 Subject: [PATCH 06/20] pycalc implementation 5 --- final_task/pycalc/UnitTests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/final_task/pycalc/UnitTests.py b/final_task/pycalc/UnitTests.py index a0783af..9cb22e7 100644 --- a/final_task/pycalc/UnitTests.py +++ b/final_task/pycalc/UnitTests.py @@ -4,11 +4,11 @@ import unittest # import of classes to be tested from pycalc modules -from tokenizer import Tokenizer -from addmultsigns import Multsignsadder -from rpn import RPN -from constsreplacer import Constsreplacer -from rpncalculator import RPNcalculator +from .tokenizer import Tokenizer +from .addmultsigns import Multsignsadder +from .rpn import RPN +from .constsreplacer import Constsreplacer +from .rpncalculator import RPNcalculator # Tests of 'Tokenizer' class from 'tokenizer' module From 4aefbfeeffd798651d8a5beb70619e4b5753790a Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 00:49:56 +0300 Subject: [PATCH 07/20] pycalc implementation 6 --- final_task/pycalc/pycalc.py | 9 ++++++--- final_task/setup.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/pycalc.py index 2478744..c2e5772 100644 --- a/final_task/pycalc/pycalc.py +++ b/final_task/pycalc/pycalc.py @@ -22,9 +22,8 @@ def createparser(): return parser - -# main -if __name__ == "__main__": +def main(): + """calculation chain""" parser = createparser() main_input, spare_input = parser.parse_known_args(sys.argv[1:]) # get user's expression @@ -64,3 +63,7 @@ def createparser(): else: print(result) sys.exit(0) + +# main +if __name__ == "__main__": + main() diff --git a/final_task/setup.py b/final_task/setup.py index 8c1c271..eb12249 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -7,5 +7,5 @@ author_email = 'antt0n.chern1chenk0@gmail.com', description = 'pure Python command line calculator', packages = ['pycalc'], - entry_points = {'console_scripts': ['pycalc = pycalc.pycalc']}, + entry_points = {'console_scripts': ['pycalc = pycalc.pycalc:main']}, classifiers=["Programming Language :: Python :: 3.6", "Operating System :: Linux Mint"]) From 1a135a1eb7da60a1be5788fb6b20f2b6387a9907 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 01:05:27 +0300 Subject: [PATCH 08/20] implementation 7 --- final_task/pycalc/{pycalc.py => __main__.py} | 0 final_task/setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename final_task/pycalc/{pycalc.py => __main__.py} (100%) diff --git a/final_task/pycalc/pycalc.py b/final_task/pycalc/__main__.py similarity index 100% rename from final_task/pycalc/pycalc.py rename to final_task/pycalc/__main__.py diff --git a/final_task/setup.py b/final_task/setup.py index eb12249..aee268f 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -7,5 +7,5 @@ author_email = 'antt0n.chern1chenk0@gmail.com', description = 'pure Python command line calculator', packages = ['pycalc'], - entry_points = {'console_scripts': ['pycalc = pycalc.pycalc:main']}, + entry_points = {'console_scripts': ['pycalc = pycalc.__main__:main']}, classifiers=["Programming Language :: Python :: 3.6", "Operating System :: Linux Mint"]) From 1150f6d231bfff8352b415ec3024a91e983374aa Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 01:12:45 +0300 Subject: [PATCH 09/20] implementation8 --- final_task/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/final_task/setup.py b/final_task/setup.py index aee268f..1328d97 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -6,6 +6,6 @@ author = 'Anton Charnichenka', author_email = 'antt0n.chern1chenk0@gmail.com', description = 'pure Python command line calculator', - packages = ['pycalc'], + packages = setuptools.find_packages(), entry_points = {'console_scripts': ['pycalc = pycalc.__main__:main']}, classifiers=["Programming Language :: Python :: 3.6", "Operating System :: Linux Mint"]) From 036fa96f3a3c4e26a31d8a0a0c98f4d174c430c1 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 01:21:23 +0300 Subject: [PATCH 10/20] implementation9 --- final_task/pycalc/__main__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc/__main__.py index c2e5772..8825dff 100644 --- a/final_task/pycalc/__main__.py +++ b/final_task/pycalc/__main__.py @@ -17,7 +17,7 @@ def createparser(): parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', epilog="""Anton Charnichenka for EPAM: Introduction to Python and Golang programming, 2018.""") - parser.add_argument('expression', nargs=argparse.REMAINDER, help="""mathematical expression string to evaluate; + parser.add_argument('expression', help="""mathematical expression string to evaluate; implicit multiplication is supported""") return parser @@ -25,12 +25,14 @@ def createparser(): def main(): """calculation chain""" parser = createparser() - main_input, spare_input = parser.parse_known_args(sys.argv[1:]) + namespace = parser.parse_args(sys.argv[1:]) + user_expr = namespace.expression + #main_input, spare_input = parser.parse_known_args(sys.argv[1:]) # get user's expression - if main_input.expression: - user_expr = main_input.expression[0] - else: - user_expr = spare_input[0] + #if main_input.expression: + #user_expr = main_input.expression[0] + #else: + #user_expr = spare_input[0] # calculation chain # tokenize user's expression string From 63f81b64eaafa3d670f65ce8020184e9924a26a3 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 12:38:41 +0300 Subject: [PATCH 11/20] implementation 10 - corrections for pip8 --- final_task/pycalc/UnitTests.py | 50 ++++++++++++++--------------- final_task/pycalc/__main__.py | 18 ++++------- final_task/pycalc/addmultsigns.py | 6 ++-- final_task/pycalc/constsreplacer.py | 8 ++--- final_task/pycalc/pycalclib.py | 26 +++++++-------- final_task/pycalc/rpn.py | 8 ++--- final_task/pycalc/rpncalculator.py | 8 ++--- final_task/pycalc/tokenizer.py | 8 ++--- final_task/setup.py | 19 ++++++----- 9 files changed, 73 insertions(+), 78 deletions(-) diff --git a/final_task/pycalc/UnitTests.py b/final_task/pycalc/UnitTests.py index 9cb22e7..62663f8 100644 --- a/final_task/pycalc/UnitTests.py +++ b/final_task/pycalc/UnitTests.py @@ -14,7 +14,7 @@ # Tests of 'Tokenizer' class from 'tokenizer' module class TokenizerTestCase(unittest.TestCase): """Tests for Tokenizer class""" - + def test_extract_operators_and_pos_int_numbers(self): """Are operators and positive int numbers extracted properly?""" user_expr = '1+2-3*4/5^6**7//8%9' @@ -23,7 +23,7 @@ def test_extract_operators_and_pos_int_numbers(self): self.assertEqual(tokens, ['1', '+', '2', '-', '3', '*', '4', '/', '5', '^', '6', '**', '7', '//', '8', '%', '9']) self.assertEqual(error_msg, None) - + def test_extract_operators_and_neg_int_numbers(self): """Are operators and negative int numbers extracted properly?""" user_expr = '-1+-2--3*-4/-5^-6**-7//-8%-9' @@ -32,7 +32,7 @@ def test_extract_operators_and_neg_int_numbers(self): self.assertEqual(tokens, ['-1.0', '-', '2', '+', '3', '*', '-4', '/', '-5', '^', '-6', '**', '-7', '//', '-8', '%', '-9']) self.assertEqual(error_msg, None) - + def test_extract_pos_float_numbers(self): """Are positive float numbers extracted properly?""" user_expr = '0.1+1.55-112.12' @@ -40,7 +40,7 @@ def test_extract_pos_float_numbers(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['0.1', '+', '1.55', '-', '112.12']) self.assertEqual(error_msg, None) - + def test_extract_neg_float_numbers(self): """Are negative float numbers extracted properly?""" user_expr = '-0.1+-1.55--112.12' @@ -48,7 +48,7 @@ def test_extract_neg_float_numbers(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-0.1', '-', '1.55', '+', '112.12']) self.assertEqual(error_msg, None) - + def test_extract_comparison_operators(self): """Are comparison operators extracted properly?""" user_expr = '><>=<=!===' @@ -56,7 +56,7 @@ def test_extract_comparison_operators(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['>', '<', '>=', '<=', '!=', '==']) self.assertEqual(error_msg, None) - + def test_extract_pos_constants(self): """Are positive constants extracted properly?""" user_expr = 'e+pi-tau/inf*nan' @@ -64,7 +64,7 @@ def test_extract_pos_constants(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['e', '+', 'pi', '-', 'tau', '/', 'inf', '*', 'nan']) self.assertEqual(error_msg, None) - + def test_extract_neg_constants(self): """Are negative constants extracted properly?""" user_expr = '-e+-pi--tau/-inf*-nan' @@ -72,7 +72,7 @@ def test_extract_neg_constants(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-e', '-', 'pi', '+', 'tau', '/', '-inf', '*', '-nan']) self.assertEqual(error_msg, None) - + def test_extract_brackets(self): """Are brackets extracted properly?""" user_expr = '()' @@ -80,7 +80,7 @@ def test_extract_brackets(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['(', ')']) self.assertEqual(error_msg, None) - + def test_extract_comma(self): """Is comma extracted?""" user_expr = 'pow(2,3)' @@ -88,7 +88,7 @@ def test_extract_comma(self): tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['pow', '(', '2', ',', '3', ')']) self.assertEqual(error_msg, None) - + def test_extract_functions(self): """Are functions extracted properly?""" user_expr = "round(sin(2)-asin(1))-abs(exp(3))" @@ -114,7 +114,7 @@ def test_is_number_method(self): for token in tokens: is_numbers.append(tokenizer.is_number(token)) self.assertEqual(is_numbers, [True, True, True, False]) - + def test_extract_tokens_error_msg(self): """Is error_message created?""" user_expr = "2+shikaka(3)" @@ -126,7 +126,7 @@ def test_extract_tokens_error_msg(self): # Tests of 'Multsignsadder' class from 'addmultsigns' module class MultsignsadderTestCase(unittest.TestCase): """Tests for Multsignsadder class""" - + def test_is_number_method(self): """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" tokens = ['2.3', '-0.6', '5', 'sin', 'exp'] @@ -135,7 +135,7 @@ def test_is_number_method(self): for token in tokens: is_numbers.append(mult_signs_adder.is_number(token)) self.assertEqual(is_numbers, [True, True, True, False, False]) - + def test_addmultsigns_add_mult_signs(self): """Are multiplication signs added to where they implicit were to be in expression?""" tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', '-', '9', '(', '1', '+', '10', ')'] @@ -143,7 +143,7 @@ def test_addmultsigns_add_mult_signs(self): extd_tokens = mult_signs_adder.addmultsigns() self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '-', '9', '*', '(', '1', '+', '10', ')']) - + def test_addmultsigns_dont_add_mult_signs(self): """Aren't multiplication signs added if it's not needed?""" tokens = ['2', '+', '3', '*', '5'] @@ -169,7 +169,7 @@ def test_consider_log_args_method(self): # Tests of 'RPN class' from 'rpn' module class RPNTestCase(unittest.TestCase): """Tests for RPN class""" - + def test_is_left_associative_method(self): """Are left associative operators recognized?""" tokens = ['^', '**', '+', '/'] @@ -178,7 +178,7 @@ def test_is_left_associative_method(self): for token in tokens: is_left_associative.append(rpn.is_left_associative(token)) self.assertEqual(is_left_associative, [False, False, True, True]) - + def test_is_number_method(self): """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" tokens = ['1.3', '-0.5', '/', '%', '9'] @@ -187,7 +187,7 @@ def test_is_number_method(self): for token in tokens: is_numbers.append(rpn.is_number(token)) self.assertEqual(is_numbers, [True, True, False, False, True]) - + def test_convert2rpn_method(self): """Does 'convert2rpn' method work correctly?""" tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] @@ -195,7 +195,7 @@ def test_convert2rpn_method(self): result, error_msg = rpn.convert2rpn() self.assertEqual(result, ['-pi', '2.23', 'round', '*', '5', '//', '2', '3', 'pow', '*']) self.assertEqual(error_msg, None) - + def test_convert2rpn_method_error_msg(self): """Is error_message created?""" tokens = ['(', '2', '+', '3', ')', ')'] @@ -207,7 +207,7 @@ def test_convert2rpn_method_error_msg(self): # Tests of 'Constsreplacer' class from 'constsreplacer' module class ConstsreplacerTestCase(unittest.TestCase): """Tests for Constsreplacer class""" - + def test_replace_constants_method(self): """Are constants replaced and not constants aren't replaced?""" tokens = ['e', '-e', 'pi', '-pi', 'tau', '-tau', '2', 'cos', 'inf', '-nan', '+'] @@ -222,7 +222,7 @@ def test_replace_constants_method(self): # Tests of 'RPNcalculator' class from 'rpncalculator' module class RPNcalculatorTestCase(unittest.TestCase): """Tests for RPNcalculator class""" - + def test_evaluate_method_result(self): """Does 'evaluate' method actually evaluate RPN math expression and give out correct result?""" rpn_tokens = ['2', 'sqrt', '3', '/', '3.14', '*', 'tan'] @@ -230,14 +230,14 @@ def test_evaluate_method_result(self): result, error_msg = rpncalculator.evaluate() self.assertEqual(result, 11.009005500434151) self.assertEqual(error_msg, None) - + def test_evaluate_method_error_msg_zero_division(self): """Is 'division by zero' error message created?""" rpn_tokens = ['2', '0', '/'] rpncalculator = RPNcalculator(rpn_tokens) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, 'ERROR: float division by zero') - + def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): """Is 'negative number cannot be raised to a fractional power' error message created?""" rpn_tokens = [['-2', '0.5', '**'], ['-2', '0.5', '^']] @@ -247,21 +247,21 @@ def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): error_msgs.append(rpncalculator.evaluate()[1]) for error_msg in error_msgs: self.assertEqual(error_msg, 'ERROR: negative number cannot be raised to a fractional power') - + def test_evaluate_method_error_msg_neg_num_sqrt(self): """Is 'root can't be extracted from a negative number' error message created?""" rpn_tokens = ['-2', 'sqrt'] rpncalculator = RPNcalculator(rpn_tokens) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, "ERROR: a root can't be extracted from a negative number") - + def test_evaluate_method_error_msg_invalid_syntax(self): """Is 'invalid syntax' error message created?""" rpn_tokens = ['2', '+'] rpncalculator = RPNcalculator(rpn_tokens) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, "ERROR: invalid syntax") - + if __name__ == '__main__': unittest.main() diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc/__main__.py index 8825dff..c34d102 100644 --- a/final_task/pycalc/__main__.py +++ b/final_task/pycalc/__main__.py @@ -17,22 +17,17 @@ def createparser(): parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', epilog="""Anton Charnichenka for EPAM: Introduction to Python and Golang programming, 2018.""") - parser.add_argument('expression', help="""mathematical expression string to evaluate; - implicit multiplication is supported""") - + parser.add_argument('expression', help="""mathematical expression string to evaluate; + implicit multiplication is supported""") + return parser + def main(): - """calculation chain""" + """Calculate user's expression""" parser = createparser() namespace = parser.parse_args(sys.argv[1:]) user_expr = namespace.expression - #main_input, spare_input = parser.parse_known_args(sys.argv[1:]) - # get user's expression - #if main_input.expression: - #user_expr = main_input.expression[0] - #else: - #user_expr = spare_input[0] # calculation chain # tokenize user's expression string @@ -65,7 +60,8 @@ def main(): else: print(result) sys.exit(0) - + + # main if __name__ == "__main__": main() diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 46f9d01..65a638c 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -6,7 +6,7 @@ class Multsignsadder(): """A model of mult_signs_adder capable of adding implicit multiplications signs in list of tokens""" - + def __init__(self, tokens): """Initialize mult_signs_adder""" self.tokens = tokens @@ -52,7 +52,7 @@ def addmultsigns(self): for index in range(len(self.tokens)-1): self.extended_tokens.append(self.tokens[index]) if (self.is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants) - or (self.tokens[index+1] in self.functions) + or (self.tokens[index+1] in self.functions) or (self.tokens[index+1] == '('))): self.extended_tokens.append('*') continue @@ -68,7 +68,7 @@ def addmultsigns(self): if __name__ == '__main__': - print("""This module contains class that allows to insert multiplications signs to where they where supposed + print("""This module contains class that allows to insert multiplications signs to where they where supposed to be in a list with math tokens. For example: \n""") test_tokens = ['-0.1', 'tan', '+', '23', '*', '-sin', '(', '3', ')', '/', '.12', 'e'] mult_signs_adder = Multsignsadder(test_tokens) diff --git a/final_task/pycalc/constsreplacer.py b/final_task/pycalc/constsreplacer.py index 467b7d7..fd74d05 100644 --- a/final_task/pycalc/constsreplacer.py +++ b/final_task/pycalc/constsreplacer.py @@ -6,23 +6,23 @@ class Constsreplacer(): """A model of constants replacer capable of replacing constants (from math module) by their numeric equivalents""" - + def __init__(self, rpn_tokens): """Initialize constsreplacer""" self.rpn_tokens = rpn_tokens self.constants_numeric_equivalents = constants_numeric_equivalents - + def replace_constants(self): """Replaces tokens which are math module constants by their numeric equivalent""" for index in range(len(self.rpn_tokens)): if self.rpn_tokens[index] in self.constants_numeric_equivalents.keys(): self.rpn_tokens[index] = str(self.constants_numeric_equivalents[self.rpn_tokens[index]]) - + return self.rpn_tokens if __name__ == '__main__': - print("""This module contains class that allows to replace tokens which are math module constants by their + print("""This module contains class that allows to replace tokens which are math module constants by their numeric equivalents. For example: \n""") test_tokens = ['2', '*', 'nan', '-', '-inf', '+', '-tau', '*', '-pi', '+', 'e'] print('RPN tokens with constants: ', test_tokens) diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py index 78ac9bf..201eb00 100644 --- a/final_task/pycalc/pycalclib.py +++ b/final_task/pycalc/pycalclib.py @@ -38,9 +38,9 @@ # acceptable constants and functions constants = ['e', 'pi', 'tau', 'inf', 'nan'] negative_constants = ['-e', '-pi', '-tau', '-inf', '-nan'] -functions = ['acosh', 'acos', 'asinh', 'asin', 'atan2', 'atanh', 'atan', 'ceil', 'copysign', 'cosh', 'cos', - 'degrees', 'erfc', 'erf', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', - 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log10', +functions = ['acosh', 'acos', 'asinh', 'asin', 'atan2', 'atanh', 'atan', 'ceil', 'copysign', 'cosh', 'cos', + 'degrees', 'erfc', 'erf', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', + 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log10', 'log1p', 'log2', 'log', 'modf', 'pow', 'radians', 'sinh', 'sin', 'sqrt', 'tanh', 'tan', 'trunc', 'round', 'abs'] @@ -51,7 +51,7 @@ '-pow', '-radians', '-sinh', '-sin', '-sqrt', '-tanh', '-tan', '-trunc', '-round', '-abs'] -# acceptable operators +# acceptable operators operators = ['+', '-', '*', '/', '//', '%', '^', '**'] @@ -65,7 +65,7 @@ # numeric equivalents of constants -constants_numeric_equivalents = {'e': math.e, '-e': -math.e, 'pi': math.pi, '-pi': -math.pi, 'tau': math.tau, +constants_numeric_equivalents = {'e': math.e, '-e': -math.e, 'pi': math.pi, '-pi': -math.pi, 'tau': math.tau, '-tau': -math.tau} @@ -77,17 +77,17 @@ # function's actions # first element in a tuple associated with each key is a number of arguments for corresponding function -functions_dict = {'acos': (1, math.acos), 'acosh': (1, math.acosh), 'asin': (1, math.asin), 'asinh': (1, math.asinh), - 'atan': (1, math.atan), 'atan2': (2, math.atan2), 'atanh': (1, math.atanh), 'ceil': (1, math.ceil), - 'copysign': (2, math.copysign), 'cos': (1, math.cos), 'cosh': (1, math.cosh), - 'degrees': (1, math.degrees), 'erf': (1, math.erf), 'erfc': (1, math.erfc), 'exp': (1, math.exp), +functions_dict = {'acos': (1, math.acos), 'acosh': (1, math.acosh), 'asin': (1, math.asin), 'asinh': (1, math.asinh), + 'atan': (1, math.atan), 'atan2': (2, math.atan2), 'atanh': (1, math.atanh), 'ceil': (1, math.ceil), + 'copysign': (2, math.copysign), 'cos': (1, math.cos), 'cosh': (1, math.cosh), + 'degrees': (1, math.degrees), 'erf': (1, math.erf), 'erfc': (1, math.erfc), 'exp': (1, math.exp), 'expm1': (1, math.expm1), 'fabs': (1, math.fabs), 'factorial': (1, math. factorial), - 'floor': (1, math.floor), 'fmod': (2, math.fmod), 'gamma': (1, math.gamma), 'gcd': (2, math.gcd), - 'hypot': (2, math.hypot), 'isfinite': (1, math.isfinite), 'isinf': (1, math.isinf), + 'floor': (1, math.floor), 'fmod': (2, math.fmod), 'gamma': (1, math.gamma), 'gcd': (2, math.gcd), + 'hypot': (2, math.hypot), 'isfinite': (1, math.isfinite), 'isinf': (1, math.isinf), 'isnan': (1, math.isnan), 'ldexp': (2, math.ldexp), 'lgamma': (1, math.lgamma), 'log': (2, math.log), 'log10': (1, math.log10), 'log1p': (1, math.log1p), 'log2': (1, math.log2), 'pow': (2, math.pow), - 'radians': (1, math.radians), 'sin': (1, math.sin), 'sinh': (1, math.sinh), 'sqrt': (1, math.sqrt), - 'tan': (1, math.tan), 'tanh': (1, math.tanh), 'trunc': (1, math.trunc), 'abs': (1, lambda x: abs(x)), + 'radians': (1, math.radians), 'sin': (1, math.sin), 'sinh': (1, math.sinh), 'sqrt': (1, math.sqrt), + 'tan': (1, math.tan), 'tanh': (1, math.tanh), 'trunc': (1, math.trunc), 'abs': (1, lambda x: abs(x)), 'round': (1, lambda x: round(x)), '-abs': (1, lambda x: -abs(x))} if __name__ == '__main__': diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index efb32ac..4a75ab6 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -7,7 +7,7 @@ class RPN(): """A model of rpn capable of converting infix to postfix (RPN) notation""" - + def __init__(self, tokens): """Initialize rpn""" self.tokens = tokens @@ -36,7 +36,7 @@ def is_number(self, token): return True except ValueError: return False - + def convert2rpn(self): """Converts list of tokens in infix notation into RPN""" counter = 0 @@ -99,7 +99,7 @@ def convert2rpn(self): self.output_queue.append(remaining_operator) else: self.error_msg = 'ERROR: brackets are not balanced' - + return self.output_queue, self.error_msg @@ -111,6 +111,6 @@ def convert2rpn(self): rpn = RPN(test_tokens) rpn_tokens, error_msg = rpn.convert2rpn() if not error_msg: - print('RPN tokens: ', rpn_tokens) + print('RPN tokens: ', rpn_tokens) else: print(error_msg) diff --git a/final_task/pycalc/rpncalculator.py b/final_task/pycalc/rpncalculator.py index 65ea1d7..7b0ee27 100644 --- a/final_task/pycalc/rpncalculator.py +++ b/final_task/pycalc/rpncalculator.py @@ -6,16 +6,16 @@ class RPNcalculator(): """A model of RPN math expression evaluator""" - + def __init__(self, rpn_tokens): """Initialize RPNcalculator object""" - + self.rpn_tokens = rpn_tokens self.error_msg = None self.operators_dict = operators_dict self.functions_dict = functions_dict self.stack = [] - + def evaluate(self): """Evaluates math expression given in a form of RPN tokens""" for token in self.rpn_tokens: @@ -73,7 +73,7 @@ def evaluate(self): self.error_msg = 'ERROR: invalid syntax' else: result = None - + return result, self.error_msg diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index 4a56286..e79a363 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -1,6 +1,6 @@ """This module contains a class that allows to find and extract tokens from the user's mathematical expression""" -# general import +# general import import re # import from pycalc self library from .pycalclib import r_strings, operators, constants @@ -59,7 +59,7 @@ def check_first_tokens(self, tokens): def extract_tokens(self): """Extracts tokens from string math expression""" got_token = False # flag that switches to True every time some token has been found - + while len(self.user_expr) != 0: for r_string in self.r_strings: search_result = re.search(r''.join(r_string), self.user_expr) @@ -87,13 +87,13 @@ def extract_tokens(self): return self.tokens, self.error_msg -if __name__ == '__main__': +if __name__ == '__main__': print("This module contains class that allows to extract tokens from math strings. For example: \n") test_string = '1---1*-5-sin(-3)' print("Math string: ", test_string) tokenizer = Tokenizer(test_string) tokens, error_msg = tokenizer.extract_tokens() if not error_msg: - print('Extracted tokes: ', tokens) + print('Extracted tokes: ', tokens) else: print(error_msg) diff --git a/final_task/setup.py b/final_task/setup.py index 1328d97..b850946 100644 --- a/final_task/setup.py +++ b/final_task/setup.py @@ -1,11 +1,10 @@ -from setuptools import setup +import setuptools -setup( - name = 'pycalc', - version = '1.0.0', - author = 'Anton Charnichenka', - author_email = 'antt0n.chern1chenk0@gmail.com', - description = 'pure Python command line calculator', - packages = setuptools.find_packages(), - entry_points = {'console_scripts': ['pycalc = pycalc.__main__:main']}, - classifiers=["Programming Language :: Python :: 3.6", "Operating System :: Linux Mint"]) +setuptools.setup(name='pycalc', + version='1.0.0', + author='Anton Charnichenka', + author_email='antt0n.chern1chenk0@gmail.com', + description='pure Python command line calculator', + packages=setuptools.find_packages(), + entry_points={'console_scripts': ['pycalc = pycalc.__main__:main']}, + classifiers=["Programming Language :: Python :: 3.6", "Operating System :: Linux Mint"]) From ddaef8cf7676ab52eaa6bec766912f227366b217 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 13:20:36 +0300 Subject: [PATCH 12/20] implementation 11 corrections pip8 2 --- final_task/pycalc/__main__.py | 2 +- final_task/pycalc/pycalclib.py | 46 +++++++++++++++++----------------- final_task/pycalc/tokenizer.py | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc/__main__.py index c34d102..04d7815 100644 --- a/final_task/pycalc/__main__.py +++ b/final_task/pycalc/__main__.py @@ -15,7 +15,7 @@ def createparser(): """Creates parser with one positional argument 'expression' to parse user's mathematical expression""" parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', - epilog="""Anton Charnichenka for EPAM: Introduction to Python + epilog="""Anton Charnichenka for EPAM: Introduction to Python and Golang programming, 2018.""") parser.add_argument('expression', help="""mathematical expression string to evaluate; implicit multiplication is supported""") diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py index 201eb00..a72b4ce 100644 --- a/final_task/pycalc/pycalclib.py +++ b/final_task/pycalc/pycalclib.py @@ -6,29 +6,29 @@ # r_strings that are used to find operators / functions / etc -r_one_sign_operators = ['^\+', '^-', '^\*', '^/', '^\^', '^%'] -r_two_signs_operators = ['^//', '^\*\*'] -r_comparison_operators = ['^<=', '^>=', '^<', '^>', '^==', '^!='] -r_functions = ['^acosh', '^acos', '^asinh', '^asin', '^atan2', '^atanh', '^atan', '^ceil', '^copysign', '^cosh', - '^cos', '^degrees', '^erfc', '^erf', '^expm1', '^exp', '^fabs', '^factorial', '^floor', '^fmod', - '^gamma', '^gcd', '^hypot', '^isfinite', '^isinf', '^isnan', '^ldexp', '^lgamma', '^log10', '^log1p', - '^log2', '^log', '^pow', '^radians', '^sinh', '^sin', '^sqrt', '^tanh', '^tan', '^trunc', - '^abs', '^round'] -r_negative_functions = ['^-acosh', '^-acos', '^-asinh', '^-asin', '^-atan2', '^-atanh', '^-atan', '^-ceil', - '^-copysign', '^-cosh', '^-cos', '^-degrees', '^-erfc', '^-erf', '^-expm1', '^-exp', - '^-fabs', '^-factorial', '^-floor', '^-fmod', '^-gamma', '^-gcd', '^-hypot', '^-isfinite', - '^-isinf', '^-isnan', '^-ldexp', '^-lgamma', '^-log10', '^-log1p', '^-log2', '^-log', - '^-pow', '^-radians', '^-sinh', '^-sin', '-^sqrt', '^-tanh', '^-tan', '^-trunc', - '^-abs', '^-round'] -r_constants = ['^e', '^pi', '^tau', '^inf', '^nan'] -r_negative_constants = ['^\-e', '^\-pi', '^\-tau', '^\-inf', '^\-nan'] -r_int_numbers = ['^\d+'] -r_negative_int_numbers = ['^\-\d+'] -r_float_numbers = ['^\d+\.\d+|^\.\d+'] -r_negative_float_numbers = ['^\-\d+\.\d+|^\-\.\d+'] -r_brackets = ['^\(', '^\)'] -r_comma = ['^,'] -r_space = ['^\s'] +r_one_sign_operators = [r'^\+', r'^-', r'^\*', r'^/', r'^\^', r'^%'] +r_two_signs_operators = [r'^//', r'^\*\*'] +r_comparison_operators = [r'^<=', r'^>=', r'^<', r'^>', r'^==', r'^!='] +r_functions = [r'^acosh', r'^acos', r'^asinh', r'^asin', r'^atan2', r'^atanh', r'^atan', r'^ceil', r'^copysign', r'^cosh', + r'^cos', r'^degrees', r'^erfc', r'^erf', r'^expm1', r'^exp', r'^fabs', r'^factorial', r'^floor', r'^fmod', + r'^gamma', r'^gcd', r'^hypot', r'^isfinite', r'^isinf', r'^isnan', r'^ldexp', r'^lgamma', r'^log10', r'^log1p', + r'^log2', r'^log', r'^pow', r'^radians', r'^sinh', r'^sin', r'^sqrt', r'^tanh', r'^tan', r'^trunc', + r'^abs', r'^round'] +r_negative_functions = [r'^-acosh', r'^-acos', r'^-asinh', r'^-asin', r'^-atan2', r'^-atanh', r'^-atan', r'^-ceil', + r'^-copysign', r'^-cosh', r'^-cos', r'^-degrees', r'^-erfc', r'^-erf', r'^-expm1', r'^-exp', + r'^-fabs', r'^-factorial', r'^-floor', r'^-fmod', r'^-gamma', r'^-gcd', r'^-hypot', r'^-isfinite', + r'^-isinf', r'^-isnan', r'^-ldexp', r'^-lgamma', r'^-log10', r'^-log1p', r'^-log2', r'^-log', + r'^-pow', r'^-radians', r'^-sinh', r'^-sin', r'-^sqrt', r'^-tanh', r'^-tan', r'^-trunc', + r'^-abs', r'^-round'] +r_constants = [r'^e', r'^pi', r'^tau', r'^inf', r'^nan'] +r_negative_constants = [r'^\-e', r'^\-pi', r'^\-tau', r'^\-inf', r'^\-nan'] +r_int_numbers = [r'^\d+'] +r_negative_int_numbers = [r'^\-\d+'] +r_float_numbers = [r'^\d+\.\d+|^\.\d+'] +r_negative_float_numbers = [r'^\-\d+\.\d+|^\-\.\d+'] +r_brackets = [r'^\(', r'^\)'] +r_comma = [r'^,'] +r_space = [r'^\s'] # all r_strings together r_strings = r_brackets + r_two_signs_operators + r_one_sign_operators + r_negative_functions + r_functions + \ r_comparison_operators + r_negative_float_numbers + r_negative_int_numbers + r_negative_constants + \ diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index e79a363..9351d1d 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -62,7 +62,7 @@ def extract_tokens(self): while len(self.user_expr) != 0: for r_string in self.r_strings: - search_result = re.search(r''.join(r_string), self.user_expr) + search_result = re.search(r_string, self.user_expr) if search_result is not None: if (search_result.group(0) == '-' and len(self.tokens) != 0 and self.tokens[-1] in (['('] + self.operators[2:])): From 03d82b6f0ea8533931e2f1c4f42895e619daed60 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Tue, 6 Nov 2018 13:27:47 +0300 Subject: [PATCH 13/20] implementation 12 corrections pip8 3 --- final_task/pycalc/pycalclib.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py index a72b4ce..60256b5 100644 --- a/final_task/pycalc/pycalclib.py +++ b/final_task/pycalc/pycalclib.py @@ -9,17 +9,17 @@ r_one_sign_operators = [r'^\+', r'^-', r'^\*', r'^/', r'^\^', r'^%'] r_two_signs_operators = [r'^//', r'^\*\*'] r_comparison_operators = [r'^<=', r'^>=', r'^<', r'^>', r'^==', r'^!='] -r_functions = [r'^acosh', r'^acos', r'^asinh', r'^asin', r'^atan2', r'^atanh', r'^atan', r'^ceil', r'^copysign', r'^cosh', - r'^cos', r'^degrees', r'^erfc', r'^erf', r'^expm1', r'^exp', r'^fabs', r'^factorial', r'^floor', r'^fmod', - r'^gamma', r'^gcd', r'^hypot', r'^isfinite', r'^isinf', r'^isnan', r'^ldexp', r'^lgamma', r'^log10', r'^log1p', - r'^log2', r'^log', r'^pow', r'^radians', r'^sinh', r'^sin', r'^sqrt', r'^tanh', r'^tan', r'^trunc', - r'^abs', r'^round'] +r_functions = [r'^acosh', r'^acos', r'^asinh', r'^asin', r'^atan2', r'^atanh', r'^atan', r'^ceil', r'^copysign', + r'^cosh', r'^cos', r'^degrees', r'^erfc', r'^erf', r'^expm1', r'^exp', r'^fabs', r'^factorial', + r'^floor', r'^fmod', r'^gamma', r'^gcd', r'^hypot', r'^isfinite', r'^isinf', r'^isnan', r'^ldexp', + r'^lgamma', r'^log10', r'^log1p', r'^log2', r'^log', r'^pow', r'^radians', r'^sinh', r'^sin', + r'^sqrt', r'^tanh', r'^tan', r'^trunc', r'^abs', r'^round'] r_negative_functions = [r'^-acosh', r'^-acos', r'^-asinh', r'^-asin', r'^-atan2', r'^-atanh', r'^-atan', r'^-ceil', r'^-copysign', r'^-cosh', r'^-cos', r'^-degrees', r'^-erfc', r'^-erf', r'^-expm1', r'^-exp', - r'^-fabs', r'^-factorial', r'^-floor', r'^-fmod', r'^-gamma', r'^-gcd', r'^-hypot', r'^-isfinite', - r'^-isinf', r'^-isnan', r'^-ldexp', r'^-lgamma', r'^-log10', r'^-log1p', r'^-log2', r'^-log', - r'^-pow', r'^-radians', r'^-sinh', r'^-sin', r'-^sqrt', r'^-tanh', r'^-tan', r'^-trunc', - r'^-abs', r'^-round'] + r'^-fabs', r'^-factorial', r'^-floor', r'^-fmod', r'^-gamma', r'^-gcd', r'^-hypot', + r'^-isfinite', r'^-isinf', r'^-isnan', r'^-ldexp', r'^-lgamma', r'^-log10', r'^-log1p', + r'^-log2', r'^-log', r'^-pow', r'^-radians', r'^-sinh', r'^-sin', r'-^sqrt', r'^-tanh', + r'^-tan', r'^-trunc', r'^-abs', r'^-round'] r_constants = [r'^e', r'^pi', r'^tau', r'^inf', r'^nan'] r_negative_constants = [r'^\-e', r'^\-pi', r'^\-tau', r'^\-inf', r'^\-nan'] r_int_numbers = [r'^\d+'] From 814f33c4f6eb15e65b5758f9fab946ee0201dbe2 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Thu, 15 Nov 2018 00:11:09 +0300 Subject: [PATCH 14/20] corrections according to the comments 1 --- final_task/pycalc/__main__.py | 8 +++----- final_task/pycalc/addmultsigns.py | 4 ++-- final_task/pycalc/constsreplacer.py | 4 ++-- final_task/pycalc/pycalclib.py | 2 +- final_task/pycalc/rpn.py | 4 ++-- final_task/pycalc/rpncalculator.py | 4 ++-- final_task/pycalc/tokenizer.py | 5 ++--- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc/__main__.py index 04d7815..2a10536 100644 --- a/final_task/pycalc/__main__.py +++ b/final_task/pycalc/__main__.py @@ -1,10 +1,8 @@ #! /usr/bin/python3 -# -*- coding: UTF-8 -*- -# general import +# import import argparse import sys -# import from pycalc self library from .tokenizer import Tokenizer from .addmultsigns import Multsignsadder from .rpn import RPN @@ -12,7 +10,7 @@ from .rpncalculator import RPNcalculator -def createparser(): +def create_parser(): """Creates parser with one positional argument 'expression' to parse user's mathematical expression""" parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', epilog="""Anton Charnichenka for EPAM: Introduction to Python @@ -25,7 +23,7 @@ def createparser(): def main(): """Calculate user's expression""" - parser = createparser() + parser = create_parser() namespace = parser.parse_args(sys.argv[1:]) user_expr = namespace.expression diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 65a638c..3c70b69 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -1,10 +1,10 @@ """This module contains a class that allows to take into account implicit multiplication signs""" -# import from pycalc self library +# import from .pycalclib import constants, functions, negative_functions -class Multsignsadder(): +class Multsignsadder: """A model of mult_signs_adder capable of adding implicit multiplications signs in list of tokens""" def __init__(self, tokens): diff --git a/final_task/pycalc/constsreplacer.py b/final_task/pycalc/constsreplacer.py index fd74d05..531bd9f 100644 --- a/final_task/pycalc/constsreplacer.py +++ b/final_task/pycalc/constsreplacer.py @@ -1,10 +1,10 @@ """This module contains a class that allows to replace constants by their numeric equivalents""" -# import from pyalc self library +# import from .pycalclib import constants_numeric_equivalents -class Constsreplacer(): +class Constsreplacer: """A model of constants replacer capable of replacing constants (from math module) by their numeric equivalents""" def __init__(self, rpn_tokens): diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py index 60256b5..046cb2d 100644 --- a/final_task/pycalc/pycalclib.py +++ b/final_task/pycalc/pycalclib.py @@ -1,6 +1,6 @@ """This module contains lists and dictionaries of data that are used in other pycalc modules """ -# general import +# import import math import operator diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index 4a75ab6..e4cad38 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -1,11 +1,11 @@ """This module contains a class that allows to transform infix notation math tokens into RPN""" -# import from pycalc self library +# import from .pycalclib import constants, negative_constants, operators, comparison_operators, precedence from .pycalclib import functions, negative_functions -class RPN(): +class RPN: """A model of rpn capable of converting infix to postfix (RPN) notation""" def __init__(self, tokens): diff --git a/final_task/pycalc/rpncalculator.py b/final_task/pycalc/rpncalculator.py index 7b0ee27..ea93cd3 100644 --- a/final_task/pycalc/rpncalculator.py +++ b/final_task/pycalc/rpncalculator.py @@ -1,10 +1,10 @@ """This module contains a class that allows to evaluate math expression in RPN""" -# import from pycalc self library +# import from .pycalclib import operators_dict, functions_dict -class RPNcalculator(): +class RPNcalculator: """A model of RPN math expression evaluator""" def __init__(self, rpn_tokens): diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index 9351d1d..a25f5e8 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -1,12 +1,11 @@ """This module contains a class that allows to find and extract tokens from the user's mathematical expression""" -# general import +# import import re -# import from pycalc self library from .pycalclib import r_strings, operators, constants -class Tokenizer(): +class Tokenizer: """A model of tokenizer capable of finding and extracting tokens from string math expression""" def __init__(self, user_expr): """Initialize tokenizer""" From b31cfc1cc4122a7a54ef3f14948a1334c1930c4a Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Thu, 15 Nov 2018 00:22:36 +0300 Subject: [PATCH 15/20] corrections according to the comments 2 --- final_task/pycalc/rpn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index e4cad38..b645548 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -22,7 +22,8 @@ def __init__(self, tokens): self.functions = functions self.negative_functions = negative_functions - def is_left_associative(self, operator): + @staticmethod + def is_left_associative(operator): """Determines whether operator is left associative""" if operator in ['^', '**']: return False From 75d4d64c68065e0366c32cc0c9055fe8acc63b70 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Thu, 15 Nov 2018 00:51:49 +0300 Subject: [PATCH 16/20] corrections according to the comments 3 --- final_task/pycalc/addmultsigns.py | 15 ++++----------- final_task/pycalc/rpn.py | 11 ++--------- final_task/pycalc/tokenizer.py | 15 ++++----------- final_task/pycalc/utils.py | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 31 deletions(-) create mode 100644 final_task/pycalc/utils.py diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 3c70b69..652054c 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -2,6 +2,7 @@ # import from .pycalclib import constants, functions, negative_functions +from .utils import is_number class Multsignsadder: @@ -15,14 +16,6 @@ def __init__(self, tokens): self.functions = functions self.negative_functions = negative_functions - def is_number(self, token): - """Determines whether token is a number""" - try: - float(token) - return True - except ValueError: - return False - def consider_neg_functions(self, tokens): """Replaces negative functions tokens by '-1*function' tokens""" index = 0 @@ -51,9 +44,9 @@ def addmultsigns(self): """Adds implicit multiplication signs in list of math tokens to where they are supposed to be""" for index in range(len(self.tokens)-1): self.extended_tokens.append(self.tokens[index]) - if (self.is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants) - or (self.tokens[index+1] in self.functions) - or (self.tokens[index+1] == '('))): + if (is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants) + or (self.tokens[index+1] in self.functions) + or (self.tokens[index+1] == '('))): self.extended_tokens.append('*') continue elif self.tokens[index] == ')' and self.tokens[index+1] == '(': diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index b645548..c4fc238 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -3,6 +3,7 @@ # import from .pycalclib import constants, negative_constants, operators, comparison_operators, precedence from .pycalclib import functions, negative_functions +from .utils import is_number class RPN: @@ -30,20 +31,12 @@ def is_left_associative(operator): else: return True - def is_number(self, token): - """Determines whether token is a number""" - try: - float(token) - return True - except ValueError: - return False - def convert2rpn(self): """Converts list of tokens in infix notation into RPN""" counter = 0 while counter != (len(self.tokens)): current_token = self.tokens[counter] - if self.is_number(current_token) or (current_token in (self.constants + self.negative_constants)): + if is_number(current_token) or (current_token in (self.constants + self.negative_constants)): self.output_queue.append(current_token) counter += 1 elif current_token in self.functions or current_token in self.negative_functions: diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index a25f5e8..e9e887e 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -3,6 +3,7 @@ # import import re from .pycalclib import r_strings, operators, constants +from .utils import is_number class Tokenizer: @@ -16,16 +17,8 @@ def __init__(self, user_expr): self.error_msg = None self.tokens = [] - def is_number(self, token): - """Determines whether token is a number""" - try: - float(token) - return True - except ValueError: - return False - def consider_sub_signs(self, tokens): - """Considers and replaces several subtraction signs when they follow each other""" + """Considers and replaces several subtraction and addition signs when they follow each other""" index = 0 while True: if tokens[index] == '-' and (tokens[index+1] == '-' or tokens[index+1] == '+'): @@ -47,8 +40,8 @@ def consider_sub_signs(self, tokens): def check_first_tokens(self, tokens): """Check whether first two tokens are a negative number (negative constant) and replaces them by negative number if so""" - if tokens[0] == '-' and (self.is_number(tokens[1]) or tokens[1] in self.constants): - if self.is_number(tokens[1]): + if tokens[0] == '-' and (is_number(tokens[1]) or tokens[1] in self.constants): + if is_number(tokens[1]): first_neg_token = str(float(tokens[1])*-1) else: first_neg_token = '-{}'.format(tokens[1]) diff --git a/final_task/pycalc/utils.py b/final_task/pycalc/utils.py new file mode 100644 index 0000000..30bb743 --- /dev/null +++ b/final_task/pycalc/utils.py @@ -0,0 +1,16 @@ +"""This module contains 'is_number' function that is used in a few other pycalc modules""" + + +def is_number(token): + """Determines whether token is a number""" + try: + float(token) + return True + except ValueError: + return False + + +if __name__ == "__main__": + print("This module contains 'is_number' function that is used in a few other pycalc modules. For example: \n") + print("For '3' token 'is_number' returns -", is_number('3')) + print("For '+' token 'is_number' returns -", is_number('+')) From 6173c0531f9f76952763641ac2fe7fdeaf3d8762 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Thu, 15 Nov 2018 01:12:53 +0300 Subject: [PATCH 17/20] implicit multiplication correction --- final_task/pycalc/addmultsigns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 652054c..3c6c6bc 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -44,12 +44,12 @@ def addmultsigns(self): """Adds implicit multiplication signs in list of math tokens to where they are supposed to be""" for index in range(len(self.tokens)-1): self.extended_tokens.append(self.tokens[index]) - if (is_number(self.tokens[index]) and ((self.tokens[index+1] in self.constants) - or (self.tokens[index+1] in self.functions) - or (self.tokens[index+1] == '('))): + if ((is_number(self.tokens[index]) or self.tokens[index] in self.constants) + and ((self.tokens[index+1] in self.constants) or (self.tokens[index+1] in self.functions) + or (self.tokens[index+1] == '('))): self.extended_tokens.append('*') continue - elif self.tokens[index] == ')' and self.tokens[index+1] == '(': + elif self.tokens[index] == ')' and (self.tokens[index+1] == '(' or self.tokens[index+1] in self.functions): self.extended_tokens.append('*') continue self.extended_tokens.append(self.tokens[-1]) From d3eff94a794dcd3745eac1387af38f4b14ab387a Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Thu, 15 Nov 2018 21:43:05 +0300 Subject: [PATCH 18/20] UnitTests correction --- final_task/pycalc/UnitTests.py | 49 +++++++++++++--------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/final_task/pycalc/UnitTests.py b/final_task/pycalc/UnitTests.py index 62663f8..f3bf797 100644 --- a/final_task/pycalc/UnitTests.py +++ b/final_task/pycalc/UnitTests.py @@ -9,6 +9,7 @@ from .rpn import RPN from .constsreplacer import Constsreplacer from .rpncalculator import RPNcalculator +from .utils import is_number # Tests of 'Tokenizer' class from 'tokenizer' module @@ -106,15 +107,6 @@ def test_consider_sub_signs_method(self): self.assertEqual(tokens, ['-1.0', '-', '2', '-', '3', '+', '4', '-', '2']) self.assertEqual(error_msg, None) - def test_is_number_method(self): - """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" - tokens = ['.3', '-0.3', '7', 'tan'] - tokenizer = Tokenizer(user_expr='') - is_numbers = [] - for token in tokens: - is_numbers.append(tokenizer.is_number(token)) - self.assertEqual(is_numbers, [True, True, True, False]) - def test_extract_tokens_error_msg(self): """Is error_message created?""" user_expr = "2+shikaka(3)" @@ -127,22 +119,13 @@ def test_extract_tokens_error_msg(self): class MultsignsadderTestCase(unittest.TestCase): """Tests for Multsignsadder class""" - def test_is_number_method(self): - """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" - tokens = ['2.3', '-0.6', '5', 'sin', 'exp'] - mult_signs_adder = Multsignsadder(tokens) - is_numbers = [] - for token in tokens: - is_numbers.append(mult_signs_adder.is_number(token)) - self.assertEqual(is_numbers, [True, True, True, False, False]) - def test_addmultsigns_add_mult_signs(self): """Are multiplication signs added to where they implicit were to be in expression?""" - tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', '-', '9', '(', '1', '+', '10', ')'] + tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', 'sin', '(', '3', ')', '-', '9', '(', '1', '+', '10', ')'] mult_signs_adder = Multsignsadder(tokens) extd_tokens = mult_signs_adder.addmultsigns() - self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '-', - '9', '*', '(', '1', '+', '10', ')']) + self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '*', 'sin', '(', '3', ')', + '-', '9', '*', '(', '1', '+', '10', ')']) def test_addmultsigns_dont_add_mult_signs(self): """Aren't multiplication signs added if it's not needed?""" @@ -159,7 +142,7 @@ def test_consider_neg_funcs_method(self): self.assertEqual(mult_signs_adder.tokens, ['2', '*', '-1', '*', 'sin', '(', '2', ')']) def test_consider_log_args_method(self): - """Is 'e' added as a base for log functions if last was entered with one argument?""" + """Is 'e' added as a base for log function if last was entered with one argument?""" tokens = ['log', '(', '33', ')'] mult_signs_adder = Multsignsadder(tokens) mult_signs_adder.consider_log_args(mult_signs_adder.tokens) @@ -179,15 +162,6 @@ def test_is_left_associative_method(self): is_left_associative.append(rpn.is_left_associative(token)) self.assertEqual(is_left_associative, [False, False, True, True]) - def test_is_number_method(self): - """Does 'is_number' method distinguish tokens which are numbers from ones which are not?""" - tokens = ['1.3', '-0.5', '/', '%', '9'] - rpn = RPN(tokens) - is_numbers = [] - for token in tokens: - is_numbers.append(rpn.is_number(token)) - self.assertEqual(is_numbers, [True, True, False, False, True]) - def test_convert2rpn_method(self): """Does 'convert2rpn' method work correctly?""" tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] @@ -263,5 +237,18 @@ def test_evaluate_method_error_msg_invalid_syntax(self): self.assertEqual(error_msg, "ERROR: invalid syntax") +# Tests of 'is_number' function from 'utils' module +class IsNumberTestCase(unittest.TestCase): + """Test for 'is_number' function""" + + def test_is_number_function(self): + """Does 'is_number' function distinguish tokens which are numbers from ones which are not?""" + tokens = ['.3', '-0.3', '7', 'tan'] + is_numbers = [] + for token in tokens: + is_numbers.append(is_number(token)) + self.assertEqual(is_numbers, [True, True, True, False]) + + if __name__ == '__main__': unittest.main() From 2e7747fc36fdf9a36513128d3a490e342abedbb6 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Fri, 16 Nov 2018 20:30:33 +0300 Subject: [PATCH 19/20] user module support --- final_task/pycalc/UnitTests.py | 70 +++++---- final_task/pycalc/__main__.py | 20 ++- final_task/pycalc/addmultsigns.py | 15 +- final_task/pycalc/constsreplacer.py | 12 +- final_task/pycalc/pycalclib.py | 203 ++++++++++++++------------ final_task/pycalc/rpn.py | 24 +-- final_task/pycalc/rpncalculator.py | 38 +++-- final_task/pycalc/test_user_module.py | 10 ++ final_task/pycalc/tokenizer.py | 21 +-- 9 files changed, 231 insertions(+), 182 deletions(-) create mode 100644 final_task/pycalc/test_user_module.py diff --git a/final_task/pycalc/UnitTests.py b/final_task/pycalc/UnitTests.py index f3bf797..24de0e0 100644 --- a/final_task/pycalc/UnitTests.py +++ b/final_task/pycalc/UnitTests.py @@ -1,15 +1,17 @@ -"""This module contains unit tests for methods from all pycalc modules""" +"""This module contains unit tests for methods and functions from all pycalc modules""" -# general import +# import import unittest - -# import of classes to be tested from pycalc modules from .tokenizer import Tokenizer from .addmultsigns import Multsignsadder from .rpn import RPN from .constsreplacer import Constsreplacer from .rpncalculator import RPNcalculator from .utils import is_number +from .pycalclib import Pycalclib + +# create pycalclib +pycalclib = Pycalclib(user_module='test_user_module') # Tests of 'Tokenizer' class from 'tokenizer' module @@ -19,7 +21,7 @@ class TokenizerTestCase(unittest.TestCase): def test_extract_operators_and_pos_int_numbers(self): """Are operators and positive int numbers extracted properly?""" user_expr = '1+2-3*4/5^6**7//8%9' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['1', '+', '2', '-', '3', '*', '4', '/', '5', '^', '6', '**', '7', '//', '8', '%', '9']) @@ -28,7 +30,7 @@ def test_extract_operators_and_pos_int_numbers(self): def test_extract_operators_and_neg_int_numbers(self): """Are operators and negative int numbers extracted properly?""" user_expr = '-1+-2--3*-4/-5^-6**-7//-8%-9' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-1.0', '-', '2', '+', '3', '*', '-4', '/', '-5', '^', '-6', '**', '-7', '//', '-8', '%', '-9']) @@ -37,7 +39,7 @@ def test_extract_operators_and_neg_int_numbers(self): def test_extract_pos_float_numbers(self): """Are positive float numbers extracted properly?""" user_expr = '0.1+1.55-112.12' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['0.1', '+', '1.55', '-', '112.12']) self.assertEqual(error_msg, None) @@ -45,7 +47,7 @@ def test_extract_pos_float_numbers(self): def test_extract_neg_float_numbers(self): """Are negative float numbers extracted properly?""" user_expr = '-0.1+-1.55--112.12' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-0.1', '-', '1.55', '+', '112.12']) self.assertEqual(error_msg, None) @@ -53,7 +55,7 @@ def test_extract_neg_float_numbers(self): def test_extract_comparison_operators(self): """Are comparison operators extracted properly?""" user_expr = '><>=<=!===' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['>', '<', '>=', '<=', '!=', '==']) self.assertEqual(error_msg, None) @@ -61,7 +63,7 @@ def test_extract_comparison_operators(self): def test_extract_pos_constants(self): """Are positive constants extracted properly?""" user_expr = 'e+pi-tau/inf*nan' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['e', '+', 'pi', '-', 'tau', '/', 'inf', '*', 'nan']) self.assertEqual(error_msg, None) @@ -69,7 +71,7 @@ def test_extract_pos_constants(self): def test_extract_neg_constants(self): """Are negative constants extracted properly?""" user_expr = '-e+-pi--tau/-inf*-nan' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-e', '-', 'pi', '+', 'tau', '/', '-inf', '*', '-nan']) self.assertEqual(error_msg, None) @@ -77,7 +79,7 @@ def test_extract_neg_constants(self): def test_extract_brackets(self): """Are brackets extracted properly?""" user_expr = '()' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['(', ')']) self.assertEqual(error_msg, None) @@ -85,7 +87,7 @@ def test_extract_brackets(self): def test_extract_comma(self): """Is comma extracted?""" user_expr = 'pow(2,3)' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['pow', '(', '2', ',', '3', ')']) self.assertEqual(error_msg, None) @@ -93,7 +95,7 @@ def test_extract_comma(self): def test_extract_functions(self): """Are functions extracted properly?""" user_expr = "round(sin(2)-asin(1))-abs(exp(3))" - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['round', '(', 'sin', '(', '2', ')', '-', 'asin', '(', '1', ')', ')', '-', 'abs', '(', 'exp', '(', '3', ')', ')']) @@ -102,7 +104,7 @@ def test_extract_functions(self): def test_consider_sub_signs_method(self): """Are several subtraction and addition signs replaced by one integrated sign?""" user_expr = '-1---2+-3+++4+-2' - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(tokens, ['-1.0', '-', '2', '-', '3', '+', '4', '-', '2']) self.assertEqual(error_msg, None) @@ -110,7 +112,7 @@ def test_consider_sub_signs_method(self): def test_extract_tokens_error_msg(self): """Is error_message created?""" user_expr = "2+shikaka(3)" - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() self.assertEqual(error_msg, 'ERROR: invalid syntax') @@ -122,7 +124,7 @@ class MultsignsadderTestCase(unittest.TestCase): def test_addmultsigns_add_mult_signs(self): """Are multiplication signs added to where they implicit were to be in expression?""" tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', 'sin', '(', '3', ')', '-', '9', '(', '1', '+', '10', ')'] - mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder = Multsignsadder(tokens, pycalclib) extd_tokens = mult_signs_adder.addmultsigns() self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '*', 'sin', '(', '3', ')', '-', '9', '*', '(', '1', '+', '10', ')']) @@ -130,21 +132,21 @@ def test_addmultsigns_add_mult_signs(self): def test_addmultsigns_dont_add_mult_signs(self): """Aren't multiplication signs added if it's not needed?""" tokens = ['2', '+', '3', '*', '5'] - mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder = Multsignsadder(tokens, pycalclib) extd_tokens = mult_signs_adder.addmultsigns() self.assertEqual(extd_tokens, ['2', '+', '3', '*', '5']) def test_consider_neg_funcs_method(self): """Are negative functions tokens replaced by '-1*function' tokens?""" tokens = ['2', '*', '-sin', '(', '2', ')'] - mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder = Multsignsadder(tokens, pycalclib) mult_signs_adder.consider_neg_functions(mult_signs_adder.tokens) self.assertEqual(mult_signs_adder.tokens, ['2', '*', '-1', '*', 'sin', '(', '2', ')']) def test_consider_log_args_method(self): """Is 'e' added as a base for log function if last was entered with one argument?""" tokens = ['log', '(', '33', ')'] - mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder = Multsignsadder(tokens, pycalclib) mult_signs_adder.consider_log_args(mult_signs_adder.tokens) self.assertEqual(mult_signs_adder.tokens, ['log', '(', '33', ',', 'e', ')']) @@ -156,7 +158,7 @@ class RPNTestCase(unittest.TestCase): def test_is_left_associative_method(self): """Are left associative operators recognized?""" tokens = ['^', '**', '+', '/'] - rpn = RPN(tokens) + rpn = RPN(tokens, pycalclib) is_left_associative = [] for token in tokens: is_left_associative.append(rpn.is_left_associative(token)) @@ -165,7 +167,7 @@ def test_is_left_associative_method(self): def test_convert2rpn_method(self): """Does 'convert2rpn' method work correctly?""" tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] - rpn = RPN(tokens) + rpn = RPN(tokens, pycalclib) result, error_msg = rpn.convert2rpn() self.assertEqual(result, ['-pi', '2.23', 'round', '*', '5', '//', '2', '3', 'pow', '*']) self.assertEqual(error_msg, None) @@ -173,7 +175,7 @@ def test_convert2rpn_method(self): def test_convert2rpn_method_error_msg(self): """Is error_message created?""" tokens = ['(', '2', '+', '3', ')', ')'] - rpn = RPN(tokens) + rpn = RPN(tokens, pycalclib) result, error_msg = rpn.convert2rpn() self.assertEqual(error_msg, 'ERROR: brackets are not balanced') @@ -185,7 +187,7 @@ class ConstsreplacerTestCase(unittest.TestCase): def test_replace_constants_method(self): """Are constants replaced and not constants aren't replaced?""" tokens = ['e', '-e', 'pi', '-pi', 'tau', '-tau', '2', 'cos', 'inf', '-nan', '+'] - constsreplacer = Constsreplacer(tokens) + constsreplacer = Constsreplacer(tokens, pycalclib) replaced_tokens = constsreplacer.replace_constants() self.assertEqual(replaced_tokens, ['2.718281828459045', '-2.718281828459045', '3.141592653589793', '-3.141592653589793', @@ -200,7 +202,7 @@ class RPNcalculatorTestCase(unittest.TestCase): def test_evaluate_method_result(self): """Does 'evaluate' method actually evaluate RPN math expression and give out correct result?""" rpn_tokens = ['2', 'sqrt', '3', '/', '3.14', '*', 'tan'] - rpncalculator = RPNcalculator(rpn_tokens) + rpncalculator = RPNcalculator(rpn_tokens, pycalclib) result, error_msg = rpncalculator.evaluate() self.assertEqual(result, 11.009005500434151) self.assertEqual(error_msg, None) @@ -208,7 +210,7 @@ def test_evaluate_method_result(self): def test_evaluate_method_error_msg_zero_division(self): """Is 'division by zero' error message created?""" rpn_tokens = ['2', '0', '/'] - rpncalculator = RPNcalculator(rpn_tokens) + rpncalculator = RPNcalculator(rpn_tokens, pycalclib) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, 'ERROR: float division by zero') @@ -217,7 +219,7 @@ def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): rpn_tokens = [['-2', '0.5', '**'], ['-2', '0.5', '^']] error_msgs = [] for rpn_tokens_list in rpn_tokens: - rpncalculator = RPNcalculator(rpn_tokens_list) + rpncalculator = RPNcalculator(rpn_tokens_list, pycalclib) error_msgs.append(rpncalculator.evaluate()[1]) for error_msg in error_msgs: self.assertEqual(error_msg, 'ERROR: negative number cannot be raised to a fractional power') @@ -225,18 +227,28 @@ def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): def test_evaluate_method_error_msg_neg_num_sqrt(self): """Is 'root can't be extracted from a negative number' error message created?""" rpn_tokens = ['-2', 'sqrt'] - rpncalculator = RPNcalculator(rpn_tokens) + rpncalculator = RPNcalculator(rpn_tokens, pycalclib) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, "ERROR: a root can't be extracted from a negative number") def test_evaluate_method_error_msg_invalid_syntax(self): """Is 'invalid syntax' error message created?""" rpn_tokens = ['2', '+'] - rpncalculator = RPNcalculator(rpn_tokens) + rpncalculator = RPNcalculator(rpn_tokens, pycalclib) result, error_msg = rpncalculator.evaluate() self.assertEqual(error_msg, "ERROR: invalid syntax") +# Test of 'Pycalclib' class from 'pycalclib' module +class PycalclibTestCase(unittest.TestCase): + """Tests for Pycalclib class""" + + def test_consider_user_module(self): + """Are user functions added to pycalclib?""" + self.assertIn('five', pycalclib.functions) + self.assertIn('squaressum', pycalclib.functions) + + # Tests of 'is_number' function from 'utils' module class IsNumberTestCase(unittest.TestCase): """Test for 'is_number' function""" diff --git a/final_task/pycalc/__main__.py b/final_task/pycalc/__main__.py index 2a10536..3fbcbfb 100644 --- a/final_task/pycalc/__main__.py +++ b/final_task/pycalc/__main__.py @@ -8,28 +8,34 @@ from .rpn import RPN from .constsreplacer import Constsreplacer from .rpncalculator import RPNcalculator +from .pycalclib import Pycalclib def create_parser(): - """Creates parser with one positional argument 'expression' to parse user's mathematical expression""" + """Creates parser to parse user mathematical expression""" parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', epilog="""Anton Charnichenka for EPAM: Introduction to Python and Golang programming, 2018.""") parser.add_argument('expression', help="""mathematical expression string to evaluate; implicit multiplication is supported""") + parser.add_argument('--user_module', '-m', default='', help='additional module with user defined functions') return parser def main(): - """Calculate user's expression""" + """Calculates user math expression""" parser = create_parser() namespace = parser.parse_args(sys.argv[1:]) user_expr = namespace.expression + user_module = namespace.user_module + + # create pycalclib + pycalclib = Pycalclib(user_module) # calculation chain # tokenize user's expression string - tokenizer = Tokenizer(user_expr) + tokenizer = Tokenizer(user_expr, pycalclib) tokens, error_msg = tokenizer.extract_tokens() if error_msg: print(error_msg) @@ -38,19 +44,19 @@ def main(): print('ERROR: no expression was entered') sys.exit(1) # add implicit multiplication signs to the list of extracted tokens - mult_signs_adder = Multsignsadder(tokens) + mult_signs_adder = Multsignsadder(tokens, pycalclib) tokens = mult_signs_adder.addmultsigns() # transform extracted tokens into RPN - rpn = RPN(tokens) + rpn = RPN(tokens, pycalclib) rpn_tokens, error_msg = rpn.convert2rpn() if error_msg: print(error_msg) sys.exit(1) # replace constants with their numeric equivalents - constsreplacer = Constsreplacer(rpn_tokens) + constsreplacer = Constsreplacer(rpn_tokens, pycalclib) rpn_tokens = constsreplacer.replace_constants() # evaluate user's expression - rpncalculator = RPNcalculator(rpn_tokens) + rpncalculator = RPNcalculator(rpn_tokens, pycalclib) result, error_msg = rpncalculator.evaluate() if error_msg: print(error_msg) diff --git a/final_task/pycalc/addmultsigns.py b/final_task/pycalc/addmultsigns.py index 3c6c6bc..c85df14 100644 --- a/final_task/pycalc/addmultsigns.py +++ b/final_task/pycalc/addmultsigns.py @@ -1,23 +1,23 @@ """This module contains a class that allows to take into account implicit multiplication signs""" # import -from .pycalclib import constants, functions, negative_functions from .utils import is_number +from .pycalclib import Pycalclib class Multsignsadder: """A model of mult_signs_adder capable of adding implicit multiplications signs in list of tokens""" - def __init__(self, tokens): + def __init__(self, tokens, pycalclib): """Initialize mult_signs_adder""" self.tokens = tokens self.extended_tokens = [] - self.constants = constants - self.functions = functions - self.negative_functions = negative_functions + self.constants = pycalclib.constants + self.functions = pycalclib.functions + self.negative_functions = pycalclib.negative_functions def consider_neg_functions(self, tokens): - """Replaces negative functions tokens by '-1*function' tokens""" + """Replaces negative functions tokens with '-1*function' tokens""" index = 0 while index != len(tokens)-1: if tokens[index] in self.negative_functions: @@ -64,7 +64,8 @@ def addmultsigns(self): print("""This module contains class that allows to insert multiplications signs to where they where supposed to be in a list with math tokens. For example: \n""") test_tokens = ['-0.1', 'tan', '+', '23', '*', '-sin', '(', '3', ')', '/', '.12', 'e'] - mult_signs_adder = Multsignsadder(test_tokens) + pycalclib = Pycalclib(user_module='') + mult_signs_adder = Multsignsadder(test_tokens, pycalclib) extended_tokens = mult_signs_adder.addmultsigns() print('Original tokens: ', test_tokens) print('Extended tokens: ', extended_tokens) diff --git a/final_task/pycalc/constsreplacer.py b/final_task/pycalc/constsreplacer.py index 531bd9f..c57a640 100644 --- a/final_task/pycalc/constsreplacer.py +++ b/final_task/pycalc/constsreplacer.py @@ -1,16 +1,15 @@ -"""This module contains a class that allows to replace constants by their numeric equivalents""" +"""This module contains a class that allows to replace constants with their numeric equivalents""" -# import -from .pycalclib import constants_numeric_equivalents +from .pycalclib import Pycalclib class Constsreplacer: """A model of constants replacer capable of replacing constants (from math module) by their numeric equivalents""" - def __init__(self, rpn_tokens): + def __init__(self, rpn_tokens, pycalclib): """Initialize constsreplacer""" self.rpn_tokens = rpn_tokens - self.constants_numeric_equivalents = constants_numeric_equivalents + self.constants_numeric_equivalents = pycalclib.constants_numeric_equivalents def replace_constants(self): """Replaces tokens which are math module constants by their numeric equivalent""" @@ -26,6 +25,7 @@ def replace_constants(self): numeric equivalents. For example: \n""") test_tokens = ['2', '*', 'nan', '-', '-inf', '+', '-tau', '*', '-pi', '+', 'e'] print('RPN tokens with constants: ', test_tokens) - constsreplacer = Constsreplacer(test_tokens) + pycalclib = Pycalclib(user_module='') + constsreplacer = Constsreplacer(test_tokens, pycalclib) rpn_tokens = constsreplacer.replace_constants() print('RPN tokens after replacement of constants: ', rpn_tokens) diff --git a/final_task/pycalc/pycalclib.py b/final_task/pycalc/pycalclib.py index 046cb2d..812ded7 100644 --- a/final_task/pycalc/pycalclib.py +++ b/final_task/pycalc/pycalclib.py @@ -1,94 +1,117 @@ -"""This module contains lists and dictionaries of data that are used in other pycalc modules """ +"""This module contains a class that represents pycalclib and stores lists and dictionaries of data +that are used in other pycalc modules""" # import import math import operator - - -# r_strings that are used to find operators / functions / etc -r_one_sign_operators = [r'^\+', r'^-', r'^\*', r'^/', r'^\^', r'^%'] -r_two_signs_operators = [r'^//', r'^\*\*'] -r_comparison_operators = [r'^<=', r'^>=', r'^<', r'^>', r'^==', r'^!='] -r_functions = [r'^acosh', r'^acos', r'^asinh', r'^asin', r'^atan2', r'^atanh', r'^atan', r'^ceil', r'^copysign', - r'^cosh', r'^cos', r'^degrees', r'^erfc', r'^erf', r'^expm1', r'^exp', r'^fabs', r'^factorial', - r'^floor', r'^fmod', r'^gamma', r'^gcd', r'^hypot', r'^isfinite', r'^isinf', r'^isnan', r'^ldexp', - r'^lgamma', r'^log10', r'^log1p', r'^log2', r'^log', r'^pow', r'^radians', r'^sinh', r'^sin', - r'^sqrt', r'^tanh', r'^tan', r'^trunc', r'^abs', r'^round'] -r_negative_functions = [r'^-acosh', r'^-acos', r'^-asinh', r'^-asin', r'^-atan2', r'^-atanh', r'^-atan', r'^-ceil', - r'^-copysign', r'^-cosh', r'^-cos', r'^-degrees', r'^-erfc', r'^-erf', r'^-expm1', r'^-exp', - r'^-fabs', r'^-factorial', r'^-floor', r'^-fmod', r'^-gamma', r'^-gcd', r'^-hypot', - r'^-isfinite', r'^-isinf', r'^-isnan', r'^-ldexp', r'^-lgamma', r'^-log10', r'^-log1p', - r'^-log2', r'^-log', r'^-pow', r'^-radians', r'^-sinh', r'^-sin', r'-^sqrt', r'^-tanh', - r'^-tan', r'^-trunc', r'^-abs', r'^-round'] -r_constants = [r'^e', r'^pi', r'^tau', r'^inf', r'^nan'] -r_negative_constants = [r'^\-e', r'^\-pi', r'^\-tau', r'^\-inf', r'^\-nan'] -r_int_numbers = [r'^\d+'] -r_negative_int_numbers = [r'^\-\d+'] -r_float_numbers = [r'^\d+\.\d+|^\.\d+'] -r_negative_float_numbers = [r'^\-\d+\.\d+|^\-\.\d+'] -r_brackets = [r'^\(', r'^\)'] -r_comma = [r'^,'] -r_space = [r'^\s'] -# all r_strings together -r_strings = r_brackets + r_two_signs_operators + r_one_sign_operators + r_negative_functions + r_functions + \ - r_comparison_operators + r_negative_float_numbers + r_negative_int_numbers + r_negative_constants + \ - r_float_numbers + r_int_numbers + r_constants + r_space + r_comma # the order matters - - -# acceptable constants and functions -constants = ['e', 'pi', 'tau', 'inf', 'nan'] -negative_constants = ['-e', '-pi', '-tau', '-inf', '-nan'] -functions = ['acosh', 'acos', 'asinh', 'asin', 'atan2', 'atanh', 'atan', 'ceil', 'copysign', 'cosh', 'cos', - 'degrees', 'erfc', 'erf', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', - 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log10', - 'log1p', 'log2', 'log', 'modf', 'pow', 'radians', 'sinh', 'sin', 'sqrt', 'tanh', 'tan', 'trunc', - 'round', 'abs'] - -negative_functions = ['-acosh', '-acos', '-asinh', '-asin', '-atan2', '-atanh', '-atan', '-ceil', '-copysign', - '-cosh', '-cos', '-degrees', '-erfc', '-erf', '-exp', '-expm1', '-fabs', '-factorial', - '-floor', '-fmod', '-frexp', '-fsum', '-gamma', '-gcd', '-hypot', '-isclose', '-isfinite', - '-isinf', '-isnan', '-ldexp', '-lgamma', '-log10', '-log1p', '-log2', '-log', '-modf', - '-pow', '-radians', '-sinh', '-sin', '-sqrt', '-tanh', '-tan', '-trunc', '-round', '-abs'] - - -# acceptable operators -operators = ['+', '-', '*', '/', '//', '%', '^', '**'] - - -# acceptable comparison operators -comparison_operators = ['<=', '>=', '<', '>', '==', '!='] - - -# operators precedence -precedence = {'(': 0, ')': 0, '<': 0, '>': 0, '<=': 0, '>=': 0, '==': 0, '!=': 0, '+': 1, '-': 1, - '*': 2, '/': 2, '//': 2, '%': 2, '^': 3, '**': 3} - - -# numeric equivalents of constants -constants_numeric_equivalents = {'e': math.e, '-e': -math.e, 'pi': math.pi, '-pi': -math.pi, 'tau': math.tau, - '-tau': -math.tau} - - -# operator's actions -operators_dict = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, '^': operator.pow, - '**': operator.pow, '//': operator.floordiv, '%': operator.mod, '<': operator.lt, '>': operator.gt, - '<=': operator.le, '>=': operator.ge, '==': operator.eq, '!=': operator.ne} - - -# function's actions -# first element in a tuple associated with each key is a number of arguments for corresponding function -functions_dict = {'acos': (1, math.acos), 'acosh': (1, math.acosh), 'asin': (1, math.asin), 'asinh': (1, math.asinh), - 'atan': (1, math.atan), 'atan2': (2, math.atan2), 'atanh': (1, math.atanh), 'ceil': (1, math.ceil), - 'copysign': (2, math.copysign), 'cos': (1, math.cos), 'cosh': (1, math.cosh), - 'degrees': (1, math.degrees), 'erf': (1, math.erf), 'erfc': (1, math.erfc), 'exp': (1, math.exp), - 'expm1': (1, math.expm1), 'fabs': (1, math.fabs), 'factorial': (1, math. factorial), - 'floor': (1, math.floor), 'fmod': (2, math.fmod), 'gamma': (1, math.gamma), 'gcd': (2, math.gcd), - 'hypot': (2, math.hypot), 'isfinite': (1, math.isfinite), 'isinf': (1, math.isinf), - 'isnan': (1, math.isnan), 'ldexp': (2, math.ldexp), 'lgamma': (1, math.lgamma), 'log': (2, math.log), - 'log10': (1, math.log10), 'log1p': (1, math.log1p), 'log2': (1, math.log2), 'pow': (2, math.pow), - 'radians': (1, math.radians), 'sin': (1, math.sin), 'sinh': (1, math.sinh), 'sqrt': (1, math.sqrt), - 'tan': (1, math.tan), 'tanh': (1, math.tanh), 'trunc': (1, math.trunc), 'abs': (1, lambda x: abs(x)), - 'round': (1, lambda x: round(x)), '-abs': (1, lambda x: -abs(x))} - -if __name__ == '__main__': - print('This module contains lists and dictionaries of data that are used in other pycalc modules') +import inspect +import sys + + +class Pycalclib: + """A model of pycalclib""" + + def __init__(self, user_module): + """Initialize pycalclib""" + self.user_module = user_module + # r_strings that are used to find operators / functions / etc + self.r_one_sign_operators = [r'^\+', r'^-', r'^\*', r'^/', r'^\^', r'^%'] + self.r_two_signs_operators = [r'^//', r'^\*\*'] + self.r_comparison_operators = [r'^<=', r'^>=', r'^<', r'^>', r'^==', r'^!='] + self.r_functions = [r'^acosh', r'^acos', r'^asinh', r'^asin', r'^atan2', r'^atanh', r'^atan', r'^ceil', + r'^copysign', r'^cosh', r'^cos', r'^degrees', r'^erfc', r'^erf', r'^expm1', r'^exp', + r'^fabs', r'^factorial', r'^floor', r'^fmod', r'^gamma', r'^gcd', r'^hypot', r'^isfinite', + r'^isinf', r'^isnan', r'^ldexp', r'^lgamma', r'^log10', r'^log1p', r'^log2', r'^log', + r'^pow', r'^radians', r'^sinh', r'^sin', r'^sqrt', r'^tanh', r'^tan', r'^trunc', + r'^abs', r'^round'] + self.r_negative_functions = [r'^-acosh', r'^-acos', r'^-asinh', r'^-asin', r'^-atan2', r'^-atanh', r'^-atan', + r'^-ceil', r'^-copysign', r'^-cosh', r'^-cos', r'^-degrees', r'^-erfc', r'^-erf', + r'^-expm1', r'^-exp', r'^-fabs', r'^-factorial', r'^-floor', r'^-fmod', + r'^-gamma', r'^-gcd', r'^-hypot', r'^-isfinite', r'^-isinf', r'^-isnan', + r'^-ldexp', r'^-lgamma', r'^-log10', r'^-log1p', r'^-log2', r'^-log', r'^-pow', + r'^-radians', r'^-sinh', r'^-sin', r'-^sqrt', r'^-tanh', r'^-tan', r'^-trunc', + r'^-abs', r'^-round'] + self.r_constants = [r'^e', r'^pi', r'^tau', r'^inf', r'^nan'] + self.r_negative_constants = [r'^\-e', r'^\-pi', r'^\-tau', r'^\-inf', r'^\-nan'] + self.r_int_numbers = [r'^\d+'] + self.r_negative_int_numbers = [r'^\-\d+'] + self.r_float_numbers = [r'^\d+\.\d+|^\.\d+'] + self.r_negative_float_numbers = [r'^\-\d+\.\d+|^\-\.\d+'] + self.r_brackets = [r'^\(', r'^\)'] + self.r_comma = [r'^,'] + self.r_space = [r'^\s'] + # acceptable constants and functions + self.constants = ['e', 'pi', 'tau', 'inf', 'nan'] + self.negative_constants = ['-e', '-pi', '-tau', '-inf', '-nan'] + self.functions = ['acosh', 'acos', 'asinh', 'asin', 'atan2', 'atanh', 'atan', 'ceil', 'copysign', 'cosh', + 'cos', 'degrees', 'erfc', 'erf', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', + 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', + 'lgamma', 'log10', 'log1p', 'log2', 'log', 'modf', 'pow', 'radians', 'sinh', 'sin', 'sqrt', + 'tanh', 'tan', 'trunc', 'round', 'abs'] + self.negative_functions = ['-acosh', '-acos', '-asinh', '-asin', '-atan2', '-atanh', '-atan', '-ceil', + '-copysign', '-cosh', '-cos', '-degrees', '-erfc', '-erf', '-exp', '-expm1', + '-fabs', '-factorial', '-floor', '-fmod', '-frexp', '-fsum', '-gamma', '-gcd', + '-hypot', '-isclose', '-isfinite', '-isinf', '-isnan', '-ldexp', '-lgamma', + '-log10', '-log1p', '-log2', '-log', '-modf', '-pow', '-radians', '-sinh', + '-sin', '-sqrt', '-tanh', '-tan', '-trunc', '-round', '-abs'] + # acceptable operators + self.operators = ['+', '-', '*', '/', '//', '%', '^', '**'] + # acceptable comparison operators + self.comparison_operators = ['<=', '>=', '<', '>', '==', '!='] + # operators precedence + self.precedence = {'(': 0, ')': 0, '<': 0, '>': 0, '<=': 0, '>=': 0, '==': 0, '!=': 0, '+': 1, '-': 1, + '*': 2, '/': 2, '//': 2, '%': 2, '^': 3, '**': 3} + # numeric equivalents of constants + self.constants_numeric_equivalents = {'e': math.e, '-e': -math.e, 'pi': math.pi, '-pi': -math.pi, + 'tau': math.tau, '-tau': -math.tau} + # operators actions + self.operators_dict = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, + '^': operator.pow, '**': operator.pow, '//': operator.floordiv, '%': operator.mod, + '<': operator.lt, '>': operator.gt, '<=': operator.le, '>=': operator.ge, + '==': operator.eq, '!=': operator.ne} + # functions actions + # first element in a tuple associated with each key is a number of parameters for corresponding function + self.functions_dict = {'acos': (1, math.acos), 'acosh': (1, math.acosh), 'asin': (1, math.asin), + 'asinh': (1, math.asinh), 'atan': (1, math.atan), 'atan2': (2, math.atan2), + 'atanh': (1, math.atanh), 'ceil': (1, math.ceil), 'copysign': (2, math.copysign), + 'cos': (1, math.cos), 'cosh': (1, math.cosh), 'degrees': (1, math.degrees), + 'erf': (1, math.erf), 'erfc': (1, math.erfc), 'exp': (1, math.exp), + 'expm1': (1, math.expm1), 'fabs': (1, math.fabs), 'factorial': (1, math. factorial), + 'floor': (1, math.floor), 'fmod': (2, math.fmod), 'gamma': (1, math.gamma), + 'gcd': (2, math.gcd), 'hypot': (2, math.hypot), 'isfinite': (1, math.isfinite), + 'isinf': (1, math.isinf), 'isnan': (1, math.isnan), 'ldexp': (2, math.ldexp), + 'lgamma': (1, math.lgamma), 'log': (2, math.log), 'log10': (1, math.log10), + 'log1p': (1, math.log1p), 'log2': (1, math.log2), 'pow': (2, math.pow), + 'radians': (1, math.radians), 'sin': (1, math.sin), 'sinh': (1, math.sinh), + 'sqrt': (1, math.sqrt), 'tan': (1, math.tan), 'tanh': (1, math.tanh), + 'trunc': (1, math.trunc), 'abs': (1, lambda x: abs(x)), + 'round': (1, lambda x: round(x)), '-abs': (1, lambda x: -abs(x))} + + if self.user_module != '': + self.consider_user_module() + + self.r_strings = (self.r_brackets + self.r_two_signs_operators + self.r_one_sign_operators + + self.r_negative_functions + self.r_functions + self.r_comparison_operators + + self.r_negative_float_numbers + self.r_negative_int_numbers + self.r_negative_constants + + self.r_float_numbers + self.r_int_numbers + self.r_constants + self.r_space + self.r_comma) + + def consider_user_module(self): + """Adds user functions into pycalclib""" + try: + user_module = __import__(self.user_module) + except ModuleNotFoundError: + print("ERROR: '{}' module hasn't been found".format(self.user_module)) + sys.exit(0) + user_functions_info = inspect.getmembers(user_module, inspect.isfunction) + # dictionary of user functions: key - func name string, value: (func_obj, func_num_params) + user_functions = {} + for item in user_functions_info: + user_functions[item[0]] = (item[1], len(inspect.getfullargspec(item[1]).args)) + # add or replace functions in self.functions + for func in user_functions.keys(): + if func not in self.functions: + self.functions.insert(0, func) + self.negative_functions.insert(0, ('-{}'.format(func))) + self.r_functions.insert(0, (r'^{}'.format(func))) + self.r_negative_functions.insert(0, (r'^-{}'.format(func))) + self.functions_dict[func] = (user_functions[func][1], user_functions[func][0]) diff --git a/final_task/pycalc/rpn.py b/final_task/pycalc/rpn.py index c4fc238..2025a48 100644 --- a/final_task/pycalc/rpn.py +++ b/final_task/pycalc/rpn.py @@ -1,27 +1,26 @@ """This module contains a class that allows to transform infix notation math tokens into RPN""" # import -from .pycalclib import constants, negative_constants, operators, comparison_operators, precedence -from .pycalclib import functions, negative_functions from .utils import is_number +from .pycalclib import Pycalclib class RPN: """A model of rpn capable of converting infix to postfix (RPN) notation""" - def __init__(self, tokens): + def __init__(self, tokens, pycalclib): """Initialize rpn""" self.tokens = tokens self.operators_stack = [] self.output_queue = [] self.error_msg = None - self.constants = constants - self.negative_constants = negative_constants - self.operators = operators - self.comparison_operators = comparison_operators - self.precedence = precedence - self.functions = functions - self.negative_functions = negative_functions + self.constants = pycalclib.constants + self.negative_constants = pycalclib.negative_constants + self.operators = pycalclib.operators + self.comparison_operators = pycalclib.comparison_operators + self.precedence = pycalclib.precedence + self.functions = pycalclib.functions + self.negative_functions = pycalclib.negative_functions @staticmethod def is_left_associative(operator): @@ -99,10 +98,11 @@ def convert2rpn(self): if __name__ == '__main__': print("""This module contains class that allows to transform a list of math tokens in infix notation into list of -tokens in RPN. For example: \n""") + tokens in RPN. For example: \n""") test_tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] print("Infix_tokens: ", test_tokens) - rpn = RPN(test_tokens) + pycalclib = Pycalclib(user_module='') + rpn = RPN(test_tokens, pycalclib) rpn_tokens, error_msg = rpn.convert2rpn() if not error_msg: print('RPN tokens: ', rpn_tokens) diff --git a/final_task/pycalc/rpncalculator.py b/final_task/pycalc/rpncalculator.py index ea93cd3..4adc045 100644 --- a/final_task/pycalc/rpncalculator.py +++ b/final_task/pycalc/rpncalculator.py @@ -1,19 +1,19 @@ """This module contains a class that allows to evaluate math expression in RPN""" # import -from .pycalclib import operators_dict, functions_dict +from .pycalclib import Pycalclib class RPNcalculator: """A model of RPN math expression evaluator""" - def __init__(self, rpn_tokens): + def __init__(self, rpn_tokens, pycalclib): """Initialize RPNcalculator object""" self.rpn_tokens = rpn_tokens self.error_msg = None - self.operators_dict = operators_dict - self.functions_dict = functions_dict + self.operators_dict = pycalclib.operators_dict + self.functions_dict = pycalclib.functions_dict self.stack = [] def evaluate(self): @@ -34,34 +34,29 @@ def evaluate(self): self.error_msg = 'ERROR: {}'.format(e) break elif token in self.functions_dict.keys(): - if self.functions_dict[token][0] == 1: + func_params_num = self.functions_dict[token][0] + func_args = [] + for param_index in range(func_params_num): try: - op1 = self.stack.pop() + arg = self.stack.pop() except IndexError: self.error_msg = 'ERROR: invalid syntax' break - if token == 'sqrt' and float(op1) < 0: # check sqrt operation + if token == 'sqrt' and float(arg) < 0: # check sqrt function self.error_msg = "ERROR: a root can't be extracted from a negative number" break - try: - self.stack.append(self.functions_dict[token][1](op1)) - except Exception: - self.error_msg = 'ERROR: incorrect use of {} function'.format(token) - break - elif self.functions_dict[token][0] == 2: - try: - op2, op1 = self.stack.pop(), self.stack.pop() - except IndexError: - self.error_msg = 'ERROR: invalid syntax' - break - if token == 'pow' and (float(op1) < 0 and (not float(op2).is_integer())): # check pow function + if token == 'pow' and param_index == 1 and float(arg) < 0 and not func_args[-1].is_integer(): self.error_msg = 'ERROR: negative number cannot be raised to a fractional power' break + func_args.insert(0, arg) + if not self.error_msg: try: - self.stack.append(self.functions_dict[token][1](op1, op2)) + self.stack.append(self.functions_dict[token][1](*func_args)) except Exception: self.error_msg = 'ERROR: incorrect use of {} function'.format(token) break + else: + break else: self.stack.append(float(token)) @@ -81,7 +76,8 @@ def evaluate(self): print("This module contains class that allows to evaluate math expression in form of RPN tokens. For example: \n") test_rpn = ['2', '3', '4', '*', '-', '2', '5', '/', '-'] print('RPN tokens math expression: ', test_rpn) - rpncalculator = RPNcalculator(test_rpn) + pycalclib = Pycalclib(user_module='') + rpncalculator = RPNcalculator(test_rpn, pycalclib) result, error_msg = rpncalculator.evaluate() if not error_msg: print('Result: ', result) diff --git a/final_task/pycalc/test_user_module.py b/final_task/pycalc/test_user_module.py new file mode 100644 index 0000000..925a5f7 --- /dev/null +++ b/final_task/pycalc/test_user_module.py @@ -0,0 +1,10 @@ +"""This module contains test user functions""" + +def five(param1): + """Returns 5""" + return 5 + + +def squaressum(param1, param2): + """Calculates sum of squares""" + return param1**2 + param2**2 diff --git a/final_task/pycalc/tokenizer.py b/final_task/pycalc/tokenizer.py index e9e887e..ebdf258 100644 --- a/final_task/pycalc/tokenizer.py +++ b/final_task/pycalc/tokenizer.py @@ -1,19 +1,19 @@ -"""This module contains a class that allows to find and extract tokens from the user's mathematical expression""" +"""This module contains a class that allows to find and extract tokens from the user mathematical expression""" # import import re -from .pycalclib import r_strings, operators, constants from .utils import is_number +from .pycalclib import Pycalclib class Tokenizer: """A model of tokenizer capable of finding and extracting tokens from string math expression""" - def __init__(self, user_expr): + def __init__(self, user_expr, pycalclib): """Initialize tokenizer""" self.user_expr = user_expr - self.r_strings = r_strings - self.operators = operators - self.constants = constants + self.r_strings = pycalclib.r_strings + self.operators = pycalclib.operators + self.constants = pycalclib.constants self.error_msg = None self.tokens = [] @@ -38,8 +38,8 @@ def consider_sub_signs(self, tokens): break def check_first_tokens(self, tokens): - """Check whether first two tokens are a negative number (negative constant) - and replaces them by negative number if so""" + """Checks whether first two tokens are a negative number (negative constant) + and replaces them with negative number if so""" if tokens[0] == '-' and (is_number(tokens[1]) or tokens[1] in self.constants): if is_number(tokens[1]): first_neg_token = str(float(tokens[1])*-1) @@ -66,7 +66,7 @@ def extract_tokens(self): break else: continue - if got_token is False: # is True when an unknown sign / signs has been found + if got_token is False: # is True when an unknown sign/signs has been found self.error_msg = "ERROR: invalid syntax" break got_token = False # if one of acceptable sign/signs has been found, switch flag back to False for @@ -83,7 +83,8 @@ def extract_tokens(self): print("This module contains class that allows to extract tokens from math strings. For example: \n") test_string = '1---1*-5-sin(-3)' print("Math string: ", test_string) - tokenizer = Tokenizer(test_string) + pycalclib = Pycalclib(user_module='') + tokenizer = Tokenizer(test_string, pycalclib) tokens, error_msg = tokenizer.extract_tokens() if not error_msg: print('Extracted tokes: ', tokens) From d3e553d86c31a58b5eb4317143c0a8ea21500835 Mon Sep 17 00:00:00 2001 From: AntonCharnichenka Date: Fri, 16 Nov 2018 20:35:02 +0300 Subject: [PATCH 20/20] test_user_module pep8 correction --- final_task/pycalc/test_user_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/final_task/pycalc/test_user_module.py b/final_task/pycalc/test_user_module.py index 925a5f7..5fba791 100644 --- a/final_task/pycalc/test_user_module.py +++ b/final_task/pycalc/test_user_module.py @@ -1,5 +1,6 @@ """This module contains test user functions""" + def five(param1): """Returns 5""" return 5