Skip to content

Commit 891b4c9

Browse files
Merge pull request #8
v10.0 : handle variable not defined error
2 parents 3e1ab33 + c08bafd commit 891b4c9

File tree

7 files changed

+169
-21
lines changed

7 files changed

+169
-21
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ python interpreter.py assignments.txt
5555

5656
```shell
5757
python genastdot.py assignments.txt > ast.dot && dot -Tpng -o ast_v9.png ast.dot
58-
```
58+
```
59+
60+
### v10.0
61+
增加符号表记录变量的定义,以处理使用未处理的变量

assignments.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
a=1
1+
a=1.34
22
b=2
33
c=a+b
44
d=a+b-c
5-
e=45+d
5+
e=45+f

func_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@date: 2024/8/29
88
@desc:
99
"""
10-
from sip_token import Token
10+
from spi_token import Token
1111
from abs_syntax_tree import Num, BinOp, UnaryOp
1212
from interpreter import Analyzer, Parser, Interpreter, INTEGER, MINUS
1313

@@ -31,7 +31,7 @@ def test_analyzer():
3131
print(text)
3232
analyzer = Analyzer(text)
3333
token = analyzer.get_next_token()
34-
while token.type != 'EOF':
34+
while token.symbol_type != 'EOF':
3535
print(token)
3636
token = analyzer.get_next_token()
3737

genptdot.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def eat(self, token_type):
5959
# type and if they match then "eat" the current token
6060
# and assign the next token to the self.current_token,
6161
# otherwise raise an exception.
62-
if self.current_token.type == token_type:
62+
if self.current_token.symbol_type == token_type:
6363
self.current_node.add(TokenNode(self.current_token.value))
6464
self.current_token = self.lexer.get_next_token()
6565
else:
@@ -73,9 +73,9 @@ def factor(self):
7373
self.current_node = node
7474

7575
token = self.current_token
76-
if token.type == INTEGER:
76+
if token.symbol_type == INTEGER:
7777
self.eat(INTEGER)
78-
elif token.type == LPAREN:
78+
elif token.symbol_type == LPAREN:
7979
self.eat(LPAREN)
8080
self.expr()
8181
self.eat(RPAREN)
@@ -91,11 +91,11 @@ def term(self):
9191

9292
self.factor()
9393

94-
while self.current_token.type in (MUL, DIV):
94+
while self.current_token.symbol_type in (MUL, DIV):
9595
token = self.current_token
96-
if token.type == MUL:
96+
if token.symbol_type == MUL:
9797
self.eat(MUL)
98-
elif token.type == DIV:
98+
elif token.symbol_type == DIV:
9999
self.eat(DIV)
100100

101101
self.factor()
@@ -119,11 +119,11 @@ def expr(self):
119119

120120
self.term()
121121

122-
while self.current_token.type in (PLUS, MINUS):
122+
while self.current_token.symbol_type in (PLUS, MINUS):
123123
token = self.current_token
124-
if token.type == PLUS:
124+
if token.symbol_type == PLUS:
125125
self.eat(PLUS)
126-
elif token.type == MINUS:
126+
elif token.symbol_type == MINUS:
127127
self.eat(MINUS)
128128

129129
self.term()

interpreter.py

+83-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
v7.0 : using ASTs represent the operator-operand model of arithmetic expressions.
1616
v8.0 : support unary operators (+, -)
1717
v9.0 : support to handle python assignment statements.
18+
v10.0 : handle variable not defined error
1819
"""
1920

2021
import keyword
2122
from abs_syntax_tree import BinOp, Num, UnaryOp, Var, NoOp, Compound, Assign
22-
from sip_token import Token
23+
from spi_token import Token
24+
from spi_symbol import VarSymbol, SymbolTable
2325

2426

25-
INTEGER, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN, ID, ASSIGN, REPL = 'INTEGER', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN', 'ID', 'ASSIGN', 'REPL'
27+
INTEGER, FLOAT, PLUS, EOF, MINUS, MUL, DIV, LPAREN, RPAREN, ID, ASSIGN, REPL = 'INTEGER', 'FLOAT', 'PLUS', 'EOF', 'MINUS', 'MUL', 'DIV', 'LPAREN', 'RPAREN', 'ID', 'ASSIGN', 'REPL'
2628
PYTHON_RESERVED_KEYWORDS = {key: Token(key, key) for key in keyword.kwlist}
2729

2830
class Analyzer(object):
@@ -55,13 +57,21 @@ def peek(self):
5557
else:
5658
return self.text[peek_pos]
5759

58-
def integer(self):
60+
def number(self):
5961
"""return a multi-digit integer"""
6062
result = ''
6163
while self.current_char is not None and self.current_char.isdigit():
6264
result += self.current_char
6365
self.advance()
64-
return int(result)
66+
if self.current_char == '.':
67+
result += self.current_char
68+
self.advance()
69+
while self.current_char is not None and self.current_char.isdigit():
70+
result += self.current_char
71+
self.advance()
72+
return float(result)
73+
else:
74+
return int(result)
6575

6676
def identifier(self):
6777
"""return a multi-digit identifier"""
@@ -81,7 +91,8 @@ def get_next_token(self):
8191
self.skip_whitespace()
8292
continue
8393
if self.current_char.isdigit():
84-
return Token(INTEGER, self.integer())
94+
number = self.number()
95+
return Token(INTEGER, number) if isinstance(number, int) else Token(FLOAT, number)
8596
if self.current_char == '+':
8697
self.advance()
8798
return Token(PLUS, '+')
@@ -228,6 +239,9 @@ def factor(self):
228239
elif self.current_token.type == INTEGER:
229240
self.eat(INTEGER)
230241
return Num(token)
242+
elif self.current_token.type == FLOAT:
243+
self.eat(FLOAT)
244+
return Num(token)
231245
elif self.current_token.type == LPAREN:
232246
self.eat(LPAREN)
233247
node = self.expr()
@@ -317,12 +331,75 @@ def visit(self, node):
317331

318332
def interpret(self):
319333
tree = self.parser.parse()
334+
symbol_builder = SymbolTableBuilder()
335+
symbol_builder.visit(tree)
320336
return self.visit(tree)
321337

322338

339+
class SymbolTableBuilder(NodeVisitor):
340+
def __init__(self):
341+
self.symtab = SymbolTable()
342+
343+
def visit_BinOp(self, node):
344+
self.visit(node.left)
345+
self.visit(node.right)
346+
347+
def visit_Num(self, node):
348+
pass
349+
350+
def visit_UnaryOp(self, node):
351+
self.visit(node.expr)
352+
353+
def visit_Compound(self, node):
354+
for child in node.children:
355+
self.visit(child)
356+
357+
def visit_NoOp(self, node):
358+
pass
359+
360+
def visit_VarDecl(self, node):
361+
type_name = node.type_node.value
362+
type_symbol = self.symtab.lookup(type_name)
363+
var_name = node.var_node.value
364+
var_symbol = VarSymbol(var_name, type_symbol)
365+
self.symtab.define(var_symbol)
366+
367+
def visit_Assign(self, node):
368+
# python代码中赋值就是定义,没有声明
369+
var_name = node.left.value
370+
var_symbol = VarSymbol(var_name, None)
371+
self.symtab.define(var_symbol)
372+
self.visit(node.right)
373+
374+
def visit_Var(self, node):
375+
var_name = node.value
376+
var_symbol = self.symtab.lookup(var_name)
377+
if var_symbol is None:
378+
raise NameError(repr(var_name))
379+
380+
def visit(self, node):
381+
if isinstance(node, BinOp):
382+
return self.visit_BinOp(node)
383+
elif isinstance(node, Num):
384+
return self.visit_Num(node)
385+
elif isinstance(node, UnaryOp):
386+
return self.visit_UnaryOp(node)
387+
elif isinstance(node, Var):
388+
return self.visit_Var(node)
389+
elif isinstance(node, Assign):
390+
return self.visit_Assign(node)
391+
elif isinstance(node, Compound):
392+
return self.visit_Compound(node)
393+
elif isinstance(node, NoOp):
394+
return self.visit_NoOp(node)
395+
396+
397+
323398
def main():
324399
import sys
325-
text = open(sys.argv[1], 'r').read()
400+
py_file = sys.argv[1]
401+
# py_file = 'assignments.txt'
402+
text = open(py_file, 'r').read()
326403
print(f"begin parse input: {text}")
327404
lexer = Analyzer(text)
328405
parser = Parser(lexer)

spi_symbol.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
@file: spi_symbol.py
6+
@author: amazing coder
7+
@date: 2024/8/31
8+
@desc: 增加通用符号类
9+
"""
10+
11+
class Symbol(object):
12+
def __int__(self, name, type=None):
13+
self.name = name
14+
self.symbol_type = type
15+
16+
17+
class BuiltinTypeSymbol(Symbol):
18+
def __init__(self, name):
19+
super().__int__(self, name)
20+
21+
def __str__(self):
22+
return self.name
23+
24+
__repr__ = __str__
25+
26+
27+
class VarSymbol(Symbol):
28+
def __init__(self, name, type=None):
29+
# python 定义时可以不指定类型
30+
super().__int__(name, type)
31+
32+
def __str__(self):
33+
return f'VarSymbol:name={self.name}: type={str(self.symbol_type)}'
34+
35+
__repr__ = __str__
36+
37+
38+
class SymbolTable(object):
39+
def __init__(self):
40+
self._symbols = {}
41+
42+
def __str__(self):
43+
return 'Symbols: {symbols}'.format(symbols=[value for value in self._symbols.values()])
44+
45+
__repr__ = __str__
46+
47+
def define(self, symbol):
48+
print('Define: %s' % symbol)
49+
self._symbols[symbol.name] = symbol
50+
return symbol
51+
52+
def lookup(self, name):
53+
print('Lookup: %s' % name)
54+
symbol = self._symbols.get(name)
55+
return symbol
56+
57+
58+
def test_class():
59+
int_type = BuiltinTypeSymbol('INTEGER')
60+
float_type = BuiltinTypeSymbol('FLOAT')
61+
var_x = VarSymbol('x', int_type)
62+
var_y = VarSymbol('y', float_type)
63+
print(var_x)
64+
print(var_y)
65+
66+
67+
if __name__ == '__main__':
68+
test_class()

sip_token.py renamed to spi_token.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
"""
5-
@file: sip_token.py
5+
@file: spi_token.py
66
@author: amazing coder
77
@date: 2024/8/29
88
@desc:

0 commit comments

Comments
 (0)