Skip to content

Commit

Permalink
isinstance checks converted to match/case; small cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
ssloy committed Feb 9, 2024
1 parent 1341273 commit e5d75fb
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 127 deletions.
127 changes: 60 additions & 67 deletions analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def build_symtable(ast):
ast.deco['scope_cnt'] = symtable.scope_cnt # total number of functions, necessary for the static scope display table allocation

def process_scope(fun, symtable):
fun.deco['nonlocal'] = set() # set of nonlocal variable names in the function body, used in "readable" python transpilation only
fun.deco['local'] = [] # set of local variable names: len*4 is the memory necessary on the stack, the names are here to be put in comments
symtable.push_scope(fun.deco)
for v in fun.args: # process function arguments
Expand All @@ -37,71 +36,65 @@ def process_scope(fun, symtable):
symtable.pop_scope()

def process_stat(n, symtable): # process "statement" syntax tree nodes
if isinstance(n, Print): # no type checking is necessary
process_expr(n.expr, symtable)
elif isinstance(n, Return):
if n.expr is None: return
process_expr(n.expr, symtable)
if symtable.ret_stack[-1]['type'] != n.expr.deco['type']:
raise Exception('Incompatible types in return statement, line %s', n.deco['lineno'])
elif isinstance(n, Assign):
process_expr(n.expr, symtable)
deco = symtable.find_var(n.name)
n.deco['scope'] = deco['scope']
n.deco['offset'] = deco['offset']
n.deco['type'] = deco['type']
if n.deco['type'] != n.expr.deco['type']:
raise Exception('Incompatible types in assignment statement, line %s', n.deco['lineno'])
update_nonlocals(n.name, symtable) # used in "readable" python transpilation only
elif isinstance(n, FunCall): # no type checking is necessary
process_expr(n, symtable)
elif isinstance(n, While):
process_expr(n.expr, symtable)
if n.expr.deco['type'] != Type.BOOL:
raise Exception('Non-boolean expression in while statement, line %s', n.deco['lineno'])
for s in n.body:
process_stat(s, symtable)
elif isinstance(n, IfThenElse):
process_expr(n.expr, symtable)
if n.expr.deco['type'] != Type.BOOL:
raise Exception('Non-boolean expression in if statement, line %s', n.deco['lineno'])
for s in n.ibody + n.ebody:
process_stat(s, symtable)
else:
raise Exception('Unknown statement type')
match n:
case Print(): # no type checking is necessary
process_expr(n.expr, symtable)
case Return():
if n.expr is None: return
process_expr(n.expr, symtable)
if symtable.ret_stack[-1]['type'] != n.expr.deco['type']:
raise Exception('Incompatible types in return statement, line %s', n.deco['lineno'])
case Assign():
process_expr(n.expr, symtable)
deco = symtable.find_var(n.name)
n.deco['scope'] = deco['scope']
n.deco['offset'] = deco['offset']
n.deco['type'] = deco['type']
if n.deco['type'] != n.expr.deco['type']:
raise Exception('Incompatible types in assignment statement, line %s', n.deco['lineno'])
case FunCall(): # no type checking is necessary
process_expr(n, symtable)
case While():
process_expr(n.expr, symtable)
if n.expr.deco['type'] != Type.BOOL:
raise Exception('Non-boolean expression in while statement, line %s', n.deco['lineno'])
for s in n.body:
process_stat(s, symtable)
case IfThenElse():
process_expr(n.expr, symtable)
if n.expr.deco['type'] != Type.BOOL:
raise Exception('Non-boolean expression in if statement, line %s', n.deco['lineno'])
for s in n.ibody + n.ebody:
process_stat(s, symtable)
case other: raise Exception('Unknown statement type')

