changeset 334:6f4753202b9a

Added more recipes
author Windel Bouwman
date Thu, 13 Feb 2014 22:02:08 +0100
parents dcae6574c974
children 582a1aaa3983
files examples/c3/recipe.yaml examples/c3/startup_stm32f4.asm kernel/recipe.yaml python/asm.py python/asmnodes.py python/parserlib.py python/ppci/asmnodes.py python/ppci/assembler.py python/ppci/buildtasks.py python/ppci/c3/builder.py python/ppci/c3/codegenerator.py python/ppci/c3/parser.py python/ppci/codegen/codegen.py python/ppci/codegen/registerallocator.py python/ppci/common.py python/ppci/linker.py python/ppci/objectfile.py python/ppci/tasks.py python/ppci/transform.py python/target/arminstructions.py python/target/armtarget.py python/target/basetarget.py python/target/msp430.py python/yacc.py python/zcc.py test/testasm.py test/testmsp430asm.py test/testparserlib.py test/testzcc.py user/recipe.yaml
diffstat 30 files changed, 484 insertions(+), 495 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/c3/recipe.yaml	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,14 @@
+
+link:
+  inputs:
+    - assemble:
+       source: startup_stm32f4.asm
+       machine: arm
+    - compile:
+       sources: [burn2.c3]
+       includes: [stm32f4xx.c3]
+       machine: arm
+       output: burn.elf2
+  code: 0x08000000
+  data: 0x20000000
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/c3/startup_stm32f4.asm	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,6 @@
+
+
+DCD 0x20000678  ; Setup stack pointer
+DCD 0x08000009  ; Reset vector, jump to address 8
+B main          ; Branch to main (this is actually in the interrupt vector)
+
--- a/kernel/recipe.yaml	Sun Feb 09 15:27:57 2014 +0100
+++ b/kernel/recipe.yaml	Thu Feb 13 22:02:08 2014 +0100
@@ -1,13 +1,14 @@
 
 link:
   inputs:
-    compile:
+    - assemble:
+       source: ../examples/c3/startup_stm32f4.asm
+       machine: arm
+    - compile:
        sources: [memory.c3, kernel.c3, syscall.c3, process.c3, schedule.c3, arch_arm.c3]
        includes: []
        machine: arm
        output: kernel.elf2
-    assemble:
-       source: boot_arm.asm
   code: 0x8000
   data: 0x9000
 
--- a/python/asm.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/asm.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,205 +1,7 @@
 #!/usr/bin/env python3
 
-import re
 import argparse
