#!/usr/bin/env python # line-directive.py - Transform line numbers in error messages -*- python -*- # # This source file is part of the Swift.org open source project # # Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors # # ---------------------------------------------------------------------------- # # Converts line numbers in error messages according to "line directive" # comments. # # ---------------------------------------------------------------------------- import bisect import re import subprocess import sys line_pattern = re.compile(r'^// ###setline ([0-9]+) "([^"]+)"\s*') def _make_line_map(filename, stream=None): """ >>> from StringIO import StringIO >>> _make_line_map('box', ... StringIO('''// ###setline 3 "foo.bar" ... line 2 ... line 3 ... line 4 ... // ###setline 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, universal_newlines=True ) 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()