From 60fc07557432e5649b04c11c0eacac62357ac123 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Mon, 26 Aug 2024 16:17:43 +0800 Subject: [PATCH 01/11] v4.0-support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them --- interpreter.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/interpreter.py b/interpreter.py index bf41d06..bd34b7c 100644 --- a/interpreter.py +++ b/interpreter.py @@ -8,10 +8,11 @@ @desc: simple demo for python interpreter v2.0 v1.0 : only support single-digit integers + v2.0 : support multi-digit integers +/-, support process whitespace -v3.0 : support parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. +v3.0 : support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. +v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” """ -INTEGER, PLUS, EOF, MINUS = 'INTEGER', 'PLUS', 'EOF', 'MINUS' +INTEGER, PLUS, EOF, MINUS, MUL, DIV = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV' class Token(object): def __init__(self, type, value): @@ -71,6 +72,13 @@ def get_next_token(self): if self.current_char == '-': self.advance() return Token(MINUS, '-') + if self.current_char == '*': + self.advance() + return Token(MUL, '*') + if self.current_char == '/': + self.advance() + return Token(DIV, '/') + self.error() return Token(EOF, None) @@ -84,25 +92,40 @@ def eat(self, token_type): self.error() def term(self): + # 目前只支持整型类型的表达式 if self.current_token.type != INTEGER: self.error() term = self.current_token self.eat(INTEGER) return term.value + def factor(self): + if self.current_token.type != INTEGER: + self.error() + factor = self.current_token + self.eat(INTEGER) + return factor.value + def expr(self): """Parser / Parser / Interpreter, this function takes a tokenized stream and produces an abstract syntax tree, or more commonly a "value".""" self.current_token = self.get_next_token() result = self.term() - while True: + while self.current_token.type in (PLUS, MINUS, MUL, DIV): + # 目前只能支持纯加减,或者纯乘除的表达式,加减乘除的复合运算涉及优先级问题暂不支持 op = self.current_token if op.type == PLUS: + self.eat(PLUS) result += self.term() elif op.type == MINUS: + self.eat(MINUS) result -= self.term() - else: - break + elif op.type == MUL: + self.eat(MUL) + result *= self.factor() + elif op.type == DIV: + self.eat(DIV) + result /= self.factor() return result def main(): From b9c295d2c34be2e6248d43afb63ad8f862d399cb Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Tue, 27 Aug 2024 11:50:00 +0800 Subject: [PATCH 02/11] v5.0-support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. --- interpreter.py | 64 ++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/interpreter.py b/interpreter.py index bd34b7c..709dcbc 100644 --- a/interpreter.py +++ b/interpreter.py @@ -10,6 +10,7 @@ v2.0 : support multi-digit integers +/-, support process whitespace v3.0 : support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” +v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. """ INTEGER, PLUS, EOF, MINUS, MUL, DIV = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV' @@ -28,18 +29,18 @@ def __str__(self): def __repr__(self): return self.__str__() -class Interpreter(object): - +class Analyzer(object): + """Lexical analyzer 表达式的语法解析器,用于将表达式解析成token流""" def __init__(self, text): self.text = text self.pos = 0 - self.current_token = None self.current_char = self.text[self.pos] def error(self): - raise Exception('Error parsing input') + return Exception("Invalid input") def advance(self): + """Advance the 'pos' pointer and set the 'current_char' variable.""" self.pos += 1 if self.pos > len(self.text) - 1: self.current_char = None @@ -47,6 +48,7 @@ def advance(self): self.current_char = self.text[self.pos] def skip_whitespace(self): + """Skip whitespace, tab, newline.""" while self.current_char is not None and self.current_char == ' ': self.advance() @@ -59,7 +61,7 @@ def integer(self): return int(result) def get_next_token(self): - """Lexical analyzer / scanner / tokenizer, this function breaking a sentence apart into tokens.""" + """this function breaking a sentence apart into tokens.""" while self.current_char is not None: if self.current_char.isspace(): self.skip_whitespace() @@ -82,24 +84,38 @@ def get_next_token(self): self.error() return Token(EOF, None) + +class Interpreter(object): + def __init__(self, analyzer): + self.analyzer = analyzer + self.current_token = self.analyzer.get_next_token() + + def error(self): + raise Exception('Invalid Syntax') + def eat(self, token_type): """compare the current token type with the passed token type and if they match then "eat" the current token and assign the next token to the self.current_token, otherwise raise an exception.""" if self.current_token.type == token_type: - self.current_token = self.get_next_token() + self.current_token = self.analyzer.get_next_token() else: self.error() def term(self): - # 目前只支持整型类型的表达式 - if self.current_token.type != INTEGER: - self.error() - term = self.current_token - self.eat(INTEGER) - return term.value + """计算乘除表达块: factor((MUL|DIV) factor)* """ + result = self.factor() + while self.current_token.type in (MUL, DIV): + if self.current_token.type == MUL: + self.eat(MUL) + result *= self.factor() + elif self.current_token.type == DIV: + self.eat(DIV) + result /= self.factor() + return result def factor(self): + """返回乘除表达式的数,目前只支持整型""" if self.current_token.type != INTEGER: self.error() factor = self.current_token @@ -107,41 +123,33 @@ def factor(self): return factor.value def expr(self): - """Parser / Parser / Interpreter, this function takes a tokenized stream - and produces an abstract syntax tree, or more commonly a "value".""" - self.current_token = self.get_next_token() + """计算加减表达块:term((PLUS|MINUS) term)* .""" result = self.term() while self.current_token.type in (PLUS, MINUS, MUL, DIV): - # 目前只能支持纯加减,或者纯乘除的表达式,加减乘除的复合运算涉及优先级问题暂不支持 - op = self.current_token - if op.type == PLUS: + if self.current_token.type == PLUS: self.eat(PLUS) result += self.term() - elif op.type == MINUS: + elif self.current_token.type == MINUS: self.eat(MINUS) result -= self.term() - elif op.type == MUL: - self.eat(MUL) - result *= self.factor() - elif op.type == DIV: - self.eat(DIV) - result /= self.factor() return result + def main(): while True: try: - text = input('input a express like "1+2"(Only single digit integers are allowed in the input)> ') + text = input('input a express like "1+2*3+16/4"(Only single digit integers are allowed in the input)> ') except EOFError: break if not text: continue - - interpreter = Interpreter(text) + analyzer = Analyzer(text) + interpreter = Interpreter(analyzer) result = interpreter.expr() print(result) + if __name__ == '__main__': main() From e85b2c189ce239b2dd9911f3c9e798e503c4c7d9 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Tue, 27 Aug 2024 19:46:36 +0800 Subject: [PATCH 03/11] v6.0-support to evaluates arithmetic expressions that have different operators and parentheses. --- interpreter.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/interpreter.py b/interpreter.py index 709dcbc..d4497d5 100644 --- a/interpreter.py +++ b/interpreter.py @@ -11,9 +11,10 @@ v3.0 : support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. +v6.0 : support to evaluates arithmetic expressions that have different operators and parentheses. """ -INTEGER, PLUS, EOF, MINUS, MUL, DIV = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV' +INTEGER, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN' class Token(object): def __init__(self, type, value): @@ -80,6 +81,12 @@ def get_next_token(self): if self.current_char == '/': self.advance() return Token(DIV, '/') + if self.current_char == '(': + self.advance() + return Token(LPAREN, '(') + if self.current_char == ')': + self.advance() + return Token(RPAREN, ')') self.error() return Token(EOF, None) @@ -115,15 +122,25 @@ def term(self): return result def factor(self): - """返回乘除表达式的数,目前只支持整型""" - if self.current_token.type != INTEGER: + """返回参与运算的数,支持整型或者带括号的表达式""" + token = self.current_token + if self.current_token.type == INTEGER: + self.eat(INTEGER) + return token.value + elif self.current_token.type == LPAREN: + self.eat(LPAREN) + result = self.expr() + self.eat(RPAREN) + return result + else: self.error() - factor = self.current_token - self.eat(INTEGER) - return factor.value def expr(self): - """计算加减表达块:term((PLUS|MINUS) term)* .""" + """表达式解析:term((PLUS|MINUS) term)* . + expr : term ((PLUS | MINUS) term)* + term : factor ((MUL | DIV) factor)* + factor : INTEGER | LPAREN expr RPAREN + """ result = self.term() while self.current_token.type in (PLUS, MINUS, MUL, DIV): if self.current_token.type == PLUS: @@ -138,7 +155,7 @@ def expr(self): def main(): while True: try: - text = input('input a express like "1+2*3+16/4"(Only single digit integers are allowed in the input)> ') + text = input('input a express like "10+2*3+16/(4+4)-(3-2)*2"(Only single digit integers are allowed in the input)> ') except EOFError: break From f1779e6eba81c64eeb16952ec003469cfac7843a Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Wed, 28 Aug 2024 18:05:15 +0800 Subject: [PATCH 04/11] v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. --- README.md | 29 +++++++- ast.png | Bin 0 -> 26115 bytes ast.py | 43 +++++++++++ genastdot.py | 81 ++++++++++++++++++++ genptdot.py | 198 +++++++++++++++++++++++++++++++++++++++++++++++++ interpreter.py | 77 ++++++++++++++----- 6 files changed, 409 insertions(+), 19 deletions(-) create mode 100644 ast.png create mode 100644 ast.py create mode 100644 genastdot.py create mode 100644 genptdot.py diff --git a/README.md b/README.md index 305027e..28fe998 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ # simple_interpreter -simple demo for python interpreter +simple demo for python interpreter + +一个用 python 实现的简单解释器,支持解释 python 代码 + +分版本逐步实现完整功能,适合初学者学习解释器的工作原理 + +## 版本说明 +### v1.0 : only support single-digit integers + +支持加法运算 +### v2.0 : support multi-digit integers +/-, support process whitespace +支持加法减法,支持处理表达式中的空格 +### v3.0 : support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. +支持包含多个数字的加减表达式 +### v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” +支持包含多个数字的乘除表达式 +### v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. +支持包含多个数字的加减乘除混合表达式 +### v6.0 : support to evaluates arithmetic expressions that have different operators and parentheses. +支持包含括号的混合表达式处理 +### v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. +支持使用 AST (abstract syntax tree 抽象语法树)来表示算术表达式 +#### 语法树可视化 +```shell +python genastdot.py "7 + 3 * (10 / (12 / (3 + 1) - 1))" > ast.dot && dot -Tpng -o ast.png ast.dot +``` +![ast.png](ast.png) + +执行之前需要先按照dot, 参考:https://graphviz.org/ \ No newline at end of file diff --git a/ast.png b/ast.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ffb2d446f90cb71701a93ddaabd41f21789396 GIT binary patch literal 26115 zcmX_ocOaE-|Nm){%v40OLMk(4gk(m^il{`CRgn-9Wp4^89SR8v36+(Mij>GqS!HEJ zS&{L3U45SKujh|XPmXi$bKlqXe!tfH3enTmWT4}qqfjUe2es4;@ZUiSWhDdcTKw&G zf$bRnv-Y%(rW$3L{O^8QdJKiaLpi9XV&qO8?euVYZ}V4qV)p&^7{v`L8>CqHSbTSD zXY#0sq{VjZ)V_CfXGo&iywR8O)JHp?evab{rL)iK*zmW@U#&)ikM|byXTdMl1M`c$ z+m*Go`tsj9m)HN0%kF>KbxGPpDdx_{uh0F1Y3ZwMInsXDUPHS4Bc$H0&#bG{?mXlGioYS5?bFz%++D7GaQlYev$N@a zeb&0IN3&WAuFPJr?<|(SbNhBfl=70+rcIluk3Wb@KPap67;50PZ-2sDR9yV2Hb%%J zQ{UWNSLfDlUMnlB-;KMjIsQdOa=Fvr zzI_w-`r~9|WYjZWz8sj9wJUd;eQk$(ke-v1xS%dIIAA^7>B)f)rA&epcTdl6l>zIY zmso zb$HN?Lt;z3s)0RSWmjIf|4M1sXYWk;tbFOh*B31Kvg}f}(e3(=q|bLqohWu$hd-w^ zEnULjFs7SjYgk)ve-plCRi=@ZRpr~aG`hOF!QtT>73fl}q-L^|czIWR{`{F;(mJ?Z z-&}|9hQ&lr#h<_9<*y?vhu9;y>Tlm>Z*OnEf3n2ww5CUDlHwIUUf$k9?%}hyJ3h7d zhKGfzzK!IPo)ad|bX^4~ExZ^|xDy}iM&sdwyH&5s0%g_YGR zPEOA4LPD2b%A}S{xyj}~ecJHYbdPRZct}Xa_qS1UC-V*P>~Gw;Q(M;&?u}2_y?eKR zT3TAWg#G)_(f+XlQ7xt*wK(Qr~=P zYU&*t+Tlj85o9~+^vu3R+o3DLec_Kp(ms!sY;0`*-HD*4y1MmjV&?v4*#qZ3U=LPy zbtUCb?@Uzo*=CTrF)1mDn)_i$EP_k-)TvYZuFl6cm0XZ_8G2Q>>($#RZcUVnhWje3 z>f6pv#p8*Vlqht0404Tj6zYub$W1fO%Cq^nZEB?L$movn$j0{GSgq~sik?!>E_A;8 zVR$?zai7PAJ9q9(%+9j?d*q%%{>{xs1@+KgG zox{?#`Iz+jUEHqE7Luh6e|8B?jI!=DH#c{3ajC|lQCp_WV3+;+Su*}DEB9sPr!*b) z50yi^Klqp(7nYIPe6rZ3qOUe4@7!m}M(2GVzgJ_82E@f}(oK|KqpGUf_*7i}@Z`N# zV-)a8Y+{xT8+`A1&mWAX4+scgmv!RN*3rpxXcLvWO`j_@V|-8Wu|s#5);*3!7&f_pD~-TyI3v3GblbdOE_tM+zw?2@6bGKH^SzxI6l z=8OGP_4Vu4{{H?QJ9k$4(J)0*sp0MI$EvHVd6bm485&l{y?*uYjNR+V$#C6I zNwad@eUl%R-=Z^K{QibvpVuE|bO%ir7peMt%F7*#))C?1Jp%)Q=y9o+$Ge6)OZL(; zFp$Tcq8_#x_bTXGl&|HZp*K#r^=u+$1+NmM9mG-b;=NBL>-EfxeX05IL0w;;1(p2$ zhYyVz`utK3t@M(2M$^h!H*E@F;n(QtD0FbTe3`9BU;O=g7c1HHBljXRGIo3|c3tmW zM2(RSp&jce^a~22d3Nq|Wo-hz;rfnCL3*yvmoDXbFM7VG%EjbYtWLVxex<9_qy5== zP6Go2+2iq+_Sz#sdv@=pcP`qBnl<)0_ceSW&;$B!S>+>he9sV!ASZewP#v9Ubj;?d7JQZ=JAnjRkhg?lz> z>}P4N?hs^{X~egE`|1@dR+L^iXlH91Ty$yhbX6e3Xt1(|o|yvm#*G^qSJ^Sw3u_~`f3;mie- z*|CaOW_kub+`TP#>7j)*YE2X#wAI}2e%}`Bsk_GJawRT5Rs!`@T=rvPWPJ0y+dB*m zR_a{4UPDKCSL4fFHvNlF^Ur?RP&!i`!U~8K7#+=A;jm|3b0mk#?KQE&Xy>L#Ze>yJ zMQprvVQgZ6PHP`$XRFt~8d@_}>}(ELgYHq)cFIS@Eb!H<{Wbm0{s93Yx2`ubq04Vo z@{%<$G-ToA+%>FGue>m^%Gb{iUwOr@UAr2cvAeJ2P@iAuti%G6@LBR|ynO7Qyz9>G z8`c?Tn+e!6YCO055O=B4e_e1$h>ElG?_J71!Uc{m=hkMK!o1K>QJfe_8W!koH zKbqvrrFn-4_Cr0NZM5p@>hjKh;2`B&dspD4&ri=hdHS^SwuDu6q`~VqZ`QI3=~km0 z=^GgExoy>;v0HE&7#y_fEOy166bz4uzz*=ev&Sao$rI+0wp>zp{iK{_n?8MtzITs_ zLd(QNGcjtTd)UgsA#5G1pvA9muXl)utRbx`j2hawi9?QBzPzN6AZ6#fWzSh2tN;}i zl~V4aiD%>*`}+GWD}2|G?J4g%yjsK~BBIfW!^2qH%}qvITbp^?HpV#Z0ENpxc|=4Q z>g(%IYhF3%<+X3OhzQHpt@L*et;J6@qqbGFwi=JxhTm$c*)`Z%ayi*JOJ84q1z#oB zM=y3I@LDj}*c!4ZyI)@Y_2-ur<(hWdO3F;~xjb>WaG32P^up-Dagv+Zeu7Ne%IQ%S?EZTzn|YK)CFAMjkq`gx7`{vwqKpP%6-a* zTk5%e7WAVPeTUz5b-f%Otq%|3Dqg={)791GH}?|pes;W8supRCIev!tXXPnAUa_NFma3h$0n1zyj1E1PMn^{{xw5S9M^n1{c-d^jgM+MF6+Mk& z7ya?_>!ov7>rp5I-@kvqLqg)lc%6Gh_3PI+^716mg|uSlA9IKAH(%Z}2y}8#G~UqG zHbd{aeP8-LZ$4I5*5a#wpWSyg#iy1n+0Q0%=}%ri|c=%`QsRt2}+-@boW zSB`%m4-WbB=TER27Hcb;RwfOL+^%0Ill3gH&S+_A8*0SVDAC$!f4+r@J+XSr*tOU7 z?4m{W#Jk?F&mFnn)zwLyuBJ_%Y<`Ce4oyknLz%tZCXesLZZ1c~Gg3|0P1Gy*zKWNG zmX#^)+qaLIi|c~}7e0tS_v^aesUJ=3()LD)Y|EetSaT~34V&1!%S`jn{+S!RH&UuZ z`7<}y3vM@HojvT-5;d4@n!h$xBjVeF;9B1K3fk{!y{JX6L3J8mUi~XCCW_XG^6m2D zQ`&GFA-u1{*xEeCvozH^=0c#8@@8d^HK1GrVc zz>TR&S2=9zsVb;R5;pZ)89)8*X3~%qTYP^fCT10-v{Q?Vm30+Rd3|ese*UU!k50A~ zy*Z;iIy(B~{1+Kn1qCbMEb_F@zQ0}Z*}}M$@@IsGpBf*SpOHaJahJ=MQ{6cmiE z>!)U$&J{elfB#y(^u4#P%JlT~6KL2L9}=WdIQk3ND9iu8k&|)K?5_ouRVU^~a?2-x zJDrQq_Mdkl3_Ry}$cQ&VQP3vv05(=uRz7j=*Vb*#uD-0y-dj4g$;2ao+#NOrlqhua zw?7g9bt$jDXg+`OGblS-1oFaKij%W*jFVYLY3bH8XU-6| z`_Sinp>&6p`?a;{9vBEG>~*Z4>!A27 z_N@Q!$vByrnpV`;GlqnOP&cDZZB&{c;@53keT{SPg;y07y=k0WFDn24TmL>9DSEQF zr#aJ*y4kGM{bEbq!U_3{-&Pm9jlG*I{`ftW+lwWIJ6s$K^ySN!1cqZ72hD|`xav?ETtzB_4dFIm7X-tENzo8mGuZ%N^#0y^agr`Wdd|+yDFCH(EJG3aYldCZ!@luxG3~3k4jxCPUHy%zniEq~ z)mO&Lg77i=dtVr2wGgNcDTIZMP3WD7J*h?ZZlenjV`>{3qR)Oz`Y=8lx)*Dl50Z?$ z&r)fI94Ud#3$(9lYP9e*ra(6)w`7=24t^B6b4UYSKc#sYHz&}hP+{L?EXeIKwL5@F zW3>fNC7bJ^u>j->ty;Be9{p=|rm5j$Zu%&DpHVLe6;Ozxq0Ee@%cZBM8=`R3eEmwD z8Snn+kp28M+QfsIs)K4SK>yB{FQ59@1b(H=E2a!Sz7G%~>7P0h@1-o48yuzkBz zu~HkZb6aVr(*_pdjdTghK1#Ly&h{;jHZ9J4aWn#9N|(aYoWj1inV-LB3nyn2q@hd@ zFI@*Z(rVhjfC8Z&fT3+g$1_05v3(aS?DDO8-OgRRvKqU^goTrbhHMk|c}PmzHe3U3 zxccigo7t6CYkT{h4D909^1a#7DT7(~3!ZVmKYnHMFI74Y@(emMucYLL+S*!iY3YFY zc(xNKPxkiptws-F-?AmSpkN~e0<<(u1A`4{)OrR6zG%!<-QC>xuFh-$imO!LuHVjEt<;)YQze3O>EGFn8?v1unpjEE$?C9c8?uKQJYawDeIM z&+2Ac<*LkF2y=~4S>7iqu$}T*looQ>8K+%v?(+sh`%z(q93;(pTLv#lI@$pHqjTJr zvfE(ez`?rw1SqOli}VLRkb&KeOCgYTXq!KQ5~1%ycgVW-?_uf$LHJRy5+;0 zz!-kC1HDWmT6+3Euk(h6hG6`Be0(YPO|}}ddA)tt4kYi#Lw|VaPURON)s?GOB^&L` z0|hc~wUJ`t)Mb2ZR!D~(eOvMjoul+{TW%Bq$G9Yp^7TAb3^G(#Bj^hKb@5ie`)X|= zLgp5t?p~)-w}Ms$w8@8`wWu=H1fWpAa6$AxP6YMQ;{9!jW5q7Q&Gypc)9e@XdLR4Y zmhL`Tyb&nAZ|q60&t1hUGT<3RM^PD;_-476x4B}tB})Qo8knL^!*NFCxjt4x8UdhE zF2qz$gm&^yJ;VOMLTJoArTq9<-hqaWix)3W%*?z2@o&g75mRy4<7)}$9}4>&m#I zzNC2R>8d#yK7M|Q3twfwmU^(Fn{5=)9IQ`l1a67=dOay~j|{aCQV<&I6?YGhkfkkk zC>P=E5-TAE+IAKfW|*?q$KdQWsu@NX7{&4!wzc4H1+e|m%d;IfIraCficx6#k}L!Z*Z+VjkR{QCZuNPh`3 z=R>g#lOZ-h>StBhJUP?e)X`StI-*}=F~cH-0crp=p; z#}eyoxo}@}Ra7e0`w=$3S;UM-Uf#@VEh`1Z$Zf16z~oXs~N>Ic=gi4?XPYX5ra!Nvw-;UdKS z(!W1TDE2c2x5oO?i|D%+Gz$z-9Z#P=?F7z77TyNk+@Htx!)pd-9e_x_!p^T6a zP5I&sr;KA)IEXUQn1IH&VdXK{*w{dKe%0K(5x|`;U?m~xz#6BmtZqa{Q|=rJ#I3R% zZO_A_t71FlwF0_w9(U_;;T=0(K^L>Mw|{p~c`XCS>N|%7XR1C4y_4ro%3AlcyZi{g zuK-HZ(`U~f=DwT{)lJ%GniZ4At?Yg1__K4#N>}HjA(7$J=cn6F$tfQ&r+v&VpZrtk zco-15zPr1usi~O1v`y%g|MIQdx3v^Kr+Cm$&7NAX!@{a}a0-}K9kd;Edh_e?WtW>X41*BWYCAd-Jg0_K;Ai-@C<0=UD}(r& z@#Kl#lVZop`Q-cp2cpo{;Du`$x9)g%c@%)g9#3rgM^g|az}30Sj`((muLMp4Xfzkt z84fq524x&c%Q;bW0Bc$b1-?;|InR4&#TdJ8oVGgDJpmyhA31PZAp*(#+nBSGd33%y=bLoBApk2gIw%|}sK z2}+Byn2L{IZx%nT>S(*(^H(di{x>HG9nMnRX&o`D2k? zlOR2(%%RlTiY=1Xv>`2fNL6J*GSr z^@h>r&>?!L2n~e}CiSh`laz`(Q0u8s1_T5IN*^;#G(~CejH6q>d3Vn_Mc(;jCMh=| ze<6h z=tKJ4t24?=B7iMROH0&jAvNWx>u(qbaj$wuN8i_)0f+eAxntVm6UC*l8BV~U^?3?h zuTpLw{9e*!Al*On^9MxZFFkw6yx2g)@wY%mOpFOcftq_u_tC?sIS%K~M=UIO z>9%=oJGFQWexLW>vAy+e{UzM<`>k@RbOGN2vbfj;58b{72|g8qAFL|obcf@|j@^Iq zB)SP)NK+ZmfA!wIdygGE=2R^D%IDvereD9V(()|ggekC%?meZ5>S{G`wS=ZZs6^>U z7?vZVya^BizN6-thKzd;t%7~>^5tZ4aq(#^$l%~$5E!5p^PK&0FMet|J4-^CAQEwX ztJk(}@{JUz&z?Oi#G5~(4*|Uybv#Q)!8hB?k z@wEDp?Sg`Qii(Q?rxd&wWYY(eAmH|(_Spid+Bht6nk-V17<5fdw@|56S<~_E|DK(R z!oPzYkR%SVd$-WZ z%M5Db7HgjkICnTf>UwUj*mxnfPk8&0$KBOmzTAZ#E$9|*;t8V+K8B%_(-RA6Q{}0^ zvyO%b)L=e)=e!dAzbwE7SlTX23mya;0QOlsIMmf8t3U;6;7m}as;PljZHIAduruzo zW~OuhyG=K1t#Q8uD;)B@Au_YqKX|ZRQd&9<%6TYPKyWbK`t`LfEpc8~ud2(fMa2-a zeG9^oeX2YOGXeq7V@&7|SMJ=h+noeAs` zo0$0FbB7o1op6UD?j1VE#*G`*)~;RK44*1AI5?vzj-QwJI*@s7b+tc;)FCwVesPgy zbe+H%&?{7mCX}n%=H^&b!J*VBrId*Yr_iXV^rugs+AnuOH6`Lefknm2-xCu&6tEsO zaDtR=gXSiLdbo~t#Km|^UHE#tw5%-i#fxLFH1E&86HwcisLyk=vho02&GlDiMk_K6 z)3GUOAXdX{1%}}MUnBwwuhV#!BHorlF?)6{1b+1C<_vvEVb|68Smth&mVNztg1A7a zz^?!fhrdQ)x7`PfZ7+0STg$X<_wUpuPVGd_`oT=2onK#ETm?nx1rQ&UXYzqXCm7eS zU*G=ub45$bCfM#OXvfu&b$9;HHt_VEUw`*pOBy$xvIg{al&2*FS$a3V48pl9HM# zcQW2XAWg%kJs~p@CDlJD$O<}W>F5>(sAF*2dH`Wz6qkmdlyj{?x*!N<3w-ET&`aYv zi1%4oC{3t$-Ta|VZvD-hKuT%Xudjjt?aq0Ka$DiDD6r@?NEu!B;vb*%HnSurC+lUk zKq>fESbuHBDq`e9s=d=DPtOSj5Pu5wrMTa&UiyOU1oNg%uRk+Rz0bloSkc?tn~{<6 z3UbYw%-LF@d-nmMk3F>x;hCP=#HlM{0@jKea_EkvdUTfH#3)Y&Fg^#+RRw_a>defj zb|?EHEE^O~TDO+RWMyMTu%m3V7UU>dR%O0M^5wvR1L9}iZIrTY*x%5BmJah}E%;Fo zEX4I@$4}wsR710CqnSF;1wEv)ojK3W$r;9_$@YY^yAU|YA^Ps!FjzjpT>WeEZ9cvM zmB{m$Je!v~M2{!Rha3c=MQvob6-4CVTN^ zZc97dLZ-tGmV3?n_tjuy)sZQz?Qw=Wv2k$~uq%N{)U~vN;l)dPPT4}??uBR=t^Ij+ zN#4Rl_wX%vyfAKgrKL9kn`YA~3`N1{w_EQXk3NmA>|$;y63blxakV zjzGU{A3L0q({DZbpG61?;Ui1`wq+Z zv}U9IIa%p;ER;vyP|aF+_!b~4b1Vx-lRQnC=X51p?Hz6#v)=?7`lY3(t7aOlq@cp0 z8aYk$s6crRp^koNmq~Da>pjqH0o5otEX?naAbI|gTjhMQVS~A{-jEkmx_J%y8Dv%i z6Tw*u09e?ukq%lRaL9cCl_z<5*Tcfils z{()53@ZzGCe_-J0#uW8Yky{CtRkZ={-ycGk<`)!Pi&&6*?~ff6h&U>u@u^RqOazp- zfw#N6yT58{V}qVKF)@)bUnwFieErUyEx@*r+Zh}k9aXqlO*#M?uztYYXh4I&fv#m| zXSe4Stl+GF9*4DpYq!F_dEwN%61HPwnhu-GSjQ$ryaHkSfT`=jSzx2sHw5-0s@wBCJ`3$H`)m| zW(}nWtlaExgocH7qTD`{9pD>MuER#?t^#}Z>>&X%xRxclG-Hoi?2A38cYa7zh$m4M zbgyRM<&LLk)(6!FNTZsqocN^dvox39-dGUulX~vMbCmuBY;Yp@ftd&R`|H6<8+1is z)5twGYwE>qlsb{R!srXl)Y|&&sN**O6%=?M|)LKw+8KfJRWP%!-`* z9iq+;8UU|*xVuN65KUjRelr{wosUczH*T=t+Dt~ZI(!)hM^J_u(9_mYt}e|D#>T|( zy9b7b($TG3$BR}l+>pEq7+q(A-c=d~0lo@8xTd@N)TnGn3bl;KDB|(s-Sk^!nJ@qR zT7fbbijY=tf)LamevR<`o}TjF%Zrz%#=B27?<*v4Ky)WqBTDsDxyeuC@$vD*k4GYA zMQB(U)G*5n7Z|~0r5wAqqT{jh7G|`sKalH81XL#{p39dn6L$$4mRvKo3sHQDgy=>! zyDROmxe#O_I2Z+OtUb?f<;s;AN3)C}=95T5q9$?gpZ z@QjRv!7;almEoJHY+$J74{~W$ds6@)n)J}>NTXe-E;~d;HK6!{hBo9`X@VW3VE;Q^ zyr}a1O$6vRUD1h1$S~;4J@`gihYnSvhm(lo#?6~6KYnBaGI(=e#gFHPR<-vrFAJ0U z!OoXg?-GcC9q>SJM|I?+87x!2Oe1Z83^oPrhzAcJegdLFnk5>UCosCQmX8xqfJr)$ z0~$E^I8l|c4=19Jxs0w!)<(Wg0I93rQ&SI6lJmYd*8mA>iN-gf8orJ+5J@vQNKyol zjtzmv?Z96j7-z|u{zciFLK|bI%tK-N0f&hCIE%O4$(RGUSNg)&V_8cySuOVDhnXmd zQxmu-cllkMEU~kVr-sn>919;MCTNkEzn7tAZM*urR>Cd~K^p;*4WQ2p= zi1fuSx7g=yzrLvxhu-9TW0w68SeLjLAZb|eLg!== zB{~Gn@NrXFUINLQoI7`J2$Dt0z>BMYWj=rT5_9h!_a-*Bu$wAh%y(x6dNU2;r|T$O za70IUxZaA7*929Px;pRDD7lf1joiE1x;mXHwibx28sG=`MkHhGy;au8kKca^$D6>n44UE4HyWT-xf#$ z1i@kvt~dXSjY%{HXq~=oIrS(3M9lcthhm>6msiQTjUK}m^=p@qorSGoh$icP<;%CD zP^U5=+fRWL(HZAIM3X)U4B;|4a2QYEmNz5uxlNB98|t%JzeiG1i|+=lXWM%Sp<045 z`r6u|7SaydPif%dPeMo9Nc1~r=cn!Z_J_s;C&51` zNn`0d1;72WDnJ+!zH%wIy9+Qjj1}A0dy7fig%pGg4GZ=pK1EPKpaS{D$@5}>{LBay zkxVH3l@4VSsPiD8mdJw)Ayn*`zQ3fn__uc)>A-4yjYH`Lam4mSz>7~t#&~px^ux75GQ7;vr1-`f?dXf&FEjnZAOu%gcom=;@q}$7`?1eENth~Gs6VL06Xf;IW!4W06 zIqMKr@cy2+A))g1>j1^<7VEjWIjW=Lrze)G5C@2e4&$|DNsg})w?)FXfroNUjgKUO zpIqpa*PWyg$Ovyl?$oRYdYNtO<1M+;FHph!(Y^Dmns0Y8*jQR7+jjqe`Fm>K^f?Ni z*5SkbrMJ@31i(bxuPUS=;dATOt4GkjMqD9CR?*iG~k0={R!f15{4<9;e@CNR)Bv0zjd>2OHcCHkWPA*uY)#7Iv6c znt^#xP@OqRB_ib5vTK>>)I!o|8^~R~sWm&$P|{oQ!Z(f~)aVY-aKAxR}7mga7{?149_LYwf&H8g>sABal?X%%*h=9!glf!TotQb%}|WF9j{{27n4m>~p8y15XPJH+jvLp;j6o z7q%wL)DUcNdwiX+oSZ2>E3LfB108DRe!MTM0-X)ENrq)rAQ1#A9PUBE+rA-0gX=m{ z@9Id6n*Fl>y=!P3(hAT{7&MvJkrj7HGX)TKbZjiG1^mbW7IztG711A7K9Xs46K*r| z4|WSGH7)2j`w$XXaUVig7*|?7Vw;)zsT!dFyBj63g`#0%p*==|+pE>7OpRIyVQWFF zV3XG58k)t25?P&^m{_4?P zwiAi?1_l|fPJY-60onnbi61`(mD#L7$HD?9TMzD51`M^SzFO9bVPIkKy3GJ9X8C6$ zEL=S*17n6hdvHkJ2`AA=w^*~f4<7`uk}!u+u90{yF*a5WMsM@eGdiIW5&dQ6U>*z{ zQo*o%&UY5a&3dkc$!XXA#F8jIkc?STl_6yiK!o_e5r5Pzk_bg1{1;a;FTD+&-#zmZ zxRJAq%L6p*P|uH2Zvk$QphyMm)5jN6TQ16eNJO9Z_S*oa{Lt=T(l;Q;K!hG-fJy&- zV2~+msvs&FNs=5Z_XFNG)Hv$7z6zZ5h!Z=DCv^Bb(}`@aM*f{UwNcw|CMD_Oxd;Kz zK?h91L)CY4bIWc+MM>sTa6`ao5p6dbYnDsS**~Z{Wmi1&uNTuVHY4>UkCgzKIvmjM z)tfgOqgrA-e6YS~z;vq6HCdRMdE6eDeSUF~zcu$%6cQ!Sl9>^spO~B^VP3ehmNqst zmo8m`ou7gvP87GY2A~M`_H}g9ZwN4vupZt`0`S3Wak{Pa!fQT0ffDDJ2l~UdAk!9` zkgyXx9W_}B%sAV-vFh#HAXq3`Ag=|miAjW@sHB8=J}WY2xS>&TNSt1coYTW$o6pFV zge^M#sZMG&9>nHHl~qTJC_oY`?|TCj;M&g4M2OLaGY>-m1OA0VynoE1GNEoYp}_t? z>bte7q6Q1`sOvmGn6QZm>M0fQ{_zS`=Hj+f#6vD)u1h)C7Iu z03==rGeYlbYMA$}G9f~(eOnHfkmDTcXmm^rD_~KCpEXAagUw-t`J+G*YoH)OZdqM& zcL>r#-_M`Az;p%J>xiqdJrL#o*;S?k^hE92L9)p)0Re0J2M1vcthKB%H3Tt~fpVA>?Y(x}M|vKudZNOT+EqKu3T z6c)bvwj^=Ol|?UJ*fw|SZ?$wxE6pw~-G@*jE76{i$F+dC4NO#lDVYCJWNgi~>(_@p z%GU$cq@$z5@XIRjUS9CGc+r!UFz$&W138bRIbaC%F5EsK5ddZGKT)M`Av+V!M8Wwl zn}e9RTmOv7IUKAFj9G>5hM6!8a8LL$!N{cG;g~GGgca52Jhwh*2xMDUR`x#d1X;2` z-$(?y{!~;bG-E&qoch_xiIvfprh!gmVP_YD*Zq3>QmftRw@R7;FF*gP|0P-hVzF$X zOX%mCD&n?I&@0#BGk&=E8% zZQZZ|G2gvF`nF%5iD1)ulvUL-9TfF4EeBAQ%z3jXXrku|RT@`aP+C$Ux4KS(00aiYk14FVV) zp5>CbPYPH+73fD!V;$0sG<>3>j6|J6HXTn{?ZfX|?M{+kYwGI*u}52TPG(66X#u(a z#q3+ve*p@a_T$3zdm(pNo;gE?8_=P(_4Jsr&$0>~v!3)?4?l*X2<1@~(rpV(opc2X ziKU}sl(a)D1jTIfigO_AAo6cOtKjCWgjEX((E@Zq%&Xz#aq@}>zaRR2RQh`;_AX-= z31a{mRG`@-`0g;ehWDW^bju(x(i60ka2=s#@8P zp2Iu4eTXgX<>gf!X=pgwV)oDt&*?^N>}pDIRMcB+F8A&q_j5{1OB+zIpm(pLK)XOR zMO}CL>^a#xW{3hG!1rlHj2ikXE6Kc0PO>Vrazfg+#NtKR^;%*g7a~U0ky9G~extT_ z1xqiK5m~oU_N09tzNmK4V-Jnm=BlZYfg9LZ$p)EEz@9g5+QhqeugR#bqcx8kMWcjx z3hvLob5RHYg$^!Jva$Ii0_CfZOlFsk>cOFJ_ogEFzq9VBRFqWjPo0M)``V!1rM z&RTM6>Q1(9W1$N{<#l^+1LH{7s5h`1rRaqlM5fm%ij(5X6mKsXgpitntUPON1qOFtZ2(TS^4|K1- z%=#TUCp(Q{J38E5k|BYVj6y{sYw$ykp7KYJAd3!SrM{8TY9t@ruUtuW?0(6<=PWVP znUS%@{mA34KYqN(SpYB&BOhVdgw2`UrQ4|68&vLxYazZxq(E#-4GPIgNI{4C$iii2r)jA;HuO zoyGbXK};VxRqPTXfBEMj*l1GFHsM54b3RIojL~rFp1ec`Y@0J#$+#9=Ppxd@NdwJn zzI`WR=jEH3X7;4Z)Ya7RAZue* zV0!~j#Keca-O-@Vh~Xb;D3)*i_YzJ3C*;~Mq9|heC-1uv0HO+nmxi({my!I^e?nde zXl9S3=XQ9(*tP<0u_j0$vesHlVmM*bgCxcoB*_X};!k;pAl<_&A;DshIZ&2kf_WCK zWsG3&j~>%;dBTknbe=>ANeb?i_gtEWH}A@iy0@1|P#$x2Xws8rIwpUU??m}~WP=_N z> znZ3l4D}9`ek^=@*-#R%tsfw*+Gg3x(Z?VVh{5)P(M>6U#SpZ;5AD1KZp{v|E{^Lgo zaIMkUr8?=Hzr>yh4yHxiixoNr_3>P3#}uyM7ABN2Cb0%mn!Bm;|3y6pF~o;d04#$b z5;Z@gj4JiS<%a?0B+xtj;u&7qH!;aj%?5x5DAy2>bb2|1zxIxd)cxT6gWkJ#V)U$w zlGEoXELc8tIc%_jF~LB55T5&b-)%BEBt}wE@Jp!U_FNu89sDhr(!`0bS zNAAvVS=?Tkc&CNy1C#%k1#q&z^X_nT(Y{Ji#Pg6vu|<^0#=-c}dsE<_`nI)ye$d?e z@5{Y`z4a>JM7Zdw*PJt!vtRynQhY)JU5Hxcmw`I3DZeBCy8u`P(B~)+VSB90Mn}&P zYYgIS%=FS2P@nCm2kRWg+MPO?UbZ~sCYB(G(8c*FJKa|J1OJ!HK!hv=zS6`qrB^4( z-R>D5kAe_<8u~KK)k>h9JlubzI}EeDCuY~l1d0)D)XK_m@Ix3DgdKp-)-+F3s6wPn zpx$_7sZ^g-{$pI{{p7rMfj(RW4Cu?dPgo?j3X6(9gr;;UcK8fr75Mty?rRU8L{Q{q z%hP}R_YsNU0W)AUiWfYN0oDXGbJF_83$X>x-Q9408by44F@x55kX3Rj03sB$21jWA zR^(7SAbVDP_`m@F?G3mhMERQpG(u{FGz}52x9oqL?K2t&l=%jrYLe{)ykeoqJM~<< z@9O%Pd-PrA*!;{`7<`5kFTJ*4X5jQ&Ot7th=iiIoNIuW}RM0&6JW&*G$kDc_EvGdh zHUQT|V^SeEcV&4bgSL@3xOb_<i&W9OG2t|GNk$9v(ICLs+N?f&KFJ6R68oB%f*_mqy2ar1+-hwVI+ z+nt6JfX&EzUj@4;<>vT{@sQ!BbS8LG9i<+7atpg|ZJs4P0O2-FmB~S1BPTF~OlAc_ zatF?Uejox>tO4q~?8$o$W*y|>hG9KyWM(E`fb@1a9Q!K7v5m1ITt5$>Qba<_4zv zE+OqBdg|pNOlJ2KkZi(0D)*@1>Z`=50kp2d6D8B$Xxt#iL1a)rCnpDO-5<##)NDNn zO<+n06Y$_k!?1h>v(N&_5o!t<;=rT;$+kO|aMJQZiNmP|8JCooChslZ$Jh2!GeV~H zqGse)73e!oKflV6pb@YL!qS8Up+{nDmds?qlfkS`jkV-4%!X7{tPm9yMPND*;Afwr zB5#~FfLTMas{?FEGD>|~xl}&OLD{`J(69}L!77Z{AwfF9xxAaDW$5833e`8HBh`@X za{O}F);D3s4^D^cXDkpr0UN69#g*tYgXd1B8H7OQNXDo+iKp3&NQz-i)F~}YsP+vE ze1q{o%`HQa?ofi%!NJ@lzSC8iS-{(Ckty0L>qG%J8uYrFX0VR%vL8(}S;pDlysic^ zq}Owh=~M#)WS!oA`FNP5&M^%Rg%eYOH98E6@Tb{8(vxAq5xX0;^v!WDybsh)*n!;0 zF7#waCgIb&l$U?Lk)VntjJndcPkZs*-`^C_vA{?v2~!$;gxgLc1OQAVF{5HXyk3SX--q1{ z?PwLKbk&#I_qmctc!D@aH(9M`$BzYMW!VAy$mH1SYzJ#=f4NHoWJC#2V{^eXQLCy< zeqcue{>b?w7;N%wy9dgR4LpHoL2G{81o04(E1{6gd#FJ-CdtV~0PeK6#PH)9$p`>K zv=4O?-+fv4N&26FizF{CHlm21C;}C$jC0K+ZrKBdF(M0ZYlRlXo*(h9CKK z=i87oOvs=a3+vI3@88qI3e<$gybTn=ZEH4Gdf4vc&*;Ift8k*iucjl)z2Bph14~Lu z+VdW5+PHBw^h+`y&85Bz_n8kWKyYZNFFqS+wz6)9|Age;y>B7@uyAmMVyY;VOLGL} zguL$P@ks2uaBk&o0K%^!LaFN-m)*b5!_Llb)M;MRycJ&;ie2QpwsBZzm~Dll8;S-P zMt$sw1wni&C`9co`CzH`H=t!3FP5NGB7RIJ{wpw8RBhxagD?Pm3f!PDE_D#n<_*Ze z*VNUmnm8v8-X4g$REhXepS2^Nu_EF@Yw#40MqT&7^nw|mf)in#Xf_}QMhg)GG(XboWRG<{WCtVtf4fv3ZDzs}?6adocRpmKKYZV%>^Wm6a1wJ`1Z*aR2=6US`DM z3(p>$<${)zfx#ivNRr3IK{R1_U@4HlFg}B#_)qycbLQyc`!fd*u1iw(i3B#uXt5{r ziX0JZu}+Bv8A!jy7fx*zzz+VAm<>2-$J)hjU8z?dSd^b})@vM)vErIYnko0o%M0g1 z;dKn*oDLp9R$^OYIQ0|i!w??ycaX6vN@jOE23fSH=o%eDMKAkeel2^UH%ZzW#7n%4rH9v zcki@SF2MkZ3uNTdrtCm>N1{>8R*4q^Q~%{v3HR=axta!sG7Odw*%-X$25e90uz^^` zY9G)XkAM>)U9dw^(xCaPS3Ayi*|o|O6VRDDl1ArbyI4T49K!f~X*R%P$6Jgv;0oJ3NUu>YFDQFN-_$e&;tV|)>H#C8*!#za%c2k?B!^64 z@k5H)^XRIi)#=kj%_6T`UKmuq*z;-yyn-rB({stYV1}PerdvQC@9AIl6*nJGj>G_9 z=HA!aG4WP;i3J*Y&QDc-{R&2hk^5~Ub_iKppE;wBIoTVJw( zV1Ohm;3_=*uC|sJD{2^26|%>{le&pg91IBWg;VsIycq%OgrKa8(_;IvxrzVaDm&tnXqL+y7IC#&N0Ju%<~}+oApj|&8O)IHH6y6dKjtAH&A`({| zJC6~lPsyq@R`Puw4j5A2PKixQ5;dy_JVLc0VW39mmm(1ws&oOqC|0|$k}S#4DWc#j z_v1;$;_805$U9%Wn2bb0@U1=DxBFwJeYMY<>8l^^>)y2RInS8>9;=Q{gVXKuQidtp}p&;uzCF zSOLS#k9&$3xs}3^BO~6|#0droU^FOdN5u^7gA*2B zo9JlFTepS-Ag1CxG60Kjg^@b}<8cUtZ*`6{^qJUOw<_U9H|+Dmu^HE3#NLdFNfS*o zKI|>qVlPcFe9W)5P~<4d94CGg?vLjg&EW(P6jJptk%%)EHWGS)LWl9?x5q_}RzBL=(4Y|&!^c^t&iji-A)Lh$zX_GYM0 zZu;S_*!JoqxalJ~LdtqK)@A;1(;EEY9&y|e7 ztO8vj^PuLZO>X?=|L{fpYdpgZEk!CKxCD0Mz?<>-OZFSSvms$T0F=fmMRO0}o?(8E za}2kxjm@BZt}5LR9L@6@Wtz;!0@fZ~UW78>{xPl57RN$WUiq2;@!kn_epuC3730e| zct*f=OQHSmphDbc{JZLzGXjoT0c;yL^6cBU6~ojoE`DE&j+d;rgRnGABtM@Yxg~ar zk*^X4@2NTOMBTT4`!`B>vt0G^(!(^=)RYVO;Kb(3)xj*erS8A_af5Dya*SW};2$%T z_*CLK{b=Z;f_4SQN{B*W8;R^DBv>*6qB1$5lG?~jQ8o8UP?T&%4LN3JMi`$#Jl}0R z?FZ@UWMI*E08zc`<>ktx57};0qC&N!3pnum%A=7_eL+nyejr$=oj#q?oJK1MZ4oC& z#n&-SZ^0QVP=4$tgaQ#v|GU;?@tQhvmM+M5zAe^sa_Yl^(*i?O>@ zll5rbRw;8s!6kWI1P>0L)J|-7T1n1yAY%y8(LAogrB{A^TSEfN5RyV~9)bEwEHE%b zIH~Hjb2;RmAtOw*&0&LK!Rd5!)kY|$7&gDzd}cyaP%seq4Xrxt)*c0gyEt#duI16u zlzy+{P`Y?X3?ee}7OAakUOrM&QzNQu|GCX7KnLu2DxDB`JY zBefqtMj;cSE1#%4>^xi*n6C}R3PMOD?r555z+P-@9tya>T;rwqTeogvP96nv%X^-M zi7F-}0kv@tVv1v*R|Mui#36_{(Iq19!j@SD@CN~9W#!u6r%K!+NOwev`euusfSlYN zz@-Q~bsrh3G*7{TWD;W@2SVhCfewpYhtbok zR$Z$&>)U{NYIwXeIa6M|xD9<)$ME5y8^s&>_{i`jk#=5QEg33u>?%D7HKMuXvLjMi zEQ&s>p7eUfsQ9fVparwL+9%&#fsw~cLqp@&5#@|4L}x_9I#lrSK6;iQ=4|O`X~o|x z#{*Mthn54q-VVLPApUnf@xiygJ@_>L*j1b|_RpR##{q1$wJf`jpk~0a2}a|L;8yOs zJ=6CXw)}Zya!F|Q-tvMCkcR`guQ0hWHj+(%45nMr$2E}1EP!kQLt;?c8^=2Vn3Jb? zo=n2G^I27y#uMm8X+Vq#HQF3AOFe{l%DH-CrfNE}4OKT4B964}h)l|-MgsdG3a1bQ z{BIeW8p*lRF)<0D;jP?{?B~rBO`EhlVtm?*l`x=v-8}a#Dih|@q9E01pZv57N7RwV zL#D-XB8@LZlV*W3Y3MCv{ujy>5fG56kN~?R>MS|{Y72}dRBCigjAB%xp5b_?U-0gS zrah5NCTFIBqiO)5k$_aD&+aZKwY5*Cs4O4=doUy-jv(~8?w4Fhr2N0C&OI*YyAR{{ zl4P`Gc&tLHQ7h3_q#mbA3DH5tq*5E19I_%*k}M;q78z~T5;_;rs8+4gA}WL&l0&k_ ziZLqkyg&B5UeD|K!yk0t`Q5+Y_xt%?pX+m7*X*WS5B1wHIoJP z?cb}rrZXF8m&UY=rpq5o;&Y<7>eu`cWGSqqG-`}OY!Ju&)bem4!vIN*g6ChqL(&<# zaA<|>7;Q>(Ws`HwmFT^bUo~vUT)PbWY~%;asLoFsPee1h7aacZ;t#XvcXC60uM-r+ z=3uuEUuEj*;-acRoATEXSL*-^Eke!q2*^iIF7TH*%Eya-UcI^C74C$jlAaei5ANQb zMvcVtITca--7&w!iIVC!$KmMcQ&5(54EIn9u;?V)!dC3UsNj^QGTJqRTM_JOb=%s* z#l;%FE|nl4SDqowgEM|rLu2C@q$ozsb9J(DF(^%Ktxas5#P%x0tX7fqqh8qDmd62O zYO}pdBt}DDU%#2fU-x^Epp}18J49i>e0e`?WgMGX?mOsFX{!CrGzVzhC|I?dc1^iY zA3t0jC=K^@Ro!cVyHBq7Uroc<Ad0{% zA@s-q7`lXMw&EZ`VaoLDJ=!^P8SbiYdesYcQmVw8hA>!dUyqjC3 zYFBgUgSNjKJiK+W>mV+qYG37pMY43RGiJ25u~G9aP9=+^6fPXVKvPnHvP7eT)11{o z0L7?aLxq=@dtWs*I$ULxb*Uz;S-B@p#^^|rFX>dxNHGB6jw$t}WHO2%)@p6$m6j{f zi)Jo<6;HP=B5LR?3{J6U5~k{^vorUX*=K2M_clQg;2aKsDp|c0yxyPNLab%;U zolpOc6NlR)9yRun@%-FU9gjA@!WdJvXA-VOzAX$Nduck8f&Sr@RwJz$pgt{EpHhlc z#`cRn{Wj;Vn{#9u>|Od>Pq+=VRNO2dL@EQ_-3qzYz&b6208z6?*)(+4!I8}JY;2e7 zIh>TF__Xo7x86vlsz98V_6*xT&VGD>QYJ1aM#+zY&a{=}bdK~h;907$zJf-`BKkp=hac=qX@NYH@~Jex&Wml>^UU5dRr8!Kf)hBb)hz@bAaFzrb2D}e(^ zbJ_UX71qx; zOoCWM0Y4I@tUwF?xHvdeTUE+9698C`GYIR&UV6%57OSTwrEm3Xe&b`caKd0t5M2-n zo&KPK1Fh*rizSUry26Ck(u73K3W%#_^=;@tD;LeLQz(Y6}_8K zC^J)IIon6i_~4hJ4h;LoFWFH1Bix_8c2zxEvNR+@b)m4;BH(~SdRUt{cSEsPU1hDWuu2Z3Do23w4x6OTAtXsTqM&>J^2;;2oL3grc8+V{$WQ?*ZnWm^d!iIlyZ435^X1lSpkTCx|FhV2e@sO2#d8Wp?>PC6fKh!rmf4%c>rn?m=0#Q6 z4%@<2lYe1;8lWuWkeh`S%yUI`K?;c)stkK7^Vq-)$tDCs7o80qmiPwc>$_-Zf_|7) zd--0S=fUF8JdJi9or(06@gZC>lN=4CO?f=}2d%C5Nq#M=P*D)0N)t(9bX36s^QVow z|Bv{H^vY>X{?((Qft6wRCxu(#RJ`5OCX7^a3>4`-?Z_Q`-~r_qad0YN{YI@aU5%)}R85hPx%OOiSCy-27x$z|wv2 znH=BgWqBF&yI5<8~j4{kdnk{t!yg# zR@$Zb5O&8a`rY*-YEb>&BDY$8oD=9>wQfq3i?>uBC6o|y5R=;~8v-nVcs8qf?hdp4 z?-s96hU_7>(*>WL_I+`2K3p!5Xxp22gqj}=-_{*ss24}G70XQoZOmA7PD=(JOrH#< zO!1Bj-I+8ZKrQ*~7gsYEAZfIPrUlPzC#+>Jow3+nuN2ueNxiCOQ&iX2?_F{+!7$h` zN0p9_*dqof1K-sLHb0%$aH=7gev`u@%ng+l6%2oZh%k3}rWGh3#*SGpX2-FezUL;a zYSL$}RDI{P)U#zZXJx*{XJvLQelwq!c~@HmL@+4>a0cQG<$&4YWiwdsiGA7Ga4>IZ#imLNh%>XS~d1) za?G(2kE5JiH9q>tf%Ex6TiZ+`Kt6_*nsp0e=>}dju;xMOps3(Q6@w4HE0eA;p*AoJ zy6E8$_2w@wSNX)iAS7o#gkL_`6rZYiPr5j=rx$idk5y?>Uwi75Aws=U`63V|G>lSG zIBDUHY>@*uKY60YcMseNs|C@@bcpW&AskwCI7DHw7%LfMrN@Vu*{R&v{r>m_$LmNm zL^LV#H7W{>v3B)Q~^WN+koUx>VLsW|fi7(%?v>3#?G;YmJaEeNf3%zb>J~ zllM>8+K(olwmQ-!EsCNcqBix(uOeP9{kPip74p(?D%(Mn;~2Y*E+1+@db3|rkGgx= X-~~75Wm@sAE((X~b8NG%eIow@T{l~( literal 0 HcmV?d00001 diff --git a/ast.py b/ast.py new file mode 100644 index 0000000..bbbf44e --- /dev/null +++ b/ast.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +@file: ast.py +@author: amazing coder +@date: 2024/8/28 +@desc: abstract-syntax tree (AST) 抽象语法树节点定义 +eg: 2 + 3 * 4 +mul_token = Token(MUL, '*') +plus_token = Token(PLUS, '+') +node_mul = BinOp(Num(Token(INTEGER, 3)), mul_token, Num(Token(INTEGER, 4))) +node_plus = BinOp(Num(Token(INTEGER, 2), plus_token, node_mul)) +""" + + +class AST(object): + """ + ASTs represent the operator-operand model. + 每一个 AST 节点都代表一个运算符和一个操作数 + """ + def __init__(self): + pass + + +class BinOp(AST): + """ + 二元运算符节点,也是非叶子节点,代表一个二元运算符 + 比如 2 + 3 这个表达式,2 和 3 都是叶子节点,+ 是二元运算符节点 + """ + def __init__(self, left, op, right): + self.left = left + self.token = self.op = op + self.right = right + + +class Num(AST): + """ + 数字节点, 也是叶子节点,代表一个数字 + """ + def __init__(self, token): + self.token = token + self.value = token.value diff --git a/genastdot.py b/genastdot.py new file mode 100644 index 0000000..3bd5dd2 --- /dev/null +++ b/genastdot.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +@file: genastdot.py +@author: amazing coder +@date: 2024/8/28 +@desc: +""" + +############################################################################### +# AST visualizer - generates a DOT file for Graphviz. # +# # +# To generate an image from the DOT file run $ dot -Tpng -o ast.png ast.dot # +# # +############################################################################### +import argparse +import textwrap + +from interpreter import Analyzer, Parser, NodeVisitor + + +class ASTVisualizer(NodeVisitor): + def __init__(self, parser): + self.parser = parser + self.ncount = 1 + self.dot_header = [textwrap.dedent("""\ + digraph astgraph { + node [shape=circle, fontsize=12, fontname="Courier", height=.1]; + ranksep=.3; + edge [arrowsize=.5] + + """)] + self.dot_body = [] + self.dot_footer = ['}'] + + def visit_Num(self, node): + s = ' node{} [label="{}"]\n'.format(self.ncount, node.token.value) + self.dot_body.append(s) + node._num = self.ncount + self.ncount += 1 + + def visit_BinOp(self, node): + s = ' node{} [label="{}"]\n'.format(self.ncount, node.op.value) + self.dot_body.append(s) + node._num = self.ncount + self.ncount += 1 + + self.visit(node.left) + self.visit(node.right) + + for child_node in (node.left, node.right): + s = ' node{} -> node{}\n'.format(node._num, child_node._num) + self.dot_body.append(s) + + def gendot(self): + tree = self.parser.parse() + self.visit(tree) + return ''.join(self.dot_header + self.dot_body + self.dot_footer) + + +def main(): + argparser = argparse.ArgumentParser( + description='Generate an AST DOT file.' + ) + argparser.add_argument( + 'text', + help='Arithmetic expression (in quotes): "1 + 2 * 3"' + ) + args = argparser.parse_args() + text = args.text + + lexer = Analyzer(text) + parser = Parser(lexer) + viz = ASTVisualizer(parser) + content = viz.gendot() + print(content) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/genptdot.py b/genptdot.py new file mode 100644 index 0000000..2198cd1 --- /dev/null +++ b/genptdot.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +@file: genptdot.py +@author: amazing coder +@date: 2024/8/28 +@desc: +""" + +############################################################################### +# # +# Parse Tree visualizer # +# # +# To generate an image from the DOT file run: # +# $ dot -Tpng -o parsetree.png parsetree.dot # +# # +############################################################################### +import argparse +import textwrap + +from interpreter import PLUS, MINUS, MUL, DIV, INTEGER, LPAREN, RPAREN, Analyzer + + +class Node(object): + def __init__(self, name): + self.name = name + self.children = [] + + def add(self, node): + self.children.append(node) + + +class RuleNode(Node): + pass + + +class TokenNode(Node): + pass + + +class Parser(object): + """Parses the input and builds a parse tree.""" + + def __init__(self, lexer): + self.lexer = lexer + # set current token to the first token taken from the input + self.current_token = self.lexer.get_next_token() + + # Parse tree root + self.root = None + self.current_node = None + + def error(self): + raise Exception('Invalid syntax') + + def eat(self, token_type): + # compare the current token type with the passed token + # type and if they match then "eat" the current token + # and assign the next token to the self.current_token, + # otherwise raise an exception. + if self.current_token.type == token_type: + self.current_node.add(TokenNode(self.current_token.value)) + self.current_token = self.lexer.get_next_token() + else: + self.error() + + def factor(self): + """factor : INTEGER | LPAREN expr RPAREN""" + node = RuleNode('factor') + self.current_node.add(node) + _save = self.current_node + self.current_node = node + + token = self.current_token + if token.type == INTEGER: + self.eat(INTEGER) + elif token.type == LPAREN: + self.eat(LPAREN) + self.expr() + self.eat(RPAREN) + + self.current_node = _save + + def term(self): + """term : factor ((MUL | DIV) factor)*""" + node = RuleNode('term') + self.current_node.add(node) + _save = self.current_node + self.current_node = node + + self.factor() + + while self.current_token.type in (MUL, DIV): + token = self.current_token + if token.type == MUL: + self.eat(MUL) + elif token.type == DIV: + self.eat(DIV) + + self.factor() + + self.current_node = _save + + def expr(self): + """ + expr : term ((PLUS | MINUS) term)* + term : factor ((MUL | DIV) factor)* + factor : INTEGER | LPAREN expr RPAREN + """ + node = RuleNode('expr') + if self.root is None: + self.root = node + else: + self.current_node.add(node) + + _save = self.current_node + self.current_node = node + + self.term() + + while self.current_token.type in (PLUS, MINUS): + token = self.current_token + if token.type == PLUS: + self.eat(PLUS) + elif token.type == MINUS: + self.eat(MINUS) + + self.term() + + self.current_node = _save + + def parse(self): + self.expr() + return self.root + + +class ParseTreeVisualizer(object): + def __init__(self, parser): + self.parser = parser + self.ncount = 1 + self.dot_header = [textwrap.dedent("""\ + digraph astgraph { + node [shape=none, fontsize=12, fontname="Courier", height=.1]; + ranksep=.3; + edge [arrowsize=.5] + + """)] + self.dot_body = [] + self.dot_footer = ['}'] + + def bfs(self, node): + ncount = 1 + queue = [] + queue.append(node) + s = ' node{} [label="{}"]\n'.format(ncount, node.name) + self.dot_body.append(s) + node._num = ncount + ncount += 1 + + while queue: + node = queue.pop(0) + for child_node in node.children: + s = ' node{} [label="{}"]\n'.format(ncount, child_node.name) + self.dot_body.append(s) + child_node._num = ncount + ncount += 1 + s = ' node{} -> node{}\n'.format(node._num, child_node._num) + self.dot_body.append(s) + queue.append(child_node) + + def gendot(self): + tree = self.parser.parse() + self.bfs(tree) + return ''.join(self.dot_header + self.dot_body + self.dot_footer) + + +def main(): + argparser = argparse.ArgumentParser( + description='Generate a Parse Tree DOT file.' + ) + argparser.add_argument( + 'text', + help='Arithmetic expression (in quotes): "1 + 2 * 3"' + ) + args = argparser.parse_args() + text = args.text + + lexer = Lexer(text) + parser = Parser(lexer) + + viz = ParseTreeVisualizer(parser) + content = viz.gendot() + print(content) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/interpreter.py b/interpreter.py index d4497d5..2e5b99d 100644 --- a/interpreter.py +++ b/interpreter.py @@ -12,8 +12,11 @@ v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. v6.0 : support to evaluates arithmetic expressions that have different operators and parentheses. +v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. """ +from ast import BinOp, Num + INTEGER, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN' class Token(object): @@ -92,7 +95,7 @@ def get_next_token(self): return Token(EOF, None) -class Interpreter(object): +class Parser(object): def __init__(self, analyzer): self.analyzer = analyzer self.current_token = self.analyzer.get_next_token() @@ -111,27 +114,27 @@ def eat(self, token_type): def term(self): """计算乘除表达块: factor((MUL|DIV) factor)* """ - result = self.factor() + node = self.factor() while self.current_token.type in (MUL, DIV): + token = self.current_token if self.current_token.type == MUL: self.eat(MUL) - result *= self.factor() elif self.current_token.type == DIV: self.eat(DIV) - result /= self.factor() - return result + node = BinOp(left=node, op=token, right=self.factor()) + return node def factor(self): - """返回参与运算的数,支持整型或者带括号的表达式""" + """返回参与运算的数,支持整型或者带括号的表达式 INTEGER | LPAREN expr RPAREN""" token = self.current_token if self.current_token.type == INTEGER: self.eat(INTEGER) - return token.value + return Num(token) elif self.current_token.type == LPAREN: self.eat(LPAREN) - result = self.expr() + node = self.expr() self.eat(RPAREN) - return result + return node else: self.error() @@ -141,16 +144,54 @@ def expr(self): term : factor ((MUL | DIV) factor)* factor : INTEGER | LPAREN expr RPAREN """ - result = self.term() - while self.current_token.type in (PLUS, MINUS, MUL, DIV): + node = self.term() + while self.current_token.type in (PLUS, MINUS): + token = self.current_token if self.current_token.type == PLUS: self.eat(PLUS) - result += self.term() elif self.current_token.type == MINUS: self.eat(MINUS) - result -= self.term() - return result - + node = BinOp(left=node, op=token, right=self.term()) + return node + + def parse(self): + return self.expr() + +class NodeVisitor(object): + def visit(self, node): + method_name = 'visit_' + type(node).__name__ + visitor = getattr(self, method_name, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + raise Exception('No visit_{} method'.format(type(node).__name__)) + +class Interpreter(NodeVisitor): + def __init__(self, parser): + self.parser = parser + + def visit_BinOp(self, node): + if node.op.type == PLUS: + return self.visit(node.left) + self.visit(node.right) + elif node.op.type == MINUS: + return self.visit(node.left) - self.visit(node.right) + elif node.op.type == MUL: + return self.visit(node.left) * self.visit(node.right) + elif node.op.type == DIV: + return self.visit(node.left) / self.visit(node.right) + + def visit_Num(self, node): + return node.token.value + + def visit(self, node): + if isinstance(node, BinOp): + return self.visit_BinOp(node) + elif isinstance(node, Num): + return self.visit_Num(node) + + def interpret(self): + tree = self.parser.parse() + return self.visit(tree) def main(): while True: @@ -162,9 +203,9 @@ def main(): if not text: continue analyzer = Analyzer(text) - interpreter = Interpreter(analyzer) - result = interpreter.expr() - print(result) + parser = Parser(analyzer) + interpreter = Interpreter(parser) + print(interpreter.interpret()) if __name__ == '__main__': From 84176a9acacc86f595decb7a6e746fd99e43b3a3 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Wed, 28 Aug 2024 18:17:55 +0800 Subject: [PATCH 05/11] add .gitignore and update readme --- .gitignore | 7 +++++++ README.md | 29 +++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21fb2c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + __pycache__ +.DS_Store +*.o +*.log +*.dot +.env + diff --git a/README.md b/README.md index 28fe998..1b34fe4 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,32 @@ simple demo for python interpreter 分版本逐步实现完整功能,适合初学者学习解释器的工作原理 ## 版本说明 -### v1.0 : only support single-digit integers + -支持加法运算 -### v2.0 : support multi-digit integers +/-, support process whitespace +### v1.0 +only support single-digit integers + + +支持整数的加法运算 +### v2.0 +support multi-digit integers +/-, support process whitespace + 支持加法减法,支持处理表达式中的空格 -### v3.0 : support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. +### v3.0 +support to parse (recognize) and interpret arithmetic expressions that have any number of plus or minus operators in it, for example “7 - 3 + 2 - 1”. + 支持包含多个数字的加减表达式 -### v4.0 : support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” +### v4.0 +support to parse and interpret arithmetic expressions with any number of multiplication and division operators in them, for example “7 * 4 / 2 * 3” + 支持包含多个数字的乘除表达式 -### v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. +### v5.0 +support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. + 支持包含多个数字的加减乘除混合表达式 -### v6.0 : support to evaluates arithmetic expressions that have different operators and parentheses. +### v6.0 +support to evaluates arithmetic expressions that have different operators and parentheses. + 支持包含括号的混合表达式处理 -### v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. +### v7.0 +using ASTs represent the operator-operand model of arithmetic expressions. 支持使用 AST (abstract syntax tree 抽象语法树)来表示算术表达式 #### 语法树可视化 ```shell From db0da33bbcdb87af07a3ae7d5390285ad34a1693 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Wed, 28 Aug 2024 20:19:01 +0800 Subject: [PATCH 06/11] Update README.md --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1b34fe4..652697f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ -# simple_interpreter -simple demo for python interpreter - -一个用 python 实现的简单解释器,支持解释 python 代码 +# simple_interpreter (A python interpreter implemented in python.) +一个用 python 实现的简单python解释器 分版本逐步实现完整功能,适合初学者学习解释器的工作原理 @@ -39,4 +37,4 @@ python genastdot.py "7 + 3 * (10 / (12 / (3 + 1) - 1))" > ast.dot && dot -Tpng - ``` ![ast.png](ast.png) -执行之前需要先按照dot, 参考:https://graphviz.org/ \ No newline at end of file +执行之前需要先按照dot, 参考:https://graphviz.org/ From e7dda55c92c80850e2c97b03c4de28b1f197d3dd Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Thu, 29 Aug 2024 16:16:36 +0800 Subject: [PATCH 07/11] v8.0-support unary operators (+, -) --- README.md | 7 +++++++ ast.py | 10 ++++++++++ ast_v8.png | Bin 0 -> 19484 bytes genastdot.py | 10 ++++++++++ interpreter.py | 40 ++++++++++++++++++++++++++++++++++++---- 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 ast_v8.png diff --git a/README.md b/README.md index 652697f..1af3c5b 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,10 @@ python genastdot.py "7 + 3 * (10 / (12 / (3 + 1) - 1))" > ast.dot && dot -Tpng - ![ast.png](ast.png) 执行之前需要先按照dot, 参考:https://graphviz.org/ + +### v8.0 +support unary operators (+, -) +```shell + python genastdot.py "5---2" > ast.dot && dot -Tpng -o ast_v8.png ast.dot +``` +![ast_v8.png](ast_v8.png) diff --git a/ast.py b/ast.py index bbbf44e..9450aa3 100644 --- a/ast.py +++ b/ast.py @@ -41,3 +41,13 @@ class Num(AST): def __init__(self, token): self.token = token self.value = token.value + + +class UnaryOp(AST): + """ + 一元运算符节点,也是非叶子节点,代表一个一元运算符 + 比如 -2 这个表达式,- 是一元运算符节点,2 是叶子节点 + """ + def __init__(self, op, expr): + self.token = self.op = op + self.expr = expr diff --git a/ast_v8.png b/ast_v8.png new file mode 100644 index 0000000000000000000000000000000000000000..b784d91e7913e70fd8706a239cb6539a515c2d0d GIT binary patch literal 19484 zcmb`vcQn`k|3CbSNF?B)hNmfQ?wvuEkDH$arLb9_*DTR<*k0BSQZ)`dd zsVho1+hrox#;jWhR)W(bkpV$8RU;FPKD$vfxt z_vhK5ddusRJ+xwCVvg6Z-^t4pZF&DbB_m^tsHmv*#f!sVD_nzugI~w&)264V_wn%| z;g_jq)wdRxCkqZZkEl}ld$yK;;Lg`C zy)|i{tE=k{YX7eM^T+3AXLS`U!zoOYy#`CX zZw0;`Sy!(5%KQA8{8-o4MnC_(H83knkYDqDa`Do!rS8ePUJ43|@y{i~*RNl9oF6;I z%El(~uGnqX^5@qIme;ReJ32dC#{1v_>zYSek`V34= z{@4c3!sj<7Xi}=Es7Ojm4!#NA`QM`^+EU0rmpT-@s7{`yS^xRzAYS)bc%5N#|0~Lu zHy6Y>Wj%wiL7#=|RNDf0|Pb5oOVR20J`tKkJ} z>pMSxUgB{&`P-So{l||VJaTeLF5CXSJZ}^gZY}k$%iY8d_Rh<(WzF3= zWF@xkWKK>_Sb(!vu80`FxY8s=dB)enko)-Y<7sJWT@P5saH9MkJ>vN9>ZNY{q~fjp z^`-QzN|u4HF2hv+t5B?Qad@p^vME#M*=HAQJD<{C?XPT-Qcs`iEBEW`Gx6NffV>m~9k(J7ofvQI?vCi%{Z|)HlrA_ksyX)U=9^LyDMeH@U48yKn!MNRp;q$)V zzFGEu`I4$}Pi%4Yo|#eOkdUr84PQ*t;K<0Gr>7p)wzfvNB+~u-TtcG}FLDCs)O$^z zrmNz-B#EBdzqi6wJl~|8%)!B-$4R}?b7@D;>Ac$Ly&7)rcEKLy6dn%vY?8NDC8xet zWE6Hf*s0@^>+30Y?b=m_E&2QR@B3pz*-uSbG7AgoWp4hxV<1$YcdisSiu}Uas`cf| z>@psVDJdz7i^CBKql4KQrc<@AGIDZ4afFFt$9lkWGpg^2gse@%IZTtC-!wLM-r2fSRe&)XyJ+oqr}poO?i{;_n4~1_{{H?$ z)^F)|P;DL=A6NaDe_kII+26y9wL+q)smZoGn~8E8t50$=x3!H;ielh)M<=Hf*a;Sw zFNgA$VGbCZ01een@kC9J9R^z|(q93tuh>7K3k zNKy<@WY^$h&33J>64X1nH_K32nY?O383TJ ze(>Ny_G8YQv1w1DIOOK$8mxYGY_6d(2><>2x{b zb-s3uCrZ`a+}!767~AWHhNmqDw}ymKWp z)ExJ${8%!#RowV>Ta&-;qnUT!Oi{`6=Y^h2&g@dxskUz2O3js|FN!AD^zL094(iPN zqXPoJMmlyKypW8;y7(g_DON%@L#k4K=) zf?VQla_G@lzP@B4Rs!!GC-o66D7+zT_k5m?Xd}&UIu!SlxFHFFc}%BNRJPz1{O;Xj ziH?bhljX`_U)}$A1D*CZc7e(H^F^-Hrg1H2jf{d$=NVh9F3$EA+k~o*jWC8f_^!7G z@7y;tH>x3W{-uG$mUT`IVF$zR+^tm&oon29ko);TZ*4O!vPPbpkW?dnXbOb5s0eVM<(MoL%MbAmfaGK9a@h2{Fud;7xr&k zTCD&3d)Tgnlto2F1OkxfHs{EQ&CL}dc0BHeT}fzD-0A5T^18aZlHTjmxQprWqnk*; zhAoedv*2|U(Pkzk(elpw_7s@Wp`qMmKNjs0OD&39up@Z$&A&e_{->4dp!J}T_{RyK zQ4lbgc6g>4}>FQN6XJ==}Z*R8t6qR{r z9#t6{YfXx{_Wi>UUa768=K@DYb4N#qIo7GlYlT-pfJ*5OJ}@4_`Q65HU0gj7W3$@++t$eQ8HAuwN+5UBqSubqf{dk5`w*_(ki`o_y-58 zHF=MXjbSG#gfffTb#nS7-NTd`UDqs}gu$tGeH)&~Ujkl+6X=!O0Srp7Hl`=~{e7VOQw^&tG)u*WFz@%h% z{JncyjvhVQSLL-E3)0rnQ43_8ef^6?u+nYR@5QAh9u(%Gp&ospi-BP+{El+oqif}AoQcT^%cdHDG$B@(vbgq|!geYGDQ zV;kEcsxY?0*-a4&GJ>rF*U|ik z_wK1=Xgzvzz^WlKC52}bfp8vRldb*z;t+fPer9nWR&``@Qe(h-V{OaQP2hVRvkWwd zY?097t{-Ioy565ZlW(4>&d<%^p!$>AdW)>&jQC@k#&G}-xzAa5eEhhNmX?-H(vkbu z*NS_VR#vtiju9vrb}+qHXljE+E>tk-f|d#Z|{xOjw1C0o`r3Lyc3WPp;6 zVw-nHD!TVkhS?6;(!2cV@DEyFr>VAi$87iY<>5`qfE%6Ioa5MvcVkZH7Zyf}nUu++ z8t*|{w`)t@^#0NDd)V|wDmn2|=aL^Cca6@^7u&mU-?Q*1I+jKE`L!Qwf|>E}*|SID z(4n+~>h%YJ&u^uS!(O@12i#izv9I{W3;q-x)zp(=PX-6gdy2Btw3t`-k zvfxloF|EFJ3OHpS%BjTBqZzcjkF=rE7&Yp#W*Mjhh^62vcaqQpQ}OHKaKjX-v&rOJ zwnxUtGuJmZ;!5aItDoQaWsK#@xbXf#d;a+f;G{?wk@T0&qtq_b{f{=2Z3^Q!emc+e z-J1R*;Yb6abeyNomV|?6Rdj=L&J;w0A^w=0>Qlp(51EyhQV0bO19)@Ec`S9WHOLlf0*@iip$;m!0d?nY{SKTEJ z9^8XN1_oPw|3a2N#Y9Is4K{U%%U!l|)c?a*`|bm;3-IyXabYjH_uzqgT>$MK>@MSq z8=|;2{$mrGzMfuQTS;HbuLZiq+C?>4KX)2F+0oXPymcqP&W#&KCRIxUaM{Ezc=6&$ z80&%CO=9OC3F_c%G;C@J;8Ti*8coF2U>(G^U# zw6e03^wMRLc~ZbIJ|;%dZwnopv7g%C1lM%k(%+5Eb3o)s@&hFCB+&tB>XWp`fOv-AlSY+RW6W zo6RRH`}pV2pKm{Ze%bR#^6YNabiL(oq2j6<8ljc1Ud73p<%zciG3?1C2%VLInx^Kq zgoh7fQd6xS9d9yx^cJkS%#r7kt!N^xKApfHXb9NrBO5_4Gp*T398H9C^Bh zj8F_m4jhPjUQ+U7W~eSV)xfs>i89}g;QL7h^=n>B z6@Gr?F5_({QtsX9Y9@Q1a4=9rL_}4D8SGdLWQI&0USyi0sM<&Bv56En6Y;r7Ih(@aR0V>c@BQ-W4D~R;H8nmb!`Yjp^b*9(s!S%Q>TBZi zP+*{)U0PpV>Rx_1A=j!+5gi@P%}9MhNkBy84TO^RQU|lXmoA|$&*@edsyF;moIDn$ z-b+#MBMznK%CFLyxw%@1SUIaMxGr=+1^xNyP#c__I|@ADz4~+aU0oa(rp|Asps=V9 zVaiQ)q?WkcBXY>Ljf9GyKld@y#&^fgof@2ZVvN)&>FNF(Yt!E29}Bp?m~RxKL|y;U zLGjAtk0pn{K0OBq2g>>uU~=WFSFgrZofM^sjg0hp^5jYMoXN}UTfsjsa^xuK>FxNC zBo~3LfSn`Qn|SORH*=`dw>QB;c?IzlxQ*l_+sl{hT3faAn#oCl+c|Cn4xEhO#K+1o z=jZ2VJ$^%EJEyc!`X7qlx6IVkfCM9BWA9W}N)y}?Qdir@kAeRF6x+6KYhHaoNy{cL z!d&<9U^$eP!#5i-i${5(&bdUvrj$q}rYkB`s1;qERhpn3=H zbnoZS-v|!(T%P(9q5BL1c1d0zr$;~CqOY${P{*$?T@)W4u{XD|p^%Z0Ayyp4T=CW| zSzrwY4vw(@r~z~cLiWP~gn{%~LU)*J&^z@Zh9C;trrCwk_M)YXF;h7*Efr zt2{oML^Q)MFT#NJRQYy91qN+ryXmFQsuL@v{bD=ZQFS}dV2Wx?c0~$_b%rt z8vjWWalWwvLTy!EH;oI;Pxwz7RLDcqfR3Yt19!-6#@qw#GK~E6$dAs9t^zZ;j`sEx zG&Lcc@CoBew*w{iU*cW1X(`8xomaR2{5;F?ksXt&P+SL?ffmTrbB{lE=>vLa_+#2p z$JNkCo@ys)5vuK@0twWRR2ekhx*6!{<)IN@ z_>kB%@|Ya@0zbMpWIo-cKKA3}l-rU~(c>yA)i3g10kZ)(tu4Ryy)9LRPMLjg?h9bV z!QVeR2`%%+&)$@|CBNIZ2gk>arXE(CeC^6PBaYJ-SFK<}y9xlI3aAUj{j}}E$1Xa# zRe2zLA#XONs+C+z7F=(M_LZtFyQE})wHsQkCa#50%i}l< z1jNMny}Z1nu8&8mj}3P#N65J_iQ}lcSd;|5(`Bg+Qel1kSY)6EBrNcI;ShrTanLq!5s3AyM;F z&#!)EL;tKpUkUJE5^w#>UBrs8e(AkIym&&7qzQG!{7g{K$Q^W)& zB<#W!8H`RI8DZe!q7xPtPH(Nt+J}FkQr#=hZ6Wgy2;h~HVgs5>khs=_&2-6aafXMN zmp4{Q+tX88L{v0RYD-2HRG=B`0#&|fy?4zbkp%_f_)S}u{=W3U7!`1owluY!C|+t= z1j(xb;>o(^IPtEpj~z6|p}$hF1aI}V(tRiDf;t_)IM4?w22lKN^+)?a!BDT%ANlE< zO>g|_?CjJK6?Rb;V&IXMW(RZ9e>*a8Yef{tT|<_cYV%^N_qreMypZ)O*wUcf#!`er zgAkp#&f4ibvRBY8%gf85$c8EKYpbf(;QTO2oIVSD75VsaIC}H_2M>fiJ+ll#z&POP z2zy@1zX3UU6NwvoaaXZkg(D;+lFnt*L+8$&BbP5%Q5)II9J*=Mt95_=Eg(&wfPj+; zTNu>seNj+J@+sa1O`?sRtf+9C^zyJ~?THExPw;tjQ~n!wQOu?KpPP70)R*Z#IW= z2=-YE&XfJZ!QUV-JY1`ZtCMbZrSCB$tsNx)^RCZejXaQcm*9}P9&}@>PYq2!0ZX zE*cgwk)_2>ZMk|VVH$ZCuU-vLOl0TVw@=Z`jLU1~ud<_~V|m7vV^UI3423uvYk?CYO?xr>cW=t_&XM^Z`dnwiV|bW#pBN>+>yy4dW}_oQJ! zzP%2HckbL7gkB@!+6Rf*P_h8+2Gq<<4@)54@>r&Ve}Jqf|9mI}+Me;tacWQ~mTsQQ zmIs*JGP6M2kA=&g;YL}2Lo*!4eyF`oLBq&!MilHpHN8WkR6aetw&cpE9WP(Lgbb0@ zw{r_6WTh(&|Rtrws!;8a_tqq4Dvc zB5n^};^|EkRqOCe8f)8-PN0`}XbqV2o6o$?$)i$tT!T z=@NFAFI&Bh#WUSs$osIokP7>Knx9IrZs4Lk<`PzZYk%(xq)GY~IB` zzwn;TKVn52j%y2-MN&e78th=bw}xftqtGNW3m;lkeIFfFmX(#I=FsQBUZE$cYif$1 zc3vC8{hR&$LtjZ*SupMOwD`#tfObZS{+m#k2vI60Cr6=i?I@&s)L#LY@MhGuy8iyE zk<4UiNDJ)j?B*9PJoT~mj!{pCdRGS~#dPH9OTy#cOm-q++ARKTGWsvEofiLmG>hZZ z;zadOob1VKFS&ds{e#t|OIgnka@bYi*jqNo3bG!#!itJKTSq5X7@h{3!pXKHO)bG8 zE_Ug=rU0Ym^6wwLr4C#;|8dTe=S-nA5p5l9@agYz^b{vx#q?JF-CzhaeiuKUUgYyL z77`Q7@1qfVw|2F9X>4~ND+i9*|6~Dh?5#G|*VJkFBOpll1?|81fjzy|#@?Qg8l03h0{AwqVc5bL`kI$f9qaD2Ez2$Nz5R z^4^wZkX`ukj>)eb3=F<#%4ID!OjjMyS7F3w+@R}xBBSVhAcN46-skveO@N%$%T!xVgIv{#q^d?(FJPf@3i= zGo%>T(wG&ourc=c?_V3Sg5OL7g%)*%=C8>-|8$?BqM^~h^3sla>(9%Z#h`w zEA*)1T3ylM~{jhtIvzgu8iV~Qhf_uLw0$5j|a}}lYxxv=(lfG zK`D7){PRah9)n|d>Cz=+6&^Q#AM163LZh?w<0o5yQI3y~XUf+f z&bXc==lvM!#!0}4=hwb#HMh1F{LtZoPnIrv?u8DRb5m{YZ5Y~VkxooQVDosZ@$Q^6 z|1fy@wTsJh@5F^oG8$lvG&eUt0hWqU<%55G&=@VX{>_^%+gzflW@PLmB&y$k{`^4O zO9#kt|0Za1;>0F&goR{)>xlMn*=3;dO$+kQG3BsL_zAKkD21uAhS4 z00OP5uC4%e4T#6w)|Lud7-$U{WNgi;QhvH%B{CWs8h9Y3BhA|RSKR?0(SCetYUHtN z&Xncr(HNW&J^zvsa9_d1ggs^*=BD)ZI8GG7oV^;WSn(`$PVAHFuGN!ZS?X9#*7 z|L|cbU>_}%x$KVgU6t~O%`aXI1Z^c88{z>s&|V;wX(q|OQlFeB>n2nhV)^2ddfo^| zz(9UWT5Y9s{Z}sbD?!31>1;HMO#_;#(lM!_dP0cqD-HYtHv)N>leH?rltqDio zWrFdswEL46`)Zxjp-XQQH?6Ee!@PXmc-H`#;9F25Qk3c)l=UJ67TV$WIsNEJ*K3W+ z+g?SXmkg26a&cy8W_C6!=IGf14ID%An>TO5w<_*9m@iyQlV;wTp&f`WNLG~@}OCBpp0direG>uEz2X@I#!nE2(*91aF%<^b$xL2DCJ?!E9A>5vM1o){&7BbD-aug@s0xh68q80&y+x=&TM}qKU$;0)$Ed?U3|ZJ_HH< zG{7Dd&TvRnLwIi?VY(WcHwG|B9MQIM;5Z0j2+EYqg1w6|pXO$%jOFk9HZ6}GSxLGhj)V7;)AKt;lq|9;r1|K;& z`R8hHIVk--QC)hrwgM%WIs@1a+bO|ev3TImqWT_wJQ6#*Aa+M0tMJd?;46uG_)vp} zS%jOgp&%UY<>xOBe?ya&4+BsGP>axbakOIZ-D{NEp^rNL6evUwJ>duLgxxcjF$z5^ zNV$ae0x#3r!r}ztUCB^?dWsB)z)4qzHW!-EA^XpVJb;!aRk;Q!vMC5h=-|Z~gVgT92jGkecHs zb){>tEUzsLbib6Go+azH1Q2@;G(i|+X#4H=4_J*Okd}M+p2i)2|8}7Mw_RPaXzFjj zetiT=ceXK``IeXohmMYp4}@$El*n2$?==dP_`7U}sd38{aEMY03RZx$GA@Q`GR{uYz^B)PMLhB zz;wK?{2=5s!)X5?dW7K6BoHaxj8o|I6W{R0v)`2_K=g3v$D3r!2kf+3`{{}98~#k ztMUnbvSeam*>n8(ZX{n2PusfzZ<||Aj*FC>lM{+p1?1+0c}#?9Qd3h+Mhz}j%KtI3 zvlIIE?HfG{OW>O~N|jzKFVa{MWrH}abm0O&*anD|1^Vi#Q}3gr58HK7F*7q0260>* z6ReDhD`%T)Y2Fx4AjZ&$UOYSAzNbgGYkM_k(C`S(rIYg@#W%@gGh|MQ>~6l2TH7n- z+4pVvzZ0##EdSphrsJLeYBW2&tc;b=Kzu-yPw+-@BR51cx3i*27 z?+57^E`)|C4K1zVTFhV<+_j%=bP-@r-eM2=#mj) zHbg%`w&K3}tC{nX`3gW0dkefs+>pJrzOw3?dg7znPIP6#Q70q8Ef2r<-OSCmjQUif zBm;;qJ=Ny>aFr(|?EZ0~0~mqe&{Uc-74`LZLaz0D_Us_wb}))jDqiQV_l}T88Uqz&B_*O;g3T$SwZIS{AO_IG6O?U8 z9`XR58tP93uv4)Xib_hGi9OEFeizv|DBZoEKD~bbo)*Q(KQvV1k=r(EaUG5w92~Up z%!s3lBLqgbm5>c^U~qbCP&m}`Yikw2py4{u0jC881^EOAZzVK$_^-)GC(~13{xK^O zC*6Es{{cEBvXiWTesmIj5T0e{>e4)6nc8#hGCw}VB%#Xn-2 zY&-$MsEDa;Z8|XS@3LgZ zyZ@PBA|e4OGEKN*f+oP|%e89CL)i@Hlu<{y-t0q3$EgVjk!xibg6ANOk^zJSEbs*F zt`lo0K;#re{9|BUe*dn5nA2t-qN94u3~rh<8vRD0cMY6cd@xY|25*pqO zY`siG)VrYley;MWFkUM$+yaH+39uQFi9ndhcd0dyMfDT{Gf^Nj#L5D%5GfyAi728B zglk1KoPce+YJUsxiAS=z&5zNRmz5D-0Qyb#uieQLW2kdP@Ee~l70`iym)(!0oi2^| zGoc4VSKcEj$!g~HhpGCK3KQ-tt%Zffcvsd=ufc z+7~%8dZJE&-n@m1coNz^f%}M#Io!1s;EWfx9W3=TpB}bU%5Ova z^n*_)YFc@}x?i^t*988X0yw01Nk#-M-vcpJ3`*#~zbOA07}Hk5)y6!L&HX}maBV0VPXju2Wu()lUqdb(I) zfqnZ_1PP4i1I7%KV0pHY3wbS9v=U+u25je0N9jT?kwvu$8CwN>9->M2=uYHe<92j* zroy_{8?u@w6V!EclO#|gq|Ah*XNCi7cqtUsOK6RVJ`NZtso~-6;H+TnIE4ixl)u=I zLmh2!9q#fD1EkqjzWUZ988ykG>XZ!g%p@D?}1}$wd7~jsLg4ZAp8$hcSkv&7vh3Rel z>_S+d)$Dmm3Q+DqL=u`NId7dVS^ivpZ6C9^>0PvU%sAvV49}1Wwn0uTy18%_3RVhi zIi&WOxOum6O0yQ2RX1SsBJ;0~1wY3FvHEbuI;5@1>eQT8!Ev0+i0~8D5Ttlp&K2eWxeu#$=d=8@o z=2ljIa_ft!pUqln_%e_@e{TPUnaB_Nl5c_AX*Y;#B}hfSS+(h}qekw&Bfo*MtY9wT zuKS|oA?8O*uq}ubWe^-n!XMB1Q8}ZIKr}mEXx+2}7d{1nNeQ>p)6+BikBcV_U*L4r z%60%3g)3KbYm{k!6}sH0HibfzJ5Kc zG8Bt0wmHdjZYwk);q&)cG9M6`M?@V@sHg!; zH7+gMYp!V@ROYzH=FKz)w9tlc!=I$w&i)n-w>4-5k!cY#tuz|)sFd#>gzy38H2<~K z!;;Wt7Z!*uTRw?FohT*V*QnC~e$99$FHCosYjuFRhEqrZ8(v3*kf293KAQ+Z$kM`M z6TBPb1#X-mXT41Lgun9JZfR%FMw}){4AN_?I0q#3C0a+;lSK_{xeUO zd`dXUfFjUO8>OflgMgalx#a;BvTPn~o9hy?EMg_ut!_T(PUHZV=0=J9^75a_O(bFv z2b;+pbQI?*Huw1dZ_XHmJ{FQW7$6lbt#XpASD|G+HA!Axo&=BC_sJ99Rfvkngr*dG z8>nZX2F+E>Y${JGHf|)6T%XkiFs;v96`l`hbe{5?LFkA)T^;}>2P6YeWwMna9Sq<<^&Q6s9Yvnka z!gC=w8jP0BBuLyA@E;E^F%$JV_k6{RlV&-Zh&$StHn-9k1TV-v|E!aoz4-s|rRFJ| z4KE?U(%sDjO@=tj>&uhkB1VPs3{KrAiE~8p4Oa)U=c^+SJISJHsxTC|p?UnqPkB>Q z)363aB2GY4SC{?p_N~s_R6!YOX~GX>mv*BEF^|0|?Lv5o7_W*_HL!ygW~hKc2k3`3 zRa=sUPy=RWW_)mHh?p?O=LTUEOFEBGfzBek7<%m57&lN17PuY}MiL3S8iE6VLMYA>?1i*`%dzImV&KRjo9zk_eH)NX(1 zq=c5sw|8$*cuM?>t6xI|^`Dye7Fu9XtR7NgjZ}0k7_U6vjyQK&KCG#4diq|#iDsz) zVIotD{px!&iXb`wd1Q#><3mKvo4gi(MK!fWZ|TAb2WjYD=OT6PN_k53q9nRbv7} z!z;db(|dpZRca`MFEW>Lk&VGQbp0o8VhQ>() z25A9!x*v>l-UA01knjYmqn3b#gYs^$^u_}FF9k;_IXRgGL&ye*A(BPhbPznysMuO9 zt~$WMcVxfol*!`aA~!$()0T?U&LyGn#L39XF(PR=dMqS&A+)p)dCXD#zp#ej+pZLd(k&|KuPaHd#~;3!6i&_p&28V9E!|e zU-`9+zLi&oOyZF{prT@vZGl~1=CyLe|7iLkB9<7qLD-8Pz8tbO%`%+cv}v1>JXr+p zB{XzwL-JLJSP#M+E&_{Vm-RH_8QoN4MlRTi7D+2_{!(nW&rU?f=|P2>_@4>_#dwxXq-6R|C*q=avAbimV(C}(ei7;^V?3#+NA zv0|1^VyMHJF;amFH1&YKf;9FL@N7^qT)u>$P4$=ll-0*inr zgk>fqj~9PFYbA=DNtxp&$oJM9Sz0%t;-VMcfs_G|M}yj9*ety&kQjoVNX)&Vt-ZlD zJI#Jm?unYnO-th={7@j5j|FCDjNM75+;L#!Z?IbXZJHVHa>}qlFgEAN`VbrvqJV5k zN?u+VV1l8?MP2t7C8Z7dR7AsxVT?6UBbTvLy}$@WSY4Hy zJ`zcEg0I_Emw+mzV<-hU-5vhJTPQnKi{EId>FAK`{fwz65Rgznd@2&?L`+0P#AlZ8 zGSI!IP?rAbUjIg4(9Sxrs@==E0ni|paq=$V1El5UooZ-kID`zuON{4D+IBY-Y{}je zL`8fD0la-;w-bstLb&<)`Hn6w%1|h>|Mcxv4h;_{@+@fknEoUsXa-76(K7n zUWR8^=HP7jg@lAS!bUZzbYlQ+cbol20mHjJ@7z(OKxq)h^~0xpaMU3`cIjDhF`=15 z7U2O$LNGy9P3;7BRZ1LZIocm)zxxH($F(9-a!o@Ct;HW5rR2l z#sC7C_afrU*rDc@mSj+x5p7oFrkBTf0uG+i9Z)8umU%D&4OfF8FgQhoq>lVA+-nk{ zLJ|t8=_`*EsMZJqUoJ_iPMvb|ogn^u2#HLT*&5=@6#mm!8yiy#3k!{{pn)N+$WB}W z6nDgxfByPK4A4SO5c+XkhpSS)Zwn%xgco`D?lz2BVYHYY69p%L2Dh_I5^#of*RIzX zlm|z2u{^5Dxf)_HMUGa8c=g7h-Z&}&(v-IcsM5EsI#d4lH530QqWu56#Q#r?HxN?( z{^3zne0%_;3=CO(L!SRO#sR~*7&%hFCCP+{y@NC2_?W>|`GzgSOKt0}jG_n=5)EL!KD}wgd4*8NO=h!9zm|hs3R)Q!oQ3Nssua*{ax+1b3gdmRC z84C*w6!a6^^ahJ1#KFT#5?|_pO^R*LA$&Sl9x|oD;`#kgiv!i1?G#0rJHc~_=?MTp zTO1NZGK`03scC2kVH=-%ADKKccLR$|aDUPAH;U|wkgSwI@b@5&Lg(%D}_)Z95P!f6&F^N#-KF@#X@Ta7b5@Z8%&y{Y6azn&b&Nk{&;g#<#M7* z8=(MxbWkjS^RHo>L`G>^0THy#ZI*A95F4gseU6KNKX!|05(wEi5`f z$+p7r3H{eom8{ogsIQ#)G1&dE_UFlrG7JBT{l&U0rm9!2soc@!ewg~Hmod7h(@3pXyrBvO_ZaWMW zHRT%$Ft!a0M40an`uy4W=k)Y2+9)JhpZ6D1+eFQeJjA$NW|yVEnJ=SKG+&~m)9q&c zElt)t=(uE+?CnKK@_DC+VQU#o4`A95Xw~kI(cl?w3Pb?9nCaH1bRYiuWq;_%5xp!r zqS7L*+BH#BL!QRHMhk?^qm74E<)$wdFWt8RkWH)f3mHIH>)}x*u@KdiuN@t?lh)?H<~#^I7;p`3y-g z$h2VNhd!UPTJ#+ZK5A-deS=8bHL*Sefxu7Z`?>+vi{MZ|r8?!aRFz|+g)84h7kL9#dJM=(jyVSPI%w|`baK_i?XC;0tIvbtt!p0ZrOJ!BxW8kO?yri&zV zm*xbN3F#}_6Q}-qdcn%gjkUYGJNHXeSV65|EY=Zls<5g`>(A`&a-1uoqZ^5wF1mSS zm1UJWO$X7Hn)P*AL@bO-oz0=K!j!DTnPOz%B(0L4Q%ABtNIjhw>HpkwJWbZoUESSL zP?CG{bm{{gslVT-7+P4F#Wy4v<(=l2v3_>;`i&dk;MJg*hb(_PN*7tn6skUF(KtV^@`5EG@Z%-7&L;QDUZHX*lWS`ra4% z`}6yVNxA37qM*9l+`%sXEJHwk^W*xCICW**$WLFtrr!!zuO+`ur53FO;eq&|NXhI0 zZHYU74d3li_VwzMZo4K_bb!kzZI?!eH%Yxg@hUPB*-Sd{@V)i*>#<6y?E~vxLUFuE zl)t%J8k3zZWO;}!xb^!U?3%qlUlz|8N^Zwshuhr9R&=z&XV1v}UXz}E+7{Ea89(SU z@=%dEm$S=yuWwU}M$e>=XvA->9NB$!po%uIZd2IFy5;FuCOUf8WFrwv7Z)Z>btoKS zt6yH00`A8%VW?5!RYG`yt7&7J?toR)*_9|{bQWEGw2KqppyexWbuDq5G~x&3boeeU k9g0Emo>Pu}NfXBfK3ETm>gX%r>%K^8%34bK3g)-}FT~Wl>;M1& literal 0 HcmV?d00001 diff --git a/genastdot.py b/genastdot.py index 3bd5dd2..20be6c3 100644 --- a/genastdot.py +++ b/genastdot.py @@ -53,6 +53,16 @@ def visit_BinOp(self, node): s = ' node{} -> node{}\n'.format(node._num, child_node._num) self.dot_body.append(s) + def visit_UnaryOp(self, node): + s = ' node{} [label="unary {}"]\n'.format(self.ncount, node.op.value) + self.dot_body.append(s) + node._num = self.ncount + self.ncount += 1 + + self.visit(node.expr) + s = ' node{} -> node{}\n'.format(node._num, node.expr._num) + self.dot_body.append(s) + def gendot(self): tree = self.parser.parse() self.visit(tree) diff --git a/interpreter.py b/interpreter.py index 2e5b99d..636e5fd 100644 --- a/interpreter.py +++ b/interpreter.py @@ -13,9 +13,10 @@ v5.0 : support to handle valid arithmetic expressions containing integers and any number of addition, subtraction, multiplication, and division operators. v6.0 : support to evaluates arithmetic expressions that have different operators and parentheses. v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. +v8.0 : support unary operators (+, -) """ -from ast import BinOp, Num +from ast import BinOp, Num, UnaryOp INTEGER, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN' @@ -125,9 +126,15 @@ def term(self): return node def factor(self): - """返回参与运算的数,支持整型或者带括号的表达式 INTEGER | LPAREN expr RPAREN""" + """返回参与运算的数,支持整型或者带括号的表达式 INTEGER | LPAREN expr RPAREN | (PLUS|MINUS) factor""" token = self.current_token - if self.current_token.type == INTEGER: + if self.current_token.type == PLUS: + self.eat(PLUS) + return UnaryOp(op=token, expr=self.factor()) + elif self.current_token.type == MINUS: + self.eat(MINUS) + return UnaryOp(op=token, expr=self.factor()) + elif self.current_token.type == INTEGER: self.eat(INTEGER) return Num(token) elif self.current_token.type == LPAREN: @@ -142,7 +149,7 @@ def expr(self): """表达式解析:term((PLUS|MINUS) term)* . expr : term ((PLUS | MINUS) term)* term : factor ((MUL | DIV) factor)* - factor : INTEGER | LPAREN expr RPAREN + factor : INTEGER | LPAREN expr RPAREN | (PLUS|MINUS) factor """ node = self.term() while self.current_token.type in (PLUS, MINUS): @@ -157,6 +164,7 @@ def expr(self): def parse(self): return self.expr() + class NodeVisitor(object): def visit(self, node): method_name = 'visit_' + type(node).__name__ @@ -166,6 +174,7 @@ def visit(self, node): def generic_visit(self, node): raise Exception('No visit_{} method'.format(type(node).__name__)) + class Interpreter(NodeVisitor): def __init__(self, parser): self.parser = parser @@ -183,17 +192,40 @@ def visit_BinOp(self, node): def visit_Num(self, node): return node.token.value + def visit_UnaryOp(self, node): + if node.op.type == PLUS: + return self.visit(node.expr) + elif node.op.type == MINUS: + return -self.visit(node.expr) + def visit(self, node): if isinstance(node, BinOp): return self.visit_BinOp(node) elif isinstance(node, Num): return self.visit_Num(node) + elif isinstance(node, UnaryOp): + return self.visit_UnaryOp(node) def interpret(self): tree = self.parser.parse() return self.visit(tree) + +def test_unary_op(): + """ + 测试一元运算符, text=6---1 + """ + text = '5---2' + six_tok = Num(Token(INTEGER, 5)) + one_tok = Num(Token(INTEGER, 2)) + minus_tok = Token(MINUS, '-') + exp_node = BinOp(six_tok, minus_tok, UnaryOp(minus_tok, UnaryOp(minus_tok, one_tok))) + interpreter = Interpreter(None) + print(interpreter.visit(exp_node)) + + def main(): + test_unary_op() while True: try: text = input('input a express like "10+2*3+16/(4+4)-(3-2)*2"(Only single digit integers are allowed in the input)> ') From 25a65484bfed6eccf7af52072dd48faacd809649 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Thu, 29 Aug 2024 20:50:38 +0800 Subject: [PATCH 08/11] v9.0-support to handle python assignment statements. --- README.md | 12 ++ ast.py => abs_syntax_tree.py | 29 ++++- assignments.txt | 5 + ast_v9.png | Bin 0 -> 40699 bytes func_test.py | 59 ++++++++++ genastdot.py | 54 ++++++--- interpreter.py | 214 +++++++++++++++++++++++++---------- sip_token.py | 22 ++++ 8 files changed, 318 insertions(+), 77 deletions(-) rename ast.py => abs_syntax_tree.py (66%) create mode 100644 assignments.txt create mode 100644 ast_v9.png create mode 100644 func_test.py create mode 100644 sip_token.py diff --git a/README.md b/README.md index 1af3c5b..26d2225 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,15 @@ support unary operators (+, -) python genastdot.py "5---2" > ast.dot && dot -Tpng -o ast_v8.png ast.dot ``` ![ast_v8.png](ast_v8.png) + + +### v9.0 +support to handle python assignment statements. + +```shell +python interpreter.py assignments.txt +``` + +```shell +python genastdot.py assignments.txt > ast.dot && dot -Tpng -o ast_v9.png ast.dot +``` \ No newline at end of file diff --git a/ast.py b/abs_syntax_tree.py similarity index 66% rename from ast.py rename to abs_syntax_tree.py index 9450aa3..73d7a25 100644 --- a/ast.py +++ b/abs_syntax_tree.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -@file: ast.py +@file: abs_syntax_tree.py @author: amazing coder @date: 2024/8/28 @desc: abstract-syntax tree (AST) 抽象语法树节点定义 @@ -14,7 +14,7 @@ """ -class AST(object): +class AST(object): """ ASTs represent the operator-operand model. 每一个 AST 节点都代表一个运算符和一个操作数 @@ -51,3 +51,28 @@ class UnaryOp(AST): def __init__(self, op, expr): self.token = self.op = op self.expr = expr + +class Assign(AST): + """ + 赋值运算符节点,也是非叶子节点,代表一个赋值运算符 + 比如 a = 2 这个表达式,a 是变量,2是值, 都是叶子节点,= 是赋值运算符节点 + """ + def __init__(self, left, op, right): + self.left = left + self.token = self.op = op + self.right = right + +class Var(AST): + """ + 变量节点,也是叶子节点,代表一个变量 + """ + def __init__(self, token): + self.token = token + self.value = token.value + +class NoOp(AST): + pass + +class Compound(AST): + def __init__(self): + self.children = [] \ No newline at end of file diff --git a/assignments.txt b/assignments.txt new file mode 100644 index 0000000..46895eb --- /dev/null +++ b/assignments.txt @@ -0,0 +1,5 @@ +a=1 +b=2 +c=a+b +d=a+b-c +e=45+d \ No newline at end of file diff --git a/ast_v9.png b/ast_v9.png new file mode 100644 index 0000000000000000000000000000000000000000..7089939182572ad4ad3c17f8da3419d0b232fac0 GIT binary patch literal 40699 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