-import pyyacc
-from ppci import Token, CompilerError, SourceLocation
-from target import Target, Label
-from asmnodes import ALabel, AInstruction, ABinop, AUnop, ASymbol, ANumber
-
-def tokenize(s):
-    """
-       Tokenizer, generates an iterator that
-       returns tokens!
-
-       This GREAT example was taken from python re doc page!
-    """
-    tok_spec = [
-       ('REAL', r'\d+\.\d+'),
-       ('HEXNUMBER', r'0x[\da-fA-F]+'),
-       ('NUMBER', r'\d+'),
-       ('ID', r'[A-Za-z][A-Za-z\d_]*'),
-       ('SKIP', r'[ \t]'),
-       ('LEESTEKEN', r':=|[\.,=:\-+*\[\]/\(\)]|>=|<=|<>|>|<|}|{'),
-       ('STRING', r"'.*?'"),
-       ('COMMENT', r";.*")
-    ]
-    tok_re = '|'.join('(?P<%s>%s)' % pair for pair in tok_spec)
-    gettok = re.compile(tok_re).match
-    line = 1
-    pos = line_start = 0
-    mo = gettok(s)
-    while mo is not None:
-       typ = mo.lastgroup
-       val = mo.group(typ)
-       if typ == 'NEWLINE':
-         line_start = pos
-         line += 1
-       elif typ != 'SKIP':
-         if typ == 'LEESTEKEN':
-           typ = val
-         elif typ == 'NUMBER':
-           val = int(val)
-         elif typ == 'HEXNUMBER':
-           val = int(val[2:], 16)
-           typ = 'NUMBER'
-         elif typ == 'REAL':
-           val = float(val)
-         elif typ == 'STRING':
-           val = val[1:-1]
-         col = mo.start() - line_start
-         loc = SourceLocation('', line, col, 0)   # TODO retrieve length?
-         yield Token(typ, val, loc)
-       pos = mo.end()
-       mo = gettok(s, pos)
-    if pos != len(s):
-       col = pos - line_start
-       loc = SourceLocation('', line, col, 0)
-       raise CompilerError('Unexpected character {0}'.format(s[pos]), loc)
-    yield Token('EOF', pyyacc.EOF)
-
-
-class Lexer:
-    def __init__(self, src):
-        self.tokens = tokenize(src)
-        self.curTok = self.tokens.__next__()
-
-    def next_token(self):
-        t = self.curTok
-        if t.typ != 'EOF':
-            self.curTok = self.tokens.__next__()
-        return t
-
-
-class Parser:
-    def __init__(self):
-        # Construct a parser given a grammar:
-        ident = lambda x: x   # Identity helper function
-        g = pyyacc.Grammar(['ID', 'NUMBER', ',', '[', ']', ':', '+', '-', '*', pyyacc.EPS, 'COMMENT', '{', '}',
-            pyyacc.EOF])
-        g.add_production('asmline', ['asmline2'])
-        g.add_production('asmline', ['asmline2', 'COMMENT'])
-        g.add_production('asmline2', ['label', 'instruction'])
-        g.add_production('asmline2', ['instruction'])
-        g.add_production('asmline2', ['label'])
-        g.add_production('asmline2', [])
-        g.add_production('label', ['ID', ':'], self.p_label)
-        #g.add_production('label', [])
-        g.add_production('instruction', ['opcode', 'operands'], self.p_ins_1)
-        g.add_production('instruction', ['opcode'], self.p_ins_2)
-        #g.add_production('instruction', [])
-        g.add_production('opcode', ['ID'], lambda x: x.val)
-        g.add_production('operands', ['operand'], self.p_operands_1)
-        g.add_production('operands', ['operands', ',', 'operand'], self.p_operands_2)
-        g.add_production('operand', ['expression'], ident)
-        g.add_production('operand', ['[', 'expression', ']'], self.p_mem_op)
-        g.add_production('operand', ['{', 'listitems', '}'], self.p_list_op)
-        g.add_production('listitems', ['expression'], self.p_listitems_1)
-        g.add_production('listitems', ['listitems', ',', 'expression'], self.p_listitems_2)
-        g.add_production('expression', ['term'], ident)
-        g.add_production('expression', ['expression', 'addop', 'term'], self.p_binop)
-        g.add_production('addop', ['-'], lambda x: x.val)
-        g.add_production('addop', ['+'], lambda x: x.val)
-        g.add_production('mulop', ['*'], lambda x: x.val)
-        g.add_production('term', ['factor'], ident)
-        g.add_production('term', ['term', 'mulop', 'factor'], self.p_binop)
-        g.add_production('factor', ['ID'], lambda name: ASymbol(name.val))
-        g.add_production('factor', ['NUMBER'], lambda num: ANumber(int(num.val)))
-        g.start_symbol = 'asmline'
-        self.p = g.genParser()
-
-    # Parser handlers:
-    def p_ins_1(self, opc, ops):
-        ins = AInstruction(opc, ops)
-        self.emit(ins)
-
-    def p_ins_2(self, opc):
-        self.p_ins_1(opc, [])
-
-    def p_operands_1(self, op1):
-        return [op1]
-
-    def p_operands_2(self, ops, comma, op2):
-        assert type(ops) is list
-        ops.append(op2)
-        return ops
-
-    def p_listitems_1(self, li1):
-        return [li1]
-
-    def p_listitems_2(self, lis, comma, li2):
-        assert type(lis) is list
-        lis.append(li2)
-        return lis
-
-    def p_list_op(self, brace_open, lst, brace_close):
-        return AUnop('{}', lst)
-
-    def p_mem_op(self, brace_open, exp, brace_close):
-        return AUnop('[]', exp)
-
-    def p_label(self, lname, cn):
-        lab = ALabel(lname.val)
-        self.emit(lab)
-
-    def p_binop(self, exp1, op, exp2):
-        return ABinop(op, exp1, exp2)
-
-    def parse(self, lexer, emitter):
-        self.emit = emitter
-        self.p.parse(lexer)
-
-# Pre construct parser to save time:
-asmParser = Parser()
-
-class Assembler:
-    def __init__(self, target=None, stream=None):
-        self.target = target
-        self.stream = stream
-        self.restart()
-        self.p = asmParser
-
-    # Top level interface:
-    def restart(self):
-        self.stack = []
-
-    def emit(self, a):
-        """ Emit a parsed instruction """
-        self.stack.append(a)
-
-    def parse_line(self, line):
-        """ Parse line into asm AST """
-        tokens = Lexer(line)
-        self.p.parse(tokens, self.emit)
-
-    def assemble(self, asmsrc):
-        """ Assemble this source snippet """
-        for line in asmsrc.split('\n'):
-            self.assemble_line(line)
-
-    def assemble_line(self, line):
-        """
-            Assemble a single source line.
-            Do not take newlines into account
-        """
-        self.parse_line(line)
-        self.assemble_aast()
-
-    def assemble_aast(self):
-        """ Assemble a parsed asm line """
-        if not self.target:
-            raise CompilerError('Cannot assemble without target')
-        while self.stack:
-            vi = self.stack.pop(0)
-            if type(vi) is AInstruction:
-                mi = self.target.mapInstruction(vi)
-            elif type(vi) is ALabel:
-                mi = Label(vi.name)
-            else:
-                raise NotImplementedError('{}'.format(vi))
-            if self.stream:
-                self.stream.emit(mi)
-
+from ppci.assembler import Assembler
 
 if __name__ == '__main__':
     # When run as main file, try to grab command line arguments:
--- a/python/asmnodes.py	Sun Feb 09 15:27:57 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-
-""" Assembler AST nodes """
-
-class ANode:
-    def __eq__(self, other):
-        return self.__repr__() == other.__repr__()
-
-class ALabel(ANode):
-    def __init__(self, name):
-        self.name = name
-    def __repr__(self):
-        return '{0}:'.format(self.name)
-
-
-class AInstruction(ANode):
-    def __init__(self, mnemonic, operands):
-        self.mnemonic = mnemonic
-        self.operands = operands
-    def __repr__(self):
-        ops = ', '.join(map(str, self.operands))
-        return '{0} {1}'.format(self.mnemonic, ops)
-
-class AExpression(ANode):
-    def __add__(self, other):
-        assert isinstance(other, AExpression)
-        return ABinop('+', self, other)
-    def __mul__(self, other):
-        assert isinstance(other, AExpression)
-        return ABinop('*', self, other)
-
-class ABinop(AExpression):
-    def __init__(self, op, arg1, arg2):
-        self.op = op
-        self.arg1 = arg1
-        self.arg2 = arg2
-    def __repr__(self):
-        return '{0} {1} {2}'.format(self.op, self.arg1, self.arg2)
-
-class AUnop(AExpression):
-    def __init__(self, operation, arg):
-        self.operation = operation
-        self.arg = arg
-    def __repr__(self):
-        return '{0} {1}'.format(self.operation, self.arg)
-
-class ASymbol(AExpression):
-    def __init__(self, name):
-        self.name = name
-    def __repr__(self):
-        return self.name
-
-class ANumber(AExpression):
-    def __init__(self, n):
-        assert type(n) is int
-        self.n = n
-        self.number = n
-    def __repr__(self):
-        return '{0}'.format(self.n)
-
--- a/python/parserlib.py	Sun Feb 09 15:27:57 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-
-
-
-class Token:
-    pass
-
-# Base functions:
-class Pbase:
-    def __init__(self):
-        self.pa = None
-    def parse(self, txt):
-        r = self.do(txt)
-        if r:
-            match, rest = r
-            # Apply action:
-            if self.ParseAction:
-                match = self.ParseAction(match)
-            return match, rest
-        else:
-            # TODO: fail in some way
-            pass
-    def getParseAction(self):
-        return self.pa
-    def setParseAction(self, pa):
-        self.pa = pa
-
-    ParseAction = property(getParseAction, setParseAction)
-
-# basic elements:
-
-class Literal(Pbase):
-    def __init__(self, s):
-        super().__init__()
-        self.pat = s
-    def do(self, txt):
-        if txt.startswith(self.pat):
-            return self.pat, txt[len(self.pat):]
-
-class Or(Pbase):
-    def __init__(self, options):
-        super().__init__()
-        self.options = options
-    def do(self, txt):
-        for option in self.options:
-            r = option.parse(txt)
-            if r:
-                return r
-
-class And:
-    def __init__(self, options):
-        self.options = options
-
-class Sequence(Pbase):
-    def __init__(self, seq):
-        super().__init__()
-        self.seq = seq
-    def do(self, txt):
-        results = []
-        for thung in self.seq:
-            r = thung.parse(txt)
-            if r:
-                res, txt = r
-                results.append(res)
-            else:
-                return
-        return results, txt
-
-class Optional(Pbase):
-    def __init__(self, thung):
-        super().__init__()
-        self.thung = thung
-    def do(self, txt):
-        r = self.thung.do(txt)
-        if r:
-            return r
-        return (0, txt)
-
-# Contraptions of basic blocks:
-
-def OneOrMore():
-    def __init__(self, thingy):
-        pass
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/ppci/asmnodes.py	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,59 @@
+
+""" Assembler AST nodes """
+
+class ANode:
+    def __eq__(self, other):
+        return self.__repr__() == other.__repr__()
+
+class ALabel(ANode):
+    def __init__(self, name):
+        self.name = name
+    def __repr__(self):
+        return '{0}:'.format(self.name)
+
+
+class AInstruction(ANode):
+    def __init__(self, mnemonic, operands):
+        self.mnemonic = mnemonic
+        self.operands = operands
+    def __repr__(self):
+        ops = ', '.join(map(str, self.operands))
+        return '{0} {1}'.format(self.mnemonic, ops)
+
+class AExpression(ANode):
+    def __add__(self, other):
+        assert isinstance(other, AExpression)
+        return ABinop('+', self, other)
+    def __mul__(self, other):
+        assert isinstance(other, AExpression)
+        return ABinop('*', self, other)
+
+class ABinop(AExpression):
+    def __init__(self, op, arg1, arg2):
+        self.op = op
+        self.arg1 = arg1
+        self.arg2 = arg2
+    def __repr__(self):
+        return '{0} {1} {2}'.format(self.op, self.arg1, self.arg2)
+
+class AUnop(AExpression):
+    def __init__(self, operation, arg):
+        self.operation = operation
+        self.arg = arg
+    def __repr__(self):
+        return '{0} {1}'.format(self.operation, self.arg)
+
+class ASymbol(AExpression):
+    def __init__(self, name):
+        self.name = name
+    def __repr__(self):
+        return self.name
+
+class ANumber(AExpression):
+    def __init__(self, n):
+        assert type(n) is int
+        self.n = n
+        self.number = n
+    def __repr__(self):
+        return '{0}'.format(self.n)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/ppci/assembler.py	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,203 @@
+
+import re
+import pyyacc
+from . import Token, CompilerError, SourceLocation
+from target import Target, Label
+from .asmnodes import ALabel, AInstruction, ABinop, AUnop, ASymbol, ANumber
+
+def tokenize(s):
+    """
+       Tokenizer, generates an iterator that
+       returns tokens!
+
+       This GREAT example was taken from python re doc page!
+    """
+    tok_spec = [
+       ('REAL', r'\d+\.\d+'),
+       ('HEXNUMBER', r'0x[\da-fA-F]+'),
+       ('NUMBER', r'\d+'),
+       ('ID', r'[A-Za-z][A-Za-z\d_]*'),
+       ('SKIP', r'[ \t]'),
+       ('LEESTEKEN', r':=|[\.,=:\-+*\[\]/\(\)]|>=|<=|<>|>|<|}|{'),
+       ('STRING', r"'.*?'"),
+       ('COMMENT', r";.*")
+    ]
+    tok_re = '|'.join('(?P<%s>%s)' % pair for pair in tok_spec)
+    gettok = re.compile(tok_re).match
+    line = 1
+    pos = line_start = 0
+    mo = gettok(s)
+    while mo is not None:
+       typ = mo.lastgroup
+       val = mo.group(typ)
+       if typ == 'NEWLINE':
+         line_start = pos
+         line += 1
+       elif typ != 'SKIP':
+         if typ == 'LEESTEKEN':
+           typ = val
+         elif typ == 'NUMBER':
+           val = int(val)
+         elif typ == 'HEXNUMBER':
+           val = int(val[2:], 16)
+           typ = 'NUMBER'
+         elif typ == 'REAL':
+           val = float(val)
+         elif typ == 'STRING':
+           val = val[1:-1]
+         col = mo.start() - line_start
+         loc = SourceLocation('', line, col, 0)   # TODO retrieve length?
+         yield Token(typ, val, loc)
+       pos = mo.end()
+       mo = gettok(s, pos)
+    if pos != len(s):
+       col = pos - line_start
+       loc = SourceLocation('', line, col, 0)
+       raise CompilerError('Unexpected character {0}'.format(s[pos]), loc)
+    yield Token('EOF', pyyacc.EOF)
+
+
+class Lexer:
+    def __init__(self, src):
+        self.tokens = tokenize(src)
+        self.curTok = self.tokens.__next__()
+
+    def next_token(self):
+        t = self.curTok
+        if t.typ != 'EOF':
+            self.curTok = self.tokens.__next__()
+        return t
+
+
+class Parser:
+    def __init__(self):
+        # Construct a parser given a grammar:
+        ident = lambda x: x   # Identity helper function
+        g = pyyacc.Grammar(['ID', 'NUMBER', ',', '[', ']', ':', '+', '-', '*', pyyacc.EPS, 'COMMENT', '{', '}',
+            pyyacc.EOF])
+        g.add_production('asmline', ['asmline2'])
+        g.add_production('asmline', ['asmline2', 'COMMENT'])
+        g.add_production('asmline2', ['label', 'instruction'])
+        g.add_production('asmline2', ['instruction'])
+        g.add_production('asmline2', ['label'])
+        g.add_production('asmline2', [])
+        g.add_production('label', ['ID', ':'], self.p_label)
+        #g.add_production('label', [])
+        g.add_production('instruction', ['opcode', 'operands'], self.p_ins_1)
+        g.add_production('instruction', ['opcode'], self.p_ins_2)
+        #g.add_production('instruction', [])
+        g.add_production('opcode', ['ID'], lambda x: x.val)
+        g.add_production('operands', ['operand'], self.p_operands_1)
+        g.add_production('operands', ['operands', ',', 'operand'], self.p_operands_2)
+        g.add_production('operand', ['expression'], ident)
+        g.add_production('operand', ['[', 'expression', ']'], self.p_mem_op)
+        g.add_production('operand', ['{', 'listitems', '}'], self.p_list_op)
+        g.add_production('listitems', ['expression'], self.p_listitems_1)
+        g.add_production('listitems', ['listitems', ',', 'expression'], self.p_listitems_2)
+        g.add_production('expression', ['term'], ident)
+        g.add_production('expression', ['expression', 'addop', 'term'], self.p_binop)
+        g.add_production('addop', ['-'], lambda x: x.val)
+        g.add_production('addop', ['+'], lambda x: x.val)
+        g.add_production('mulop', ['*'], lambda x: x.val)
+        g.add_production('term', ['factor'], ident)
+        g.add_production('term', ['term', 'mulop', 'factor'], self.p_binop)
+        g.add_production('factor', ['ID'], lambda name: ASymbol(name.val))
+        g.add_production('factor', ['NUMBER'], lambda num: ANumber(int(num.val)))
+        g.start_symbol = 'asmline'
+        self.p = g.genParser()
+
+    # Parser handlers:
+    def p_ins_1(self, opc, ops):
+        ins = AInstruction(opc, ops)
+        self.emit(ins)
+
+    def p_ins_2(self, opc):
+        self.p_ins_1(opc, [])
+
+    def p_operands_1(self, op1):
+        return [op1]
+
+    def p_operands_2(self, ops, comma, op2):
+        assert type(ops) is list
+        ops.append(op2)
+        return ops
+
+    def p_listitems_1(self, li1):
+        return [li1]
+
+    def p_listitems_2(self, lis, comma, li2):
+        assert type(lis) is list
+        lis.append(li2)
+        return lis
+
+    def p_list_op(self, brace_open, lst, brace_close):
+        return AUnop('{}', lst)
+
+    def p_mem_op(self, brace_open, exp, brace_close):
+        return AUnop('[]', exp)
+
+    def p_label(self, lname, cn):
+        lab = ALabel(lname.val)
+        self.emit(lab)
+
+    def p_binop(self, exp1, op, exp2):
+        return ABinop(op, exp1, exp2)
+
+    def parse(self, lexer, emitter):
+        self.emit = emitter
+        self.p.parse(lexer)
+
+# Pre construct parser to save time:
+asmParser = Parser()
+
+class Assembler:
+    def __init__(self, target=None, stream=None):
+        self.target = target
+        self.stream = stream
+        self.restart()
+        self.p = asmParser
+
+    # Top level interface:
+    def restart(self):
+        self.stack = []
+
+    def emit(self, a):
+        """ Emit a parsed instruction """
+        self.stack.append(a)
+
+    def parse_line(self, line):
+        """ Parse line into asm AST """
+        tokens = Lexer(line)
+        self.p.parse(tokens, self.emit)
+
+    def assemble(self, asmsrc):
+        """ Assemble this source snippet """
+        if type(asmsrc) is not str:
+            asmsrc2 = asmsrc.read()
+            asmsrc.close()
+            asmsrc = asmsrc2
+        for line in asmsrc.split('\n'):
+            self.assemble_line(line)
+
+    def assemble_line(self, line):
+        """
+            Assemble a single source line.
+            Do not take newlines into account
+        """
+        self.parse_line(line)
+        self.assemble_aast()
+
+    def assemble_aast(self):
+        """ Assemble a parsed asm line """
+        if not self.target:
+            raise CompilerError('Cannot assemble without target')
+        while self.stack:
+            vi = self.stack.pop(0)
+            if type(vi) is AInstruction:
+                mi = self.target.mapInstruction(vi)
+            elif type(vi) is ALabel:
+                mi = Label(vi.name)
+            else:
+                raise NotImplementedError('{}'.format(vi))
+            if self.stream:
+                self.stream.emit(mi)
--- a/python/ppci/buildtasks.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/buildtasks.py	Thu Feb 13 22:02:08 2014 +0100
@@ -12,24 +12,33 @@
 from .transform import CleanPass, RemoveAddZero
 from .tasks import Task
 from . import DiagnosticsManager
