Chromium Code Reviews| Index: pkg/compiler/lib/src/io/source_map_builder.dart |
| diff --git a/pkg/compiler/lib/src/io/source_map_builder.dart b/pkg/compiler/lib/src/io/source_map_builder.dart |
| index bf614abebe85b6e5a0094f05545e7a3125f96b67..2499acb8ee51b1d1270c6baf8af0c96dbf65332f 100644 |
| --- a/pkg/compiler/lib/src/io/source_map_builder.dart |
| +++ b/pkg/compiler/lib/src/io/source_map_builder.dart |
| @@ -10,111 +10,24 @@ import 'line_column_provider.dart'; |
| import 'source_information.dart' show SourceLocation; |
| class SourceMapBuilder { |
| - static const int VLQ_BASE_SHIFT = 5; |
| - static const int VLQ_BASE_MASK = (1 << 5) - 1; |
| - static const int VLQ_CONTINUATION_BIT = 1 << 5; |
| - static const int VLQ_CONTINUATION_MASK = 1 << 5; |
| - static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' |
| - 'opqrstuvwxyz0123456789+/'; |
| /// The URI of the source map file. |
| final Uri sourceMapUri; |
| /// The URI of the target language file. |
| final Uri targetFileUri; |
| - LineColumnProvider lineColumnProvider; |
| - List<SourceMapEntry> entries; |
| - |
| - Map<Uri, int> sourceUriMap; |
| - List<Uri> sourceUriList; |
| - Map<String, int> sourceNameMap; |
| - List<String> sourceNameList; |
| - |
| - int previousTargetLine; |
| - int previousTargetColumn; |
| - int previousSourceUriIndex; |
| - int previousSourceLine; |
| - int previousSourceColumn; |
| - int previousSourceNameIndex; |
| - bool firstEntryInLine; |
| + final LineColumnProvider lineColumnProvider; |
| + final List<SourceMapEntry> entries = new List<SourceMapEntry>(); |
| SourceMapBuilder(this.sourceMapUri, |
| this.targetFileUri, |
| - this.lineColumnProvider) { |
| - entries = new List<SourceMapEntry>(); |
| - |
| - sourceUriMap = new Map<Uri, int>(); |
| - sourceUriList = new List<Uri>(); |
| - sourceNameMap = new Map<String, int>(); |
| - sourceNameList = new List<String>(); |
| - |
| - previousTargetLine = 0; |
| - previousTargetColumn = 0; |
| - previousSourceUriIndex = 0; |
| - previousSourceLine = 0; |
| - previousSourceColumn = 0; |
| - previousSourceNameIndex = 0; |
| - firstEntryInLine = true; |
| - } |
| - |
| - resetPreviousSourceLocation() { |
| - previousSourceUriIndex = 0; |
| - previousSourceLine = 0; |
| - previousSourceColumn = 0; |
| - previousSourceNameIndex = 0; |
| - } |
| - |
| - updatePreviousSourceLocation(SourceLocation sourceLocation) { |
| - previousSourceLine = sourceLocation.line; |
| - previousSourceColumn = sourceLocation.column; |
| - Uri sourceUri = sourceLocation.sourceUri; |
| - previousSourceUriIndex = indexOf(sourceUriList, sourceUri, sourceUriMap); |
| - String sourceName = sourceLocation.sourceName; |
| - if (sourceName != null) { |
| - previousSourceNameIndex = |
| - indexOf(sourceNameList, sourceName, sourceNameMap); |
| - } |
| - } |
| - |
| - bool sameAsPreviousLocation(SourceLocation sourceLocation) { |
| - if (sourceLocation == null) { |
| - return true; |
| - } |
| - int sourceUriIndex = |
| - indexOf(sourceUriList, sourceLocation.sourceUri, sourceUriMap); |
| - return |
| - sourceUriIndex == previousSourceUriIndex && |
| - sourceLocation.line == previousSourceLine && |
| - sourceLocation.column == previousSourceColumn; |
| - } |
| + this.lineColumnProvider); |
| void addMapping(int targetOffset, SourceLocation sourceLocation) { |
| - |
| - bool sameLine(int position, otherPosition) { |
| - return lineColumnProvider.getLine(position) == |
| - lineColumnProvider.getLine(otherPosition); |
| - } |
| - |
| - if (!entries.isEmpty && sameLine(targetOffset, entries.last.targetOffset)) { |
| - if (sameAsPreviousLocation(sourceLocation)) { |
| - // The entry points to the same source location as the previous entry in |
| - // the same line, hence it is not needed for the source map. |
| - // |
| - // TODO(zarah): Remove this check and make sure that [addMapping] is not |
| - // called for this position. Instead, when consecutive lines in the |
| - // generated code point to the same source location, record this and use |
| - // it to generate the entries of the source map. |
| - return; |
| - } |
| - } |
| - |
| - if (sourceLocation != null) { |
| - updatePreviousSourceLocation(sourceLocation); |
| - } |
| entries.add(new SourceMapEntry(sourceLocation, targetOffset)); |
| } |
| - void printStringListOn(List<String> strings, StringBuffer buffer) { |
| + void printStringListOn(Iterable<String> strings, StringBuffer buffer) { |
| bool first = true; |
| buffer.write('['); |
| for (String string in strings) { |
| @@ -128,11 +41,46 @@ class SourceMapBuilder { |
| } |
| String build() { |
| - resetPreviousSourceLocation(); |
| - StringBuffer mappingsBuffer = new StringBuffer(); |
| - entries.forEach((SourceMapEntry entry) { |
| - writeEntry(entry, mappingsBuffer); |
| + |
| + LineColumnMap<SourceMapEntry> lineColumnMap = |
| + new LineColumnMap<SourceMapEntry>(); |
| + Map<Uri, LineColumnMap<SourceMapEntry>> sourceLocationMap = |
| + <Uri, LineColumnMap<SourceMapEntry>>{}; |
| + entries.forEach((SourceMapEntry sourceMapEntry) { |
| + int line = lineColumnProvider.getLine(sourceMapEntry.targetOffset); |
| + int column = |
| + lineColumnProvider.getColumn(line, sourceMapEntry.targetOffset); |
| + lineColumnMap.add(line, column, sourceMapEntry); |
| + |
| + SourceLocation location = sourceMapEntry.sourceLocation; |
| + if (location != null) { |
| + LineColumnMap<SourceMapEntry> sourceLineColumnMap = |
| + sourceLocationMap.putIfAbsent(location.sourceUri, |
| + () => new LineColumnMap<SourceMapEntry>()); |
| + sourceLineColumnMap.add(location.line, location.column, sourceMapEntry); |
| + } |
| + }); |
| + |
| + return _build(lineColumnMap); |
| + } |
| + |
| + String _build(LineColumnMap<SourceMapEntry> lineColumnMap) { |
| + IndexMap<Uri> uriMap = new IndexMap<Uri>(); |
| + IndexMap<String> nameMap = new IndexMap<String>(); |
| + |
| + lineColumnMap.forEachElement((SourceMapEntry entry) { |
| + SourceLocation sourceLocation = entry.sourceLocation; |
| + if (sourceLocation != null) { |
| + uriMap.register(sourceLocation.sourceUri); |
| + if (sourceLocation.sourceName != null) { |
| + nameMap.register(sourceLocation.sourceName); |
| + } |
| + } |
| }); |
| + |
| + StringBuffer mappingsBuffer = new StringBuffer(); |
| + writeEntries(lineColumnMap, uriMap, nameMap, mappingsBuffer); |
| + |
| StringBuffer buffer = new StringBuffer(); |
| buffer.write('{\n'); |
| buffer.write(' "version": 3,\n'); |
| @@ -142,16 +90,15 @@ class SourceMapBuilder { |
| } |
| buffer.write(' "sourceRoot": "",\n'); |
| buffer.write(' "sources": '); |
| - List<String> relativeSourceUriList = <String>[]; |
| + Iterable<String> relativeSourceUriList = const <String>[]; |
| if (sourceMapUri != null) { |
| - relativeSourceUriList = sourceUriList |
| - .map((u) => relativize(sourceMapUri, u, false)) |
| - .toList(); |
| + relativeSourceUriList = uriMap.elements |
| + .map((u) => relativize(sourceMapUri, u, false)); |
| } |
| printStringListOn(relativeSourceUriList, buffer); |
| buffer.write(',\n'); |
| buffer.write(' "names": '); |
| - printStringListOn(sourceNameList, buffer); |
| + printStringListOn(nameMap.elements, buffer); |
| buffer.write(',\n'); |
| buffer.write(' "mappings": "'); |
| buffer.write(mappingsBuffer); |
| @@ -159,73 +106,105 @@ class SourceMapBuilder { |
| return buffer.toString(); |
| } |
| - void writeEntry(SourceMapEntry entry, StringBuffer output) { |
| - int targetLine = lineColumnProvider.getLine(entry.targetOffset); |
| - int targetColumn = |
| - lineColumnProvider.getColumn(targetLine, entry.targetOffset); |
| + void writeEntries(LineColumnMap<SourceMapEntry> entries, |
| + IndexMap<Uri> uriMap, |
| + IndexMap<String> nameMap, |
| + StringBuffer output) { |
| + SourceLocation previousSourceLocation; |
| + int previousTargetLine = 0; |
| + DeltaEncoder targetColumnEncoder = new DeltaEncoder(); |
| + bool firstEntryInLine = true; |
| + DeltaEncoder sourceUriIndexEncoder = new DeltaEncoder(); |
| + DeltaEncoder sourceLineEncoder = new DeltaEncoder(); |
| + DeltaEncoder sourceColumnEncoder = new DeltaEncoder(); |
| + DeltaEncoder sourceNameIndexEncoder = new DeltaEncoder(); |
| + |
| + entries.forEach((int targetLine, |
| + int targetColumn, |
| + SourceMapEntry entry) { |
| + SourceLocation sourceLocation = entry.sourceLocation; |
| + if (sourceLocation == previousSourceLocation) { |
| + return; |
| + } |
| - if (targetLine > previousTargetLine) { |
| - for (int i = previousTargetLine; i < targetLine; ++i) { |
| - output.write(';'); |
| + if (targetLine > previousTargetLine) { |
| + for (int i = previousTargetLine; i < targetLine; ++i) { |
| + output.write(';'); |
| + } |
| + previousTargetLine = targetLine; |
| + previousSourceLocation = null; |
| + targetColumnEncoder.reset(); |
| + firstEntryInLine = true; |
| } |
| - previousTargetLine = targetLine; |
| - previousTargetColumn = 0; |
| - firstEntryInLine = true; |
| - } |
| - if (!firstEntryInLine) { |
| - output.write(','); |
| - } |
| - firstEntryInLine = false; |
| + if (!firstEntryInLine) { |
| + output.write(','); |
| + } |
| + firstEntryInLine = false; |
| + |
| + targetColumnEncoder.encode(output, targetColumn); |
| - encodeVLQ(output, targetColumn - previousTargetColumn); |
| - previousTargetColumn = targetColumn; |
| + if (sourceLocation == null) { |
| + return; |
| + } |
| - if (entry.sourceLocation == null) return; |
| + Uri sourceUri = sourceLocation.sourceUri; |
| + sourceUriIndexEncoder.encode(output, uriMap[sourceUri]); |
| + sourceLineEncoder.encode(output, sourceLocation.line); |
| + sourceColumnEncoder.encode(output, sourceLocation.column); |
| - Uri sourceUri = entry.sourceLocation.sourceUri; |
| - int sourceLine = entry.sourceLocation.line; |
| - int sourceColumn = entry.sourceLocation.column; |
| - String sourceName = entry.sourceLocation.sourceName; |
| + String sourceName = sourceLocation.sourceName; |
| + if (sourceName != null) { |
| + sourceNameIndexEncoder.encode(output, nameMap[sourceName]); |
| + } |
| - int sourceUriIndex = indexOf(sourceUriList, sourceUri, sourceUriMap); |
| - encodeVLQ(output, sourceUriIndex - previousSourceUriIndex); |
| - encodeVLQ(output, sourceLine - previousSourceLine); |
| - encodeVLQ(output, sourceColumn - previousSourceColumn); |
| + previousSourceLocation = sourceLocation; |
| + }); |
| + } |
| +} |
| - if (sourceName != null) { |
| - int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); |
| - encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); |
| - } |
| +/// Encoder for value deltas in VLQ format. |
| +class DeltaEncoder { |
| + /// The last emitted value of the encoder. |
| + int _value = 0; |
| - // Update previous source location to ensure the next indices are relative |
| - // to those if [entry.sourceLocation]. |
| - updatePreviousSourceLocation(entry.sourceLocation); |
| + /// Reset the encoder to its initial state. |
| + void reset() { |
| + _value = 0; |
| } |
| - int indexOf(List list, value, Map<dynamic, int> map) { |
| - return map.putIfAbsent(value, () { |
| - int index = list.length; |
| - list.add(value); |
| - return index; |
| - }); |
| + /// Writes the VLQ of delta between [value] and the last emitted value into |
| + /// [output] and updates the last emitted value of the encoder. |
| + void encode(StringBuffer output, int value) { |
| + _value = encodeVLQ(output, value, _value); |
| } |
| - static void encodeVLQ(StringBuffer output, int value) { |
| + static const int VLQ_BASE_SHIFT = 5; |
| + static const int VLQ_BASE_MASK = (1 << 5) - 1; |
| + static const int VLQ_CONTINUATION_BIT = 1 << 5; |
| + static const int VLQ_CONTINUATION_MASK = 1 << 5; |
| + static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' |
| + 'opqrstuvwxyz0123456789+/'; |
| + |
| + /// Writes the VLQ of delta between [value] and [offset] into [output] and |
| + /// return [value]. |
| + static int encodeVLQ(StringBuffer output, int value, int offset) { |
| + int delta = value - offset; |
| int signBit = 0; |
| - if (value < 0) { |
| + if (delta < 0) { |
| signBit = 1; |
| - value = -value; |
| + delta = -delta; |
| } |
| - value = (value << 1) | signBit; |
| + delta = (delta << 1) | signBit; |
| do { |
| - int digit = value & VLQ_BASE_MASK; |
| - value >>= VLQ_BASE_SHIFT; |
| - if (value > 0) { |
| + int digit = delta & VLQ_BASE_MASK; |
| + delta >>= VLQ_BASE_SHIFT; |
| + if (delta > 0) { |
| digit |= VLQ_CONTINUATION_BIT; |
| } |
| output.write(BASE64_DIGITS[digit]); |
| - } while (value > 0); |
| + } while (delta > 0); |
| + return value; |
| } |
| } |
| @@ -235,3 +214,93 @@ class SourceMapEntry { |
| SourceMapEntry(this.sourceLocation, this.targetOffset); |
| } |
| + |
| +/// Map from line/column pairs to lists of [T] elements. |
| +class LineColumnMap<T> { |
| + Map<int, Map<int, List<T>>> map = <int, Map<int, List<T>>>{}; |
|
floitsch
2015/04/22 09:38:22
make private?
Johnni Winther
2015/04/22 11:04:08
Done.
|
| + |
| + /// Returns the list of elements associated with ([line],[column]). |
| + List<T> _getList(int line, int column) { |
| + Map<int, List<T>> lineMap = map.putIfAbsent(line, () => <int, List<T>>{}); |
| + return lineMap.putIfAbsent(column, () => <T>[]); |
| + } |
| + |
| + /// Adds [element] to the end of the list of elements associated with |
| + /// ([line],[column]). |
| + void add(int line, int column, T element) { |
| + _getList(line, column).add(element); |
| + } |
| + |
| + /// Adds [element] to the beginning of the list of elements associated with |
| + /// ([line],[column]). |
| + void addFirst(int line, int column, T element) { |
| + _getList(line, column).insert(0, element); |
| + } |
| + |
| + /// Calls [f] with the line number for each line with associated elements. |
| + /// |
| + /// [f] is called in increasing line order. |
| + void forEachLine(f(int line)) { |
| + List<int> lines = map.keys.toList()..sort(); |
| + lines.forEach(f); |
| + } |
| + |
| + /// Returns the elements for the first the column in [line] that has |
| + /// associated elements. |
| + List<T> getFirstElementsInLine(int line) { |
| + Map<int, List<T>> lineMap = map[line]; |
| + if (lineMap == null) return null; |
| + List<int> columns = lineMap.keys.toList()..sort(); |
| + return lineMap[columns.first]; |
| + } |
| + |
| + /// Calls [f] for each column with associated elements in [line]. |
| + /// |
| + /// [f] is called in increasing column order. |
| + void forEachColumn(int line, f(int column, List<T> elements)) { |
| + Map<int, List<T>> lineMap = map[line]; |
| + if (lineMap != null) { |
| + List<int> columns = lineMap.keys.toList()..sort(); |
| + columns.forEach((int column) { |
| + f(column, lineMap[column]); |
| + }); |
| + } |
| + } |
| + |
| + /// Calls [f] for each line/column/element triplet in the map. |
| + /// |
| + /// [f] is called in increasing line, column, element order. |
| + void forEach(f(int line, int column, T element)) { |
| + List<int> lines = map.keys.toList()..sort(); |
| + for (int line in lines) { |
| + Map<int, List<T>> lineMap = map[line]; |
| + List<int> columns = lineMap.keys.toList()..sort(); |
| + for (int column in columns) { |
| + lineMap[column].forEach((e) => f(line, column, e)); |
| + } |
| + } |
| + } |
| + |
| + /// Calls [f] for each element associated in the map. |
| + /// |
| + /// [f] is called in increasing line, column, element order. |
| + void forEachElement(f(T element)) { |
| + forEach((line, column, element) => f(element)); |
| + } |
| +} |
| + |
| +/// Map from [T] elements to assigned indices. |
| +class IndexMap<T> { |
| + Map<T, int> map = <T, int>{}; |
| + |
| + /// Register [element] and returns its index. |
| + int register(T element) { |
| + return map.putIfAbsent(element, () => map.length); |
| + } |
| + |
| + /// Returns the index of [element]. |
| + int operator [](T element) => map[element]; |
| + |
| + /// Returns the indexed elements. |
| + Iterable<T> get elements => map.keys; |
| +} |