Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 311 lines (259 sloc) 11.478 kB
a6161d1 @jscheid Add support for source maps
authored
1 ;;; kite-sourcemap.el --- Kite helpers for source map decoding
2
3 ;; Copyright (C) 2012 Julian Scheid
4
5 ;; Author: Julian Scheid <julians37@gmail.com>
6 ;; Keywords: tools
7 ;; Package: kite
8 ;; Compatibility: GNU Emacs 24
9
10 ;; This file is not part of GNU Emacs.
11
12 ;; Kite is free software: you can redistribute it and/or modify it
13 ;; under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
16
17 ;; Kite is distributed in the hope that it will be useful, but WITHOUT
18 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
20 ;; License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with Kite. If not, see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26
27 ;; This package providers helper functions for decoding source maps
28 ;; and looking up mappings in them.
29 ;;
30 ;; It is mostly a transliteration of Mozilla's code found at
31 ;; https://github.com/mozilla/source-map/
32 ;;
33 ;; It is part of Kite, a WebKit inspector front-end.
34 ;;
35 ;; See also:
36 ;; * http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/
37 ;; * https://github.com/mozilla/source-map/
38 ;; * https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
39
40
41 ;;; Code:
42
2937874 @jscheid Roll back CL adaptor layer
authored
43 (require 'cl)
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
44
a6161d1 @jscheid Add support for source maps
authored
45 (defconst kite--vlq-base-shift 5)
46
47 (defconst kite--vlq-base (lsh 1 kite--vlq-base-shift))
48
49 (defconst kite--vlq-base-mask (- kite--vlq-base 1))
50
51 (defconst kite--vlq-continuation-bit kite--vlq-base)
52
53 (defconst kite--supported-source-map-version 3)
54
55 (defconst kite--base64-char-to-int-map
56 (let* ((index 0)
57 (base64-chars "\
58 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
59 (map (make-hash-table :size 64)))
60 (dolist (char
61 (string-to-list base64-chars))
62 (puthash char index map)
2937874 @jscheid Roll back CL adaptor layer
authored
63 (incf index))
a6161d1 @jscheid Add support for source maps
authored
64 map))
65
2937874 @jscheid Roll back CL adaptor layer
authored
66 (defstruct (kite-source-mapping)
a6161d1 @jscheid Add support for source maps
authored
67 "Holds the parsed mapping coordinates from the source map's
68 `mappings' attribute."
69 generated-line
70 generated-column
71 source
72 original-line
73 original-column
74 name)
75
2937874 @jscheid Roll back CL adaptor layer
authored
76 (defstruct (kite-source-map)
a6161d1 @jscheid Add support for source maps
authored
77 "Representation of a parsed source map suitable for fast
78 lookup."
79 names
80 sources
81 generated-mappings)
82
83 (defun kite--base64-decode (char)
84 "Decode a single base64 character into its corresponding
85 integer value, or raise an error if the character is invalid."
86 (or (gethash char kite--base64-char-to-int-map)
87 (error "Invalid base 64 characters: %c" char)))
88
89 (defun kite--from-vlq-signed (value)
90 "Converts to a two-complement value from a value where the sign
91 bit is is placed in the least significant bit. For example, as
92 decimals:
93 2 (10 binary) becomes 1,
94 3 (11 binary) becomes -1,
95 4 (100 binary) becomes 2,
96 5 (101 binary) becomes -2"
97 (let ((shifted (lsh value -1)))
98 (if (eq 1 (logand value 1))
99 (- shifted)
100 shifted)))
101
102 (defun kite--base64-vlq-decode (string-as-list)
103 "Decode the next base 64 VLQ value from the given
104 STRING-AS-LIST and return the value and the rest of the string as
105 values, that is a list (VALUE STRING-REST)."
106 (let ((result 0)
107 (continuation t)
108 (shift 0))
109 (while continuation
110 (when (null string-as-list)
111 (error "Expected more digits in base 64 VLQ value"))
112 (let ((digit (kite--base64-decode (car string-as-list))))
113 (setq continuation
114 (not (eq 0 (logand digit kite--vlq-continuation-bit))))
115 (setq digit (logand digit kite--vlq-base-mask))
2937874 @jscheid Roll back CL adaptor layer
authored
116 (incf result (lsh digit shift)))
117 (incf shift kite--vlq-base-shift)
a6161d1 @jscheid Add support for source maps
authored
118 (setq string-as-list (cdr string-as-list)))
119 (list :value (kite--from-vlq-signed result)
120 :rest string-as-list)))
121
122 (defun kite--source-map-decode (source-map)
123 "Decode SOURCE-MAP, which should be a deserialized JSON
124 object, and return a `kite-source-map' struct."
125
126 (when (not (eq (plist-get source-map :version)
127 kite--supported-source-map-version))
128 (error "Unsupported source map version %s"
129 (plist-get source-map :version)))
130
131 (let* ((source-root (plist-get source-map :sourceRoot))
132 (names (plist-get source-map :names))
133 (sources (plist-get source-map :sources))
134 (string (string-to-list (plist-get source-map :mappings)))
135 (result (make-kite-source-map
136 :names names
137 :sources sources))
138 (generated-mappings-list)
139 (generated-line 1)
140 (previous-generated-column 0)
141 (previous-original-line 0)
142 (previous-original-column 0)
143 (previous-source 0)
144 (previous-name 0))
2937874 @jscheid Roll back CL adaptor layer
authored
145 (flet
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
146 ((starts-with-mapping-separator (string)
147 (or (null string)
148 (eq (car string) ?,)
149 (eq (car string) ?\;))))
150 (while string
151 (cond
152 ((eq (car string) ?\;)
2937874 @jscheid Roll back CL adaptor layer
authored
153 (incf generated-line)
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
154 (setq string (cdr string))
155 (setq previous-generated-column 0))
156 ((eq (car string) ?,)
157 (setq string (cdr string)))
158 (t
159 (let ((mapping (make-kite-source-mapping
160 :generated-line generated-line)))
161
162 ;; Generated column.
163 (let ((temp (kite--base64-vlq-decode string)))
164 (setf (kite-source-mapping-generated-column mapping)
165 (+ previous-generated-column
166 (plist-get temp :value)))
167 (setq previous-generated-column
168 (kite-source-mapping-generated-column mapping))
169 (setq string (plist-get temp :rest)))
170
171 (when (not (starts-with-mapping-separator string))
172
173 ;; Original source.
174 (let ((temp (kite--base64-vlq-decode string)))
175 (setf (kite-source-mapping-source mapping)
176 (concat source-root
177 (elt sources
178 (+ previous-source
179 (plist-get temp :value)))))
2937874 @jscheid Roll back CL adaptor layer
authored
180 (incf previous-source (plist-get temp :value))
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
181 (setq string (plist-get temp :rest)))
182
183 (when (starts-with-mapping-separator string)
184 (error "Found a source, but no line and column"))
185
186 ;; Original line.
187 (let ((temp (kite--base64-vlq-decode string)))
188 (setf (kite-source-mapping-original-line mapping)
189 (+ previous-original-line
190 (plist-get temp :value)))
191 (setq previous-original-line
192 (kite-source-mapping-original-line mapping))
193
194 ;; Lines are stored 0-based
2937874 @jscheid Roll back CL adaptor layer
authored
195 (incf (kite-source-mapping-original-line mapping))
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
196
197 (setq string (plist-get temp :rest)))
198
199 (when (starts-with-mapping-separator string)
200 (error "Found a source and line, but no column"))
201
202 ;; Original column
203 (let ((temp (kite--base64-vlq-decode string)))
204 (setf (kite-source-mapping-original-column mapping)
205 (+ previous-original-column
206 (plist-get temp :value)))
207 (setq previous-original-column
208 (kite-source-mapping-original-column mapping))
209
210 (setq string (plist-get temp :rest)))
211
212 (when (not (starts-with-mapping-separator string))
213
214 ;; Original name
215 (let ((temp (kite--base64-vlq-decode string)))
216 (setf (kite-source-mapping-name mapping)
217 (elt names (+ previous-name
218 (plist-get temp :value))))
2937874 @jscheid Roll back CL adaptor layer
authored
219 (incf previous-name (plist-get temp :value))
40e90bf @jscheid Add CL compat layer, fix remaining byte compilation issues
authored
220
221 (setq string (plist-get temp :rest)))))
222
223 (push mapping generated-mappings-list)))))
224
225 (setf (kite-source-map-generated-mappings result)
226 (vconcat (nreverse generated-mappings-list))))
a6161d1 @jscheid Add support for source maps
authored
227 result))
228
229 (defun kite-source-map-original-position-for (source-map
230 line column)
231 "Given SOURCE-MAP, which should be a `kite-source-map' struct
232 as returned by `kite--source-map-decode', find the original
233 position corresponding to LINE and COLUMN. Return a plist with
234 `:source', `:line', `:column', and `:name', or nil if not
235 found."
236
237 (when (<= line 0)
238 (error "Line must be greater than or equal to 1"))
239 (when (< column 0)
240 (error "Column must be greater than or equal to 0."))
241
242 ;; This is an implementation of binary search which will always try
243 ;; and return the next lowest value checked if there is no exact
244 ;; hit. This is because mappings between original and generated
245 ;; line/col pairs are single points, and there is an implicit region
246 ;; between each of them, so a miss just means that you aren't on the
247 ;; very start of a region.
248
249 (let* ((haystack (kite-source-map-generated-mappings source-map))
250 (low -1)
251 (high (length haystack))
252 terminate
253 found)
254
255 (when (> (length source-map) 0)
256
257 ;; This terminates when one of the following is true:
258 ;;
259 ;; 1. We find the exact element we are looking for.
260 ;;
261 ;; 2. We did not find the exact element, but we can return the
262 ;; next closest element that is less than that element.
263 ;;
264 ;; 3. We did not find the exact element, and there is no
265 ;; next-closest element which is less than the one we are
266 ;; searching for, so we return null.
267 (while (not terminate)
268 (let* ((mid (floor (+ (/ (- high low) 2) low)))
269 (cur (elt haystack mid)))
270 (cond
271 ((and (eq (kite-source-mapping-generated-line cur) line)
272 (eq (kite-source-mapping-generated-column cur) column))
273 ;; Found the element we are looking for.
274 (setq found cur)
275 (setq terminate t))
276
277 ((or (< (kite-source-mapping-generated-line cur) line)
278 (and (eq (kite-source-mapping-generated-line cur) line)
279 (< (kite-source-mapping-generated-column cur)
280 column)))
281 ;; haystack[mid] is greater than our needle.
282 (if (> (- high mid) 1)
283 ;; The element is in the upper half.
284 (setq low mid)
285 ;; We did not find an exact match, return the next
286 ;; closest one (termination case 2).
287 (setq found cur)
288 (setq terminate t)))
289
290 (t
291 ;; haystack[mid] is less than our needle.
292 (if (> (- mid low) 1)
293 ;; The element is in the lower half.
294 (setq high mid)
295 ;; The exact needle element was not found in this
296 ;; haystack. Determine if we are in termination case (2)
297 ;; or (3) and return the appropriate thing.
298 (unless (< low 0)
299 (setq found (elt haystack low)))
300 (setq terminate t))))))
301
302 (when found
303 (list :source (kite-source-mapping-source found)
304 :line (kite-source-mapping-original-line found)
305 :column (kite-source-mapping-original-column found)
306 :name (kite-source-mapping-name found))))))
307
308 (provide 'kite-sourcemap)
309
310 ;;; kite-sourcemap.el ends here
Something went wrong with that request. Please try again.