-
+from .assembler import Assembler
+from .objectfile import ObjectFile
+from .linker import Linker
+import outstream
 
 class BuildTask(Task):
+    """ Base task for all kind of weird build tasks """
     def __init__(self, name):
         super().__init__(name)
         self.logger = logging.getLogger('buildtask')
 
 
 class Assemble(BuildTask):
-    def __init__(self):
+    """ Task that can runs the assembler over the source and enters the 
+        output into an object file """
+    def __init__(self, source, target, output_object):
         super().__init__('Assemble')
+        self.source = source
+        self.assembler = Assembler(target=target)
+        self.output = output_object
 
     def run(self):
-        pass
+        self.assembler.assemble(self.source)
 
 
 class Compile(BuildTask):
-    """ Task that compiles source to some target """
+    """ Task that compiles C3 source for some target into an object file """
     def __init__(self, sources, includes, target, output_object):
         super().__init__('Compile')
         self.sources = sources
@@ -38,7 +47,7 @@
         self.output = output_object
 
     def run(self):
-        self.logger.info('Zcc started {}'.format(self.sources))
+        self.logger.debug('Compile started')
         diag = DiagnosticsManager()
         c3b = Builder(diag, self.target)
         cg = CodeGenerator(self.target)
@@ -48,7 +57,7 @@
                 return
 
             d = {'ircode':ircode}
