#!/usr/bin/python3 """A compiler for a small language. The objective here is to get *something* running in assembly that resembles a program. Like: int i = 10 while i >= 0: printf("%d\n", i) i-- int j = 1000000 while j > 0: j-- The AST of this program is represented as a Python term down in main(). I’m trying to use Henry Baker’s COMFY approach here for control flow: we compile from the end of the program backwards, and each compilation method takes two locations, one to pass control to if it succeeds (‘yes’) and one for if it fails (‘no’). Then it returns a new location. For expression evaluation, for the moment I’m using a runtime stack. So far I haven’t implemented any of the code generation! Just hundreds of lines of boilerplate crap. But I'm only 70 minutes in I guess. """ import os class Statement: pass class Initialize(Statement): def __init__(self, typeobj, varname, val): self.typeobj = typeobj self.varname = varname self.val = val def compile(self, scope, yes, no): scope.add_var(self.varname, self.typeobj) yes = scope.set_var(self.varname, yes) return self.val.eval(scope, yes, no) class While(Statement): def __init__(self, cond, body): self.cond = cond self.body = body def compile(self, scope, yes, no): start = scope.backpatchable() # If the body (a statement) fails it goes to our ‘no’. a = self.body.compile(scope, yes=start, no=no) # If the condition (an expression) fails, it goes to our ‘yes’. b = self.cond.eval(scope, yes=a, no=yes) start.resolve(b) return b class Sequence(Statement): def __init__(self, statements): self.statements = statements def compile(self, scope, yes, no): for statement in reversed(self.statements): yes = statement.compile(scope, yes, no) return yes class ExpressionStatement(Statement): def __init__(self, expr): self.expr = expr def compile(self, scope, yes, no): yes = self.expr.eval(scope, yes, no) scope.discard_result() return yes class Decrement(Statement): def __init__(self, lvalue): self.lvalue = lvalue def compile(self, scope, yes, no): return scope.decrement(self.lvalue, yes) class Expression: pass class IntConst(Expression): def __init__(self, val): self.val = val def eval(self, scope, yes, no): return scope.push_constant(self.val, yes) class StringConst(Expression): def __init__(self, val): self.val = val def eval(self, scope, yes, no): val = scope.intern(self.val) return scope.push_constant(val, yes) class VarRef(Expression): def __init__(self, varname): self.varname = varname def eval(self, scope, yes, no): return scope.fetch_var(self.varname, yes) class Comparison(Expression): def __init__(self, lhs, relation, rhs): self.lhs = lhs self.relation = relation self.rhs = rhs def eval(self, scope, yes, no): return scope.compare(self.lhs, self.relation, self.rhs, yes, no) class FunctionCall(Expression): def __init__(self, f, args): self.f = f # for now, an identifier, not an expression self.args = args def eval(self, scope, yes, no): yes = scope.call(self.f, len(self.args), yes, no) for arg in self.args: yes = arg.eval(scope, yes, no) return yes class AsmScope: def __init__(self): self.statements = [] self.vars = [] self.backpatchables = 0 def generate_assembly(self): return '# XXX TODO' def call(self, f, nargs, yes, no): pass def get_end_location(self): return 'BOOGA BOOGA' def add_var(self, varname, typeobj): self.vars.append((varname, typeobj)) def set_var(self, varname, yes): pass def backpatchable(self): self.backpatchables += 1 return Backpatchable(self, self.backpatchables) def resolve_backpatchable(self, i, dest): pass def discard_result(self): pass def decrement(self, lvalue, yes): pass def push_constant(self, val, yes): pass def intern(self, a_string): return 42 def fetch_var(self, varname, yes): pass def compare(self, lhs, relation, rhs, yes, no): assert relation in '>=', '>' class Backpatchable: def __init__(self, scope, i): self.scope = scope self.i = i def resolve(self, dest): self.scope.resolve_backpatchable(self.i, dest) class SubprocessError(Exception): pass def spawn(*args): rv = os.spawnv(os.P_WAIT, args[0], args) if rv: raise SubprocessError(args) def main(): my_program = Sequence([ Initialize(int, 'i', IntConst(10)), While(Comparison(VarRef('i'), '>=', IntConst(0)), Sequence([ ExpressionStatement( FunctionCall('printf', [StringConst("%d\n"), VarRef('i')])), Decrement('i'), Initialize(int, 'j', IntConst(1000000)), While(Comparison(VarRef('j'), '>', IntConst(0)), Decrement('j')), ])), ]) scope = AsmScope() my_program.compile(scope, scope.get_end_location(), scope.get_end_location()) out = 'tmp.s' with open(out, 'w') as outf: outf.write(''' .globl main main: ''') outf.write(scope.generate_assembly()) outf.write(''' mov $0, %rax ret ''') #spawn('/usr/bin/cc', out) if __name__ == '__main__': main()