def process_expr(n, symtable): # process "expression" syntax tree nodes
if isinstance(n, ArithOp):
process_expr(n.left, symtable)
process_expr(n.right, symtable)
if n.left.deco['type'] != Type.INT or n.right.deco['type'] != Type.INT:
raise Exception('Arithmetic operation over non-integer type in line %s', n.deco['lineno'])
elif isinstance(n, LogicOp):
process_expr(n.left, symtable)
process_expr(n.right, symtable)
if (n.left.deco['type'] != n.right.deco['type']) or \
(n.op in ['<=', '<', '>=', '>'] and n.left.deco['type'] != Type.INT) or \
(n.op in ['&&', '||'] and n.left.deco['type'] != Type.BOOL):
raise Exception('Boolean operation over incompatible types in line %s', n.deco['lineno'])
elif isinstance(n, Var): # no type checking is necessary
deco = symtable.find_var(n.name)
n.deco['type'] = deco['type']
n.deco['scope'] = deco['scope']
n.deco['offset'] = deco['offset']
update_nonlocals(n.name, symtable) # used in "readable" python transpilation only
elif isinstance(n, FunCall):
for s in n.args:
process_expr(s, symtable)
deco = symtable.find_fun(n.name, [a.deco['type'] for a in n.args])
n.deco['fundeco'] = deco # save the function symbol, useful for overloading and for stack preparation
n.deco['type'] = deco['type']
elif isinstance(n, String): # no type checking is necessary
n.deco['label'] = LabelFactory.new_label() # unique label for assembly code
symtable.ret_stack[1]['strings'].append((n.deco['label'], n.value))
elif not isinstance(n, Integer) and not isinstance(n, Boolean): # no type checking is necessary
raise Exception('Unknown expression type', n)

def update_nonlocals(var, symtable): # add the variable name to the set of nonlocals
for i in reversed(range(len(symtable.variables))): # for all the enclosing scopes until we find the instance
if var in symtable.variables[i]: break # used in "readable" python transpilation only
symtable.ret_stack[i]['nonlocal'].add(var)
match n:
case ArithOp():
process_expr(n.left, symtable)
process_expr(n.right, symtable)
if n.left.deco['type'] != Type.INT or n.right.deco['type'] != Type.INT:
raise Exception('Arithmetic operation over non-integer type in line %s', n.deco['lineno'])
case LogicOp():
process_expr(n.left, symtable)
process_expr(n.right, symtable)
if (n.left.deco['type'] != n.right.deco['type']) or \
(n.op in ['<=', '<', '>=', '>'] and n.left.deco['type'] != Type.INT) or \
(n.op in ['&&', '||'] and n.left.deco['type'] != Type.BOOL):
raise Exception('Boolean operation over incompatible types in line %s', n.deco['lineno'])
case Var(): # no type checking is necessary
deco = symtable.find_var(n.name)
n.deco['type'] = deco['type']
n.deco['scope'] = deco['scope']
n.deco['offset'] = deco['offset']
case FunCall():
for s in n.args:
process_expr(s, symtable)
deco = symtable.find_fun(n.name, [a.deco['type'] for a in n.args])
n.deco['fundeco'] = deco # save the function symbol, useful for overloading and for stack preparation
n.deco['type'] = deco['type']
case String(): # no type checking is necessary
n.deco['label'] = LabelFactory.new_label() # unique label for assembly code
symtable.ret_stack[1]['strings'].append((n.deco['label'], n.value))
case Integer() | Boolean(): pass # no type checking is necessary
case other: raise Exception('Unknown expression type', n)
1 change: 0 additions & 1 deletion compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@
print(transasm(ast))
except Exception as e:
print(e)

7 changes: 3 additions & 4 deletions lexer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
class Token:
def __init__(self, t, v, l=None):
self.type, self.value, self.lineno = t, v, (l or 0) +1 # mwahaha, +1, sly, wtf? # TODO remove +1
self.index, self.end = None, None # TODO needed for sly, remove in the future
self.type, self.value, self.lineno = t, v, (l or 0)