-            self.logger.info('Verifying code {}'.format(ircode), extra=d)
+            self.logger.debug('Verifying code {}'.format(ircode), extra=d)
             Verifier().verify(ircode)
 
             # Optimization passes:
@@ -61,23 +70,31 @@
 
             # Code generation:
             d = {'ircode':ircode}
-            self.logger.info('Starting code generation for {}'.format(ircode), extra=d)
-            cg.generate(ircode, self.output)
+            self.logger.debug('Starting code generation for {}'.format(ircode), extra=d)
+            o = outstream.TextOutputStream()
+            cg.generate(ircode, o)
 
-        # TODO: fixup references, do this in another way?
-        self.output.backpatch()
         if not c3b.ok:
             diag.printErrors()
             raise TaskError('Compile errors')
 
 
 class Link(BuildTask):
+    """ Link together a collection of object files """
     def __init__(self, objects, output_file):
         super().__init__('Link')
+        self.objects = objects
+        self.linker = Linker()
+        self.duration = 0.1337
+
+    def run(self):
+        print('LNK')
+        print('LNK', self.objects)
+        print('LNK')
+        print('LNK')
+        self.linker.link(self.objects)
 
 
 class ObjCopy(Task):
     pass
 
-
-
--- a/python/ppci/c3/builder.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/c3/builder.py	Thu Feb 13 22:02:08 2014 +0100
@@ -31,7 +31,7 @@
 
     """ Scope is attached to the correct modules. """
     def addScope(self, pkg):
-        self.logger.info('Adding scoping to package {}'.format(pkg.name))
+        self.logger.debug('Adding scoping to package {}'.format(pkg.name))
         self.pkg = pkg
         # Prepare top level scope and set scope to all objects:
         self.scopeStack = [self.topScope]
@@ -40,7 +40,7 @@
         self.visit(pkg, self.enterScope, self.quitScope)
         assert len(self.scopeStack) == 2
 
-        self.logger.info('Resolving imports for package {}'.format(pkg.name))
+        self.logger.debug('Resolving imports for package {}'.format(pkg.name))
         # Handle imports:
         for i in pkg.imports:
             if i not in self.packages:
@@ -95,7 +95,7 @@
 
     def build(self, srcs, imps=[]):
         """ Create IR-code from sources """
-        self.logger.info('Building {} source files'.format(len(srcs)))
+        self.logger.debug('Building {} source files'.format(len(srcs)))
         iter(srcs)  # Check if srcs are iterable
         iter(imps)
         self.ok = True
--- a/python/ppci/c3/codegenerator.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/c3/codegenerator.py	Thu Feb 13 22:02:08 2014 +0100
@@ -34,7 +34,7 @@
         self.pkg = pkg
         self.intType = pkg.scope['int']
         self.boolType = pkg.scope['bool']
-        self.logger.info('Generating ir-code for {}'.format(pkg.name), extra={'c3_ast':pkg})
+        self.logger.debug('Generating ir-code for {}'.format(pkg.name), extra={'c3_ast':pkg})
         self.varMap = {}    # Maps variables to storage locations.
         self.funcMap = {}
         self.m = ir.Module(pkg.name)
--- a/python/ppci/c3/parser.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/c3/parser.py	Thu Feb 13 22:02:08 2014 +0100
@@ -19,7 +19,7 @@
         self.diag = diag
 
     def parseSource(self, tokens):
-        self.logger.info('Parsing source')
+        self.logger.debug('Parsing source')
         self.tokens = tokens
         self.token = self.tokens.__next__()
         try:
--- a/python/ppci/codegen/codegen.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/codegen/codegen.py	Thu Feb 13 22:02:08 2014 +0100
@@ -21,20 +21,20 @@
 
     def generateFunc(self, irfunc, outs):
         """ Generate code for one function into a frame """
-        self.logger.info('Generating code for {}'.format(irfunc.name))
+        self.logger.debug('Generating code for {}'.format(irfunc.name))
         # Create a frame for this function:
         frame = self.target.FrameClass(irfunc.name)
 
         # Canonicalize the intermediate language:
         canonicalize(irfunc, frame)
-        self.logger.info('after canonicalize', extra={'irfunc': irfunc})
+        self.logger.debug('after canonicalize', extra={'irfunc': irfunc})
         self.verifier.verify_function(irfunc)
         self.ins_sel.munchFunction(irfunc, frame)
-        self.logger.info('Selected instructions', extra={'ppci_frame': frame})
+        self.logger.debug('Selected instructions', extra={'ppci_frame': frame})
 
         # Do register allocation:
         self.ra.allocFrame(frame)
-        self.logger.info('Registers allocated, now adding final glue')
+        self.logger.debug('Registers allocated, now adding final glue')
         # TODO: Peep-hole here?
 
         # Add label and return and stack adjustment:
@@ -43,7 +43,7 @@
         # Materialize the register allocated instructions into a stream of
         # real instructions.
         frame.lower_to(outs)
-        self.logger.info('Instructions materialized')
+        self.logger.debug('Instructions materialized')
         return frame
 
     def generate(self, ircode, outs):
--- a/python/ppci/codegen/registerallocator.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/codegen/registerallocator.py	Thu Feb 13 22:02:08 2014 +0100
@@ -41,9 +41,9 @@
     def Build(self):
         """ 1. Construct interference graph from instruction list """
         self.f.cfg = FlowGraph(self.f.instructions)
-        self.logger.info('Constructed flowgraph', extra={'ra_cfg':self.f.cfg})
+        self.logger.debug('Constructed flowgraph', extra={'ra_cfg':self.f.cfg})
         self.f.ig = InterferenceGraph(self.f.cfg)
-        self.logger.info('Constructed interferencegraph', extra={'ra_ig':self.f.ig})
+        self.logger.debug('Constructed interferencegraph', extra={'ra_ig':self.f.ig})
 
         self.Node = self.f.ig.getNode
 
--- a/python/ppci/common.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/common.py	Thu Feb 13 22:02:08 2014 +0100
@@ -58,7 +58,7 @@
         self.logger = logging.getLogger('diagnostics')
 
     def addSource(self, name, src):
-        self.logger.info('Adding source {}'.format(name))
+        self.logger.debug('Adding source {}'.format(name))
         self.sources[name] = src
 
     def addDiag(self, d):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/ppci/linker.py	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,19 @@
+
+from .objectfile import ObjectFile
+
+class Linker:
+    def set_symbol(self, sym):
+        self.dst.add_symbol(sym.name, sym.value)
+
+    def link(self, objs):
+        self.dst = ObjectFile()
+        # First copy all sections into output sections:
+        for iobj in objs:
+            for sym in iobj.symbols:
+                print(sym)
+                self.set_symbol(sym)
+        # Do relocations:
+        # TODO
+        # Check that there are no more unresolved symbols:
+        # TODO
+        return self.dst
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/python/ppci/objectfile.py	Thu Feb 13 22:02:08 2014 +0100
@@ -0,0 +1,32 @@
+
+"""
+Object files are used to store assembled code. Information contained
+is code, symbol table and relocation information.
+"""
+
+class Symbol:
+    def __init__(self, name, value):
+        self.name = name
+        self.value = value
+
+
+class Relocation:
+    def __init__(self, typ):
+        pass
+
+
+class Section:
+    def __init__(self, name):
+        self.name = name
+
+
+class ObjectFile:
+    def __init__(self):
+        self.symbols = {}
+        self.sections = {}
+        self.relocations = []
+
+    def add_symbol(self, name, value):
+        sym = Symbol(name, value)
+        self.symbols[name] = sym
+        return sym
--- a/python/ppci/tasks.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/tasks.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,3 +1,5 @@
+
+import logging
 
 class TaskError(Exception):
     pass
@@ -10,6 +12,7 @@
         self.subtasks = []
         self.completed = False
         self.dependencies = []
+        self.duration = 1
 
     def run(self):
         raise NotImplementedError("Implement this abstract method!")
@@ -34,22 +37,34 @@
 class TaskRunner:
     """ Basic task runner that can run some tasks in sequence """
     def __init__(self):
+        self.logger = logging.getLogger('taskrunner')
         self.task_list = []
 
     def add_task(self, task):
         self.task_list.append(task)
 
+    @property
+    def total_duration(self):
+        return sum(t.duration for t in self.task_list)
+
     def run_tasks(self):
+        passed_time = 0.0
+        total_time = self.total_duration
         try:
             for t in self.task_list:
-                #print('Running {}'.format(t.name))
+                self.report_progress(passed_time / total_time, t.name)
                 t.fire()
+                passed_time += t.duration
         except TaskError as e:
             print('Error: {}'.format(e))
             return 1
+        self.report_progress(1, 'OK')
         return 0
 
     def display(self):
         """ Display task how they would be run """
         for task in self.task_list:
             print(task)
+
+    def report_progress(self, percentage, text):
+        self.logger.info('[{:3.1%}] {}'.format(percentage, text))
--- a/python/ppci/transform.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/ppci/transform.py	Thu Feb 13 22:02:08 2014 +0100
@@ -12,7 +12,7 @@
 
     def run(self, ir):
         """ Main entry point for the pass """
-        self.logger.info('Running pass {}'.format(type(self)))
+        self.logger.debug('Running pass {}'.format(self.__class__.__name__))
         self.prepare()
         for f in ir.Functions:
             self.onFunction(f)
@@ -157,7 +157,7 @@
 
     def glue_blocks(self, block1, block2, f):
         """ Glue two blocks together into the first block """
-        self.logger.info('Glueing {} and {}'.format(block1, block2))
+        self.logger.debug('Glueing {} and {}'.format(block1, block2))
 
         # Remove the last jump:
         block1.removeInstruction(block1.LastInstruction)
--- a/python/target/arminstructions.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/target/arminstructions.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,5 +1,5 @@
 import struct
-from asmnodes import ASymbol, AInstruction, ANumber, AUnop, ABinop
+from ppci.asmnodes import ASymbol, AInstruction, ANumber, AUnop, ABinop
 from .basetarget import Register, Instruction, Target, Label, LabelRef
 from .basetarget import Imm32, Imm8, Imm7, Imm3
 
--- a/python/target/armtarget.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/target/armtarget.py	Thu Feb 13 22:02:08 2014 +0100
@@ -25,12 +25,3 @@
         self.check()
         self.ins_sel = ArmInstructionSelector()
         self.FrameClass = ArmFrame
