Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(833)

Side by Side Diff: pkg/source_maps/lib/parser.dart

Issue 12207008: Move source maps package to the dart repo, so it can be used by other internal (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: updates Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/source_maps/lib/builder.dart ('k') | pkg/source_maps/lib/printer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Contains the top-level function to parse source maps version 3.
6 library source_maps.parser;
7
8 import 'dart:json' as json;
9
10 import 'span.dart';
11 import 'src/utils.dart';
12 import 'src/vlq.dart';
13
14 /// Parses a source map directly from a json string.
15 // TODO(sigmund): evaluate whether other maps should have the json parsed, or
16 // the string represenation.
17 Mapping parse(String jsonMap, {Map<String, Map> otherMaps}) =>
18 parseJson(json.parse(jsonMap), otherMaps: otherMaps);
19
20 /// Parses a source map directly from a json map object.
21 Mapping parseJson(Map map, {Map<String, Map> otherMaps}) {
22 if (map['version'] != 3) {
23 throw new ArgumentError(
24 'unexpected source map version: ${map["version"]}. '
25 'Only version 3 is supported.');
26 }
27
28 // TODO(sigmund): relax this? dart2js doesn't generate the file entry.
29 if (!map.containsKey('file')) {
30 throw new ArgumentError('missing "file" in source map');
31 }
32
33 if (map.containsKey('sections')) {
34 if (map.containsKey('mappings') || map.containsKey('sources') ||
35 map.containsKey('names')) {
36 throw new FormatException('map containing "sections" '
37 'cannot contain "mappings", "sources", or "names".');
38 }
39 return new MultiSectionMapping.fromJson(map['sections'], otherMaps);
40 }
41 return new SingleMapping.fromJson(map);
42 }
43
44
45 /// A mapping parsed our of a source map.
46 abstract class Mapping {
47 Span spanFor(int line, int column, {Map<String, SourceFile> files});
48
49 Span spanForLocation(Location loc, {Map<String, SourceFile> files}) {
50 return spanFor(loc.line, loc.column, files: files);
51 }
52 }
53
54 /// A meta-level map containing sections.
55 class MultiSectionMapping extends Mapping {
56 /// For each section, the start line offset.
57 final List<int> _lineStart = <int>[];
58
59 /// For each section, the start column offset.
60 final List<int> _columnStart = <int>[];
61
62 /// For each section, the actual source map information, which is not adjusted
63 /// for offsets.
64 final List<Mapping> _maps = <Mapping>[];
65
66 /// Creates a section mapping from json.
67 MultiSectionMapping.fromJson(List sections, Map<String, Map> otherMaps) {
68 for (var section in sections) {
69 var offset = section['offset'];
70 if (offset == null) throw new FormatException('section missing offset');
71
72 var line = section['offset']['line'];
73 if (line == null) throw new FormatException('offset missing line');
74
75 var column = section['offset']['column'];
76 if (column == null) throw new FormatException('offset missing column');
77
78 _lineStart.add(line);
79 _columnStart.add(column);
80
81 var url = section['url'];
82 var map = section['map'];
83
84 if (url != null && map != null) {
85 throw new FormatException("section can't use both url and map entries");
86 } else if (url != null) {
87 if (otherMaps == null || otherMaps[url] == null) {
88 throw new FormatException(
89 'section contains refers to $url, but no map was '
90 'given for it. Make sure a map is passed in "otherMaps"');
91 }
92 _maps.add(parseJson(otherMaps[url], otherMaps: otherMaps));
93 } else if (map != null) {
94 _maps.add(parseJson(map, otherMaps: otherMaps));
95 } else {
96 throw new FormatException('section missing url or map');
97 }
98 }
99 if (_lineStart.length == 0) {
100 throw new FormatException('expected at least one section');
101 }
102 }
103
104 int _indexFor(line, column) {
105 for(int i = 0; i < _lineStart.length; i++) {
106 if (line < _lineStart[i]) return i - 1;
107 if (line == _lineStart[i] && column < _columnStart[i]) return i - 1;
108 }
109 return _lineStart.length - 1;
110 }
111
112 Span spanFor(int line, int column, {Map<String, SourceFile> files}) {
113 int index = _indexFor(line, column);
114 return _maps[index].spanFor(
115 line - _lineStart[index], column - _columnStart[index], files: files);
116 }
117
118 String toString() {
119 var buff = new StringBuffer("$runtimeType : [");
120 for (int i = 0; i < _lineStart.length; i++) {
121 buff..write('(')
122 ..write(_lineStart[i])
123 ..write(',')
124 ..write(_columnStart[i])
125 ..write(':')
126 ..write(_maps[i])
127 ..write(')');
128 }
129 buff.write(']');
130 return buff.toString();
131 }
132 }
133
134 /// A map containing direct source mappings.
135 // TODO(sigmund): integrate mapping and sourcemap builder?
136 class SingleMapping extends Mapping {
137 /// Url of the target file.
138 final String targetUrl;
139
140 /// Source urls used in the mapping, indexed by id.
141 final List<String> urls;
142
143 /// Source names used in the mapping, indexed by id.
144 final List<String> names;
145
146 /// Entries indicating the beginning of each span.
147 final List<TargetLineEntry> lines = <TargetLineEntry>[];
148
149 SingleMapping.fromJson(Map map)
150 : targetUrl = map['file'],
151 // TODO(sigmund): add support for 'sourceRoot'
152 urls = map['sources'],
153 names = map['names'] {
154 int line = 0;
155 int column = 0;
156 int srcUrlId = 0;
157 int srcLine = 0;
158 int srcColumn = 0;
159 int srcNameId = 0;
160 var tokenizer = new _MappingTokenizer(map['mappings']);
161 var entries = <TargetEntry>[];
162
163 while (tokenizer.hasTokens) {
164 if (tokenizer.nextKind.isNewLine) {
165 if (!entries.isEmpty) {
166 lines.add(new TargetLineEntry(line, entries));
167 entries = <TargetEntry>[];
168 }
169 line++;
170 column = 0;
171 tokenizer._consumeNewLine();
172 continue;
173 }
174
175 // Decode the next entry, using the previous encountered values to
176 // decode the relative values.
177 //
178 // We expect 1, 4, or 5 values. If present, values are expected in the
179 // following order:
180 // 0: the starting column in the current line of the generated file
181 // 1: the id of the original source file
182 // 2: the starting line in the original source
183 // 3: the starting column in the original source
184 // 4: the id of the original symbol name
185 // The values are relative to the previous encountered values.
186 if (tokenizer.nextKind.isNewSegment) throw _segmentError(0, line);
187 column += tokenizer._consumeValue();
188 if (!tokenizer.nextKind.isValue) {
189 entries.add(new TargetEntry(column));
190 } else {
191 srcUrlId += tokenizer._consumeValue();
192 if (srcUrlId >= urls.length) {
193 throw new StateError(
194 'Invalid source url id. $targetUrl, $line, $srcUrlId');
195 }
196 if (!tokenizer.nextKind.isValue) throw _segmentError(2, line);
197 srcLine += tokenizer._consumeValue();
198 if (!tokenizer.nextKind.isValue) throw _segmentError(3, line);
199 srcColumn += tokenizer._consumeValue();
200 if (!tokenizer.nextKind.isValue) {
201 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn));
202 } else {
203 srcNameId += tokenizer._consumeValue();
204 if (srcNameId >= names.length) {
205 throw new StateError(
206 'Invalid name id: $targetUrl, $line, $srcNameId');
207 }
208 entries.add(
209 new TargetEntry(column, srcUrlId, srcLine, srcColumn, srcNameId));
210 }
211 }
212 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment();
213 }
214 if (!entries.isEmpty) {
215 lines.add(new TargetLineEntry(line, entries));
216 }
217 }
218
219 _segmentError(int seen, int line) => new StateError(
220 'Invalid entry in sourcemap, expected 1, 4, or 5'
221 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line');
222
223 /// Returns [TargetLineEntry] which includes the location in the target [line]
224 /// number. In particular, the resulting entry is the last entry whose line
225 /// number is lower or equal to [line].
226 TargetLineEntry _findLine(int line) {
227 int index = binarySearch(lines, (e) => e.line > line);
228 return (index <= 0) ? null : lines[index - 1];
229 }
230
231 /// Returns [TargetEntry] which includes the location denoted by
232 /// [line], [column]. If [lineEntry] corresponds to [line], then this will be
233 /// the last entry whose column is lower or equal than [column]. If
234 /// [lineEntry] corresponds to a line prior to [line], then the result will be
235 /// the very last entry on that line.
236 TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) {
237 if (lineEntry == null || lineEntry.entries.length == 0) return null;
238 if (lineEntry.line != line) return lineEntry.entries.last;
239 var entries = lineEntry.entries;
240 int index = binarySearch(entries, (e) => e.column > column);
241 return (index <= 0) ? null : entries[index - 1];
242 }
243
244 Span spanFor(int line, int column, {Map<String, SourceFile> files}) {
245 var lineEntry = _findLine(line);
246 var entry = _findColumn(line, column, _findLine(line));
247 if (entry == null) return null;
248 var url = urls[entry.sourceUrlId];
249 if (files != null && files[url] != null) {
250 var file = files[url];
251 var start = file.getOffset(entry.sourceLine, entry.sourceColumn);
252 if (entry.sourceNameId != null) {
253 var text = names[entry.sourceNameId];
254 return new FileSpan(files[url], start, start + text.length, true);
255 } else {
256 return new FileSpan(files[url], start);
257 }
258 } else {
259 // Offset and other context is not available.
260 if (entry.sourceNameId != null) {
261 return new FixedSpan(url, 0, entry.sourceLine, entry.sourceColumn,
262 text: names[entry.sourceNameId], isIdentifier: true);
263 } else {
264 return new FixedSpan(url, 0, entry.sourceLine, entry.sourceColumn);
265 }
266 }
267 }
268
269 String toString() {
270 return (new StringBuffer("$runtimeType : [")
271 ..write('targetUrl: ')
272 ..write(targetUrl)
273 ..write(', urls: ')
274 ..write(urls)
275 ..write(', names: ')
276 ..write(names)
277 ..write(', lines: ')
278 ..write(lines)
279 ..write(']')).toString();
280 }
281
282 String get debugString {
283 var buff = new StringBuffer();
284 for (var lineEntry in lines) {
285 var line = lineEntry.line;
286 for (var entry in lineEntry.entries) {
287 buff..write(targetUrl)
288 ..write(': ')
289 ..write(line)
290 ..write(':')
291 ..write(entry.column)
292 ..write(' --> ')
293 ..write(urls[entry.sourceUrlId])
294 ..write(': ')
295 ..write(entry.sourceLine)
296 ..write(':')
297 ..write(entry.sourceColumn);
298 if (entry.sourceNameId != null) {
299 buff..write(' (')
300 ..write(names[entry.sourceNameId])
301 ..write(')');
302 }
303 buff.write('\n');
304 }
305 }
306 return buff.toString();
307 }
308 }
309
310 /// A line entry read from a source map.
311 class TargetLineEntry {
312 final int line;
313 List<TargetEntry> entries = <TargetEntry>[];
314 TargetLineEntry(this.line, this.entries);
315
316 String toString() => '$runtimeType: $line $entries';
317 }
318
319 /// A target segment entry read from a source map
320 class TargetEntry {
321 final int column;
322 final int sourceUrlId;
323 final int sourceLine;
324 final int sourceColumn;
325 final int sourceNameId;
326 TargetEntry(this.column, [this.sourceUrlId, this.sourceLine,
327 this.sourceColumn, this.sourceNameId]);
328
329 String toString() => '$runtimeType: '
330 '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)';
331 }
332
333 /** A character iterator over a string that can peek one character ahead. */
334 class _MappingTokenizer implements Iterator<String> {
335 final String _internal;
336 final int _length;
337 int index = -1;
338 _MappingTokenizer(String internal)
339 : _internal = internal,
340 _length = internal.length;
341
342 // Iterator API is used by decodeVlq to consume VLQ entries.
343 bool moveNext() => ++index < _length;
344 String get current =>
345 (index >= 0 && index < _length) ? _internal[index] : null;
346
347 bool get hasTokens => index < _length - 1 && _length > 0;
348
349 _TokenKind get nextKind {
350 if (!hasTokens) return _TokenKind.EOF;
351 var next = _internal[index + 1];
352 if (next == ';') return _TokenKind.LINE;
353 if (next == ',') return _TokenKind.SEGMENT;
354 return _TokenKind.VALUE;
355 }
356
357 int _consumeValue() => decodeVlq(this);
358 void _consumeNewLine() { ++index; }
359 void _consumeNewSegment() { ++index; }
360
361 // Print the state of the iterator, with colors indicating the current
362 // position.
363 String toString() {
364 var buff = new StringBuffer();
365 for (int i = 0; i < index; i++) {
366 buff.write(_internal[i]);
367 }
368 buff.write('');
369 buff.write(current == null ? '' : current);
370 buff.write('');
371 for (int i = index + 1; i < _internal.length; i++) {
372 buff.write(_internal[i]);
373 }
374 buff.write(' ($index)');
375 return buff.toString();
376 }
377 }
378
379 class _TokenKind {
380 static const _TokenKind LINE = const _TokenKind(isNewLine: true);
381 static const _TokenKind SEGMENT = const _TokenKind(isNewSegment: true);
382 static const _TokenKind EOF = const _TokenKind(isEof: true);
383 static const _TokenKind VALUE = const _TokenKind();
384 final bool isNewLine;
385 final bool isNewSegment;
386 final bool isEof;
387 bool get isValue => !isNewLine && !isNewSegment && !isEof;
388
389 const _TokenKind(
390 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false});
391 }
OLDNEW
« no previous file with comments | « pkg/source_maps/lib/builder.dart ('k') | pkg/source_maps/lib/printer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698