| Index: client/tools/sourcemap.py
|
| ===================================================================
|
| --- client/tools/sourcemap.py (revision 5923)
|
| +++ client/tools/sourcemap.py (working copy)
|
| @@ -1,368 +0,0 @@
|
| -# Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
| -# for details. All rights reserved. Use of this source code is governed by a
|
| -# BSD-style license that can be found in the LICENSE file.
|
| -
|
| -"""Utilities to extract source map information
|
| -
|
| -A set of utilities to extract source map information. This python library
|
| -consumes source map files v3, but doesn't provide any funcitonality to produce
|
| -source maps. This code is an adaptation of the Java implementation originally
|
| -written for the closure compiler by John Lenz (johnlenz@google.com)
|
| -"""
|
| -
|
| -import bisect
|
| -import json
|
| -import sys
|
| -
|
| -class SourceMap():
|
| - """ An in memory representation of a source map. """
|
| -
|
| - def get_source_location(self, line, column):
|
| - """ Fetches the original location for a line and column.
|
| -
|
| - Args:
|
| - line: line in the output file to query
|
| - column: column in the output file to query
|
| -
|
| - Returns:
|
| - A tuple of the form:
|
| - file, src_line, src_column, opt_identifier
|
| - When available, opt_identifier contains the name of an identifier
|
| - associated with the given program location.
|
| - """
|
| - pass # implemented by subclasses
|
| -
|
| -def parse(sourcemap_file):
|
| - """ Parse a file containing source map information as a json string and return
|
| - a source map object representing it.
|
| - Args:
|
| - sourcemap_file: path to a file (optionally containing a 'file:' prefix)
|
| - which can either contain a meta-level source map or an
|
| - actual source map.
|
| - """
|
| - if sourcemap_file.startswith('file:'):
|
| - sourcemap_file = sourcemap_file[5:]
|
| -
|
| - with open(sourcemap_file, 'r') as f:
|
| - sourcemap_json = json.load(f)
|
| -
|
| - return _parseFromJson(sourcemap_json)
|
| -
|
| -
|
| -def _parseFromJson(sourcemap_json):
|
| - if sourcemap_json['version'] != 3:
|
| - raise SourceMapException("unexpected source map version")
|
| -
|
| - if not 'file' in sourcemap_json:
|
| - raise SourceMapException("unexpected, no file in source map file")
|
| -
|
| - if 'sections' in sourcemap_json:
|
| - sections = sourcemap_json['sections']
|
| - # a meta file
|
| - if ('mappings' in sourcemap_json
|
| - or 'sources' in sourcemap_json
|
| - or 'names' in sourcemap_json):
|
| - raise SourceMapException("Invalid map format")
|
| - return _MetaSourceMap(sections)
|
| -
|
| - return _SourceMapFile(sourcemap_json)
|
| -
|
| -class _MetaSourceMap(SourceMap):
|
| - """ A higher-order source map containing nested source maps. """
|
| -
|
| - def __init__(self, sections):
|
| - """ creates a source map instance given its json input (already parsed). """
|
| - # parse a regular sourcemap file
|
| - self.offsets = []
|
| - self.maps = []
|
| -
|
| - for section in sections:
|
| - line = section['offset']['line']
|
| - if section['offset']['column'] != 0:
|
| - # TODO(sigmund): implement if needed
|
| - raise Exception("unimplemented")
|
| -
|
| - if 'url' in section and 'map' in section:
|
| - raise SourceMapException(
|
| - "Invalid format: section may not contain both 'url' and 'map'")
|
| -
|
| - self.offsets.append(line)
|
| - if 'url' in section:
|
| - self.maps.append(parse(section['url']))
|
| - elif 'map' in section:
|
| - self.maps.append(_parseFromJson(section['map']))
|
| - else:
|
| - raise SourceMapException(
|
| - "Invalid format: section must contain either 'url' or 'map'")
|
| -
|
| - def get_source_location(self, line, column):
|
| - """ Fetches the original location from the target location. """
|
| - index = bisect.bisect(self.offsets, line) - 1
|
| - return self.maps[index].get_source_location(
|
| - line - self.offsets[index], column)
|
| -
|
| -class _SourceMapFile(SourceMap):
|
| - def __init__(self, sourcemap):
|
| - """ creates a source map instance given its json input (already parsed). """
|
| - # parse a regular sourcemap file
|
| - self.sourcemap_file = sourcemap['file']
|
| - self.sources = sourcemap['sources']
|
| - self.names = sourcemap['names']
|
| - self.lines = []
|
| - self._build(sourcemap['mappings'])
|
| -
|
| - def get_source_location(self, line, column):
|
| - """ Fetches the original location from the target location. """
|
| -
|
| - # Normalize the line and column numbers to 0.
|
| - line -= 1
|
| - column -= 1
|
| -
|
| - if line < 0 or line >= len(self.lines):
|
| - return None
|
| -
|
| - entries = self.lines[line]
|
| - # If the line is empty return the previous mapping.
|
| - if not entries or entries == [] or entries[0].gen_column > column:
|
| - return self._previousMapping(line)
|
| -
|
| - index = bisect.bisect(entries, _Entry(column)) - 1
|
| - return self._originalEntryMapping(entries[index])
|
| -
|
| - def _previousMapping(self, line):
|
| - while True:
|
| - if line == 0:
|
| - return None
|
| - line -= 1
|
| - if self.lines[line]:
|
| - return self._originalEntryMapping(self.lines[line][-1])
|
| -
|
| - def _originalEntryMapping(self, entry):
|
| - if entry.src_file_id is None:
|
| - return None
|
| -
|
| - if entry.name_id:
|
| - identifier = self.names[entry.name_id]
|
| - else:
|
| - identifier = None
|
| -
|
| - filename = self.sources[entry.src_file_id]
|
| - return filename, entry.src_line, entry.src_column, identifier
|
| -
|
| - def _build(self, linemap):
|
| - """ builds this source map from the sourcemap json """
|
| - entries = []
|
| - line = 0
|
| - prev_col = 0
|
| - prev_src_id = 0
|
| - prev_src_line = 0
|
| - prev_src_column = 0
|
| - prev_name_id = 0
|
| - content = _StringCharIterator(linemap)
|
| - while content.hasNext():
|
| - # ';' denotes a new line.
|
| - token = content.peek()
|
| - if token == ';':
|
| - content.next()
|
| - # The line is complete, store the result for the line, None if empty.
|
| - result = entries if len(entries) > 0 else None
|
| - self.lines.append(result)
|
| - entries = []
|
| - line += 1
|
| - prev_col = 0
|
| - else:
|
| - # Grab the next entry for the current line.
|
| - values = []
|
| - while (content.hasNext()
|
| - and content.peek() != ',' and content.peek() != ';'):
|
| - values.append(_Base64VLQDecode(content))
|
| -
|
| - # Decodes the next entry, using the previous encountered values to
|
| - # decode the relative values.
|
| - #
|
| - # The values, if present are in the following order:
|
| - # 0: the starting column in the current line of the generated file
|
| - # 1: the id of the original source file
|
| - # 2: the starting line in the original source
|
| - # 3: the starting column in the original source
|
| - # 4: the id of the original symbol name
|
| - # The values are relative to the previous encountered values.
|
| -
|
| - total = len(values)
|
| - if not(total == 1 or total == 4 or total == 5):
|
| - raise SourceMapException(
|
| - "Invalid entry in source map file: %s\nline: %d\nvalues: %s\n"
|
| - % (self.sourcemap_file, line, str(values)))
|
| - prev_col += values[0]
|
| - if total == 1:
|
| - entry = _Entry(prev_col)
|
| - else:
|
| - prev_src_id += values[1]
|
| - if prev_src_id >= len(self.sources):
|
| - raise SourceMapException(
|
| - "Invalid source id\nfile: %s\nline: %d\nid: %d\n"
|
| - % (self.sourcemap_file, line, prev_src_id))
|
| - prev_src_line += values[2]
|
| - prev_src_column += values[3]
|
| - if total == 4:
|
| - entry = _Entry(
|
| - prev_col, prev_src_id, prev_src_line, prev_src_column)
|
| - elif total == 5:
|
| - prev_name_id += values[4]
|
| - if prev_name_id >= len(self.names):
|
| - raise SourceMapException(
|
| - "Invalid name id\nfile: %s\nline: %d\nid: %d\n"
|
| - % (self.sourcemap_file, line, prev_name_id))
|
| - entry = _Entry(
|
| - prev_col, prev_src_id, prev_src_line, prev_src_column,
|
| - prev_name_id)
|
| - entries.append(entry);
|
| - if content.peek() == ',':
|
| - content.next()
|
| -
|
| -class _StringCharIterator():
|
| - """ An iterator over a string that allows you to peek into the next value. """
|
| - def __init__(self, string):
|
| - self.string = string
|
| - self.length = len(string)
|
| - self.current = 0
|
| -
|
| - def __iter__(self):
|
| - return self
|
| -
|
| - def next(self):
|
| - res = self.string[self.current]
|
| - self.current += 1
|
| - return res
|
| -
|
| - def peek(self):
|
| - return self.string[self.current]
|
| -
|
| - def hasNext(self):
|
| - return self.current < self.length
|
| -
|
| -
|
| -# Base64VLQ decoding
|
| -
|
| -VLQ_BASE_SHIFT = 5
|
| -VLQ_BASE = 1 << VLQ_BASE_SHIFT
|
| -VLQ_BASE_MASK = VLQ_BASE - 1
|
| -VLQ_CONTINUATION_BIT = VLQ_BASE
|
| -BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
| -BASE64_DECODE_MAP = dict()
|
| -for c in range(64):
|
| - BASE64_DECODE_MAP[BASE64_MAP[c]] = c
|
| -
|
| -def _Base64VLQDecode(iterator):
|
| - """
|
| - Decodes the next VLQValue from the provided char iterator.
|
| -
|
| - Sourcemaps are encoded with variable length numbers as base64 encoded strings
|
| - with the least significant digit coming first. Each base64 digit encodes a
|
| - 5-bit value (0-31) and a continuation bit. Signed values can be represented
|
| - by using the least significant bit of the value as the
|
| - sign bit.
|
| -
|
| - This function only contains the decoding logic, since the encoding logic is
|
| - only needed to produce source maps.
|
| -
|
| - Args:
|
| - iterator: a _StringCharIterator
|
| - """
|
| - result = 0
|
| - stop = False
|
| - shift = 0
|
| - while not stop:
|
| - c = iterator.next()
|
| - if c not in BASE64_DECODE_MAP:
|
| - raise Exception("%s not a valid char" % c)
|
| - digit = BASE64_DECODE_MAP[c]
|
| - stop = digit & VLQ_CONTINUATION_BIT == 0
|
| - digit &= VLQ_BASE_MASK
|
| - result += (digit << shift)
|
| - shift += VLQ_BASE_SHIFT
|
| -
|
| - # Result uses the least significant bit as a sign bit. We convert it into a
|
| - # two-complement value. For example,
|
| - # 2 (10 binary) becomes 1
|
| - # 3 (11 binary) becomes -1
|
| - # 4 (100 binary) becomes 2
|
| - # 5 (101 binary) becomes -2
|
| - # 6 (110 binary) becomes 3
|
| - # 7 (111 binary) becomes -3
|
| - negate = (result & 1) == 1
|
| - result = result >> 1
|
| - return -result if negate else result
|
| -
|
| -
|
| -ERROR_DETAILS ="""
|
| - - gen_column = %s
|
| - - src_file_id = %s
|
| - - src_line = %s
|
| - - src_column = %s
|
| - - name_id = %s
|
| -"""
|
| -
|
| -class _Entry():
|
| - """ An entry in a source map file. """
|
| - def __init__(self, gen_column,
|
| - src_file_id=None,
|
| - src_line=None,
|
| - src_column=None,
|
| - name_id=None):
|
| - """ Creates an entry. Many arguments are marked as optional, but we expect
|
| - either all being None, or only name_id being none.
|
| - """
|
| -
|
| - # gen column must be defined:
|
| - if gen_column is None:
|
| - raise SourceMapException(
|
| - "Invalid entry, no gen_column specified:" +
|
| - ERROR_DETAILS % (
|
| - gen_column, src_file_id, src_line, src_column, name_id))
|
| -
|
| - # if any field other than gen_column is defined, then file_id, line, and
|
| - # column must be defined:
|
| - if ((src_file_id is not None or src_line is not None or
|
| - src_column is not None or name_id is not None) and
|
| - (src_file_id is None or src_line is None or src_column is None)):
|
| - raise SourceMapException(
|
| - "Invalid entry, only name_id is optional:" +
|
| - ERROR_DETAILS % (
|
| - gen_column, src_file_id, src_line, src_column, name_id))
|
| -
|
| - self.gen_column = gen_column
|
| - self.src_file_id = src_file_id
|
| - self.src_line = src_line
|
| - self.src_column = src_column
|
| - self.name_id = name_id
|
| -
|
| - # define comparison to perform binary search on lookups
|
| - def __cmp__(self, other):
|
| - return cmp(self.gen_column, other.gen_column)
|
| -
|
| -class SourceMapException(Exception):
|
| - """ An exception encountered while parsing or processing source map files."""
|
| - pass
|
| -
|
| -def main():
|
| - """ This module is intended to be used as a library. Main is provided to
|
| - test the functionality on the command line.
|
| - """
|
| - if len(sys.argv) < 3:
|
| - print ("Usage: %s <mapfile> line [column]" % sys.argv[0])
|
| - return 1
|
| -
|
| - sourcemap = parse(sys.argv[1])
|
| - line = int(sys.argv[2])
|
| - column = int(sys.argv[3]) if len(sys.argv) > 3 else 1
|
| - original = sourcemap.get_source_location(line, column)
|
| - if not original:
|
| - print "Source location not found"
|
| - else:
|
| - filename, srcline, srccolumn, srcid = original
|
| - print "Source location is: %s, line: %d, column: %d, identifier: %s" % (
|
| - filename, srcline, srccolumn, srcid)
|
| -
|
| -if __name__ == '__main__':
|
| - sys.exit(main())
|
|
|