-
-    def startCode(self, outs):
-        """ Emit some startup code in the output stream """
-        outs.selectSection('code')
-        # assembly glue to make it work:
-        # TODO: this must be in source code, not in compiler
-        outs.emit(Dcd(Imm32(0x20000678)))   # initial SP
-        outs.emit(Dcd(Imm32(0x08000009)))   # reset vector
-        outs.emit(B(LabelRef('main')))
--- a/python/target/basetarget.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/target/basetarget.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,4 +1,4 @@
-from asmnodes import ASymbol, AInstruction, ANumber
+from ppci.asmnodes import ASymbol, AInstruction, ANumber
 from ppci import CompilerError
 
 """
--- a/python/target/msp430.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/target/msp430.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,5 +1,5 @@
 from .basetarget import Register, Instruction, Target
-from asmnodes import ASymbol, ANumber
+from ppci.asmnodes import ASymbol, ANumber
 from ppci import CompilerError
 import struct
 import types
--- a/python/yacc.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/yacc.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 
 """
-Parser generator utility. This script can generate a python script from a 
+Parser generator utility. This script can generate a python script from a
 grammar description.
 
 Invoke the script on a grammar specification file:
@@ -42,7 +42,7 @@
 import types
 import io
 import logging
-from pyyacc import Grammar, print_grammar
+from pyyacc import Grammar
 
 
 class XaccLexer:
@@ -244,15 +244,15 @@
 
         # Fill goto table:
         self.print('        self.goto_table = {}')
-        for gt in self.goto_table:
-            to = self.goto_table[gt]
-            self.print('        self.goto_table[{}] = {}'.format(gt, to))
+        for state_number in self.goto_table:
+            to = self.goto_table[state_number]
+            self.print('        self.goto_table[{}] = {}'.format(state_number, to))
         self.print('')
 
         # Generate a function for each action:
         for rule in self.grammar.productions:
-            M = len(rule.symbols)
-            args = ', '.join('arg{}'.format(n + 1) for n in range(M))
+            num_symbols = len(rule.symbols)
+            args = ', '.join('arg{}'.format(n + 1) for n in range(num_symbols))
             self.print('    def {}(self, {}):'.format(rule.f_name, args))
             if rule.f == None:
                 semantics = 'pass'
@@ -260,7 +260,7 @@
                 semantics = str(rule.f)
                 if semantics.strip() == '':
                     semantics = 'pass'
-            for n in range(M):
+            for n in range(num_symbols):
                 semantics = semantics.replace('${}'.format(n + 1), 'arg{}'.format(n + 1))
             self.print('        {}'.format(semantics))
             self.print('')
@@ -298,7 +298,6 @@
     # Sequence source through the generator parts:
     lexer.feed(src)
     grammar = parser.parse_grammar()
-    # TODO: optionize this: print_grammar(grammar)
     generator.generate(grammar, parser.headers, args.output)
 
 
--- a/python/zcc.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/python/zcc.py	Thu Feb 13 22:02:08 2014 +0100
@@ -6,10 +6,10 @@
 import logging
 import yaml
 
-from ppci.buildtasks import Compile
+from ppci.buildtasks import Compile, Assemble, Link
 from ppci.tasks import TaskRunner
 from ppci.report import RstFormatter
-import outstream
+from ppci.objectfile import ObjectFile
 from target.target_list import target_list
 import ppci
 
@@ -29,7 +29,7 @@
     parser = argparse.ArgumentParser(description='lcfos Compiler')
 
     parser.add_argument('--log', help='Log level (INFO,DEBUG,[WARN])',
-                        type=logLevel, default='WARN')
+                        type=logLevel, default='INFO')
     parser.add_argument('--display-build-steps', action='store_true')
     sub_parsers = parser.add_subparsers(title='commands',
          description='possible commands', dest='command')
@@ -52,12 +52,21 @@
 
 
 class RecipeLoader:
+    """ Loads a recipe into a runner from a dictionary or file """
+    def __init__(self):
+        self.directive_handlers = {}
+        for a in dir(self):
+            if a.startswith('handle_'):
+                f = getattr(self, a)
+                self.directive_handlers[a[7:]] = f
+
     def load_file(self, recipe_file, runner):
         """ Loads a recipe dictionary into a task runner """
         self.recipe_dir = os.path.abspath(os.path.dirname(recipe_file))
         with open(recipe_file, 'r') as f:
             recipe = yaml.load(f)
-        self.load_dict(recipe, runner)
+        self.runner = runner
+        self.load_dict(recipe)
 
     def relpath(self, filename):
         return os.path.join(self.recipe_dir, filename)
@@ -65,24 +74,38 @@
     def openfile(self, filename):
         return open(self.relpath(filename), 'r')
 
-    def load_dict(self, recipe, runner):
+    def handle_compile(self, value):
+        sources = [self.openfile(s) for s in value['sources']]
+        includes = [self.openfile(i) for i in value['includes']]
+        target = targets[value['machine']]
+        output = ObjectFile()
+        task = Compile(sources, includes, target, output)
+        self.runner.add_task(task)
+        return task
+
+    def handle_assemble(self, value):
+        asm_src = self.openfile(value['source'])
+        target = targets[value['machine']]
+        output = ObjectFile()
+        task = Assemble(asm_src, target, output)
+        self.runner.add_task(task)
+        return task
+
+    def handle_link(self, value):
+        inputs = value['inputs']
+        objs = []
+        for i in inputs:
+            task = self.load_dict(i)
+            objs.append(task.output)
+        self.runner.add_task(Link(objs, None))
+
+    def handle_apps(self, value):
+        for a in value:
+            self.load_dict(a)
+
+    def load_dict(self, recipe):
         for command, value in recipe.items():