def __repr__(self):
return f'Token(type={self.type!r}, value={self.value!r}, lineno={self.lineno!r})'
Expand All @@ -10,7 +9,7 @@ class WendLexer:
keywords = {'true':'BOOLEAN','false':'BOOLEAN','print':'PRINT','println':'PRINT','int':'TYPE','bool':'TYPE','var':'VAR','fun':'FUN','if':'IF','else':'ELSE','while':'WHILE','return':'RETURN'}
double_char = {'==':'COMP', '<=':'COMP', '>=':'COMP', '!=':'COMP', '&&':'AND', '||':'OR'}
single_char = {'=':'ASSIGN','<':'COMP', '>':'COMP', '!':'NOT', '+':'PLUS', '-':'MINUS', '/':'DIVIDE', '*':'TIMES', '%':'MOD','(':'LPAREN',')':'RPAREN', '{':'BEGIN', '}':'END', ';':'SEMICOLON', ':':'COLON', ',':'COMMA'}
tokens = {'ID':'', 'STRING':'', 'INTEGER':''} | { v:'' for k, v in keywords.items() | double_char.items() | single_char.items() }
tokens = {'ID', 'STRING', 'INTEGER'} | { v for k, v in keywords.items() | double_char.items() | single_char.items() }

def tokenize(self, text):
lineno, idx, state, accum = 0, 0, 0, ''
Expand Down Expand Up @@ -63,5 +62,5 @@ def tokenize(self, text):
if state==1: # if comment, start new scan
state,accum = 0, ''
idx += 1
if state!=0:
if state:
raise Exception('Lexical error: unexpected EOF')
2 changes: 1 addition & 1 deletion parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def next_symbol(self):
def __eq__(self, other):
return self.rule == other.rule and self.dot == other.dot and self.start == other.start # NB no self.token, no self.prev

