Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
223 lines (177 sloc) 6.72 KB
# source-to-source compiler
from collections import OrderedDict
from spi import (
Lexer,
Parser,
NodeVisitor,
BuiltinTypeSymbol,
VarSymbol,
ProcedureSymbol
)
class ScopedSymbolTable(object):
def __init__(self, scope_name, scope_level, enclosing_scope=None):
self._symbols = OrderedDict()
self.scope_name = scope_name
self.scope_level = scope_level
self.enclosing_scope = enclosing_scope
def _init_builtins(self):
self.insert(BuiltinTypeSymbol('INTEGER'))
self.insert(BuiltinTypeSymbol('REAL'))
def __str__(self):
h1 = 'SCOPE (SCOPED SYMBOL TABLE)'
lines = ['\n', h1, '=' * len(h1)]
for header_name, header_value in (
('Scope name', self.scope_name),
('Scope level', self.scope_level),
('Enclosing scope',
self.enclosing_scope.scope_name if self.enclosing_scope else None
)
):
lines.append('%-15s: %s' % (header_name, header_value))
h2 = 'Scope (Scoped symbol table) contents'
lines.extend([h2, '-' * len(h2)])
lines.extend(
('%7s: %r' % (key, value))
for key, value in self._symbols.items()
)
lines.append('\n')
s = '\n'.join(lines)
return s
__repr__ = __str__
def insert(self, symbol):
self._symbols[symbol.name] = symbol
# To output subscripts, we get symbols to store a reference
# to their scope
symbol.scope = self
def lookup(self, name, current_scope_only=False):
# 'symbol' is either an instance of the Symbol class or None
symbol = self._symbols.get(name)
if symbol is not None:
return symbol
if current_scope_only:
return None
# recursively go up the chain and lookup the name
if self.enclosing_scope is not None:
return self.enclosing_scope.lookup(name)
class SourceToSourceCompiler(NodeVisitor):
def __init__(self):
self.current_scope = None
self.output = None
def visit_Block(self, node):
results = []
for declaration in node.declarations:
result = self.visit(declaration)
results.append(result)
results.append('\nbegin')
result = self.visit(node.compound_statement)
result = ' ' + result
results.append(result)
results.append('end')
return '\n'.join(results)
def visit_Program(self, node):
program_name = node.name
result_str = 'program %s0;\n' % program_name
global_scope = ScopedSymbolTable(
scope_name='global',
scope_level=1,
enclosing_scope=self.current_scope, # None
)
global_scope._init_builtins()
self.current_scope = global_scope
# visit subtree
result_str += self.visit(node.block)
result_str += '.'
result_str += ' {END OF %s}' % program_name
self.output = result_str
self.current_scope = self.current_scope.enclosing_scope
def visit_Compound(self, node):
results = []
for child in node.children:
result = self.visit(child)
if result is None:
continue
results.append(result)
return '\n'.join(results)
def visit_NoOp(self, node):
pass
def visit_BinOp(self, node):
t1 = self.visit(node.left)
t2 = self.visit(node.right)
return '%s %s %s' % (t1, node.op.value, t2)
def visit_ProcedureDecl(self, node):
proc_name = node.proc_name
proc_symbol = ProcedureSymbol(proc_name)
self.current_scope.insert(proc_symbol)
result_str = 'procedure %s%s' % (
proc_name, self.current_scope.scope_level
)
# Scope for parameters and local variables
procedure_scope = ScopedSymbolTable(
scope_name=proc_name,
scope_level=self.current_scope.scope_level + 1,
enclosing_scope=self.current_scope
)
self.current_scope = procedure_scope
if node.params:
result_str += '('
formal_params = []
# Insert parameters into the procedure scope
for param in node.params:
param_type = self.current_scope.lookup(param.type_node.value)
param_name = param.var_node.value
var_symbol = VarSymbol(param_name, param_type)
self.current_scope.insert(var_symbol)
proc_symbol.params.append(var_symbol)
scope_level = str(self.current_scope.scope_level)
formal_params.append(
'%s : %s' % (param_name + scope_level, param_type.name)
)
result_str += '; '.join(formal_params)
if node.params:
result_str += ')'
result_str += ';'
result_str += '\n'
result_str += self.visit(node.block_node)
result_str += '; {END OF %s}' % proc_name
# indent procedure text
result_str = '\n'.join(' ' + line for line in result_str.splitlines())
self.current_scope = self.current_scope.enclosing_scope
return result_str
def visit_VarDecl(self, node):
type_name = node.type_node.value
type_symbol = self.current_scope.lookup(type_name)
# We have all the information we need to create a variable symbol.
# Create the symbol and insert it into the symbol table.
var_name = node.var_node.value
var_symbol = VarSymbol(var_name, type_symbol)
# Signal an error if the table alrady has a symbol
# with the same name
if self.current_scope.lookup(var_name, current_scope_only=True):
raise Exception(
"Error: Duplicate identifier '%s' found" % var_name
)
self.current_scope.insert(var_symbol)
scope_level = str(self.current_scope.scope_level)
return ' var %s : %s;' % (var_name + scope_level , type_name)
def visit_Assign(self, node):
t2 = self.visit(node.right)
t1 = self.visit(node.left)
return '%s %s %s;' % (t1, ':=', t2)
def visit_Var(self, node):
var_name = node.value
var_symbol = self.current_scope.lookup(var_name)
if var_symbol is None:
raise Exception(
"Error: Symbol(identifier) not found '%s'" % var_name
)
scope_level = str(var_symbol.scope.scope_level)
return '<%s:%s>' % (var_name + scope_level, var_symbol.type.name)
if __name__ == '__main__':
import sys
text = open(sys.argv[1], 'r').read()
lexer = Lexer(text)
parser = Parser(lexer)
tree = parser.parse()
source_compiler = SourceToSourceCompiler()
source_compiler.visit(tree)
print(source_compiler.output)