import re

def Generate(outfile, parser):
    write = outfile.write

    if parser.options.get('mapping'):
        write_body = MappingBody
    elif parser.options.get('tuple'):
        write_body = TupleBody
    else:
        write_body = ActionBody
        write('\n# modules required for action routines\n')
        for (package, module) in parser.imports:
            if package:
                write('from %s ' % package)
            write('import %s\n' % module)

    if parser.options.get('debug'):
        write('\n# helper for displaying debugging information\n')
        write('def __dump_stack(name, stack):\n')
        write('    sys.stderr.write("--%s(" % name)\n')
        write('    for item in stack:\n')
        write('        sys.stderr.write(", %s" % item)\n')
        write('    sys.stderr.write("\\n")\n')
    
    # the list of all action routine function names
    functions = [None]
    # make sure we don't assign the same name twice
    lhs = {}
    write('\n# the action code for each rule\n')
    for rule in parser.grammar.rules:
        count = lhs.get(rule.lhs, 0)
        lhs[rule.lhs] = count + 1
        func_name = '%s%d' % (rule.lhs, lhs[rule.lhs])
        if rule.action.has_key('python'):
            # Only write functions that have an action
            write('def %s(self, __stack, __ptr):\n' % func_name)
            write('    """\n')
            write('    from %s, line %d\n' % (rule.filename, rule.lineno))
            grammar = reduce(lambda a, b: a + ' ' + b, rule.rhs, rule.lhs+':')
            write('    %s\n' % grammar)
            write('    """\n')
            if parser.options.get('debug'):
                write('    if self.verbose:\n')
                write('        sys.stderr.write("--%s(")\n' % rule.lhs)
                for n in range(len(rule.rhs)):
                    write('        sys.stderr.write(')
                    if n > 0:
                        write('", " + ')
                    write('repr(__stack[__ptr+%d]))\n' % (n + 1))
                write('        sys.stderr.write(")\\n")\n')
            write_body(write, rule)
            functions.append(func_name)
        else:
            functions.append('None')

    routines = 'action_routines = ['
    write(routines)
    indent = ' '*len(routines)
    for name in functions:
        write('%s,\n%s' % (name, indent))
    write(']\n')
    return


def MappingBody(write, rule):
    write("    return {'TYPE' : %s,\n" % repr(rule.lhs.upper()))
    for n in range(len(rule.rhs)):
        write('            %d : __stack[__ptr+%d],\n' % (n, n+1))
    write('            }\n\n')
    return


def TupleBody(write, rule):
    write('    return (%s,\n' % repr(rule.lhs.upper()))
    for n in range(len(rule.rhs)):
        write('            __stack[__ptr+%d],\n' % (n+1))
    write('            )\n\n')
    return


whitespace = re.compile('\s*')
equals = re.compile('\s*=')
stack_ptr = re.compile('\$(\d+|\$)')
def ActionBody(write, rule):
    # defaults to first item (allows for pass-through)
    # handle the case of no right-hand side
    if len(rule.rhs):
        offset = 1
    else:
        offset = 0
    retval = '__stack[__ptr%+d]' % offset

    lines = rule.action['python'].split('\n')
    # Remove leading and trailing lines that are only whitespace
    while not lines[0].strip():
        lines = lines[1:]
    while not lines[-1].strip():
        lines = lines[:-1]

    # find length of minimum amount of preceding whitespace
    space = len(lines[0])
    for line in lines:
        match = whitespace.match(line)
        if match and match.end() < len(line):
            if match.end() < space:
                space = match.end()

    for line in lines:
        # replace stack pointers
        match = stack_ptr.search(line)
        while match:
            pre = line[:match.start()]
            post = line[match.end():]
            if match.group(1) == '$':
                match = equals.match(post)
                # the return value
                retval = subst = '__val'
            else:
                # it is a position in the stack
                subst = '__stack[__ptr%+d]' % int(match.group(1))
            line = pre + subst + post
            match = stack_ptr.search(line)
        write('    %s\n' % line.strip()[space:])
    write('    return %s\n\n' % retval)
    return
