Skip to content

Commit 922b28d

Browse files
committed
symbol tables: correct handling of variables scope
1 parent e983025 commit 922b28d

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

analyzer.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from syntree import *
2+
from symtable import *
3+
4+
def build_symtable(ast):
5+
if not isinstance(ast, Function) or ast.name != 'main' or ast.deco['type'] != Type.VOID or len(ast.args)>0:
6+
raise Exception('Cannot find a valid entry point')
7+
symtable = SymbolTable()
8+
process_scope(ast, symtable)
9+
10+
def process_scope(fun, symtable):
11+
fun.deco['nonlocal'] = set() # set of nonlocal variable names in the function body, used in "readable" python transpilation only
12+
symtable.push_scope(fun.deco)
13+
for v in fun.args: # process function arguments
14+
symtable.add_var(*v)
15+
for v in fun.var: # process local variables
16+
symtable.add_var(*v)
17+
for f in fun.fun: # then process nested function bodies
18+
process_scope(f, symtable)
19+
for s in fun.body: # process the list of statements
20+
process_stat(s, symtable)
21+
symtable.pop_scope()
22+
23+
def process_stat(n, symtable): # process "statement" syntax tree nodes
24+
if isinstance(n, Print):
25+
process_expr(n.expr, symtable)
26+
elif isinstance(n, Return):
27+
if n.expr is None: return
28+
process_expr(n.expr, symtable)
29+
elif isinstance(n, Assign):
30+
process_expr(n.expr, symtable)
31+
deco = symtable.find_var(n.name)
32+
update_nonlocals(n.name, symtable) # used in "readable" python transpilation only
33+
elif isinstance(n, FunCall): # no type checking is necessary
34+
process_expr(n, symtable)
35+
elif isinstance(n, While):
36+
process_expr(n.expr, symtable)
37+
for s in n.body:
38+
process_stat(s, symtable)
39+
elif isinstance(n, IfThenElse):
40+
process_expr(n.expr, symtable)
41+
for s in n.ibody + n.ebody:
42+
process_stat(s, symtable)
43+
else:
44+
raise Exception('Unknown statement type')
45+
46+
def process_expr(n, symtable): # process "expression" syntax tree nodes
47+
if isinstance(n, ArithOp):
48+
process_expr(n.left, symtable)
49+
process_expr(n.right, symtable)
50+
elif isinstance(n, LogicOp):
51+
process_expr(n.left, symtable)
52+
process_expr(n.right, symtable)
53+
elif isinstance(n, Integer):
54+
n.deco['type'] = Type.INT
55+
elif isinstance(n, Boolean):
56+
n.deco['type'] = Type.BOOL
57+
elif isinstance(n, Var):
58+
deco = symtable.find_var(n.name)
59+
update_nonlocals(n.name, symtable) # used in "readable" python transpilation only
60+
elif isinstance(n, FunCall):
61+
for s in n.args:
62+
process_expr(s, symtable)
63+
elif isinstance(n, String):
64+
pass
65+
else:
66+
raise Exception('Unknown expression type', n)
67+
68+
def update_nonlocals(var, symtable): # add the variable name to the set of nonlocals
69+
for i in reversed(range(len(symtable.variables))): # for all the enclosing scopes until we find the instance
70+
if var in symtable.variables[i]: break # used in "readable" python transpilation only
71+
symtable.ret_stack[i]['nonlocal'].add(var)

symtable.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class SymbolTable():
2+
def __init__(self):
3+
self.variables = [{}] # stack of variable symbol tables
4+
self.ret_stack = [ None ] # stack of enclosing function symbols, useful for return statements
5+
6+
def add_var(self, name, deco):
7+
if name in self.variables[-1]:
8+
raise Exception('Double declaration of the variable %s' % name)
9+
self.variables[-1][name] = deco
10+
11+
def push_scope(self, deco):
12+
self.variables.append({})
13+
self.ret_stack.append(deco)
14+
15+
def pop_scope(self):
16+
self.variables.pop()
17+
self.ret_stack.pop()
18+
19+
def find_var(self, name):
20+
for i in reversed(range(len(self.variables))):
21+
if name in self.variables[i]:
22+
return self.variables[i][name]
23+
raise Exception('No declaration for the variable %s' % name)

tests/test_syntree.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
import io, sys # for exec stdout redirection
33
from syntree import *
4+
from analyzer import *
45
from transpy_naive import *
56

67
def test_syntree(tmp_path):
@@ -60,6 +61,7 @@ def test_syntree(tmp_path):
6061
],
6162
{'type':Type.VOID}) # return type
6263

64+
build_symtable(main_fun)
6365
program = transpy(main_fun)
6466

6567
print(program) # save the output to a temp dir

tests/test_transpy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io, sys
44
from lexer import WendLexer
55
from parser import WendParser
6+
from analyzer import *
67
from transpy_naive import *
78

89
@pytest.mark.parametrize('test_case', (
@@ -18,6 +19,7 @@ def test_transpy(tmp_path, test_case):
1819

1920
tokens = WendLexer().tokenize(f.read())
2021
ast = WendParser().parse(tokens)
22+
build_symtable(ast)
2123

2224
program = transpy(ast)
2325
old_stdout = sys.stdout

transpy_naive.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
def transpy(n):
44
funname = n.name
55
funargs = ', '.join([v for v,t in n.args])
6+
nonlocals = ('nonlocal ' + ', '.join([v for v in n.deco['nonlocal'] ])) if len(n.deco['nonlocal']) else 'pass # no non-local variables'
67
allocvars = ''.join(['%s = None\n' % v for v,t in n.var])
78
nestedfun = ''.join([transpy(f) for f in n.fun])
89
funbody = ''.join([stat(s) for s in n.body])
910
return f'def {funname}({funargs}):\n' + \
10-
indent(f'{allocvars}\n'
11+
indent(f'{nonlocals}\n'
12+
f'{allocvars}\n'
1113
f'{nestedfun}\n'
1214
f'{funbody}\n') + \
1315
(f'{funname}()\n' if n.name=='main' else '')

0 commit comments

Comments
 (0)