#!/usr/bin/env python
#convert line numbers in error messages according to "line directive" comments
import os
import sys
import re
import bisect
import subprocess

line_pattern = re.compile(
 r'^// ###line ([0-9]+) "([^"]+)"\s*')

def _make_line_map(filename, stream=None):
    """
    >>> from StringIO import StringIO
    >>> _make_line_map('box',
    ... StringIO('''// ###line 3 "foo.bar"
    ... line 2
    ... line 3
    ... line 4
    ... // ###line 20 "baz.txt"
    ... line 6
    ... line 7
    ... '''))
    [(0, 'box', 1), (1, 'foo.bar', 3), (5, 'baz.txt', 20)]
"""
    result = [(0, filename, 1)]
    input = stream or open(filename)
    for i, l in enumerate(input.readlines()):
        m = line_pattern.match(l)
        if m:
            result.append((i+1, m.group(2), int(m.group(1))))
    return result

_line_maps = {}

def fline_map(filename):
    map = _line_maps.get(filename)
    if map is None:
        map = _make_line_map(filename)
        _line_maps[filename] = map
    return map

def map_line(filename, line_num):
    assert(line_num > 0)
    map = fline_map(filename)
    index = bisect.bisect_left(map, (line_num,'',0))
    base = map[index - 1]
    return base[1], base[2] + (line_num - base[0]-1)

def run():
    if len(sys.argv) <= 1:
        import doctest
        doctest.testmod()
    else:
        dashes = sys.argv.index('--')
        sources = sys.argv[1:dashes]

        command = subprocess.Popen(
            sys.argv[dashes + 1:], stderr = subprocess.STDOUT, stdout = subprocess.PIPE
        )
        
        error_pattern = re.compile(
            '^(' + '|'.join(re.escape(s) for s in sources) + '):([0-9]+):([0-9]+):(.*)')

        assertion_pattern = re.compile(
            '^(.*)(: file| at|#[0-9]+:) (' + '|'.join(re.escape(s) for s in sources) + ')(, line |:)([0-9]+)(.*)')

        while True:
            line = command.stdout.readline()
            if line == '': break
            l = line.rstrip('\n')
            m = error_pattern.match(l)
            if m:
                file, line_num = map_line(m.group(1), int(m.group(2)))
                line = '%s:%s:%s:%s\n' % (file, line_num, int(m.group(3)), m.group(4))
            else:
                m = assertion_pattern.match(l)
                if m: 
                    file, line_num = map_line(m.group(3), int(m.group(5)))
                    line = '%s%s %s%s%s%s\n' % (m.group(1), m.group(2), file, m.group(4), line_num, m.group(6))
            sys.stdout.write(line)
        
        sys.exit(command.wait())
        
if __name__ == '__main__':
    run()