class WendParser: # the grammar is a list of triplets (nonterminal, production rule, AST node constructor)
class WendParser: # the grammar is a list of triplets [nonterminal, production rule, AST node constructor]
grammar = [['fun', ['FUN', 'ID', 'LPAREN', 'param_list', 'RPAREN', 'fun_type', 'BEGIN', 'var_list', 'fun_list', 'statement_list', 'END'],
lambda p: Function(p[1].value, p[3], p[7], p[8], p[9], {'type':p[5], 'lineno':p[0].lineno})],
['var', ['ID', 'COLON', 'TYPE'], lambda p: (p[0].value, {'type':Type.INT if p[2].value=='int' else Type.BOOL, 'lineno':p[0].lineno})],
Expand Down
File renamed without changes.
File renamed without changes.
109 changes: 55 additions & 54 deletions transasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,60 @@ def fun(n):
return f'{label}:\n{body}\n\tret\n{nested}\n'

def stat(n):
if isinstance(n, Print):
newline = templates['print_linebreak'] if n.newline else ''
if n.expr.deco['type']==Type.INT:
return templates['print_int'].format(expr = expr(n.expr), newline = newline)
elif n.expr.deco['type']==Type.BOOL:
return templates['print_bool'].format(expr = expr(n.expr), newline = newline)
elif n.expr.deco['type']==Type.STRING:
return templates['print_string'].format(label = n.expr.deco['label'], newline = newline)
else:
raise Exception('Unknown expression type', n.expr)
elif isinstance(n, Return):
return (expr(n.expr) if n.expr is not None and n.expr.deco['type'] != Type.VOID else '') + '\tret\n'
elif isinstance(n, Assign):
return templates['assign'].format(expression = expr(n.expr),
scope = n.deco['scope']*4,
variable = n.deco['offset']*4)
elif isinstance(n, FunCall):
return expr(n)
elif isinstance(n, While):
return templates['while'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
body = ''.join([stat(s) for s in n.body]))
elif isinstance(n, IfThenElse):
return templates['ifthenelse'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
ibody = ''.join([stat(s) for s in n.ibody]),
ebody = ''.join([stat(s) for s in n.ebody]))
raise Exception('Unknown statement type', n)
match n:
case Print():
newline = templates['print_linebreak'] if n.newline else ''
match n.expr.deco['type']:
case Type.INT:
return templates['print_int'].format(expr = expr(n.expr), newline = newline)
case Type.BOOL:
return templates['print_bool'].format(expr = expr(n.expr), newline = newline)
case Type.STRING:
return templates['print_string'].format(label = n.expr.deco['label'], newline = newline)
case other: raise Exception('Unknown expression type', n.expr)
case Return():
return (expr(n.expr) if n.expr is not None and n.expr.deco['type'] != Type.VOID else '') + '\tret\n'
case Assign():
return templates['assign'].format(expression = expr(n.expr),
scope = n.deco['scope']*4,
variable = n.deco['offset']*4)
case FunCall(): return expr(n)
case While():
return templates['while'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
body = ''.join([stat(s) for s in n.body]))
case IfThenElse():
return templates['ifthenelse'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
ibody = ''.join([stat(s) for s in n.ibody]),
ebody = ''.join([stat(s) for s in n.ebody]))
case other: raise Exception('Unknown statement type', n)

def expr(n): # convention: all expressions save their results to eax
if isinstance(n, ArithOp) or isinstance(n, LogicOp):
args = expr(n.left) + '\tpushl %eax\n' + expr(n.right) + '\tmovl %eax, %ebx\n\tpopl %eax\n'
pyeq1 = {'+':'addl', '-':'subl', '*':'imull', '||':'orl', '&&':'andl'}
pyeq2 = {'<=':'jle', '<':'jl', '>=':'jge', '>':'jg', '==':'je', '!=':'jne'}
if n.op in pyeq1:
return args + f'\t{pyeq1[n.op]} %ebx, %eax\n'
elif n.op in pyeq2:
return args + f'\tcmp %ebx, %eax\n\tmovl $1, %eax\n\t{pyeq2[n.op]} 1f\n\txorl %eax, %eax\n1:\n'
elif n.op=='/':
return args + '\tcdq\n\tidivl %ebx, %eax\n'
elif n.op=='%':
return args + '\tcdq\n\tidivl %ebx, %eax\n\tmovl %edx, %eax\n'
raise Exception('Unknown binary operation')
elif isinstance(n, Integer) or isinstance(n, Boolean):
return f'\tmovl ${int(n.value)}, %eax\n'
elif isinstance(n, Var):
return templates['var'].format(scope = n.deco['scope']*4, variable = n.deco['offset']*4)
elif isinstance(n, FunCall):
return templates['funcall'].format(allocargs = ''.join(['%s\tpushl %%eax\n' % expr(a) for a in n.args]),
varsize = len(n.deco['fundeco']['local'])*4,
disphead = len(n.deco['fundeco']['local'])*4 + len(n.args)*4 - 4,
scope = n.deco['fundeco']['scope']*4,
funlabel = n.deco['fundeco']['label'])
raise Exception('Unknown expression type', n)
match n:
case ArithOp() | LogicOp():
args = expr(n.left) + '\tpushl %eax\n' + expr(n.right) + '\tmovl %eax, %ebx\n\tpopl %eax\n'
pyeq1 = {'+':'addl', '-':'subl', '*':'imull', '||':'orl', '&&':'andl'}
pyeq2 = {'<=':'jle', '<':'jl', '>=':'jge', '>':'jg', '==':'je', '!=':'jne'}
if n.op in pyeq1:
return args + f'\t{pyeq1[n.op]} %ebx, %eax\n'
elif n.op in pyeq2:
return args + f'\tcmp %ebx, %eax\n\tmovl $1, %eax\n\t{pyeq2[n.op]} 1f\n\txorl %eax, %eax\n1:\n'
elif n.op=='/':
return args + '\tcdq\n\tidivl %ebx, %eax\n'
elif n.op=='%':
return args + '\tcdq\n\tidivl %ebx, %eax\n\tmovl %edx, %eax\n'
raise Exception('Unknown binary operation')
case Integer() | Boolean():
return f'\tmovl ${int(n.value)}, %eax\n'
case Var():
return templates['var'].format(scope = n.deco['scope']*4, variable = n.deco['offset']*4)
case FunCall():
return templates['funcall'].format(allocargs = ''.join(['%s\tpushl %%eax\n' % expr(a) for a in n.args]),
varsize = len(n.deco['fundeco']['local'])*4,
disphead = len(n.deco['fundeco']['local'])*4 + len(n.args)*4 - 4,
scope = n.deco['fundeco']['scope']*4,
funlabel = n.deco['fundeco']['label'])
case other: raise Exception('Unknown expression type', n)

0 comments on commit e5d75fb

Please sign in to comment.