-            if command == 'compile':
-                sources = [self.openfile(s) for s in value['sources']]
-                includes = [self.openfile(i) for i in value['includes']]
-                target = targets[value['machine']]
-                output = outstream.TextOutputStream()
-                runner.add_task(Compile(sources, includes, target, output))
-            elif command == 'link':
-                self.load_dict(value['inputs'], runner)
-                #runner.add_task(Link())
-            elif command == 'assemble':
-                pass
-            elif command == 'apps':
-                for a in value:
-                    self.load_dict(a, runner)
-            else:
-                raise NotImplementedError(command)
+            return self.directive_handlers[command](value)
 
 
 def main(args):
@@ -96,8 +119,8 @@
     runner = TaskRunner()
     if args.command == 'compile':
         tg = targets[args.target]
-        outs = outstream.TextOutputStream()
-        runner.add_task(Compile(args.source, args.imp, tg, outs))
+        output = ObjectFile()
+        runner.add_task(Compile(args.source, args.imp, tg, output))
     elif args.command == 'recipe':
         recipe_loader = RecipeLoader()
         recipe_loader.load_file(args.recipe_file, runner)
--- a/test/testasm.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/test/testasm.py	Thu Feb 13 22:02:08 2014 +0100
@@ -2,8 +2,8 @@
 
 import unittest, cProfile
 from ppci import CompilerError
-from asmnodes import AInstruction, ABinop, AUnop, ASymbol, ALabel, ANumber
-from asm import tokenize, Assembler
+from ppci.asmnodes import AInstruction, ABinop, AUnop, ASymbol, ALabel, ANumber
+from ppci.assembler import tokenize, Assembler
 import outstream
 from target import Label
 
--- a/test/testmsp430asm.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/test/testmsp430asm.py	Thu Feb 13 22:02:08 2014 +0100
@@ -1,8 +1,8 @@
 #!/usr/bin/python
 
 import unittest
-from asmnodes import AInstruction, ABinop, AUnop, ASymbol, ALabel, ANumber
-from asm import tokenize, Assembler
+from ppci.asmnodes import AInstruction, ABinop, AUnop, ASymbol, ALabel, ANumber
+from ppci.assembler import tokenize, Assembler
 import outstream
 from target import Label
 from target.target_list import msp430target
--- a/test/testparserlib.py	Sun Feb 09 15:27:57 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-import unittest
-from parserlib import OneOrMore, Literal, Or, Sequence, Optional
-
-class ParserCombinatorTestCase(unittest.TestCase):
-    def test1(self):
-        #p = Parser()
-        # parse and interpret:
-        n40 = Literal('40')
-        plus = Literal('+')
-        n2 = Literal('2')
-        n40.ParseAction = int
-        plus.ParseAction = replaceWith(0)
-        n2.ParseAction = int
-        p = Sequence([n40,plus,n2])
-        p.ParseAction = wordsum
-
-        result = p.parse('40+2')
-        self.assertEqual(42, result[0])
-
-def replaceWith(s):
-    def _repFunc(*args):
-        return s
-    return _repFunc
-
-wordsum = lambda t: sum(t)
-
-class WordToNumTestCase(unittest.TestCase):
-    def setUp(self):
-        numWords = OneOrMore()
-        def makeLit(s, val):
-            ret = Literal(s)
-            ret.ParseAction = replaceWith(val)
-            return ret
-        unitDefs = [('zero', 0), ('three', 3), ('one', 1)]
-        units = Or( [makeLit(s, v) for s, v in unitDefs] )
-        tensDefs = [('twenty', 20)]
-        tens = Or( [makeLit(s, v) for s, v in tensDefs] )
-
-        numPart = Sequence([Optional(tens), units])
-        numPart.ParseAction = wordsum
-        self.p = numPart
-
-    def check(self, i, o):
-        result = self.p.parse(i)[0]
-        self.assertEqual(o, result)
-
-    def test0(self):
-        self.check('zero', 0)
-        
-    def test23(self):
-        self.check('twentythree', 23)
-
-    @unittest.skip
-    def test321(self):
-        # TODO
-        self.check('three hundred and twenty one', 321)
-        
-
-if __name__ == '__main__':
-    unittest.main()
--- a/test/testzcc.py	Sun Feb 09 15:27:57 2014 +0100
+++ b/test/testzcc.py	Thu Feb 13 22:02:08 2014 +0100
@@ -61,6 +61,10 @@
     def testBurn2(self):
         self.do(['burn2.c3'], ['stm32f4xx.c3'])
 
+    def testBurn2_recipe(self):
+        recipe = os.path.join(testdir, '..', 'examples', 'c3', 'recipe.yaml')
+        self.buildRecipe(recipe)
+
     #@unittest.skip('s')
     def testBurn2WithLogging(self):
         self.do(['burn2.c3'], ['stm32f4xx.c3'], extra_args=['--report', 'x.rst'])
--- a/user/recipe.yaml	Sun Feb 09 15:27:57 2014 +0100
+++ b/user/recipe.yaml	Thu Feb 13 22:02:08 2014 +0100
@@ -1,19 +1,25 @@
 
 apps:
-  - compile:
-       sources: [lib.c3, ipc.c3, hello.c3]
-       includes: []
-       machine: arm
-       output: kernel.elf2
+ - link:
+    inputs:
+     - compile:
+        sources: [lib.c3, ipc.c3, hello.c3]
+        includes: []
+        machine: arm
+        output: kernel.elf2
 
-  - compile:
-       sources: [lib.c3, ipc.c3, screen.c3]
-       includes: []
-       machine: arm
-       output: kernel.elf2
+ - link:
+    inputs:
+        - compile:
+           sources: [lib.c3, ipc.c3, screen.c3]
+           includes: []
+           machine: arm
+           output: kernel.elf2
 
-  - compile:
-       sources: [lib.c3, ipc.c3, console.c3]
-       includes: []
-       machine: arm
-       output: kernel.elf2
+ - link:
+    inputs:
+      - compile:
+         sources: [lib.c3, ipc.c3, console.c3]
+         includes: []
+         machine: arm
+         output: kernel.elf2