Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 311 lines (259 sloc) 11.478 kb
a6161d1 Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
43 (require 'cl)
40e90bf Julian Scheid Add CL compat layer, fix remaining byte compilation issues
authored
44
a6161d1 Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
63 (incf index))
a6161d1 Julian Scheid Add support for source maps
authored
64 map))
65
2937874 Julian Scheid Roll back CL adaptor layer
authored
66 (defstruct (kite-source-mapping)
a6161d1 Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
76 (defstruct (kite-source-map)
a6161d1 Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
116 (incf result (lsh digit shift)))
117 (incf shift kite--vlq-base-shift)
a6161d1 Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
145 (flet
40e90bf Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
153 (incf generated-line)
40e90bf Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
180 (incf previous-source (plist-get temp :value))
40e90bf Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
195 (incf (kite-source-mapping-original-line mapping))
40e90bf Julian Scheid 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 Julian Scheid Roll back CL adaptor layer
authored
219 (incf previous-name (plist-get temp :value))
40e90bf Julian Scheid 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 Julian Scheid 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.