wLs`AoS0YQ9fa}j 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 len(self.text) - 1: + return None + else: + return self.text[peek_pos] + def integer(self): """return a multi-digit integer""" result = '' @@ -65,6 +63,17 @@ def integer(self): self.advance() return int(result) + def identifier(self): + """return a multi-digit identifier""" + result = '' + while self.current_char is not None and self.current_char.isalnum(): + result += self.current_char + self.advance() + + if result in PYTHON_RESERVED_KEYWORDS: + return self.error() + return Token(ID, result) + def get_next_token(self): """this function breaking a sentence apart into tokens.""" while self.current_char is not None: @@ -91,6 +100,15 @@ def get_next_token(self): if self.current_char == ')': self.advance() return Token(RPAREN, ')') + if self.current_char.isalpha(): + return self.identifier() + if self.current_char == '=': + self.advance() + return Token(ASSIGN, '=') + if self.current_char == '\\' and self.peek() == 'n': + self.advance() + self.advance() + return Token(REPL, '\\n') self.error() return Token(EOF, None) @@ -125,25 +143,62 @@ def term(self): node = BinOp(left=node, op=token, right=self.factor()) return node - def factor(self): - """返回参与运算的数,支持整型或者带括号的表达式 INTEGER | LPAREN expr RPAREN | (PLUS|MINUS) factor""" + def variable(self): + node = Var(self.current_token) + self.eat(ID) + return node + + def empty(self): + return NoOp() + + def assignment_statement(self): + """ + assignment_statement : variable ASSIGN expr + """ + left = self.variable() token = self.current_token - if self.current_token.type == PLUS: - self.eat(PLUS) - return UnaryOp(op=token, expr=self.factor()) - elif self.current_token.type == MINUS: - self.eat(MINUS) - return UnaryOp(op=token, expr=self.factor()) - elif self.current_token.type == INTEGER: - self.eat(INTEGER) - return Num(token) - elif self.current_token.type == LPAREN: - self.eat(LPAREN) - node = self.expr() - self.eat(RPAREN) - return node + self.eat(ASSIGN) + right = self.expr() + node = Assign(left=left, op=token, right=right) + return node + + def statement(self): + """statement : assignment_statement | empty""" + if self.current_token.type == ID: + node = self.assignment_statement() else: - self.error() + node = self.empty() + return node + + def statements(self): + """ + statements : statement + | statement REPL statement_list + """ + node = self.statement() + results = [node] + while self.current_token.type == ID: + results.append(self.statement()) + + return results + + def compound_statement(self): + """ + compound_statement : statement_list + """ + # self.eat(REPL) + nodes = self.statements() + # self.eat(REPL) + + root = Compound() + for node in nodes: + root.children.append(node) + return root + + def program(self): + """program : compound_statement """ + node = self.compound_statement() + return node def expr(self): """表达式解析:term((PLUS|MINUS) term)* . @@ -161,8 +216,34 @@ def expr(self): node = BinOp(left=node, op=token, right=self.term()) return node + def factor(self): + """返回参与运算的数,支持整型或者带括号的表达式 INTEGER | LPAREN expr RPAREN | (PLUS|MINUS) factor | variable""" + token = self.current_token + if self.current_token.type == PLUS: + self.eat(PLUS) + return UnaryOp(op=token, expr=self.factor()) + elif self.current_token.type == MINUS: + self.eat(MINUS) + return UnaryOp(op=token, expr=self.factor()) + elif self.current_token.type == INTEGER: + self.eat(INTEGER) + return Num(token) + elif self.current_token.type == LPAREN: + self.eat(LPAREN) + node = self.expr() + self.eat(RPAREN) + return node + elif self.current_token.type == ID: + node = self.variable() + return node + else: + self.error() + def parse(self): - return self.expr() + node = self.program() + if self.current_token.type != EOF: + self.error() + return node class NodeVisitor(object): @@ -178,6 +259,7 @@ def generic_visit(self, node): class Interpreter(NodeVisitor): def __init__(self, parser): self.parser = parser + self.GLOBAL_SCOPE = {} def visit_BinOp(self, node): if node.op.type == PLUS: @@ -198,6 +280,25 @@ def visit_UnaryOp(self, node): elif node.op.type == MINUS: return -self.visit(node.expr) + def visit_Assign(self, node): + var_name = node.left.value + self.GLOBAL_SCOPE[var_name] = self.visit(node.right) + + def visit_NoOp(self, node): + pass + + def visit_Compound(self, node): + for child in node.children: + self.visit(child) + + def visit_Var(self, node): + var_name = node.value + val = self.GLOBAL_SCOPE.get(var_name) + if val is None: + raise NameError(repr(var_name)) + else: + return val + def visit(self, node): if isinstance(node, BinOp): return self.visit_BinOp(node) @@ -205,41 +306,30 @@ def visit(self, node): return self.visit_Num(node) elif isinstance(node, UnaryOp): return self.visit_UnaryOp(node) + elif isinstance(node, Var): + return self.visit_Var(node) + elif isinstance(node, Assign): + return self.visit_Assign(node) + elif isinstance(node, Compound): + return self.visit_Compound(node) + elif isinstance(node, NoOp): + return self.visit_NoOp(node) def interpret(self): tree = self.parser.parse() return self.visit(tree) -def test_unary_op(): - """ - 测试一元运算符, text=6---1 - """ - text = '5---2' - six_tok = Num(Token(INTEGER, 5)) - one_tok = Num(Token(INTEGER, 2)) - minus_tok = Token(MINUS, '-') - exp_node = BinOp(six_tok, minus_tok, UnaryOp(minus_tok, UnaryOp(minus_tok, one_tok))) - interpreter = Interpreter(None) - print(interpreter.visit(exp_node)) - - def main(): - test_unary_op() - while True: - try: - text = input('input a express like "10+2*3+16/(4+4)-(3-2)*2"(Only single digit integers are allowed in the input)> ') - except EOFError: - break - - if not text: - continue - analyzer = Analyzer(text) - parser = Parser(analyzer) - interpreter = Interpreter(parser) - print(interpreter.interpret()) + import sys + text = open(sys.argv[1], 'r').read() + print(f"begin parse input: {text}") + lexer = Analyzer(text) + parser = Parser(lexer) + interpreter = Interpreter(parser) + result = interpreter.interpret() + print(interpreter.GLOBAL_SCOPE) if __name__ == '__main__': main() - diff --git a/sip_token.py b/sip_token.py new file mode 100644 index 0000000..f29423f --- /dev/null +++ b/sip_token.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +@file: sip_token.py +@author: amazing coder +@date: 2024/8/29 +@desc: +""" +class Token(object): + def __init__(self, type, value): + self.type = type + self.value = value + + def __str__(self): + return 'Token({type}, {value})'.format( + type=self.type, + value=repr(self.value) + ) + + def __repr__(self): + return self.__str__() From 3e1ab33b1d03e44070943c8cf6b809da358250da Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Thu, 29 Aug 2024 20:58:40 +0800 Subject: [PATCH 09/11] update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26d2225..e5f46e6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # simple_interpreter (A python interpreter implemented in python.) -一个用 python 实现的简单python解释器 - -分版本逐步实现完整功能,适合初学者学习解释器的工作原理 +一个用 python 实现的简单python解释器,分版本(与分支对应)逐步实现一个简单的python解释器功能,适合初学者了解解释器的工作原理 ## 版本说明 +为了方便渐进式学习进度,每一个版本都创建了一个独立的分支,比如 v1.0版本对应的分支名为 v1.0, 该分支只实现了 v1.0 的功能,以此类推,逐步进行功能迭代。 ### v1.0 only support single-digit integers + From c08bafdebb6d9cce3942d0109a6907b0c0879177 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Sat, 31 Aug 2024 18:05:43 +0800 Subject: [PATCH 10/11] v10.0 : handle variable not defined error --- README.md | 5 +- assignments.txt | 4 +- func_test.py | 4 +- genptdot.py | 18 ++++---- interpreter.py | 89 +++++++++++++++++++++++++++++++++--- spi_symbol.py | 68 +++++++++++++++++++++++++++ sip_token.py => spi_token.py | 2 +- 7 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 spi_symbol.py rename sip_token.py => spi_token.py (95%) diff --git a/README.md b/README.md index e5f46e6..6a80c49 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,7 @@ python interpreter.py assignments.txt ```shell python genastdot.py assignments.txt > ast.dot && dot -Tpng -o ast_v9.png ast.dot -``` \ No newline at end of file +``` + +### v10.0 +增加符号表记录变量的定义,以处理使用未处理的变量 diff --git a/assignments.txt b/assignments.txt index 46895eb..f5cba79 100644 --- a/assignments.txt +++ b/assignments.txt @@ -1,5 +1,5 @@ -a=1 +a=1.34 b=2 c=a+b d=a+b-c -e=45+d \ No newline at end of file +e=45+f \ No newline at end of file diff --git a/func_test.py b/func_test.py index 1c89ab0..6d0a32a 100644 --- a/func_test.py +++ b/func_test.py @@ -7,7 +7,7 @@ @date: 2024/8/29 @desc: """ -from sip_token import Token +from spi_token import Token from abs_syntax_tree import Num, BinOp, UnaryOp from interpreter import Analyzer, Parser, Interpreter, INTEGER, MINUS @@ -31,7 +31,7 @@ def test_analyzer(): print(text) analyzer = Analyzer(text) token = analyzer.get_next_token() - while token.type != 'EOF': + while token.symbol_type != 'EOF': print(token) token = analyzer.get_next_token() diff --git a/genptdot.py b/genptdot.py index 2198cd1..f4792c8 100644 --- a/genptdot.py +++ b/genptdot.py @@ -59,7 +59,7 @@ def eat(self, token_type): # type and if they match then "eat" the current token # and assign the next token to the self.current_token, # otherwise raise an exception. - if self.current_token.type == token_type: + if self.current_token.symbol_type == token_type: self.current_node.add(TokenNode(self.current_token.value)) self.current_token = self.lexer.get_next_token() else: @@ -73,9 +73,9 @@ def factor(self): self.current_node = node token = self.current_token - if token.type == INTEGER: + if token.symbol_type == INTEGER: self.eat(INTEGER) - elif token.type == LPAREN: + elif token.symbol_type == LPAREN: self.eat(LPAREN) self.expr() self.eat(RPAREN) @@ -91,11 +91,11 @@ def term(self): self.factor() - while self.current_token.type in (MUL, DIV): + while self.current_token.symbol_type in (MUL, DIV): token = self.current_token - if token.type == MUL: + if token.symbol_type == MUL: self.eat(MUL) - elif token.type == DIV: + elif token.symbol_type == DIV: self.eat(DIV) self.factor() @@ -119,11 +119,11 @@ def expr(self): self.term() - while self.current_token.type in (PLUS, MINUS): + while self.current_token.symbol_type in (PLUS, MINUS): token = self.current_token - if token.type == PLUS: + if token.symbol_type == PLUS: self.eat(PLUS) - elif token.type == MINUS: + elif token.symbol_type == MINUS: self.eat(MINUS) self.term() diff --git a/interpreter.py b/interpreter.py index 131eff9..c120f49 100644 --- a/interpreter.py +++ b/interpreter.py @@ -15,14 +15,16 @@ v7.0 : using ASTs represent the operator-operand model of arithmetic expressions. v8.0 : support unary operators (+, -) v9.0 : support to handle python assignment statements. +v10.0 : handle variable not defined error """ import keyword from abs_syntax_tree import BinOp, Num, UnaryOp, Var, NoOp, Compound, Assign -from sip_token import Token +from spi_token import Token +from spi_symbol import VarSymbol, SymbolTable -INTEGER, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN, ID, ASSIGN, REPL = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN', 'ID', 'ASSIGN', 'REPL' +INTEGER, FLOAT, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN, ID, ASSIGN, REPL = 'INTEGER', 'FLOAT', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN', 'ID', 'ASSIGN', 'REPL' PYTHON_RESERVED_KEYWORDS = {key: Token(key, key) for key in keyword.kwlist} class Analyzer(object): @@ -55,13 +57,21 @@ def peek(self): else: return self.text[peek_pos] - def integer(self): + def number(self): """return a multi-digit integer""" result = '' while self.current_char is not None and self.current_char.isdigit(): result += self.current_char self.advance() - return int(result) + if self.current_char == '.': + result += self.current_char + self.advance() + while self.current_char is not None and self.current_char.isdigit(): + result += self.current_char + self.advance() + return float(result) + else: + return int(result) def identifier(self): """return a multi-digit identifier""" @@ -81,7 +91,8 @@ def get_next_token(self): self.skip_whitespace() continue if self.current_char.isdigit(): - return Token(INTEGER, self.integer()) + number = self.number() + return Token(INTEGER, number) if isinstance(number, int) else Token(FLOAT, number) if self.current_char == '+': self.advance() return Token(PLUS, '+') @@ -228,6 +239,9 @@ def factor(self): elif self.current_token.type == INTEGER: self.eat(INTEGER) return Num(token) + elif self.current_token.type == FLOAT: + self.eat(FLOAT) + return Num(token) elif self.current_token.type == LPAREN: self.eat(LPAREN) node = self.expr() @@ -317,12 +331,75 @@ def visit(self, node): def interpret(self): tree = self.parser.parse() + symbol_builder = SymbolTableBuilder() + symbol_builder.visit(tree) return self.visit(tree) +class SymbolTableBuilder(NodeVisitor): + def __init__(self): + self.symtab = SymbolTable() + + def visit_BinOp(self, node): + self.visit(node.left) + self.visit(node.right) + + def visit_Num(self, node): + pass + + def visit_UnaryOp(self, node): + self.visit(node.expr) + + def visit_Compound(self, node): + for child in node.children: + self.visit(child) + + def visit_NoOp(self, node): + pass + + def visit_VarDecl(self, node): + type_name = node.type_node.value + type_symbol = self.symtab.lookup(type_name) + var_name = node.var_node.value + var_symbol = VarSymbol(var_name, type_symbol) + self.symtab.define(var_symbol) + + def visit_Assign(self, node): + # python代码中赋值就是定义,没有声明 + var_name = node.left.value + var_symbol = VarSymbol(var_name, None) + self.symtab.define(var_symbol) + self.visit(node.right) + + def visit_Var(self, node): + var_name = node.value + var_symbol = self.symtab.lookup(var_name) + if var_symbol is None: + raise NameError(repr(var_name)) + + def visit(self, node): + if isinstance(node, BinOp): + return self.visit_BinOp(node) + elif isinstance(node, Num): + return self.visit_Num(node) + elif isinstance(node, UnaryOp): + return self.visit_UnaryOp(node) + elif isinstance(node, Var): + return self.visit_Var(node) + elif isinstance(node, Assign): + return self.visit_Assign(node) + elif isinstance(node, Compound): + return self.visit_Compound(node) + elif isinstance(node, NoOp): + return self.visit_NoOp(node) + + + def main(): import sys - text = open(sys.argv[1], 'r').read() + py_file = sys.argv[1] + # py_file = 'assignments.txt' + text = open(py_file, 'r').read() print(f"begin parse input: {text}") lexer = Analyzer(text) parser = Parser(lexer) diff --git a/spi_symbol.py b/spi_symbol.py new file mode 100644 index 0000000..2780feb --- /dev/null +++ b/spi_symbol.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +@file: spi_symbol.py +@author: amazing coder +@date: 2024/8/31 +@desc: 增加通用符号类 +""" + +class Symbol(object): + def __int__(self, name, type=None): + self.name = name + self.symbol_type = type + + +class BuiltinTypeSymbol(Symbol): + def __init__(self, name): + super().__int__(self, name) + + def __str__(self): + return self.name + + __repr__ = __str__ + + +class VarSymbol(Symbol): + def __init__(self, name, type=None): + # python 定义时可以不指定类型 + super().__int__(name, type) + + def __str__(self): + return f'VarSymbol:name={self.name}: type={str(self.symbol_type)}' + + __repr__ = __str__ + + +class SymbolTable(object): + def __init__(self): + self._symbols = {} + + def __str__(self): + return 'Symbols: {symbols}'.format(symbols=[value for value in self._symbols.values()]) + + __repr__ = __str__ + + def define(self, symbol): + print('Define: %s' % symbol) + self._symbols[symbol.name] = symbol + return symbol + + def lookup(self, name): + print('Lookup: %s' % name) + symbol = self._symbols.get(name) + return symbol + + +def test_class(): + int_type = BuiltinTypeSymbol('INTEGER') + float_type = BuiltinTypeSymbol('FLOAT') + var_x = VarSymbol('x', int_type) + var_y = VarSymbol('y', float_type) + print(var_x) + print(var_y) + + +if __name__ == '__main__': + test_class() \ No newline at end of file diff --git a/sip_token.py b/spi_token.py similarity index 95% rename from sip_token.py rename to spi_token.py index f29423f..55cb73e 100644 --- a/sip_token.py +++ b/spi_token.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -@file: sip_token.py +@file: spi_token.py @author: amazing coder @date: 2024/8/29 @desc: From 26c42e2863a892ae9b3ef496ffee2570e4234d69 Mon Sep 17 00:00:00 2001 From: Amazing Coder Date: Fri, 14 Mar 2025 15:16:43 +0800 Subject: [PATCH 11/11] format code with autopep8 --- spi_symbol.py | 7 ++++--- spi_token.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spi_symbol.py b/spi_symbol.py index 2780feb..791cd08 100644 --- a/spi_symbol.py +++ b/spi_symbol.py @@ -8,6 +8,7 @@ @desc: 增加通用符号类 """ + class Symbol(object): def __int__(self, name, type=None): self.name = name @@ -16,7 +17,7 @@ def __int__(self, name, type=None): class BuiltinTypeSymbol(Symbol): def __init__(self, name): - super().__int__(self, name) + super().__int__(name) def __str__(self): return self.name @@ -30,7 +31,7 @@ def __init__(self, name, type=None): super().__int__(name, type) def __str__(self): - return f'VarSymbol:name={self.name}: type={str(self.symbol_type)}' + return f'VarSymbol:name={self.name}: type={self.symbol_type}' __repr__ = __str__ @@ -65,4 +66,4 @@ def test_class(): if __name__ == '__main__': - test_class() \ No newline at end of file + test_class() diff --git a/spi_token.py b/spi_token.py index 55cb73e..87bada4 100644 --- a/spi_token.py +++ b/spi_token.py @@ -7,6 +7,8 @@ @date: 2024/8/29 @desc: """ + + class Token(object): def __init__(self, type, value): self.type = type @@ -19,4 +21,4 @@ def __str__(self): ) def __repr__(self): - return self.__str__() + return self.__str__()