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

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

Issue 814113004: Pull args, intl, logging, shelf, and source_maps out of the SDK. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Also csslib. Created 6 years 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:collection';
9 import 'dart:convert';
10
11 import 'package:source_span/source_span.dart';
12
13 import 'builder.dart' as builder;
14 import 'src/source_map_span.dart';
15 import 'src/utils.dart';
16 import 'src/vlq.dart';
17
18 /// Parses a source map directly from a json string.
19 // TODO(sigmund): evaluate whether other maps should have the json parsed, or
20 // the string represenation.
21 // TODO(tjblasi): Ignore the first line of [jsonMap] if the JSON safety string
22 // `)]}'` begins the string representation of the map.
23 Mapping parse(String jsonMap, {Map<String, Map> otherMaps}) =>
24 parseJson(JSON.decode(jsonMap), otherMaps: otherMaps);
25
26 /// Parses a source map directly from a json map object.
27 Mapping parseJson(Map map, {Map<String, Map> otherMaps}) {
28 if (map['version'] != 3) {
29 throw new ArgumentError(
30 'unexpected source map version: ${map["version"]}. '
31 'Only version 3 is supported.');
32 }
33
34 if (map.containsKey('sections')) {
35 if (map.containsKey('mappings') || map.containsKey('sources') ||
36 map.containsKey('names')) {
37 throw new FormatException('map containing "sections" '
38 'cannot contain "mappings", "sources", or "names".');
39 }
40 return new MultiSectionMapping.fromJson(map['sections'], otherMaps);
41 }
42 return new SingleMapping.fromJson(map);
43 }
44
45
46 /// A mapping parsed out of a source map.
47 abstract class Mapping {
48 /// Returns the span associated with [line] and [column].
49 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files});
50
51 /// Returns the span associated with [location].
52 SourceMapSpan spanForLocation(SourceLocation location,
53 {Map<String, SourceFile> files}) {
54 return spanFor(location.line, location.column, files: files);
55 }
56 }
57
58 /// A meta-level map containing sections.
59 class MultiSectionMapping extends Mapping {
60 /// For each section, the start line offset.
61 final List<int> _lineStart = <int>[];
62
63 /// For each section, the start column offset.
64 final List<int> _columnStart = <int>[];
65
66 /// For each section, the actual source map information, which is not adjusted
67 /// for offsets.
68 final List<Mapping> _maps = <Mapping>[];
69
70 /// Creates a section mapping from json.
71 MultiSectionMapping.fromJson(List sections, Map<String, Map> otherMaps) {
72 for (var section in sections) {
73 var offset = section['offset'];
74 if (offset == null) throw new FormatException('section missing offset');
75
76 var line = section['offset']['line'];
77 if (line == null) throw new FormatException('offset missing line');
78
79 var column = section['offset']['column'];
80 if (column == null) throw new FormatException('offset missing column');
81
82 _lineStart.add(line);
83 _columnStart.add(column);
84
85 var url = section['url'];
86 var map = section['map'];
87
88 if (url != null && map != null) {
89 throw new FormatException("section can't use both url and map entries");
90 } else if (url != null) {
91 if (otherMaps == null || otherMaps[url] == null) {
92 throw new FormatException(
93 'section contains refers to $url, but no map was '
94 'given for it. Make sure a map is passed in "otherMaps"');
95 }
96 _maps.add(parseJson(otherMaps[url], otherMaps: otherMaps));
97 } else if (map != null) {
98 _maps.add(parseJson(map, otherMaps: otherMaps));
99 } else {
100 throw new FormatException('section missing url or map');
101 }
102 }
103 if (_lineStart.length == 0) {
104 throw new FormatException('expected at least one section');
105 }
106 }
107
108 int _indexFor(line, column) {
109 for(int i = 0; i < _lineStart.length; i++) {
110 if (line < _lineStart[i]) return i - 1;
111 if (line == _lineStart[i] && column < _columnStart[i]) return i - 1;
112 }
113 return _lineStart.length - 1;
114 }
115
116 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}) {
117 int index = _indexFor(line, column);
118 return _maps[index].spanFor(
119 line - _lineStart[index], column - _columnStart[index], files: files);
120 }
121
122 String toString() {
123 var buff = new StringBuffer("$runtimeType : [");
124 for (int i = 0; i < _lineStart.length; i++) {
125 buff..write('(')
126 ..write(_lineStart[i])
127 ..write(',')
128 ..write(_columnStart[i])
129 ..write(':')
130 ..write(_maps[i])
131 ..write(')');
132 }
133 buff.write(']');
134 return buff.toString();
135 }
136 }
137
138 /// A map containing direct source mappings.
139 class SingleMapping extends Mapping {
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;
148
149 /// Url of the target file.
150 String targetUrl;
151
152 /// Source root appended to the start of all entries in [urls].
153 String sourceRoot;
154
155 SingleMapping._(this.targetUrl, this.urls, this.names, this.lines);
156
157 factory SingleMapping.fromEntries(
158 Iterable<builder.Entry> entries, [String fileUrl]) {
159 // The entries needs to be sorted by the target offsets.
160 var sourceEntries = new List.from(entries)..sort();
161 var lines = <TargetLineEntry>[];
162
163 // Indices associated with file urls that will be part of the source map. We
164 // use a linked hash-map so that `_urls.keys[_urls[u]] == u`
165 var urls = new LinkedHashMap<String, int>();
166
167 // Indices associated with identifiers that will be part of the source map.
168 // We use a linked hash-map so that `_names.keys[_names[n]] == n`
169 var names = new LinkedHashMap<String, int>();
170
171 var lineNum;
172 var targetEntries;
173 for (var sourceEntry in sourceEntries) {
174 if (lineNum == null || sourceEntry.target.line > lineNum) {
175 lineNum = sourceEntry.target.line;
176 targetEntries = <TargetEntry>[];
177 lines.add(new TargetLineEntry(lineNum, targetEntries));
178 }
179
180 if (sourceEntry.source == null) {
181 targetEntries.add(new TargetEntry(sourceEntry.target.column));
182 } else {
183 var sourceUrl = sourceEntry.source.sourceUrl;
184 var urlId = urls.putIfAbsent(
185 sourceUrl == null ? '' : sourceUrl.toString(), () => urls.length);
186 var srcNameId = sourceEntry.identifierName == null ? null :
187 names.putIfAbsent(sourceEntry.identifierName, () => names.length);
188 targetEntries.add(new TargetEntry(
189 sourceEntry.target.column,
190 urlId,
191 sourceEntry.source.line,
192 sourceEntry.source.column,
193 srcNameId));
194 }
195 }
196 return new SingleMapping._(
197 fileUrl, urls.keys.toList(), names.keys.toList(), lines);
198 }
199
200 SingleMapping.fromJson(Map map)
201 : targetUrl = map['file'],
202 urls = map['sources'],
203 names = map['names'],
204 sourceRoot = map['sourceRoot'],
205 lines = <TargetLineEntry>[] {
206 int line = 0;
207 int column = 0;
208 int srcUrlId = 0;
209 int srcLine = 0;
210 int srcColumn = 0;
211 int srcNameId = 0;
212 var tokenizer = new _MappingTokenizer(map['mappings']);
213 var entries = <TargetEntry>[];
214
215 while (tokenizer.hasTokens) {
216 if (tokenizer.nextKind.isNewLine) {
217 if (!entries.isEmpty) {
218 lines.add(new TargetLineEntry(line, entries));
219 entries = <TargetEntry>[];
220 }
221 line++;
222 column = 0;
223 tokenizer._consumeNewLine();
224 continue;
225 }
226
227 // Decode the next entry, using the previous encountered values to
228 // decode the relative values.
229 //
230 // We expect 1, 4, or 5 values. If present, values are expected in the
231 // following order:
232 // 0: the starting column in the current line of the generated file
233 // 1: the id of the original source file
234 // 2: the starting line in the original source
235 // 3: the starting column in the original source
236 // 4: the id of the original symbol name
237 // The values are relative to the previous encountered values.
238 if (tokenizer.nextKind.isNewSegment) throw _segmentError(0, line);
239 column += tokenizer._consumeValue();
240 if (!tokenizer.nextKind.isValue) {
241 entries.add(new TargetEntry(column));
242 } else {
243 srcUrlId += tokenizer._consumeValue();
244 if (srcUrlId >= urls.length) {
245 throw new StateError(
246 'Invalid source url id. $targetUrl, $line, $srcUrlId');
247 }
248 if (!tokenizer.nextKind.isValue) throw _segmentError(2, line);
249 srcLine += tokenizer._consumeValue();
250 if (!tokenizer.nextKind.isValue) throw _segmentError(3, line);
251 srcColumn += tokenizer._consumeValue();
252 if (!tokenizer.nextKind.isValue) {
253 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn));
254 } else {
255 srcNameId += tokenizer._consumeValue();
256 if (srcNameId >= names.length) {
257 throw new StateError(
258 'Invalid name id: $targetUrl, $line, $srcNameId');
259 }
260 entries.add(new TargetEntry(column, srcUrlId, srcLine, srcColumn,
261 srcNameId));
262 }
263 }
264 if (tokenizer.nextKind.isNewSegment) tokenizer._consumeNewSegment();
265 }
266 if (!entries.isEmpty) {
267 lines.add(new TargetLineEntry(line, entries));
268 }
269 }
270
271 /// Encodes the Mapping mappings as a json map.
272 Map toJson() {
273 var buff = new StringBuffer();
274 var line = 0;
275 var column = 0;
276 var srcLine = 0;
277 var srcColumn = 0;
278 var srcUrlId = 0;
279 var srcNameId = 0;
280 var first = true;
281
282 for (var entry in lines) {
283 int nextLine = entry.line;
284 if (nextLine > line) {
285 for (int i = line; i < nextLine; ++i) {
286 buff.write(';');
287 }
288 line = nextLine;
289 column = 0;
290 first = true;
291 }
292
293 for (var segment in entry.entries) {
294 if (!first) buff.write(',');
295 first = false;
296 column = _append(buff, column, segment.column);
297
298 // Encoding can be just the column offset if there is no source
299 // information.
300 var newUrlId = segment.sourceUrlId;
301 if (newUrlId == null) continue;
302 srcUrlId = _append(buff, srcUrlId, newUrlId);
303 srcLine = _append(buff, srcLine, segment.sourceLine);
304 srcColumn = _append(buff, srcColumn, segment.sourceColumn);
305
306 if (segment.sourceNameId == null) continue;
307 srcNameId = _append(buff, srcNameId, segment.sourceNameId);
308 }
309 }
310
311 var result = {
312 'version': 3,
313 'sourceRoot': sourceRoot == null ? '' : sourceRoot,
314 'sources': urls,
315 'names' : names,
316 'mappings' : buff.toString()
317 };
318 if (targetUrl != null) {
319 result['file'] = targetUrl;
320 }
321 return result;
322 }
323
324 /// Appends to [buff] a VLQ encoding of [newValue] using the difference
325 /// between [oldValue] and [newValue]
326 static int _append(StringBuffer buff, int oldValue, int newValue) {
327 buff.writeAll(encodeVlq(newValue - oldValue));
328 return newValue;
329 }
330
331 _segmentError(int seen, int line) => new StateError(
332 'Invalid entry in sourcemap, expected 1, 4, or 5'
333 ' values, but got $seen.\ntargeturl: $targetUrl, line: $line');
334
335 /// Returns [TargetLineEntry] which includes the location in the target [line]
336 /// number. In particular, the resulting entry is the last entry whose line
337 /// number is lower or equal to [line].
338 TargetLineEntry _findLine(int line) {
339 int index = binarySearch(lines, (e) => e.line > line);
340 return (index <= 0) ? null : lines[index - 1];
341 }
342
343 /// Returns [TargetEntry] which includes the location denoted by
344 /// [line], [column]. If [lineEntry] corresponds to [line], then this will be
345 /// the last entry whose column is lower or equal than [column]. If
346 /// [lineEntry] corresponds to a line prior to [line], then the result will be
347 /// the very last entry on that line.
348 TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) {
349 if (lineEntry == null || lineEntry.entries.length == 0) return null;
350 if (lineEntry.line != line) return lineEntry.entries.last;
351 var entries = lineEntry.entries;
352 int index = binarySearch(entries, (e) => e.column > column);
353 return (index <= 0) ? null : entries[index - 1];
354 }
355
356 SourceMapSpan spanFor(int line, int column, {Map<String, SourceFile> files}) {
357 var entry = _findColumn(line, column, _findLine(line));
358 if (entry == null || entry.sourceUrlId == null) return null;
359 var url = urls[entry.sourceUrlId];
360 if (sourceRoot != null) {
361 url = '${sourceRoot}${url}';
362 }
363 if (files != null && files[url] != null) {
364 var file = files[url];
365 var start = file.getOffset(entry.sourceLine, entry.sourceColumn);
366 if (entry.sourceNameId != null) {
367 var text = names[entry.sourceNameId];
368 return new SourceMapFileSpan(
369 files[url].span(start, start + text.length),
370 isIdentifier: true);
371 } else {
372 return new SourceMapFileSpan(files[url].location(start).pointSpan());
373 }
374 } else {
375 var start = new SourceLocation(0,
376 sourceUrl: url, line: entry.sourceLine, column: entry.sourceColumn);
377 // Offset and other context is not available.
378 if (entry.sourceNameId != null) {
379 return new SourceMapSpan.identifier(start, names[entry.sourceNameId]);
380 } else {
381 return new SourceMapSpan(start, start, '');
382 }
383 }
384 }
385
386 String toString() {
387 return (new StringBuffer("$runtimeType : [")
388 ..write('targetUrl: ')
389 ..write(targetUrl)
390 ..write(', sourceRoot: ')
391 ..write(sourceRoot)
392 ..write(', urls: ')
393 ..write(urls)
394 ..write(', names: ')
395 ..write(names)
396 ..write(', lines: ')
397 ..write(lines)
398 ..write(']')).toString();
399 }
400
401 String get debugString {
402 var buff = new StringBuffer();
403 for (var lineEntry in lines) {
404 var line = lineEntry.line;
405 for (var entry in lineEntry.entries) {
406 buff..write(targetUrl)
407 ..write(': ')
408 ..write(line)
409 ..write(':')
410 ..write(entry.column);
411 if (entry.sourceUrlId != null) {
412 buff..write(' --> ')
413 ..write(sourceRoot)
414 ..write(urls[entry.sourceUrlId])
415 ..write(': ')
416 ..write(entry.sourceLine)
417 ..write(':')
418 ..write(entry.sourceColumn);
419 }
420 if (entry.sourceNameId != null) {
421 buff..write(' (')
422 ..write(names[entry.sourceNameId])
423 ..write(')');
424 }
425 buff.write('\n');
426 }
427 }
428 return buff.toString();
429 }
430 }
431
432 /// A line entry read from a source map.
433 class TargetLineEntry {
434 final int line;
435 List<TargetEntry> entries;
436 TargetLineEntry(this.line, this.entries);
437
438 String toString() => '$runtimeType: $line $entries';
439 }
440
441 /// A target segment entry read from a source map
442 class TargetEntry {
443 final int column;
444 final int sourceUrlId;
445 final int sourceLine;
446 final int sourceColumn;
447 final int sourceNameId;
448
449 TargetEntry(this.column, [this.sourceUrlId, this.sourceLine,
450 this.sourceColumn, this.sourceNameId]);
451
452 String toString() => '$runtimeType: '
453 '($column, $sourceUrlId, $sourceLine, $sourceColumn, $sourceNameId)';
454 }
455
456 /** A character iterator over a string that can peek one character ahead. */
457 class _MappingTokenizer implements Iterator<String> {
458 final String _internal;
459 final int _length;
460 int index = -1;
461 _MappingTokenizer(String internal)
462 : _internal = internal,
463 _length = internal.length;
464
465 // Iterator API is used by decodeVlq to consume VLQ entries.
466 bool moveNext() => ++index < _length;
467 String get current =>
468 (index >= 0 && index < _length) ? _internal[index] : null;
469
470 bool get hasTokens => index < _length - 1 && _length > 0;
471
472 _TokenKind get nextKind {
473 if (!hasTokens) return _TokenKind.EOF;
474 var next = _internal[index + 1];
475 if (next == ';') return _TokenKind.LINE;
476 if (next == ',') return _TokenKind.SEGMENT;
477 return _TokenKind.VALUE;
478 }
479
480 int _consumeValue() => decodeVlq(this);
481 void _consumeNewLine() { ++index; }
482 void _consumeNewSegment() { ++index; }
483
484 // Print the state of the iterator, with colors indicating the current
485 // position.
486 String toString() {
487 var buff = new StringBuffer();
488 for (int i = 0; i < index; i++) {
489 buff.write(_internal[i]);
490 }
491 buff.write('');
492 buff.write(current == null ? '' : current);
493 buff.write('');
494 for (int i = index + 1; i < _internal.length; i++) {
495 buff.write(_internal[i]);
496 }
497 buff.write(' ($index)');
498 return buff.toString();
499 }
500 }
501
502 class _TokenKind {
503 static const _TokenKind LINE = const _TokenKind(isNewLine: true);
504 static const _TokenKind SEGMENT = const _TokenKind(isNewSegment: true);
505 static const _TokenKind EOF = const _TokenKind(isEof: true);
506 static const _TokenKind VALUE = const _TokenKind();
507 final bool isNewLine;
508 final bool isNewSegment;
509 final bool isEof;
510 bool get isValue => !isNewLine && !isNewSegment && !isEof;
511
512 const _TokenKind(
513 {this.isNewLine: false, this.isNewSegment: false, this.isEof: false});
514 }
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