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

Side by Side Diff: client/tools/sourcemap.py

Issue 9837113: Move update.py to samples/swarm, remove a bunch of deprecated files for (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 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 | « client/tools/show_coverage.js ('k') | client/tools/update.py » ('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) 2011, 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 """Utilities to extract source map information
6
7 A set of utilities to extract source map information. This python library
8 consumes source map files v3, but doesn't provide any funcitonality to produce
9 source maps. This code is an adaptation of the Java implementation originally
10 written for the closure compiler by John Lenz (johnlenz@google.com)
11 """
12
13 import bisect
14 import json
15 import sys
16
17 class SourceMap():
18 """ An in memory representation of a source map. """
19
20 def get_source_location(self, line, column):
21 """ Fetches the original location for a line and column.
22
23 Args:
24 line: line in the output file to query
25 column: column in the output file to query
26
27 Returns:
28 A tuple of the form:
29 file, src_line, src_column, opt_identifier
30 When available, opt_identifier contains the name of an identifier
31 associated with the given program location.
32 """
33 pass # implemented by subclasses
34
35 def parse(sourcemap_file):
36 """ Parse a file containing source map information as a json string and return
37 a source map object representing it.
38 Args:
39 sourcemap_file: path to a file (optionally containing a 'file:' prefix)
40 which can either contain a meta-level source map or an
41 actual source map.
42 """
43 if sourcemap_file.startswith('file:'):
44 sourcemap_file = sourcemap_file[5:]
45
46 with open(sourcemap_file, 'r') as f:
47 sourcemap_json = json.load(f)
48
49 return _parseFromJson(sourcemap_json)
50
51
52 def _parseFromJson(sourcemap_json):
53 if sourcemap_json['version'] != 3:
54 raise SourceMapException("unexpected source map version")
55
56 if not 'file' in sourcemap_json:
57 raise SourceMapException("unexpected, no file in source map file")
58
59 if 'sections' in sourcemap_json:
60 sections = sourcemap_json['sections']
61 # a meta file
62 if ('mappings' in sourcemap_json
63 or 'sources' in sourcemap_json
64 or 'names' in sourcemap_json):
65 raise SourceMapException("Invalid map format")
66 return _MetaSourceMap(sections)
67
68 return _SourceMapFile(sourcemap_json)
69
70 class _MetaSourceMap(SourceMap):
71 """ A higher-order source map containing nested source maps. """
72
73 def __init__(self, sections):
74 """ creates a source map instance given its json input (already parsed). """
75 # parse a regular sourcemap file
76 self.offsets = []
77 self.maps = []
78
79 for section in sections:
80 line = section['offset']['line']
81 if section['offset']['column'] != 0:
82 # TODO(sigmund): implement if needed
83 raise Exception("unimplemented")
84
85 if 'url' in section and 'map' in section:
86 raise SourceMapException(
87 "Invalid format: section may not contain both 'url' and 'map'")
88
89 self.offsets.append(line)
90 if 'url' in section:
91 self.maps.append(parse(section['url']))
92 elif 'map' in section:
93 self.maps.append(_parseFromJson(section['map']))
94 else:
95 raise SourceMapException(
96 "Invalid format: section must contain either 'url' or 'map'")
97
98 def get_source_location(self, line, column):
99 """ Fetches the original location from the target location. """
100 index = bisect.bisect(self.offsets, line) - 1
101 return self.maps[index].get_source_location(
102 line - self.offsets[index], column)
103
104 class _SourceMapFile(SourceMap):
105 def __init__(self, sourcemap):
106 """ creates a source map instance given its json input (already parsed). """
107 # parse a regular sourcemap file
108 self.sourcemap_file = sourcemap['file']
109 self.sources = sourcemap['sources']
110 self.names = sourcemap['names']
111 self.lines = []
112 self._build(sourcemap['mappings'])
113
114 def get_source_location(self, line, column):
115 """ Fetches the original location from the target location. """
116
117 # Normalize the line and column numbers to 0.
118 line -= 1
119 column -= 1
120
121 if line < 0 or line >= len(self.lines):
122 return None
123
124 entries = self.lines[line]
125 # If the line is empty return the previous mapping.
126 if not entries or entries == [] or entries[0].gen_column > column:
127 return self._previousMapping(line)
128
129 index = bisect.bisect(entries, _Entry(column)) - 1
130 return self._originalEntryMapping(entries[index])
131
132 def _previousMapping(self, line):
133 while True:
134 if line == 0:
135 return None
136 line -= 1
137 if self.lines[line]:
138 return self._originalEntryMapping(self.lines[line][-1])
139
140 def _originalEntryMapping(self, entry):
141 if entry.src_file_id is None:
142 return None
143
144 if entry.name_id:
145 identifier = self.names[entry.name_id]
146 else:
147 identifier = None
148
149 filename = self.sources[entry.src_file_id]
150 return filename, entry.src_line, entry.src_column, identifier
151
152 def _build(self, linemap):
153 """ builds this source map from the sourcemap json """
154 entries = []
155 line = 0
156 prev_col = 0
157 prev_src_id = 0
158 prev_src_line = 0
159 prev_src_column = 0
160 prev_name_id = 0
161 content = _StringCharIterator(linemap)
162 while content.hasNext():
163 # ';' denotes a new line.
164 token = content.peek()
165 if token == ';':
166 content.next()
167 # The line is complete, store the result for the line, None if empty.
168 result = entries if len(entries) > 0 else None
169 self.lines.append(result)
170 entries = []
171 line += 1
172 prev_col = 0
173 else:
174 # Grab the next entry for the current line.
175 values = []
176 while (content.hasNext()
177 and content.peek() != ',' and content.peek() != ';'):
178 values.append(_Base64VLQDecode(content))
179
180 # Decodes the next entry, using the previous encountered values to
181 # decode the relative values.
182 #
183 # The values, if present are in the following order:
184 # 0: the starting column in the current line of the generated file
185 # 1: the id of the original source file
186 # 2: the starting line in the original source
187 # 3: the starting column in the original source
188 # 4: the id of the original symbol name
189 # The values are relative to the previous encountered values.
190
191 total = len(values)
192 if not(total == 1 or total == 4 or total == 5):
193 raise SourceMapException(
194 "Invalid entry in source map file: %s\nline: %d\nvalues: %s\n"
195 % (self.sourcemap_file, line, str(values)))
196 prev_col += values[0]
197 if total == 1:
198 entry = _Entry(prev_col)
199 else:
200 prev_src_id += values[1]
201 if prev_src_id >= len(self.sources):
202 raise SourceMapException(
203 "Invalid source id\nfile: %s\nline: %d\nid: %d\n"
204 % (self.sourcemap_file, line, prev_src_id))
205 prev_src_line += values[2]
206 prev_src_column += values[3]
207 if total == 4:
208 entry = _Entry(
209 prev_col, prev_src_id, prev_src_line, prev_src_column)
210 elif total == 5:
211 prev_name_id += values[4]
212 if prev_name_id >= len(self.names):
213 raise SourceMapException(
214 "Invalid name id\nfile: %s\nline: %d\nid: %d\n"
215 % (self.sourcemap_file, line, prev_name_id))
216 entry = _Entry(
217 prev_col, prev_src_id, prev_src_line, prev_src_column,
218 prev_name_id)
219 entries.append(entry);
220 if content.peek() == ',':
221 content.next()
222
223 class _StringCharIterator():
224 """ An iterator over a string that allows you to peek into the next value. """
225 def __init__(self, string):
226 self.string = string
227 self.length = len(string)
228 self.current = 0
229
230 def __iter__(self):
231 return self
232
233 def next(self):
234 res = self.string[self.current]
235 self.current += 1
236 return res
237
238 def peek(self):
239 return self.string[self.current]
240
241 def hasNext(self):
242 return self.current < self.length
243
244
245 # Base64VLQ decoding
246
247 VLQ_BASE_SHIFT = 5
248 VLQ_BASE = 1 << VLQ_BASE_SHIFT
249 VLQ_BASE_MASK = VLQ_BASE - 1
250 VLQ_CONTINUATION_BIT = VLQ_BASE
251 BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
252 BASE64_DECODE_MAP = dict()
253 for c in range(64):
254 BASE64_DECODE_MAP[BASE64_MAP[c]] = c
255
256 def _Base64VLQDecode(iterator):
257 """
258 Decodes the next VLQValue from the provided char iterator.
259
260 Sourcemaps are encoded with variable length numbers as base64 encoded strings
261 with the least significant digit coming first. Each base64 digit encodes a
262 5-bit value (0-31) and a continuation bit. Signed values can be represented
263 by using the least significant bit of the value as the
264 sign bit.
265
266 This function only contains the decoding logic, since the encoding logic is
267 only needed to produce source maps.
268
269 Args:
270 iterator: a _StringCharIterator
271 """
272 result = 0
273 stop = False
274 shift = 0
275 while not stop:
276 c = iterator.next()
277 if c not in BASE64_DECODE_MAP:
278 raise Exception("%s not a valid char" % c)
279 digit = BASE64_DECODE_MAP[c]
280 stop = digit & VLQ_CONTINUATION_BIT == 0
281 digit &= VLQ_BASE_MASK
282 result += (digit << shift)
283 shift += VLQ_BASE_SHIFT
284
285 # Result uses the least significant bit as a sign bit. We convert it into a
286 # two-complement value. For example,
287 # 2 (10 binary) becomes 1
288 # 3 (11 binary) becomes -1
289 # 4 (100 binary) becomes 2
290 # 5 (101 binary) becomes -2
291 # 6 (110 binary) becomes 3
292 # 7 (111 binary) becomes -3
293 negate = (result & 1) == 1
294 result = result >> 1
295 return -result if negate else result
296
297
298 ERROR_DETAILS ="""
299 - gen_column = %s
300 - src_file_id = %s
301 - src_line = %s
302 - src_column = %s
303 - name_id = %s
304 """
305
306 class _Entry():
307 """ An entry in a source map file. """
308 def __init__(self, gen_column,
309 src_file_id=None,
310 src_line=None,
311 src_column=None,
312 name_id=None):
313 """ Creates an entry. Many arguments are marked as optional, but we expect
314 either all being None, or only name_id being none.
315 """
316
317 # gen column must be defined:
318 if gen_column is None:
319 raise SourceMapException(
320 "Invalid entry, no gen_column specified:" +
321 ERROR_DETAILS % (
322 gen_column, src_file_id, src_line, src_column, name_id))
323
324 # if any field other than gen_column is defined, then file_id, line, and
325 # column must be defined:
326 if ((src_file_id is not None or src_line is not None or
327 src_column is not None or name_id is not None) and
328 (src_file_id is None or src_line is None or src_column is None)):
329 raise SourceMapException(
330 "Invalid entry, only name_id is optional:" +
331 ERROR_DETAILS % (
332 gen_column, src_file_id, src_line, src_column, name_id))
333
334 self.gen_column = gen_column
335 self.src_file_id = src_file_id
336 self.src_line = src_line
337 self.src_column = src_column
338 self.name_id = name_id
339
340 # define comparison to perform binary search on lookups
341 def __cmp__(self, other):
342 return cmp(self.gen_column, other.gen_column)
343
344 class SourceMapException(Exception):
345 """ An exception encountered while parsing or processing source map files."""
346 pass
347
348 def main():
349 """ This module is intended to be used as a library. Main is provided to
350 test the functionality on the command line.
351 """
352 if len(sys.argv) < 3:
353 print ("Usage: %s <mapfile> line [column]" % sys.argv[0])
354 return 1
355
356 sourcemap = parse(sys.argv[1])
357 line = int(sys.argv[2])
358 column = int(sys.argv[3]) if len(sys.argv) > 3 else 1
359 original = sourcemap.get_source_location(line, column)
360 if not original:
361 print "Source location not found"
362 else:
363 filename, srcline, srccolumn, srcid = original
364 print "Source location is: %s, line: %d, column: %d, identifier: %s" % (
365 filename, srcline, srccolumn, srcid)
366
367 if __name__ == '__main__':
368 sys.exit(main())
OLDNEW
« no previous file with comments | « client/tools/show_coverage.js ('k') | client/tools/update.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698