Mercurial > dotfiles
comparison .elisp/js2.el @ 27:614a83a1c5dd
Add js2-mode.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Tue, 30 Dec 2008 13:25:23 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
26:895bcf0db86a | 27:614a83a1c5dd |
---|---|
1 ;;; js2.el -- an improved JavaScript editing mode | |
2 ;;; | |
3 ;;; This file was auto-generated on Mon Jun 16 01:46:45 2008 from files: | |
4 ;;; js2-vars.el | |
5 ;;; js2-util.el | |
6 ;;; js2-scan.el | |
7 ;;; js2-messages.el | |
8 ;;; js2-ast.el | |
9 ;;; js2-highlight.el | |
10 ;;; js2-browse.el | |
11 ;;; js2-parse.el | |
12 ;;; js2-indent.el | |
13 ;;; js2-mode.el | |
14 | |
15 ;;; js2-mode.el --- an improved JavaScript editing mode | |
16 | |
17 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
18 ;; Version: 20080616 | |
19 ;; Keywords: javascript languages | |
20 | |
21 ;; This program is free software; you can redistribute it and/or | |
22 ;; modify it under the terms of the GNU General Public License as | |
23 ;; published by the Free Software Foundation; either version 2 of | |
24 ;; the License, or (at your option) any later version. | |
25 | |
26 ;; This program is distributed in the hope that it will be | |
27 ;; useful, but WITHOUT ANY WARRANTY; without even the implied | |
28 ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
29 ;; PURPOSE. See the GNU General Public License for more details. | |
30 | |
31 ;; You should have received a copy of the GNU General Public | |
32 ;; License along with this program; if not, write to the Free | |
33 ;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
34 ;; MA 02111-1307 USA | |
35 | |
36 ;;; Commentary: | |
37 | |
38 ;; This JavaScript editing mode supports: | |
39 ;; | |
40 ;; - the full JavaScript language through version 1.7 | |
41 ;; - support for most Rhino and SpiderMonkey extensions from 1.5 to 1.7 | |
42 ;; - accurate syntax highlighting using a recursive-descent parser | |
43 ;; - syntax-error and strict-mode warning reporting | |
44 ;; - "bouncing" line indentation to choose among alternate indentation points | |
45 ;; - smart line-wrapping within comments (Emacs 22+) and strings | |
46 ;; - code folding: | |
47 ;; - show some or all function bodies as {...} | |
48 ;; - show some or all block comments as /*...*/ | |
49 ;; - context-sensitive menu bar and popup menus | |
50 ;; - code browsing using the `imenu' package | |
51 ;; - typing helpers (e.g. inserting matching braces/parens) | |
52 ;; - many customization options | |
53 ;; | |
54 ;; It is only compatible with GNU Emacs versions 21 and higher (not XEmacs). | |
55 ;; | |
56 ;; Installation: | |
57 ;; | |
58 ;; - put `js2.el' somewhere in your emacs load path | |
59 ;; - M-x byte-compile-file RET <path-to-js2.el> RET | |
60 ;; Note: it will refuse to run unless byte-compiled | |
61 ;; - add these lines to your .emacs file: | |
62 ;; (autoload 'js2-mode "js2" nil t) | |
63 ;; (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) | |
64 ;; | |
65 ;; To customize how it works: | |
66 ;; M-x customize-group RET js2-mode RET | |
67 ;; | |
68 ;; The variable `js2-mode-version' is a date stamp. When you upgrade | |
69 ;; to a newer version, you must byte-compile the file again. | |
70 ;; | |
71 ;; Notes: | |
72 ;; | |
73 ;; This mode is different in many ways from standard Emacs language editing | |
74 ;; modes, inasmuch as it attempts to be more like an IDE. If this drives | |
75 ;; you crazy, it IS possible to customize it to be more like other Emacs | |
76 ;; editing modes. Please customize the group `js2-mode' to see all of the | |
77 ;; configuration options. | |
78 ;; | |
79 ;; Some of the functionality does not work in Emacs 21 -- upgrading to | |
80 ;; Emacs 22 or higher will get you better results. If you byte-compiled | |
81 ;; js2.el with Emacs 21, you should re-compile it for Emacs 22. | |
82 ;; | |
83 ;; Unlike cc-engine based language modes, js2-mode's line-indentation is not | |
84 ;; customizable. It is a surprising amount of work to support customizable | |
85 ;; indentation. The current compromise is that the tab key lets you cycle among | |
86 ;; various likely indentation points, similar to the behavior of python-mode. | |
87 ;; | |
88 ;; This mode does not yet work with "multi-mode" modes such as mmm-mode | |
89 ;; and mumamo, although it could possibly be made to do so with some effort. | |
90 ;; This means that js2-mode is currently only useful for editing JavaScript | |
91 ;; files, and not for editing JavaScript within <script> tags or templates. | |
92 ;; | |
93 ;; This code is part of a larger project, in progress, to enable writing | |
94 ;; Emacs customizations in JavaScript. | |
95 ;; | |
96 ;; Please email bug reports and suggestions to the author, or submit them | |
97 ;; at http://code.google.com/p/js2-mode/issues | |
98 | |
99 ;; TODO: | |
100 ;; - add unreachable-code warning (error?) using the inconsistent-return analysis | |
101 ;; - labeled stmt length is now 1 | |
102 ;; - "anonymous function does not always return a value" - use getter/setter name | |
103 ;; - extend js2-missing-semi-one-line-override to handle catch (e) {return x} | |
104 ;; - set a text prop on autoinserted delimiters and don't biff user-entered ones | |
105 ;; - when inserting magic curlies, look for matching close-curly before inserting | |
106 ;; - get more use out of the symbol table: | |
107 ;; - jump to declaration (put hyperlinks on all non-decl var usages?) | |
108 ;; - rename variable/function | |
109 ;; - warn on unused var | |
110 ;; - add some dabbrev-expansions for built-in keywords like finally, function | |
111 ;; - add at least some completion support, e.g. for built-ins | |
112 ;; - code formatting | |
113 | |
114 ;;; Code: | |
115 ;;; js2-vars.el -- byte-compiler support for js2-mode | |
116 | |
117 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
118 ;; Keywords: javascript languages | |
119 | |
120 ;;; Code: | |
121 | |
122 (eval-when-compile | |
123 (require 'cl)) | |
124 | |
125 (eval-and-compile | |
126 (require 'cc-mode) ; (only) for `c-populate-syntax-table' | |
127 (require 'cc-langs) ; it's here in Emacs 21... | |
128 (require 'cc-engine)) ; for `c-paragraph-start' et. al. | |
129 | |
130 (defvar js2-emacs22 (>= emacs-major-version 22)) | |
131 | |
132 (defcustom js2-highlight-level 2 | |
133 "Amount of syntax highlighting to perform. | |
134 nil, zero or negative means none. | |
135 1 adds basic syntax highlighting. | |
136 2 adds highlighting of some Ecma built-in properties. | |
137 3 adds highlighting of many Ecma built-in functions." | |
138 :type 'integer | |
139 :group 'js2-mode) | |
140 | |
141 (defvar js2-mode-dev-mode-p nil | |
142 "Non-nil if running in development mode. Normally nil.") | |
143 | |
144 (defgroup js2-mode nil | |
145 "An improved JavaScript mode." | |
146 :group 'languages) | |
147 | |
148 (defcustom js2-basic-offset (if (and (boundp 'c-basic-offset) | |
149 (numberp c-basic-offset)) | |
150 c-basic-offset | |
151 2) | |
152 "Number of spaces to indent nested statements. | |
153 Similar to `c-basic-offset'." | |
154 :group 'js2-mode | |
155 :type 'integer) | |
156 (make-variable-buffer-local 'js2-basic-offset) | |
157 | |
158 (defcustom js2-cleanup-whitespace t | |
159 "Non-nil to invoke `delete-trailing-whitespace' before saves." | |
160 :type 'boolean | |
161 :group 'js2-mode) | |
162 | |
163 (defcustom js2-move-point-on-right-click t | |
164 "Non-nil to move insertion point when you right-click. | |
165 This makes right-click context menu behavior a bit more intuitive, | |
166 since menu operations generally apply to the point. The exception | |
167 is if there is a region selection, in which case the point does -not- | |
168 move, so cut/copy/paste etc. can work properly. | |
169 | |
170 Note that IntelliJ moves the point, and Eclipse leaves it alone, | |
171 so this behavior is customizable." | |
172 :group 'js2-mode | |
173 :type 'boolean) | |
174 | |
175 (defcustom js2-mirror-mode t | |
176 "Non-nil to insert closing brackets, parens, etc. automatically." | |
177 :group 'js2-mode | |
178 :type 'boolean) | |
179 | |
180 (defcustom js2-auto-indent-flag t | |
181 "Automatic indentation with punctuation characters. If non-nil, the | |
182 current line is indented when certain punctuations are inserted." | |
183 :group 'js2-mode | |
184 :type 'boolean) | |
185 | |
186 (defcustom js2-bounce-indent-flag t | |
187 "Non-nil to have indent-line function choose among alternatives. | |
188 If nil, the indent-line function will indent to a predetermined column | |
189 based on heuristic guessing. If non-nil, then if the current line is | |
190 already indented to that predetermined column, indenting will choose | |
191 another likely column and indent to that spot. Repeated invocation of | |
192 the indent-line function will cycle among the computed alternatives. | |
193 See the function `js2-bounce-indent' for details." | |
194 :type 'boolean | |
195 :group 'js2-mode) | |
196 | |
197 (defcustom js2-indent-on-enter-key nil | |
198 "Non-nil to have Enter/Return key indent the line. | |
199 This is unusual for Emacs modes but common in IDEs like Eclipse." | |
200 :type 'boolean | |
201 :group 'js2-mode) | |
202 | |
203 (defcustom js2-enter-indents-newline t | |
204 "Non-nil to have Enter/Return key indent the newly-inserted line. | |
205 This is unusual for Emacs modes but common in IDEs like Eclipse." | |
206 :type 'boolean | |
207 :group 'js2-mode) | |
208 | |
209 (defcustom js2-rebind-eol-bol-keys t | |
210 "Non-nil to rebind beginning-of-line and end-of-line keys. | |
211 If non-nil, bounce between bol/eol and first/last non-whitespace char." | |
212 :group 'js2-mode | |
213 :type 'boolean) | |
214 | |
215 (defcustom js2-electric-keys '("{" "}" "(" ")" "[" "]" ":" ";" "," "*") | |
216 "Keys that auto-indent when `js2-auto-indent-flag' is non-nil. | |
217 Each value in the list is passed to `define-key'." | |
218 :type 'list | |
219 :group 'js2-mode) | |
220 | |
221 (defcustom js2-idle-timer-delay 0.2 | |
222 "Delay in secs before re-parsing after user makes changes. | |
223 Multiplied by `js2-dynamic-idle-timer-adjust', which see." | |
224 :type 'number | |
225 :group 'js2-mode) | |
226 (make-variable-buffer-local 'js2-idle-timer-delay) | |
227 | |
228 (defcustom js2-dynamic-idle-timer-adjust 0 | |
229 "Positive to adjust `js2-idle-timer-delay' based on file size. | |
230 The idea is that for short files, parsing is faster so we can be | |
231 more responsive to user edits without interfering with editing. | |
232 The buffer length in characters (typically bytes) is divided by | |
233 this value and used to multiply `js2-idle-timer-delay' for the | |
234 buffer. For example, a 21k file and 10k adjust yields 21k/10k | |
235 == 2, so js2-idle-timer-delay is multiplied by 2. | |
236 If `js2-dynamic-idle-timer-adjust' is 0 or negative, | |
237 `js2-idle-timer-delay' is not dependent on the file size." | |
238 :type 'number | |
239 :group 'js2-mode) | |
240 | |
241 (defcustom js2-mode-escape-quotes t | |
242 "Non-nil to disable automatic quote-escaping inside strings." | |
243 :type 'boolean | |
244 :group 'js2-mode) | |
245 | |
246 (defcustom js2-mode-squeeze-spaces t | |
247 "Non-nil to normalize whitespace when filling in comments. | |
248 Multiple runs of spaces are converted to a single space." | |
249 :type 'boolean | |
250 :group 'js2-mode) | |
251 | |
252 (defcustom js2-mode-show-parse-errors t | |
253 "True to highlight parse errors." | |
254 :type 'boolean | |
255 :group 'js2-mode) | |
256 | |
257 (defcustom js2-mode-show-strict-warnings t | |
258 "Non-nil to emit Ecma strict-mode warnings. | |
259 Some of the warnings can be individually disabled by other flags, | |
260 even if this flag is non-nil." | |
261 :type 'boolean | |
262 :group 'js2-mode) | |
263 | |
264 (defcustom js2-strict-trailing-comma-warning t | |
265 "Non-nil to warn about trailing commas in array literals. | |
266 Ecma-262 forbids them, but many browsers permit them. IE is the | |
267 big exception, and can produce bugs if you have trailing commas." | |
268 :type 'boolean | |
269 :group 'js2-mode) | |
270 | |
271 (defcustom js2-strict-missing-semi-warning t | |
272 "Non-nil to warn about semicolon auto-insertion after statement. | |
273 Technically this is legal per Ecma-262, but some style guides disallow | |
274 depending on it." | |
275 :type 'boolean | |
276 :group 'js2-mode) | |
277 | |
278 (defcustom js2-missing-semi-one-line-override nil | |
279 "Non-nil to permit missing semicolons in one-line functions. | |
280 In one-liner functions such as `function identity(x) {return x}' | |
281 people often omit the semicolon for a cleaner look. If you are | |
282 such a person, you can suppress the missing-semicolon warning | |
283 by setting this variable to t." | |
284 :type 'boolean | |
285 :group 'js2-mode) | |
286 | |
287 (defcustom js2-strict-inconsistent-return-warning t | |
288 "Non-nil to warn about mixing returns with value-returns. | |
289 It's perfectly legal to have a `return' and a `return foo' in the | |
290 same function, but it's often an indicator of a bug, and it also | |
291 interferes with type inference (in systems that support it.)" | |
292 :type 'boolean | |
293 :group 'js2-mode) | |
294 | |
295 (defcustom js2-strict-cond-assign-warning t | |
296 "Non-nil to warn about expressions like if (a = b). | |
297 This often should have been '==' instead of '='. If the warning | |
298 is enabled, you can suppress it on a per-expression basis by | |
299 parenthesizing the expression, e.g. if ((a = b)) ..." | |
300 :type 'boolean | |
301 :group 'js2-mode) | |
302 | |
303 (defcustom js2-strict-cond-assign-warning t | |
304 "Non-nil to warn about expressions like if (a = b). | |
305 This often should have been '==' instead of '='. If the warning | |
306 is enabled, you can suppress it on a per-expression basis by | |
307 parenthesizing the expression, e.g. if ((a = b)) ..." | |
308 :type 'boolean | |
309 :group 'js2-mode) | |
310 | |
311 (defcustom js2-strict-var-redeclaration-warning t | |
312 "Non-nil to warn about redeclaring variables in a script or function." | |
313 :type 'boolean | |
314 :group 'js2-mode) | |
315 | |
316 (defcustom js2-strict-var-hides-function-arg-warning t | |
317 "Non-nil to warn about a var decl hiding a function argument." | |
318 :type 'boolean | |
319 :group 'js2-mode) | |
320 | |
321 (defcustom js2-skip-preprocessor-directives nil | |
322 "Non-nil to treat lines beginning with # as comments. | |
323 Useful for viewing Mozilla JavaScript source code." | |
324 :type 'boolean | |
325 :group 'js2-mode) | |
326 | |
327 (defcustom js2-basic-offset c-basic-offset | |
328 "Functions like `c-basic-offset' in js2-mode buffers." | |
329 :type 'integer | |
330 :group 'js2-mode) | |
331 (make-variable-buffer-local 'js2-basic-offset) | |
332 | |
333 (defcustom js2-language-version 170 | |
334 "Configures what JavaScript language version to recognize. | |
335 Currently only 150, 160 and 170 are supported, corresponding | |
336 to JavaScript 1.5, 1.6 and 1.7, respectively. In a nutshell, | |
337 1.6 adds E4X support, and 1.7 adds let, yield, and Array | |
338 comprehensions." | |
339 :type 'integer | |
340 :group 'js2-mode) | |
341 | |
342 (defcustom js2-allow-keywords-as-property-names t | |
343 "If non-nil, you can use JavaScript keywords as object property names. | |
344 Examples: | |
345 | |
346 var foo = {int: 5, while: 6, continue: 7}; | |
347 foo.return = 8; | |
348 | |
349 Ecma-262 forbids this syntax, but many browsers support it." | |
350 :type 'boolean | |
351 :group 'js2-mode) | |
352 | |
353 (defcustom js2-instanceof-has-side-effects nil | |
354 "If non-nil, treats the instanceof operator as having side effects. | |
355 This is useful for xulrunner apps." | |
356 :type 'boolean | |
357 :group 'js2-mode) | |
358 | |
359 (defcustom js2-allow-rhino-new-expr-initializer nil | |
360 "Non-nil to support a Rhino's experimental syntactic construct. | |
361 | |
362 Rhino supports the ability to follow a `new' expression with an object | |
363 literal, which is used to set additional properties on the new object | |
364 after calling its constructor. Syntax: | |
365 | |
366 new <expr> [ ( arglist ) ] [initializer] | |
367 | |
368 Hence, this expression: | |
369 | |
370 new Object {a: 1, b: 2} | |
371 | |
372 results in an Object with properties a=1 and b=2. This syntax is | |
373 apparently not configurable in Rhino - it's currently always enabled, | |
374 as of Rhino version 1.7R2." | |
375 :type 'boolean | |
376 :group 'js2-mode) | |
377 | |
378 (defcustom js2-allow-member-expr-as-function-name nil | |
379 "Non-nil to support experimental Rhino syntax for function names. | |
380 | |
381 Rhino supports an experimental syntax configured via the Rhino Context | |
382 setting `allowMemberExprAsFunctionName'. The experimental syntax is: | |
383 | |
384 function <member-expr> ( [ arg-list ] ) { <body> } | |
385 | |
386 Where member-expr is a non-parenthesized 'member expression', which | |
387 is anything at the grammar level of a new-expression or lower, meaning | |
388 any expression that does not involve infix or unary operators. | |
389 | |
390 When <member-expr> is not a simple identifier, then it is syntactic | |
391 sugar for assigning the anonymous function to the <member-expr>. Hence, | |
392 this code: | |
393 | |
394 function a.b().c[2] (x, y) { ... } | |
395 | |
396 is rewritten as: | |
397 | |
398 a.b().c[2] = function(x, y) {...} | |
399 | |
400 which doesn't seem particularly useful, but Rhino permits it." | |
401 :type 'boolean | |
402 :group 'js2-mode) | |
403 | |
404 (defvar js2-mode-version 20080616 | |
405 "Release number for `js2-mode'.") | |
406 | |
407 ;; scanner variables | |
408 | |
409 ;; We record the start and end position of each token. | |
410 (defvar js2-token-beg 1) | |
411 (make-variable-buffer-local 'js2-token-beg) | |
412 (defvar js2-token-end -1) | |
413 (make-variable-buffer-local 'js2-token-end) | |
414 | |
415 (defvar js2-EOF_CHAR -1 | |
416 "Represents end of stream. Distinct from js2-EOF token type.") | |
417 | |
418 ;; I originally used symbols to represent tokens, but Rhino uses | |
419 ;; ints and then sets various flag bits in them, so ints it is. | |
420 ;; The upshot is that we need a `js2-' prefix in front of each name. | |
421 (defvar js2-ERROR -1) | |
422 (defvar js2-EOF 0) | |
423 (defvar js2-EOL 1) | |
424 (defvar js2-ENTERWITH 2) ; begin interpreter bytecodes | |
425 (defvar js2-LEAVEWITH 3) | |
426 (defvar js2-RETURN 4) | |
427 (defvar js2-GOTO 5) | |
428 (defvar js2-IFEQ 6) | |
429 (defvar js2-IFNE 7) | |
430 (defvar js2-SETNAME 8) | |
431 (defvar js2-BITOR 9) | |
432 (defvar js2-BITXOR 10) | |
433 (defvar js2-BITAND 11) | |
434 (defvar js2-EQ 12) | |
435 (defvar js2-NE 13) | |
436 (defvar js2-LT 14) | |
437 (defvar js2-LE 15) | |
438 (defvar js2-GT 16) | |
439 (defvar js2-GE 17) | |
440 (defvar js2-LSH 18) | |
441 (defvar js2-RSH 19) | |
442 (defvar js2-URSH 20) | |
443 (defvar js2-ADD 21) ; infix plus | |
444 (defvar js2-SUB 22) ; infix minus | |
445 (defvar js2-MUL 23) | |
446 (defvar js2-DIV 24) | |
447 (defvar js2-MOD 25) | |
448 (defvar js2-NOT 26) | |
449 (defvar js2-BITNOT 27) | |
450 (defvar js2-POS 28) ; unary plus | |
451 (defvar js2-NEG 29) ; unary minus | |
452 (defvar js2-NEW 30) | |
453 (defvar js2-DELPROP 31) | |
454 (defvar js2-TYPEOF 32) | |
455 (defvar js2-GETPROP 33) | |
456 (defvar js2-GETPROPNOWARN 34) | |
457 (defvar js2-SETPROP 35) | |
458 (defvar js2-GETELEM 36) | |
459 (defvar js2-SETELEM 37) | |
460 (defvar js2-CALL 38) | |
461 (defvar js2-NAME 39) ; an identifier | |
462 (defvar js2-NUMBER 40) | |
463 (defvar js2-STRING 41) | |
464 (defvar js2-NULL 42) | |
465 (defvar js2-THIS 43) | |
466 (defvar js2-FALSE 44) | |
467 (defvar js2-TRUE 45) | |
468 (defvar js2-SHEQ 46) ; shallow equality (===) | |
469 (defvar js2-SHNE 47) ; shallow inequality (!==) | |
470 (defvar js2-REGEXP 48) | |
471 (defvar js2-BINDNAME 49) | |
472 (defvar js2-THROW 50) | |
473 (defvar js2-RETHROW 51) ; rethrow caught exception: catch (e if ) uses it | |
474 (defvar js2-IN 52) | |
475 (defvar js2-INSTANCEOF 53) | |
476 (defvar js2-LOCAL_LOAD 54) | |
477 (defvar js2-GETVAR 55) | |
478 (defvar js2-SETVAR 56) | |
479 (defvar js2-CATCH_SCOPE 57) | |
480 (defvar js2-ENUM_INIT_KEYS 58) | |
481 (defvar js2-ENUM_INIT_VALUES 59) | |
482 (defvar js2-ENUM_INIT_ARRAY 60) | |
483 (defvar js2-ENUM_NEXT 61) | |
484 (defvar js2-ENUM_ID 62) | |
485 (defvar js2-THISFN 63) | |
486 (defvar js2-RETURN_RESULT 64) ; to return previously stored return result | |
487 (defvar js2-ARRAYLIT 65) ; array literal | |
488 (defvar js2-OBJECTLIT 66) ; object literal | |
489 (defvar js2-GET_REF 67) ; *reference | |
490 (defvar js2-SET_REF 68) ; *reference = something | |
491 (defvar js2-DEL_REF 69) ; delete reference | |
492 (defvar js2-REF_CALL 70) ; f(args) = something or f(args)++ | |
493 (defvar js2-REF_SPECIAL 71) ; reference for special properties like __proto | |
494 (defvar js2-YIELD 72) ; JS 1.7 yield pseudo keyword | |
495 | |
496 ;; XML support | |
497 (defvar js2-DEFAULTNAMESPACE 73) | |
498 (defvar js2-ESCXMLATTR 74) | |
499 (defvar js2-ESCXMLTEXT 75) | |
500 (defvar js2-REF_MEMBER 76) ; Reference for x.@y, x..y etc. | |
501 (defvar js2-REF_NS_MEMBER 77) ; Reference for x.ns::y, x..ns::y etc. | |
502 (defvar js2-REF_NAME 78) ; Reference for @y, @[y] etc. | |
503 (defvar js2-REF_NS_NAME 79) ; Reference for ns::y, @ns::y@[y] etc. | |
504 | |
505 (defvar js2-first-bytecode js2-ENTERWITH) | |
506 (defvar js2-last-bytecode js2-REF_NS_NAME) | |
507 | |
508 (defvar js2-TRY 80) | |
509 (defvar js2-SEMI 81) ; semicolon | |
510 (defvar js2-LB 82) ; left and right brackets | |
511 (defvar js2-RB 83) | |
512 (defvar js2-LC 84) ; left and right curly-braces | |
513 (defvar js2-RC 85) | |
514 (defvar js2-LP 86) ; left and right parens | |
515 (defvar js2-RP 87) | |
516 (defvar js2-COMMA 88) ; comma operator | |
517 | |
518 (defvar js2-ASSIGN 89) ; simple assignment (=) | |
519 (defvar js2-ASSIGN_BITOR 90) ; |= | |
520 (defvar js2-ASSIGN_BITXOR 91) ; ^= | |
521 (defvar js2-ASSIGN_BITAND 92) ; &= | |
522 (defvar js2-ASSIGN_LSH 93) ; <<= | |
523 (defvar js2-ASSIGN_RSH 94) ; >>= | |
524 (defvar js2-ASSIGN_URSH 95) ; >>>= | |
525 (defvar js2-ASSIGN_ADD 96) ; += | |
526 (defvar js2-ASSIGN_SUB 97) ; -= | |
527 (defvar js2-ASSIGN_MUL 98) ; *= | |
528 (defvar js2-ASSIGN_DIV 99) ; /= | |
529 (defvar js2-ASSIGN_MOD 100) ; %= | |
530 | |
531 (defvar js2-first-assign js2-ASSIGN) | |
532 (defvar js2-last-assign js2-ASSIGN_MOD) | |
533 | |
534 (defvar js2-HOOK 101) ; conditional (?:) | |
535 (defvar js2-COLON 102) | |
536 (defvar js2-OR 103) ; logical or (||) | |
537 (defvar js2-AND 104) ; logical and (&&) | |
538 (defvar js2-INC 105) ; increment/decrement (++ --) | |
539 (defvar js2-DEC 106) | |
540 (defvar js2-DOT 107) ; member operator (.) | |
541 (defvar js2-FUNCTION 108) ; function keyword | |
542 (defvar js2-EXPORT 109) ; export keyword | |
543 (defvar js2-IMPORT 110) ; import keyword | |
544 (defvar js2-IF 111) ; if keyword | |
545 (defvar js2-ELSE 112) ; else keyword | |
546 (defvar js2-SWITCH 113) ; switch keyword | |
547 (defvar js2-CASE 114) ; case keyword | |
548 (defvar js2-DEFAULT 115) ; default keyword | |
549 (defvar js2-WHILE 116) ; while keyword | |
550 (defvar js2-DO 117) ; do keyword | |
551 (defvar js2-FOR 118) ; for keyword | |
552 (defvar js2-BREAK 119) ; break keyword | |
553 (defvar js2-CONTINUE 120) ; continue keyword | |
554 (defvar js2-VAR 121) ; var keyword | |
555 (defvar js2-WITH 122) ; with keyword | |
556 (defvar js2-CATCH 123) ; catch keyword | |
557 (defvar js2-FINALLY 124) ; finally keyword | |
558 (defvar js2-VOID 125) ; void keyword | |
559 (defvar js2-RESERVED 126) ; reserved keywords | |
560 | |
561 (defvar js2-EMPTY 127) | |
562 | |
563 ;; Types used for the parse tree - never returned by scanner. | |
564 | |
565 (defvar js2-BLOCK 128) ; statement block | |
566 (defvar js2-LABEL 129) ; label | |
567 (defvar js2-TARGET 130) | |
568 (defvar js2-LOOP 131) | |
569 (defvar js2-EXPR_VOID 132) ; expression statement in functions | |
570 (defvar js2-EXPR_RESULT 133) ; expression statement in scripts | |
571 (defvar js2-JSR 134) | |
572 (defvar js2-SCRIPT 135) ; top-level node for entire script | |
573 (defvar js2-TYPEOFNAME 136) ; for typeof(simple-name) | |
574 (defvar js2-USE_STACK 137) | |
575 (defvar js2-SETPROP_OP 138) ; x.y op= something | |
576 (defvar js2-SETELEM_OP 139) ; x[y] op= something | |
577 (defvar js2-LOCAL_BLOCK 140) | |
578 (defvar js2-SET_REF_OP 141) ; *reference op= something | |
579 | |
580 ;; For XML support: | |
581 (defvar js2-DOTDOT 142) ; member operator (..) | |
582 (defvar js2-COLONCOLON 143) ; namespace::name | |
583 (defvar js2-XML 144) ; XML type | |
584 (defvar js2-DOTQUERY 145) ; .() -- e.g., x.emps.emp.(name == "terry") | |
585 (defvar js2-XMLATTR 146) ; @ | |
586 (defvar js2-XMLEND 147) | |
587 | |
588 ;; Optimizer-only tokens | |
589 (defvar js2-TO_OBJECT 148) | |
590 (defvar js2-TO_DOUBLE 149) | |
591 | |
592 (defvar js2-GET 150) ; JS 1.5 get pseudo keyword | |
593 (defvar js2-SET 151) ; JS 1.5 set pseudo keyword | |
594 (defvar js2-LET 152) ; JS 1.7 let pseudo keyword | |
595 (defvar js2-CONST 153) | |
596 (defvar js2-SETCONST 154) | |
597 (defvar js2-SETCONSTVAR 155) | |
598 (defvar js2-ARRAYCOMP 156) | |
599 (defvar js2-LETEXPR 157) | |
600 (defvar js2-WITHEXPR 158) | |
601 (defvar js2-DEBUGGER 159) | |
602 | |
603 (defvar js2-COMMENT 160) ; not yet in Rhino | |
604 | |
605 (defvar js2-num-tokens (1+ js2-COMMENT)) | |
606 | |
607 (defconst js2-debug-print-trees nil) | |
608 | |
609 ;; Rhino accepts any string or stream as input. | |
610 ;; Emacs character processing works best in buffers, so we'll | |
611 ;; assume the input is a buffer. JavaScript strings can be | |
612 ;; copied into temp buffers before scanning them. | |
613 | |
614 (defmacro deflocal (name value comment) | |
615 `(progn | |
616 (defvar ,name ,value ,comment) | |
617 (make-variable-buffer-local ',name))) | |
618 | |
619 ;; Buffer-local variables yield much cleaner code than using `defstruct'. | |
620 ;; They're the Emacs equivalent of instance variables, more or less. | |
621 | |
622 (deflocal js2-ts-dirty-line nil | |
623 "Token stream buffer-local variable. | |
624 Indicates stuff other than whitespace since start of line.") | |
625 | |
626 (deflocal js2-ts-regexp-flags nil | |
627 "Token stream buffer-local variable.") | |
628 | |
629 (deflocal js2-ts-string "" | |
630 "Token stream buffer-local variable. | |
631 Last string scanned.") | |
632 | |
633 (deflocal js2-ts-number nil | |
634 "Token stream buffer-local variable. | |
635 Last literal number scanned.") | |
636 | |
637 (deflocal js2-ts-hit-eof nil | |
638 "Token stream buffer-local variable.") | |
639 | |
640 (deflocal js2-ts-line-start 0 | |
641 "Token stream buffer-local variable.") | |
642 | |
643 (deflocal js2-ts-lineno 1 | |
644 "Token stream buffer-local variable.") | |
645 | |
646 (deflocal js2-ts-line-end-char -1 | |
647 "Token stream buffer-local variable.") | |
648 | |
649 (deflocal js2-ts-cursor 1 ; emacs buffers are 1-indexed | |
650 "Token stream buffer-local variable. | |
651 Current scan position.") | |
652 | |
653 (deflocal js2-ts-is-xml-attribute nil | |
654 "Token stream buffer-local variable.") | |
655 | |
656 (deflocal js2-ts-xml-is-tag-content nil | |
657 "Token stream buffer-local variable.") | |
658 | |
659 (deflocal js2-ts-xml-open-tags-count 0 | |
660 "Token stream buffer-local variable.") | |
661 | |
662 (deflocal js2-ts-string-buffer nil | |
663 "Token stream buffer-local variable. | |
664 List of chars built up while scanning various tokens.") | |
665 | |
666 (deflocal js2-ts-comment-type nil | |
667 "Token stream buffer-local variable.") | |
668 | |
669 ;;; Parser variables | |
670 | |
671 (defvar js2-parsed-errors nil | |
672 "List of errors produced during scanning/parsing.") | |
673 (make-variable-buffer-local 'js2-parsed-errors) | |
674 | |
675 (defvar js2-parsed-warnings nil | |
676 "List of warnings produced during scanning/parsing.") | |
677 (make-variable-buffer-local 'js2-parsed-warnings) | |
678 | |
679 (defvar js2-recover-from-parse-errors t | |
680 "Non-nil to continue parsing after a syntax error. | |
681 | |
682 In recovery mode, the AST will be built in full, and any error | |
683 nodes will be flagged with appropriate error information. If | |
684 this flag is nil, a syntax error will result in an error being | |
685 signaled. | |
686 | |
687 The variable is automatically buffer-local, because different | |
688 modes that use the parser will need different settings.") | |
689 (make-variable-buffer-local 'js2-recover-from-parse-errors) | |
690 | |
691 (defvar js2-parse-hook nil | |
692 "List of callbacks for receiving parsing progress.") | |
693 (make-variable-buffer-local 'js2-parse-hook) | |
694 | |
695 (defvar js2-parse-finished-hook nil | |
696 "List of callbacks to notify when parsing finishes. | |
697 Not called if parsing was interrupted.") | |
698 | |
699 (defvar js2-is-eval-code nil | |
700 "True if we're evaluating code in a string. | |
701 If non-nil, the tokenizer will record the token text, and the AST nodes | |
702 will record their source text. Off by default for IDE modes, since the | |
703 text is available in the buffer.") | |
704 (make-variable-buffer-local 'js2-is-eval-code) | |
705 | |
706 (defvar js2-parse-ide-mode t | |
707 "Non-nil if the parser is being used for `js2-mode'. | |
708 If non-nil, the parser will set text properties for fontification | |
709 and the syntax-table. The value should be nil when using the | |
710 parser as a frontend to an interpreter or byte compiler.") | |
711 | |
712 ;;; Parser instance variables (buffer-local vars for js2-parse) | |
713 | |
714 (defconst js2-clear-ti-mask #xFFFF | |
715 "Mask to clear token information bits.") | |
716 | |
717 (defconst js2-ti-after-eol (lsh 1 16) | |
718 "Flag: first token of the source line.") | |
719 | |
720 (defconst js2-ti-check-label (lsh 1 17) | |
721 "Flag: indicates to check for label.") | |
722 | |
723 ;; Inline Rhino's CompilerEnvirons vars as buffer-locals. | |
724 | |
725 (defvar js2-compiler-generate-debug-info t) | |
726 (make-variable-buffer-local 'js2-compiler-generate-debug-info) | |
727 | |
728 (defvar js2-compiler-use-dynamic-scope nil) | |
729 (make-variable-buffer-local 'js2-compiler-use-dynamic-scope) | |
730 | |
731 (defvar js2-compiler-reserved-keywords-as-identifier nil) | |
732 (make-variable-buffer-local 'js2-compiler-reserved-keywords-as-identifier) | |
733 | |
734 (defvar js2-compiler-xml-available t) | |
735 (make-variable-buffer-local 'js2-compiler-xml-available) | |
736 | |
737 (defvar js2-compiler-optimization-level 0) | |
738 (make-variable-buffer-local 'js2-compiler-optimization-level) | |
739 | |
740 (defvar js2-compiler-generating-source t) | |
741 (make-variable-buffer-local 'js2-compiler-generating-source) | |
742 | |
743 (defvar js2-compiler-strict-mode nil) | |
744 (make-variable-buffer-local 'js2-compiler-strict-mode) | |
745 | |
746 (defvar js2-compiler-report-warning-as-error nil) | |
747 (make-variable-buffer-local 'js2-compiler-report-warning-as-error) | |
748 | |
749 (defvar js2-compiler-generate-observer-count nil) | |
750 (make-variable-buffer-local 'js2-compiler-generate-observer-count) | |
751 | |
752 (defvar js2-compiler-activation-names nil) | |
753 (make-variable-buffer-local 'js2-compiler-activation-names) | |
754 | |
755 ;; SKIP: sourceURI | |
756 | |
757 ;; There's a compileFunction method in Context.java - may need it. | |
758 (defvar js2-called-by-compile-function nil | |
759 "True if `js2-parse' was called by `js2-compile-function'. | |
760 Will only be used when we finish implementing the interpreter.") | |
761 (make-variable-buffer-local 'js2-called-by-compile-function) | |
762 | |
763 ;; SKIP: ts (we just call `js2-init-scanner' and use its vars) | |
764 | |
765 (defvar js2-current-flagged-token js2-EOF) | |
766 (make-variable-buffer-local 'js2-current-flagged-token) | |
767 | |
768 (defvar js2-current-token js2-EOF) | |
769 (make-variable-buffer-local 'js2-current-token) | |
770 | |
771 ;; SKIP: node factory - we're going to just call functions directly, | |
772 ;; and eventually go to a unified AST format. | |
773 | |
774 (defvar js2-nesting-of-function 0) | |
775 (make-variable-buffer-local 'js2-nesting-of-function) | |
776 | |
777 (defvar js2-recorded-assignments nil) | |
778 (make-variable-buffer-local 'js2-assignments-from-parse) | |
779 | |
780 ;; SKIP: decompiler | |
781 ;; SKIP: encoded-source | |
782 | |
783 ;;; These variables are per-function and should be saved/restored | |
784 ;;; during function parsing. | |
785 | |
786 (defvar js2-current-script-or-fn nil) | |
787 (make-variable-buffer-local 'js2-current-script-or-fn) | |
788 | |
789 (defvar js2-current-scope nil) | |
790 (make-variable-buffer-local 'js2-current-scope) | |
791 | |
792 (defvar js2-nesting-of-with 0) | |
793 (make-variable-buffer-local 'js2-nesting-of-with) | |
794 | |
795 (defvar js2-label-set nil | |
796 "An alist mapping label names to nodes.") | |
797 (make-variable-buffer-local 'js2-label-set) | |
798 | |
799 (defvar js2-loop-set nil) | |
800 (make-variable-buffer-local 'js2-loop-set) | |
801 | |
802 (defvar js2-loop-and-switch-set nil) | |
803 (make-variable-buffer-local 'js2-loop-and-switch-set) | |
804 | |
805 (defvar js2-has-return-value nil) | |
806 (make-variable-buffer-local 'js2-has-return-value) | |
807 | |
808 (defvar js2-end-flags 0) | |
809 (make-variable-buffer-local 'js2-end-flags) | |
810 | |
811 ;;; end of per function variables | |
812 | |
813 ;; Without 2-token lookahead, labels are a problem. | |
814 ;; These vars store the token info of the last matched name, | |
815 ;; iff it wasn't the last matched token. Only valid in some contexts. | |
816 (defvar js2-prev-name-token-start nil) | |
817 (defvar js2-prev-name-token-string nil) | |
818 | |
819 (defsubst js2-save-name-token-data (pos name) | |
820 (setq js2-prev-name-token-start pos | |
821 js2-prev-name-token-string name)) | |
822 | |
823 ;; These flags enumerate the possible ways a statement/function can | |
824 ;; terminate. These flags are used by endCheck() and by the Parser to | |
825 ;; detect inconsistent return usage. | |
826 ;; | |
827 ;; END_UNREACHED is reserved for code paths that are assumed to always be | |
828 ;; able to execute (example: throw, continue) | |
829 ;; | |
830 ;; END_DROPS_OFF indicates if the statement can transfer control to the | |
831 ;; next one. Statement such as return dont. A compound statement may have | |
832 ;; some branch that drops off control to the next statement. | |
833 ;; | |
834 ;; END_RETURNS indicates that the statement can return (without arguments) | |
835 ;; END_RETURNS_VALUE indicates that the statement can return a value. | |
836 ;; | |
837 ;; A compound statement such as | |
838 ;; if (condition) { | |
839 ;; return value; | |
840 ;; } | |
841 ;; Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck() | |
842 | |
843 (defconst js2-end-unreached #x0) | |
844 (defconst js2-end-drops-off #x1) | |
845 (defconst js2-end-returns #x2) | |
846 (defconst js2-end-returns-value #x4) | |
847 (defconst js2-end-yields #x8) | |
848 | |
849 ;; Rhino awkwardly passes a statementLabel parameter to the | |
850 ;; statementHelper() function, the main statement parser, which | |
851 ;; is then used by quite a few of the sub-parsers. We just make | |
852 ;; it a buffer-local variable and make sure it's cleaned up properly. | |
853 (defvar js2-labeled-stmt nil) ; type `js2-labeled-stmt-node' | |
854 (make-variable-buffer-local 'js2-labeled-stmt) | |
855 | |
856 ;; Similarly, Rhino passes an inForInit boolean through about half | |
857 ;; the expression parsers. We use a dynamically-scoped variable, | |
858 ;; which makes it easier to funcall the parsers individually without | |
859 ;; worrying about whether they take the parameter or not. | |
860 (defvar js2-in-for-init nil) | |
861 (make-variable-buffer-local 'js2-in-for-init) | |
862 | |
863 (defvar js2-temp-name-counter 0) | |
864 (make-variable-buffer-local 'js2-temp-name-counter) | |
865 | |
866 (defvar js2-parse-stmt-count 0) | |
867 (make-variable-buffer-local 'js2-parse-stmt-count) | |
868 | |
869 (defsubst js2-get-next-temp-name () | |
870 (format "$%d" (incf js2-temp-name-counter))) | |
871 | |
872 (defvar js2-parse-interruptable-p t | |
873 "Set this to nil to force parse to continue until finished. | |
874 This will mostly be useful for interpreters.") | |
875 | |
876 (defvar js2-statements-per-pause 50 | |
877 "Pause after this many statements to check for user input. | |
878 If user input is pending, stop the parse and discard the tree. | |
879 This makes for a smoother user experience for large files. | |
880 You may have to wait a second or two before the highlighting | |
881 and error-reporting appear, but you can always type ahead if | |
882 you wish. This appears to be more or less how Eclipse, IntelliJ | |
883 and other editors work.") | |
884 | |
885 (defvar js2-record-comments t | |
886 "Instructs the scanner to record comments in `js2-scanned-comments'.") | |
887 (make-variable-buffer-local 'js2-record-comments) | |
888 | |
889 (defvar js2-scanned-comments nil | |
890 "List of all comments from the current parse.") | |
891 (make-variable-buffer-local 'js2-scanned-comments) | |
892 | |
893 (defun js2-underline-color (color) | |
894 "Return a legal value for the :underline face attribute based on COLOR." | |
895 ;; In XEmacs the :underline attribute can only be a boolean. | |
896 ;; In GNU it can be the name of a colour. | |
897 (if (featurep 'xemacs) | |
898 (if color t nil) | |
899 color)) | |
900 | |
901 (defcustom js2-mode-indent-inhibit-undo nil | |
902 "Non-nil to disable collection of Undo information when indenting lines. | |
903 Some users have requested this behavior. It's nil by default because | |
904 other Emacs modes don't work this way." | |
905 :type 'boolean | |
906 :group 'js2-mode) | |
907 | |
908 (defcustom js2-mode-indent-ignore-first-tab nil | |
909 "If non-nil, ignore first TAB keypress if we look indented properly. | |
910 It's fairly common for users to navigate to an already-indented line | |
911 and press TAB for reassurance that it's been indented. For this class | |
912 of users, we want the first TAB press on a line to be ignored if the | |
913 line is already indented to one of the precomputed alternatives. | |
914 | |
915 This behavior is only partly implemented. If you TAB-indent a line, | |
916 navigate to another line, and then navigate back, it fails to clear | |
917 the last-indented variable, so it thinks you've already hit TAB once, | |
918 and performs the indent. A full solution would involve getting on the | |
919 point-motion hooks for the entire buffer. If we come across another | |
920 use cases that requires watching point motion, I'll consider doing it. | |
921 | |
922 If you set this variable to nil, then the TAB key will always change | |
923 the indentation of the current line, if more than one alternative | |
924 indentation spot exists." | |
925 :type 'boolean | |
926 :group 'js2-mode) | |
927 | |
928 (defvar js2-indent-hook nil | |
929 "A hook for user-defined indentation rules. | |
930 | |
931 Functions on this hook should expect two arguments: (LIST INDEX) | |
932 The LIST argument is the list of computed indentation points for | |
933 the current line. INDEX is the list index of the indentation point | |
934 that `js2-bounce-indent' plans to use. If INDEX is nil, then the | |
935 indent function is not going to change the current line indentation. | |
936 | |
937 If a hook function on this list returns a non-nil value, then | |
938 `js2-bounce-indent' assumes the hook function has performed its own | |
939 indentation, and will do nothing. If all hook functions on the list | |
940 return nil, then `js2-bounce-indent' will use its computed indentation | |
941 and reindent the line. | |
942 | |
943 When hook functions on this hook list are called, the variable | |
944 `js2-mode-ast' may or may not be set, depending on whether the | |
945 parse tree is available. If the variable is nil, you can pass a | |
946 callback to `js2-mode-wait-for-parse', and your callback will be | |
947 called after the new parse tree is built. This can take some time | |
948 in large files.") | |
949 | |
950 (defface js2-warning-face | |
951 `((((class color) (background light)) | |
952 (:underline ,(js2-underline-color "orange"))) | |
953 (((class color) (background dark)) | |
954 (:underline ,(js2-underline-color "orange"))) | |
955 (t (:underline t))) | |
956 "Face for JavaScript warnings." | |
957 :group 'js2-mode) | |
958 | |
959 (defface js2-error-face | |
960 `((((class color) (background light)) | |
961 (:foreground "red")) | |
962 (((class color) (background dark)) | |
963 (:foreground "red")) | |
964 (t (:foreground "red"))) | |
965 "Face for JavaScript errors." | |
966 :group 'js2-mode) | |
967 | |
968 (defface js2-jsdoc-tag-face | |
969 '((t :foreground "SlateGray")) | |
970 "Face used to highlight @whatever tags in jsdoc comments." | |
971 :group 'js2-mode) | |
972 | |
973 (defface js2-jsdoc-type-face | |
974 '((t :foreground "SteelBlue")) | |
975 "Face used to highlight {FooBar} types in jsdoc comments." | |
976 :group 'js2-mode) | |
977 | |
978 (defface js2-jsdoc-value-face | |
979 '((t :foreground "PeachPuff3")) | |
980 "Face used to highlight tag values in jsdoc comments." | |
981 :group 'js2-mode) | |
982 | |
983 (defface js2-function-param-face | |
984 '((t :foreground "SeaGreen")) | |
985 "Face used to highlight function parameters in javascript." | |
986 :group 'js2-mode) | |
987 | |
988 (defface js2-instance-member-face | |
989 '((t :foreground "DarkOrchid")) | |
990 "Face used to highlight instance variables in javascript. | |
991 Not currently used." | |
992 :group 'js2-mode) | |
993 | |
994 (defface js2-private-member-face | |
995 '((t :foreground "PeachPuff3")) | |
996 "Face used to highlight calls to private methods in javascript. | |
997 Not currently used." | |
998 :group 'js2-mode) | |
999 | |
1000 (defface js2-private-function-call-face | |
1001 '((t :foreground "goldenrod")) | |
1002 "Face used to highlight calls to private functions in javascript. | |
1003 Not currently used." | |
1004 :group 'js2-mode) | |
1005 | |
1006 (defface js2-jsdoc-html-tag-name-face | |
1007 (if js2-emacs22 | |
1008 '((((class color) (min-colors 88) (background light)) | |
1009 (:foreground "rosybrown")) | |
1010 (((class color) (min-colors 8) (background dark)) | |
1011 (:foreground "yellow")) | |
1012 (((class color) (min-colors 8) (background light)) | |
1013 (:foreground "magenta"))) | |
1014 '((((type tty pc) (class color) (background light)) | |
1015 (:foreground "magenta")) | |
1016 (((type tty pc) (class color) (background dark)) | |
1017 (:foreground "yellow")) | |
1018 (t (:foreground "RosyBrown")))) | |
1019 "Face used to highlight jsdoc html tag names" | |
1020 :group 'js2-mode) | |
1021 | |
1022 (defface js2-jsdoc-html-tag-delimiter-face | |
1023 (if js2-emacs22 | |
1024 '((((class color) (min-colors 88) (background light)) | |
1025 (:foreground "dark khaki")) | |
1026 (((class color) (min-colors 8) (background dark)) | |
1027 (:foreground "green")) | |
1028 (((class color) (min-colors 8) (background light)) | |
1029 (:foreground "green"))) | |
1030 '((((type tty pc) (class color) (background light)) | |
1031 (:foreground "green")) | |
1032 (((type tty pc) (class color) (background dark)) | |
1033 (:foreground "green")) | |
1034 (t (:foreground "dark khaki")))) | |
1035 "Face used to highlight brackets in jsdoc html tags." | |
1036 :group 'js2-mode) | |
1037 | |
1038 (defface js2-external-variable-face | |
1039 '((t :foreground "orange")) | |
1040 "Face used to highlight assignments to undeclared variables. | |
1041 An undeclared variable is any variable not declared with var or let | |
1042 in the current scope or any lexically enclosing scope. If you assign | |
1043 to such a variable, then you are either expecting it to originate from | |
1044 another file, or you've got a potential bug." | |
1045 :group 'js2-mode) | |
1046 | |
1047 (defcustom js2-highlight-external-variables t | |
1048 "Non-nil to higlight assignments to undeclared variables." | |
1049 :type 'boolean | |
1050 :group 'js2-mode) | |
1051 | |
1052 (defvar js2-mode-map | |
1053 (let ((map (make-sparse-keymap)) | |
1054 keys) | |
1055 (define-key map [mouse-1] #'js2-mode-show-node) | |
1056 (define-key map "\C-m" #'js2-enter-key) | |
1057 (when js2-rebind-eol-bol-keys | |
1058 (define-key map "\C-a" #'js2-beginning-of-line) | |
1059 (define-key map "\C-e" #'js2-end-of-line)) | |
1060 (define-key map "\C-c\C-e" #'js2-mode-hide-element) | |
1061 (define-key map "\C-c\C-s" #'js2-mode-show-element) | |
1062 (define-key map "\C-c\C-a" #'js2-mode-show-all) | |
1063 (define-key map "\C-c\C-f" #'js2-mode-toggle-hide-functions) | |
1064 (define-key map "\C-c\C-t" #'js2-mode-toggle-hide-comments) | |
1065 (define-key map "\C-c\C-o" #'js2-mode-toggle-element) | |
1066 (define-key map "\C-c\C-w" #'js2-mode-toggle-warnings-and-errors) | |
1067 (define-key map (kbd "C-c C-'") #'js2-next-error) | |
1068 ;; also define user's preference for next-error, if available | |
1069 (if (setq keys (where-is-internal #'next-error)) | |
1070 (define-key map (car keys) #'js2-next-error)) | |
1071 (define-key map (or (car (where-is-internal #'mark-defun)) | |
1072 (kbd "M-C-h")) | |
1073 #'js2-mark-defun) | |
1074 (define-key map (or (car (where-is-internal #'narrow-to-defun)) | |
1075 (kbd "C-x nd")) | |
1076 #'js2-narrow-to-defun) | |
1077 (define-key map [down-mouse-3] #'js2-mouse-3) | |
1078 (when js2-auto-indent-flag | |
1079 (mapc (lambda (key) | |
1080 (define-key map key #'js2-insert-and-indent)) | |
1081 js2-electric-keys)) | |
1082 | |
1083 (define-key map [menu-bar javascript] | |
1084 (cons "JavaScript" (make-sparse-keymap "JavaScript"))) | |
1085 | |
1086 (define-key map [menu-bar javascript customize-js2-mode] | |
1087 '(menu-item "Customize js2-mode" js2-mode-customize | |
1088 :help "Customize the behavior of this mode")) | |
1089 | |
1090 (define-key map [menu-bar javascript js2-force-refresh] | |
1091 '(menu-item "Force buffer refresh" js2-mode-reset | |
1092 :help "Re-parse the buffer from scratch")) | |
1093 | |
1094 (define-key map [menu-bar javascript separator-2] | |
1095 '("--")) | |
1096 | |
1097 (define-key map [menu-bar javascript next-error] | |
1098 '(menu-item "Next warning or error" js2-next-error | |
1099 :enabled (and js2-mode-ast | |
1100 (or (js2-ast-root-errors js2-mode-ast) | |
1101 (js2-ast-root-warnings js2-mode-ast))) | |
1102 :help "Move to next warning or error")) | |
1103 | |
1104 (define-key map [menu-bar javascript display-errors] | |
1105 '(menu-item "Show errors and warnings" js2-mode-display-warnings-and-errors | |
1106 :visible (not js2-mode-show-parse-errors) | |
1107 :help "Turn on display of warnings and errors")) | |
1108 | |
1109 (define-key map [menu-bar javascript hide-errors] | |
1110 '(menu-item "Hide errors and warnings" js2-mode-hide-warnings-and-errors | |
1111 :visible js2-mode-show-parse-errors | |
1112 :help "Turn off display of warnings and errors")) | |
1113 | |
1114 (define-key map [menu-bar javascript separator-1] | |
1115 '("--")) | |
1116 | |
1117 (define-key map [menu-bar javascript js2-toggle-function] | |
1118 '(menu-item "Show/collapse element" js2-mode-toggle-element | |
1119 :help "Hide or show function body or comment")) | |
1120 | |
1121 (define-key map [menu-bar javascript show-comments] | |
1122 '(menu-item "Show block comments" js2-mode-toggle-hide-comments | |
1123 :visible js2-mode-comments-hidden | |
1124 :help "Expand all hidden block comments")) | |
1125 | |
1126 (define-key map [menu-bar javascript hide-comments] | |
1127 '(menu-item "Hide block comments" js2-mode-toggle-hide-comments | |
1128 :visible (not js2-mode-comments-hidden) | |
1129 :help "Show block comments as /*...*/")) | |
1130 | |
1131 (define-key map [menu-bar javascript show-all-functions] | |
1132 '(menu-item "Show function bodies" js2-mode-toggle-hide-functions | |
1133 :visible js2-mode-functions-hidden | |
1134 :help "Expand all hidden function bodies")) | |
1135 | |
1136 (define-key map [menu-bar javascript hide-all-functions] | |
1137 '(menu-item "Hide function bodies" js2-mode-toggle-hide-functions | |
1138 :visible (not js2-mode-functions-hidden) | |
1139 :help "Show {...} for all top-level function bodies")) | |
1140 | |
1141 map) | |
1142 "Keymap used in `js2-mode' buffers.") | |
1143 | |
1144 (defconst js2-mode-identifier-re "[a-zA-Z_$][a-zA-Z0-9_$]*") | |
1145 | |
1146 (defvar js2-mode-//-comment-re "^\\(\\s-*\\)//.+" | |
1147 "Matches a //-comment line. Must be first non-whitespace on line. | |
1148 First match-group is the leading whitespace.") | |
1149 | |
1150 (defvar js2-mode-ast nil "Private variable.") | |
1151 (make-variable-buffer-local 'js2-mode-ast) | |
1152 | |
1153 (defvar js2-mode-hook nil) | |
1154 | |
1155 (defvar js2-mode-parse-timer nil "Private variable.") | |
1156 (make-variable-buffer-local 'js2-mode-parse-timer) | |
1157 | |
1158 (defvar js2-mode-buffer-dirty-p nil "Private variable.") | |
1159 (make-variable-buffer-local 'js2-mode-buffer-dirty-p) | |
1160 | |
1161 (defvar js2-mode-parsing nil "Private variable.") | |
1162 (make-variable-buffer-local 'js2-mode-parsing) | |
1163 | |
1164 (defvar js2-mode-node-overlay nil) | |
1165 (make-variable-buffer-local 'js2-mode-node-overlay) | |
1166 | |
1167 (defvar js2-mode-show-overlay js2-mode-dev-mode-p | |
1168 "Debug: Non-nil to highlight AST nodes on mouse-down.") | |
1169 | |
1170 (defvar js2-mode-fontifications nil "Private variable") | |
1171 (make-variable-buffer-local 'js2-mode-fontifications) | |
1172 | |
1173 (defvar js2-mode-deferred-properties nil "Private variable") | |
1174 (make-variable-buffer-local 'js2-mode-deferred-properties) | |
1175 | |
1176 (defvar js2-imenu-recorder nil "Private variable") | |
1177 (make-variable-buffer-local 'js2-imenu-recorder) | |
1178 | |
1179 (defvar js2-imenu-function-map nil "Private variable") | |
1180 (make-variable-buffer-local 'js2-imenu-function-map) | |
1181 | |
1182 (defvar js2-paragraph-start | |
1183 "\\(@[a-zA-Z]+\\>\\|$\\)") | |
1184 | |
1185 ;; Note that we also set a 'c-in-sws text property in html comments, | |
1186 ;; so that `c-forward-sws' and `c-backward-sws' work properly. | |
1187 (defvar js2-syntactic-ws-start | |
1188 "\\s \\|/[*/]\\|[\n\r]\\|\\\\[\n\r]\\|\\s!\\|<!--\\|^\\s-*-->") | |
1189 | |
1190 (defvar js2-syntactic-ws-end | |
1191 "\\s \\|[\n\r/]\\|\\s!") | |
1192 | |
1193 (defvar js2-syntactic-eol | |
1194 (concat "\\s *\\(/\\*[^*\n\r]*" | |
1195 "\\(\\*+[^*\n\r/][^*\n\r]*\\)*" | |
1196 "\\*+/\\s *\\)*" | |
1197 "\\(//\\|/\\*[^*\n\r]*" | |
1198 "\\(\\*+[^*\n\r/][^*\n\r]*\\)*$" | |
1199 "\\|\\\\$\\|$\\)") | |
1200 "Copied from java-mode. Needed for some cc-engine functions.") | |
1201 | |
1202 (defvar js2-comment-prefix-regexp | |
1203 "//+\\|\\**") | |
1204 | |
1205 (defvar js2-comment-start-skip | |
1206 "\\(//+\\|/\\*+\\)\\s *") | |
1207 | |
1208 (defvar js2-mode-verbose-parse-p js2-mode-dev-mode-p | |
1209 "Non-nil to emit status messages during parsing.") | |
1210 | |
1211 (defvar js2-mode-functions-hidden nil "private variable") | |
1212 (defvar js2-mode-comments-hidden nil "private variable") | |
1213 | |
1214 (defvar js2-mode-syntax-table | |
1215 (let ((table (make-syntax-table))) | |
1216 (c-populate-syntax-table table) | |
1217 table) | |
1218 "Syntax table used in js2-mode buffers.") | |
1219 | |
1220 (defvar js2-mode-abbrev-table nil | |
1221 "Abbrev table in use in `js2-mode' buffers.") | |
1222 (define-abbrev-table 'js2-mode-abbrev-table ()) | |
1223 | |
1224 (defvar js2-mode-must-byte-compile (not js2-mode-dev-mode-p) | |
1225 "Non-nil to have `js2-mode' signal an error if not byte-compiled.") | |
1226 | |
1227 (defvar js2-mode-pending-parse-callbacks nil | |
1228 "List of functions waiting to be notified that parse is finished.") | |
1229 | |
1230 (defvar js2-mode-last-indented-line -1) | |
1231 | |
1232 (eval-when-compile | |
1233 (defvar c-paragraph-start nil) | |
1234 (defvar c-paragraph-separate nil) | |
1235 (defvar c-syntactic-ws-start nil) | |
1236 (defvar c-syntactic-ws-end nil) | |
1237 (defvar c-syntactic-eol nil) | |
1238 (defvar running-xemacs nil) | |
1239 (defvar font-lock-mode nil) | |
1240 (defvar font-lock-keywords nil)) | |
1241 | |
1242 (eval-when-compile | |
1243 (if (< emacs-major-version 22) | |
1244 (defun c-setup-paragraph-variables () nil))) | |
1245 | |
1246 (provide 'js2-vars) | |
1247 | |
1248 ;;; js2-vars.el ends here | |
1249 ;;; js2-util.el -- JavaScript utilities | |
1250 | |
1251 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
1252 ;; Keywords: javascript languages | |
1253 | |
1254 ;;; Code | |
1255 | |
1256 (eval-when-compile | |
1257 (require 'cl)) | |
1258 | |
1259 | |
1260 ;; Emacs21 compatibility, plus some stuff to avoid runtime dependency on CL | |
1261 | |
1262 (unless (fboundp #'looking-back) | |
1263 (defun looking-back (regexp &optional limit greedy) | |
1264 "Return non-nil if text before point matches regular expression REGEXP. | |
1265 Like `looking-at' except matches before point, and is slower. | |
1266 LIMIT if non-nil speeds up the search by specifying a minimum | |
1267 starting position, to avoid checking matches that would start | |
1268 before LIMIT. | |
1269 | |
1270 If GREEDY is non-nil, extend the match backwards as far as possible, | |
1271 stopping when a single additional previous character cannot be part | |
1272 of a match for REGEXP." | |
1273 (let ((start (point)) | |
1274 (pos | |
1275 (save-excursion | |
1276 (and (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t) | |
1277 (point))))) | |
1278 (if (and greedy pos) | |
1279 (save-restriction | |
1280 (narrow-to-region (point-min) start) | |
1281 (while (and (> pos (point-min)) | |
1282 (save-excursion | |
1283 (goto-char pos) | |
1284 (backward-char 1) | |
1285 (looking-at (concat "\\(?:" regexp "\\)\\'")))) | |
1286 (setq pos (1- pos))) | |
1287 (save-excursion | |
1288 (goto-char pos) | |
1289 (looking-at (concat "\\(?:" regexp "\\)\\'"))))) | |
1290 (not (null pos))))) | |
1291 | |
1292 (unless (fboundp #'copy-overlay) | |
1293 (defun copy-overlay (o) | |
1294 "Return a copy of overlay O." | |
1295 (let ((o1 (make-overlay (overlay-start o) (overlay-end o) | |
1296 ;; FIXME: there's no easy way to find the | |
1297 ;; insertion-type of the two markers. | |
1298 (overlay-buffer o))) | |
1299 (props (overlay-properties o))) | |
1300 (while props | |
1301 (overlay-put o1 (pop props) (pop props))) | |
1302 o1))) | |
1303 | |
1304 (unless (fboundp #'remove-overlays) | |
1305 (defun remove-overlays (&optional beg end name val) | |
1306 "Clear BEG and END of overlays whose property NAME has value VAL. | |
1307 Overlays might be moved and/or split. | |
1308 BEG and END default respectively to the beginning and end of buffer." | |
1309 (unless beg (setq beg (point-min))) | |
1310 (unless end (setq end (point-max))) | |
1311 (if (< end beg) | |
1312 (setq beg (prog1 end (setq end beg)))) | |
1313 (save-excursion | |
1314 (dolist (o (overlays-in beg end)) | |
1315 (when (eq (overlay-get o name) val) | |
1316 ;; Either push this overlay outside beg...end | |
1317 ;; or split it to exclude beg...end | |
1318 ;; or delete it entirely (if it is contained in beg...end). | |
1319 (if (< (overlay-start o) beg) | |
1320 (if (> (overlay-end o) end) | |
1321 (progn | |
1322 (move-overlay (copy-overlay o) | |
1323 (overlay-start o) beg) | |
1324 (move-overlay o end (overlay-end o))) | |
1325 (move-overlay o (overlay-start o) beg)) | |
1326 (if (> (overlay-end o) end) | |
1327 (move-overlay o end (overlay-end o)) | |
1328 (delete-overlay o)))))))) | |
1329 | |
1330 ;; we don't want a runtime dependency on the CL package, so define | |
1331 ;; our own versions of these functions. | |
1332 | |
1333 (defun js2-delete-if (predicate list) | |
1334 "Remove all items satisfying PREDICATE in LIST." | |
1335 (loop for item in list | |
1336 if (not (funcall predicate item)) | |
1337 collect item)) | |
1338 | |
1339 (defun js2-position (element list) | |
1340 "Find 0-indexed position of ELEMENT in LIST comparing with `eq'. | |
1341 Returns nil if element is not found in the list." | |
1342 (let ((count 0) | |
1343 found) | |
1344 (while (and list (not found)) | |
1345 (if (eq element (car list)) | |
1346 (setq found t) | |
1347 (setq count (1+ count) | |
1348 list (cdr list)))) | |
1349 (if found count))) | |
1350 | |
1351 (defun js2-find-if (predicate list) | |
1352 "Find first item satisfying PREDICATE in LIST." | |
1353 (let (result) | |
1354 (while (and list (not result)) | |
1355 (if (funcall predicate (car list)) | |
1356 (setq result (car list))) | |
1357 (setq list (cdr list))) | |
1358 result)) | |
1359 | |
1360 ;;; end Emacs 21 compat | |
1361 | |
1362 (defmacro js2-time (form) | |
1363 "Evaluate FORM, discard result, and return elapsed time in sec" | |
1364 (let ((beg (make-symbol "--js2-time-beg--")) | |
1365 (delta (make-symbol "--js2-time-end--"))) | |
1366 `(let ((,beg (current-time)) | |
1367 ,delta) | |
1368 ,form | |
1369 (/ (truncate (* (- (float-time (current-time)) | |
1370 (float-time ,beg))) | |
1371 10000) | |
1372 10000.0)))) | |
1373 | |
1374 (def-edebug-spec js2-time t) | |
1375 | |
1376 (defsubst neq (expr1 expr2) | |
1377 "Return (not (eq expr1 expr2))." | |
1378 (not (eq expr1 expr2))) | |
1379 | |
1380 (defsubst js2-same-line (pos) | |
1381 "Return t if POS is on the same line as current point." | |
1382 (and (>= pos (point-at-bol)) | |
1383 (<= pos (point-at-eol)))) | |
1384 | |
1385 (defsubst js2-same-line-2 (p1 p2) | |
1386 "Return t if p1 is on the same line as p2." | |
1387 (save-excursion | |
1388 (goto-char p1) | |
1389 (js2-same-line p2))) | |
1390 | |
1391 (defun js2-code-bug () | |
1392 "Signal an error when we encounter an unexpected code path." | |
1393 (error "failed assertion")) | |
1394 | |
1395 ;; I'd like to associate errors with nodes, but for now the | |
1396 ;; easiest thing to do is get the context info from the last token. | |
1397 (defsubst js2-record-parse-error (msg &optional arg pos len) | |
1398 (push (list (list msg arg) | |
1399 (or pos js2-token-beg) | |
1400 (or len (- js2-token-end js2-token-beg))) | |
1401 js2-parsed-errors)) | |
1402 | |
1403 (defsubst js2-report-error (msg &optional msg-arg pos len) | |
1404 "Signal a syntax error or record a parse error." | |
1405 (if js2-recover-from-parse-errors | |
1406 (js2-record-parse-error msg msg-arg pos len) | |
1407 (signal 'js2-syntax-error | |
1408 (list msg | |
1409 js2-ts-lineno | |
1410 (save-excursion | |
1411 (goto-char js2-ts-cursor) | |
1412 (current-column)) | |
1413 js2-ts-hit-eof)))) | |
1414 | |
1415 (defsubst js2-report-warning (msg &optional msg-arg pos len) | |
1416 (if js2-compiler-report-warning-as-error | |
1417 (js2-report-error msg msg-arg pos len) | |
1418 (push (list (list msg msg-arg) | |
1419 (or pos js2-token-beg) | |
1420 (or len (- js2-token-end js2-token-beg))) | |
1421 js2-parsed-warnings))) | |
1422 | |
1423 (defsubst js2-add-strict-warning (msg-id &optional msg-arg beg end) | |
1424 (if js2-compiler-strict-mode | |
1425 (js2-report-warning msg-id msg-arg beg | |
1426 (and beg end (- end beg))))) | |
1427 | |
1428 (put 'js2-syntax-error 'error-conditions | |
1429 '(error syntax-error js2-syntax-error)) | |
1430 (put 'js2-syntax-error 'error-message "Syntax error") | |
1431 | |
1432 (put 'js2-parse-error 'error-conditions | |
1433 '(error parse-error js2-parse-error)) | |
1434 (put 'js2-parse-error 'error-message "Parse error") | |
1435 | |
1436 (defmacro js2-clear-flag (flags flag) | |
1437 `(setq ,flags (logand ,flags (lognot ,flag)))) | |
1438 | |
1439 (defmacro js2-set-flag (flags flag) | |
1440 "Logical-or FLAG into FLAGS." | |
1441 `(setq ,flags (logior ,flags ,flag))) | |
1442 | |
1443 (defsubst js2-flag-set-p (flags flag) | |
1444 (/= 0 (logand flags flag))) | |
1445 | |
1446 (defsubst js2-flag-not-set-p (flags flag) | |
1447 (zerop (logand flags flag))) | |
1448 | |
1449 ;; Stolen shamelessly from James Clark's nxml-mode. | |
1450 (defmacro js2-with-unmodifying-text-property-changes (&rest body) | |
1451 "Evaluate BODY without any text property changes modifying the buffer. | |
1452 Any text properties changes happen as usual but the changes are not treated as | |
1453 modifications to the buffer." | |
1454 (let ((modified (make-symbol "modified"))) | |
1455 `(let ((,modified (buffer-modified-p)) | |
1456 (inhibit-read-only t) | |
1457 (inhibit-modification-hooks t) | |
1458 (buffer-undo-list t) | |
1459 (deactivate-mark nil) | |
1460 ;; Apparently these avoid file locking problems. | |
1461 (buffer-file-name nil) | |
1462 (buffer-file-truename nil)) | |
1463 (unwind-protect | |
1464 (progn ,@body) | |
1465 (unless ,modified | |
1466 (restore-buffer-modified-p nil)))))) | |
1467 | |
1468 (put 'js2-with-unmodifying-text-property-changes 'lisp-indent-function 0) | |
1469 (def-edebug-spec js2-with-unmodifying-text-property-changes t) | |
1470 | |
1471 (defmacro js2-with-underscore-as-word-syntax (&rest body) | |
1472 "Evaluate BODY with the _ character set to be word-syntax." | |
1473 (let ((old-syntax (make-symbol "old-syntax"))) | |
1474 `(let ((,old-syntax (string (char-syntax ?_)))) | |
1475 (unwind-protect | |
1476 (progn | |
1477 (modify-syntax-entry ?_ "w" js2-mode-syntax-table) | |
1478 ,@body) | |
1479 (modify-syntax-entry ?_ ,old-syntax js2-mode-syntax-table))))) | |
1480 | |
1481 (put 'js2-with-underscore-as-word-syntax 'lisp-indent-function 0) | |
1482 (def-edebug-spec js2-with-underscore-as-word-syntax t) | |
1483 | |
1484 (defmacro with-buffer (buf form) | |
1485 "Executes FORM in buffer BUF. | |
1486 BUF can be a buffer name or a buffer object. | |
1487 If the buffer doesn't exist, it's created." | |
1488 `(let ((buffer (gentemp))) | |
1489 (setq buffer | |
1490 (if (stringp ,buf) | |
1491 (get-buffer-create ,buf) | |
1492 ,buf)) | |
1493 (save-excursion | |
1494 (set-buffer buffer) | |
1495 ,form))) | |
1496 | |
1497 (defsubst char-is-uppercase (c) | |
1498 "Return t if C is an uppercase character. | |
1499 Handles unicode and latin chars properly." | |
1500 (/= c (downcase c))) | |
1501 | |
1502 (defsubst char-is-lowercase (c) | |
1503 "Return t if C is an uppercase character. | |
1504 Handles unicode and latin chars properly." | |
1505 (/= c (upcase c))) | |
1506 | |
1507 (put 'with-buffer 'lisp-indent-function 1) | |
1508 (def-edebug-spec with-buffer t) | |
1509 | |
1510 (provide 'js2-util) | |
1511 | |
1512 ;;; js2-util.el ends here | |
1513 ;;; js2-scan.el --- JavaScript scanner | |
1514 | |
1515 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
1516 ;; Keywords: javascript languages | |
1517 | |
1518 ;;; Commentary: | |
1519 | |
1520 ;; A port of Mozilla Rhino's scanner. | |
1521 ;; Corresponds to Rhino files Token.java and TokenStream.java. | |
1522 | |
1523 ;;; Code: | |
1524 | |
1525 | |
1526 (eval-when-compile | |
1527 (require 'cl)) | |
1528 | |
1529 (defvar js2-tokens nil | |
1530 "List of all defined token names.") ; intialized below | |
1531 | |
1532 (defvar js2-token-names | |
1533 (let* ((names (make-vector js2-num-tokens -1)) | |
1534 (case-fold-search nil) ; only match js2-UPPER_CASE | |
1535 (syms (apropos-internal "^js2-\\(?:[A-Z_]+\\)"))) | |
1536 (loop for sym in syms | |
1537 for i from 0 | |
1538 do | |
1539 (unless (or (memq sym '(js2-EOF_CHAR js2-ERROR)) | |
1540 (not (boundp sym))) | |
1541 (aset names (symbol-value sym) ; code, e.g. 152 | |
1542 (substring (symbol-name sym) 4)) ; name, e.g. "LET" | |
1543 (push sym js2-tokens))) | |
1544 names) | |
1545 "Vector mapping int values to token string names, sans `js2-' prefix.") | |
1546 | |
1547 (defun js2-token-name (tok) | |
1548 "Return a string name for TOK, a token symbol or code. | |
1549 Signals an error if it's not a recognized token." | |
1550 (let ((code tok)) | |
1551 (if (symbolp tok) | |
1552 (setq code (symbol-value tok))) | |
1553 (if (eq code -1) | |
1554 "ERROR" | |
1555 (if (and (numberp code) | |
1556 (not (minusp code)) | |
1557 (< code js2-num-tokens)) | |
1558 (aref js2-token-names code) | |
1559 (error "Invalid token: %s" code))))) | |
1560 | |
1561 (defsubst js2-token-sym (tok) | |
1562 "Return symbol for TOK given its code, e.g. 'js2-LP for code 86." | |
1563 (intern (js2-token-name tok))) | |
1564 | |
1565 (defvar js2-token-codes | |
1566 (let ((table (make-hash-table :test 'eq :size 256))) | |
1567 (loop for name across js2-token-names | |
1568 for sym = (intern (concat "js2-" name)) | |
1569 do | |
1570 (puthash sym (symbol-value sym) table)) | |
1571 ;; clean up a few that are "wrong" in Rhino's token codes | |
1572 (puthash 'js2-DELETE js2-DELPROP table) | |
1573 table) | |
1574 "Hashtable mapping token symbols to their bytecodes.") | |
1575 | |
1576 (defsubst js2-token-code (sym) | |
1577 "Return code for token symbol SYM, e.g. 86 for 'js2-LP." | |
1578 (or (gethash sym js2-token-codes) | |
1579 (error "Invalid token symbol: %s " sym))) ; signal code bug | |
1580 | |
1581 (defsubst js2-report-scan-error (msg &optional no-throw beg len) | |
1582 (setq js2-token-end js2-ts-cursor) | |
1583 (js2-report-error msg nil | |
1584 (or beg js2-token-beg) | |
1585 (or len (- js2-token-end js2-token-beg))) | |
1586 (unless no-throw | |
1587 (throw 'return js2-ERROR))) | |
1588 | |
1589 (defsubst js2-get-string-from-buffer () | |
1590 "Reverse the char accumulator and return it as a string." | |
1591 (setq js2-token-end js2-ts-cursor) | |
1592 (if js2-ts-string-buffer | |
1593 (apply #'string (nreverse js2-ts-string-buffer)) | |
1594 "")) | |
1595 | |
1596 ;; TODO: could potentially avoid a lot of consing by allocating a | |
1597 ;; char buffer the way Rhino does. | |
1598 (defsubst js2-add-to-string (c) | |
1599 (push c js2-ts-string-buffer)) | |
1600 | |
1601 ;; Note that when we "read" the end-of-file, we advance js2-ts-cursor | |
1602 ;; to (1+ (point-max)), which lets the scanner treat end-of-file like | |
1603 ;; any other character: when it's not part of the current token, we | |
1604 ;; unget it, allowing it to be read again by the following call. | |
1605 (defsubst js2-unget-char () | |
1606 (decf js2-ts-cursor)) | |
1607 | |
1608 ;; Rhino distinguishes \r and \n line endings. We don't need to | |
1609 ;; because we only scan from Emacs buffers, which always use \n. | |
1610 (defsubst js2-get-char () | |
1611 "Read and return the next character from the input buffer. | |
1612 Increments `js2-ts-lineno' if the return value is a newline char. | |
1613 Updates `js2-ts-cursor' to the point after the returned char. | |
1614 Returns `js2-EOF_CHAR' if we hit the end of the buffer. | |
1615 Also updates `js2-ts-hit-eof' and `js2-ts-line-start' as needed." | |
1616 (let (c) | |
1617 ;; check for end of buffer | |
1618 (if (>= js2-ts-cursor (point-max)) | |
1619 (setq js2-ts-hit-eof t | |
1620 js2-ts-cursor (1+ js2-ts-cursor) | |
1621 c js2-EOF_CHAR) ; return value | |
1622 | |
1623 ;; otherwise read next char | |
1624 (setq c (char-before (incf js2-ts-cursor))) | |
1625 | |
1626 ;; if we read a newline, update counters | |
1627 (if (= c ?\n) | |
1628 (setq js2-ts-line-start js2-ts-cursor | |
1629 js2-ts-lineno (1+ js2-ts-lineno))) | |
1630 | |
1631 ;; TODO: skip over format characters | |
1632 c))) | |
1633 | |
1634 (defsubst js2-read-unicode-escape () | |
1635 "Read a \\uNNNN sequence from the input. | |
1636 Assumes the ?\ and ?u have already been read. | |
1637 Returns the unicode character, or nil if it wasn't a valid character. | |
1638 Doesn't change the values of any scanner variables." | |
1639 ;; I really wish I knew a better way to do this, but I can't | |
1640 ;; find the Emacs function that takes a 16-bit int and converts | |
1641 ;; it to a Unicode/utf-8 character. So I basically eval it with (read). | |
1642 ;; Have to first check that it's 4 hex characters or it may stop | |
1643 ;; the read early. | |
1644 (ignore-errors | |
1645 (let ((s (buffer-substring-no-properties js2-ts-cursor | |
1646 (+ 4 js2-ts-cursor)))) | |
1647 (if (string-match "[a-zA-Z0-9]\\{4\\}" s) | |
1648 (read (concat "?\\u" s)))))) | |
1649 | |
1650 (defsubst js2-match-char (test) | |
1651 "Consume and return next character if it matches TEST, a character. | |
1652 Returns nil and consumes nothing if TEST is not the next character." | |
1653 (let ((c (js2-get-char))) | |
1654 (if (eq c test) | |
1655 t | |
1656 (js2-unget-char) | |
1657 nil))) | |
1658 | |
1659 (defsubst js2-peek-char () | |
1660 (prog1 | |
1661 (js2-get-char) | |
1662 (js2-unget-char))) | |
1663 | |
1664 (defsubst js2-java-identifier-start-p (c) | |
1665 (or | |
1666 (memq c '(?$ ?_)) | |
1667 (char-is-uppercase c) | |
1668 (char-is-lowercase c))) | |
1669 | |
1670 (defsubst js2-java-identifier-part-p (c) | |
1671 "Implementation of java.lang.Character.isJavaIdentifierPart()" | |
1672 ;; TODO: make me Unicode-friendly. See comments above. | |
1673 (or | |
1674 (memq c '(?$ ?_)) | |
1675 (char-is-uppercase c) | |
1676 (char-is-lowercase c) | |
1677 (and (>= c ?0) (<= c ?9)))) | |
1678 | |
1679 (defsubst js2-alpha-p (c) | |
1680 ;; Use 'Z' < 'a' | |
1681 (if (<= c ?Z) | |
1682 (<= ?A c) | |
1683 (and (<= ?a c) | |
1684 (<= c ?z)))) | |
1685 | |
1686 (defsubst js2-digit-p (c) | |
1687 (and (<= ?0 c) (<= c ?9))) | |
1688 | |
1689 (defsubst js2-js-space-p (c) | |
1690 (if (<= c 127) | |
1691 (memq c '(#x20 #x9 #xC #xB)) | |
1692 (or | |
1693 (eq c #xA0) | |
1694 ;; TODO: change this nil to check for Unicode space character | |
1695 nil))) | |
1696 | |
1697 (defsubst js2-skip-line () | |
1698 "Skip to end of line" | |
1699 (let (c) | |
1700 (while (and (/= js2-EOF_CHAR (setq c (js2-get-char))) | |
1701 (/= c ?\n))) | |
1702 (js2-unget-char) | |
1703 (setq js2-token-end js2-ts-cursor))) | |
1704 | |
1705 (defun js2-init-scanner (&optional buf line) | |
1706 "Create token stream for BUF starting on LINE. | |
1707 BUF defaults to current-buffer and line defaults to 1. | |
1708 | |
1709 A buffer can only have one scanner active at a time, which yields | |
1710 dramatically simpler code than using a defstruct. If you need to | |
1711 have simultaneous scanners in a buffer, copy the regions to scan | |
1712 into temp buffers." | |
1713 (save-excursion | |
1714 (when buf | |
1715 (set-buffer buf)) | |
1716 (setq js2-ts-dirty-line nil | |
1717 js2-ts-regexp-flags nil | |
1718 js2-ts-string "" | |
1719 js2-ts-number nil | |
1720 js2-ts-hit-eof nil | |
1721 js2-ts-line-start 0 | |
1722 js2-ts-lineno (or line 1) | |
1723 js2-ts-line-end-char -1 | |
1724 js2-ts-cursor (point-min) | |
1725 js2-ts-is-xml-attribute nil | |
1726 js2-ts-xml-is-tag-content nil | |
1727 js2-ts-xml-open-tags-count 0 | |
1728 js2-ts-string-buffer nil))) | |
1729 | |
1730 ;; This function uses the cached op, string and number fields in | |
1731 ;; TokenStream; if getToken has been called since the passed token | |
1732 ;; was scanned, the op or string printed may be incorrect. | |
1733 (defun js2-token-to-string (token) | |
1734 ;; Not sure where this function is used in Rhino. Not tested. | |
1735 (if (not js2-debug-print-trees) | |
1736 "" | |
1737 (let ((name (js2-token-name token))) | |
1738 (cond | |
1739 ((memq token (list js2-STRING js2-REGEXP js2-NAME)) | |
1740 (concat name " `" js2-ts-string "'")) | |
1741 ((eq token js2-NUMBER) | |
1742 (format "NUMBER %g" js2-ts-number)) | |
1743 (t | |
1744 name))))) | |
1745 | |
1746 (defconst js2-keywords | |
1747 '(break | |
1748 case catch const continue | |
1749 debugger default delete do | |
1750 else enum | |
1751 false finally for function | |
1752 if in instanceof import | |
1753 let | |
1754 new null | |
1755 return | |
1756 switch | |
1757 this throw true try typeof | |
1758 var void | |
1759 while with | |
1760 yield)) | |
1761 | |
1762 ;; Token names aren't exactly the same as the keywords, unfortunately. | |
1763 ;; E.g. enum isn't in the tokens, and delete is js2-DELPROP. | |
1764 (defconst js2-kwd-tokens | |
1765 (let ((table (make-vector js2-num-tokens nil)) | |
1766 (tokens | |
1767 (list js2-BREAK | |
1768 js2-CASE js2-CATCH js2-CONST js2-CONTINUE | |
1769 js2-DEBUGGER js2-DEFAULT js2-DELPROP js2-DO | |
1770 js2-ELSE | |
1771 js2-FALSE js2-FINALLY js2-FOR js2-FUNCTION | |
1772 js2-IF js2-IN js2-INSTANCEOF js2-IMPORT | |
1773 js2-LET | |
1774 js2-NEW js2-NULL | |
1775 js2-RETURN | |
1776 js2-SWITCH | |
1777 js2-THIS js2-THROW js2-TRUE js2-TRY js2-TYPEOF | |
1778 js2-VAR | |
1779 js2-WHILE js2-WITH | |
1780 js2-YIELD))) | |
1781 (dolist (i tokens) | |
1782 (aset table i 'font-lock-keyword-face)) | |
1783 (aset table js2-STRING 'font-lock-string-face) | |
1784 (aset table js2-REGEXP 'font-lock-string-face) | |
1785 (aset table js2-COMMENT 'font-lock-comment-face) | |
1786 (aset table js2-THIS 'font-lock-builtin-face) | |
1787 (aset table js2-VOID 'font-lock-constant-face) | |
1788 (aset table js2-NULL 'font-lock-constant-face) | |
1789 (aset table js2-TRUE 'font-lock-constant-face) | |
1790 (aset table js2-FALSE 'font-lock-constant-face) | |
1791 table) | |
1792 "Vector whose values are non-nil for tokens that are keywords. | |
1793 The values are default faces to use for highlighting the keywords.") | |
1794 | |
1795 (defconst js2-reserved-words | |
1796 '(abstract | |
1797 boolean byte | |
1798 char class | |
1799 double | |
1800 enum extends | |
1801 final float | |
1802 goto | |
1803 implements int interface | |
1804 long | |
1805 native | |
1806 package private protected public | |
1807 short static super synchronized | |
1808 throws transient | |
1809 volatile)) | |
1810 | |
1811 (defconst js2-keyword-names | |
1812 (let ((table (make-hash-table :test 'equal))) | |
1813 (loop for k in js2-keywords | |
1814 do (puthash | |
1815 (symbol-name k) ; instanceof | |
1816 (intern (concat "js2-" | |
1817 (upcase (symbol-name k)))) ; js2-INSTANCEOF | |
1818 table)) | |
1819 table) | |
1820 "JavaScript keywords by name, mapped to their symbols.") | |
1821 | |
1822 (defconst js2-reserved-word-names | |
1823 (let ((table (make-hash-table :test 'equal))) | |
1824 (loop for k in js2-reserved-words | |
1825 do | |
1826 (puthash (symbol-name k) 'js2-RESERVED table)) | |
1827 table) | |
1828 "JavaScript reserved words by name, mapped to 'js2-RESERVED.") | |
1829 | |
1830 (defsubst js2-collect-string (buf) | |
1831 "Convert BUF, a list of chars, to a string. | |
1832 Reverses BUF before converting." | |
1833 (cond | |
1834 ((stringp buf) | |
1835 buf) | |
1836 ((null buf) ; for emacs21 compat | |
1837 "") | |
1838 (t | |
1839 (if buf | |
1840 (apply #'string (nreverse buf)) | |
1841 "")))) | |
1842 | |
1843 (defun js2-string-to-keyword (s) | |
1844 "Return token for S, a string, if S is a keyword or reserved word. | |
1845 Returns a symbol such as 'js2-BREAK, or nil if not keyword/reserved." | |
1846 (or (gethash s js2-keyword-names) | |
1847 (gethash s js2-reserved-word-names))) | |
1848 | |
1849 (defsubst js2-ts-set-char-token-bounds () | |
1850 "Used when next token is one character." | |
1851 (setq js2-token-beg (1- js2-ts-cursor) | |
1852 js2-token-end js2-ts-cursor)) | |
1853 | |
1854 (defsubst js2-ts-return (token) | |
1855 "Return an N-character TOKEN from `js2-get-token'. | |
1856 Updates `js2-token-end' accordingly." | |
1857 (setq js2-token-end js2-ts-cursor) | |
1858 (throw 'return token)) | |
1859 | |
1860 (defsubst js2-x-digit-to-int (c accumulator) | |
1861 "Build up a hex number. | |
1862 If C is a hexadecimal digit, return ACCUMULATOR * 16 plus | |
1863 corresponding number. Otherwise return -1." | |
1864 (catch 'return | |
1865 (catch 'check | |
1866 ;; Use 0..9 < A..Z < a..z | |
1867 (cond | |
1868 ((<= c ?9) | |
1869 (decf c ?0) | |
1870 (if (<= 0 c) | |
1871 (throw 'check nil))) | |
1872 ((<= c ?F) | |
1873 (when (<= ?A c) | |
1874 (decf c (- ?A 10)) | |
1875 (throw 'check nil))) | |
1876 ((<= c ?f) | |
1877 (when (<= ?a c) | |
1878 (decf c (- ?a 10)) | |
1879 (throw 'check nil)))) | |
1880 (throw 'return -1)) | |
1881 (logior c (lsh accumulator 4)))) | |
1882 | |
1883 (defun js2-get-token () | |
1884 "Return next JavaScript token, an int such as js2-RETURN." | |
1885 (let (c | |
1886 c1 | |
1887 identifier-start | |
1888 is-unicode-escape-start | |
1889 contains-escape | |
1890 escape-val | |
1891 escape-start | |
1892 str | |
1893 result | |
1894 base | |
1895 is-integer | |
1896 quote-char | |
1897 val | |
1898 look-for-slash | |
1899 continue) | |
1900 (catch 'return | |
1901 (while t | |
1902 ;; Eat whitespace, possibly sensitive to newlines. | |
1903 (setq continue t) | |
1904 (while continue | |
1905 (setq c (js2-get-char)) | |
1906 (cond | |
1907 ((eq c js2-EOF_CHAR) | |
1908 (js2-ts-set-char-token-bounds) | |
1909 (throw 'return js2-EOF)) | |
1910 ((eq c ?\n) | |
1911 (js2-ts-set-char-token-bounds) | |
1912 (setq js2-ts-dirty-line nil) | |
1913 (throw 'return js2-EOL)) | |
1914 ((not (js2-js-space-p c)) | |
1915 (if (/= c ?-) ; in case end of HTML comment | |
1916 (setq js2-ts-dirty-line t)) | |
1917 (setq continue nil)))) | |
1918 | |
1919 ;; Assume the token will be 1 char - fixed up below. | |
1920 (js2-ts-set-char-token-bounds) | |
1921 | |
1922 (when (eq c ?@) | |
1923 (throw 'return js2-XMLATTR)) | |
1924 | |
1925 ;; identifier/keyword/instanceof? | |
1926 ;; watch out for starting with a <backslash> | |
1927 (cond | |
1928 ((eq c ?\\) | |
1929 (setq c (js2-get-char)) | |
1930 (if (eq c ?u) | |
1931 (setq identifier-start t | |
1932 is-unicode-escape-start t | |
1933 js2-ts-string-buffer nil) | |
1934 (setq identifier-start nil) | |
1935 (js2-unget-char) | |
1936 (setq c ?\\))) | |
1937 (t | |
1938 (when (setq identifier-start (js2-java-identifier-start-p c)) | |
1939 (setq js2-ts-string-buffer nil) | |
1940 (js2-add-to-string c)))) | |
1941 | |
1942 (when identifier-start | |
1943 (setq contains-escape is-unicode-escape-start) | |
1944 (catch 'break | |
1945 (while t | |
1946 (if is-unicode-escape-start | |
1947 ;; strictly speaking we should probably push-back | |
1948 ;; all the bad characters if the <backslash>uXXXX | |
1949 ;; sequence is malformed. But since there isn't a | |
1950 ;; correct context(is there?) for a bad Unicode | |
1951 ;; escape sequence in an identifier, we can report | |
1952 ;; an error here. | |
1953 (progn | |
1954 (setq escape-val 0) | |
1955 (dotimes (i 4) | |
1956 (setq c (js2-get-char) | |
1957 escape-val (js2-x-digit-to-int c escape-val)) | |
1958 ;; Next check takes care of c < 0 and bad escape | |
1959 (if (minusp escape-val) | |
1960 (throw 'break nil))) | |
1961 (if (minusp escape-val) | |
1962 (js2-report-scan-error "msg.invalid.escape" t)) | |
1963 (js2-add-to-string escape-val) | |
1964 (setq is-unicode-escape-start nil)) | |
1965 (setq c (js2-get-char)) | |
1966 (cond | |
1967 ((eq c ?\\) | |
1968 (setq c (js2-get-char)) | |
1969 (if (eq c ?u) | |
1970 (setq is-unicode-escape-start t | |
1971 contains-escape t) | |
1972 (js2-report-scan-error "msg.illegal.character" t))) | |
1973 (t | |
1974 (if (or (eq c js2-EOF_CHAR) | |
1975 (not (js2-java-identifier-part-p c))) | |
1976 (throw 'break nil)) | |
1977 (js2-add-to-string c)))))) | |
1978 (js2-unget-char) | |
1979 | |
1980 (setq str (js2-get-string-from-buffer)) | |
1981 (unless contains-escape | |
1982 ;; OPT we shouldn't have to make a string (object!) to | |
1983 ;; check if it's a keyword. | |
1984 | |
1985 ;; Return the corresponding token if it's a keyword | |
1986 (when (setq result (js2-string-to-keyword str)) | |
1987 (if (and (< js2-language-version 170) | |
1988 (memq result '(js2-LET js2-YIELD))) | |
1989 ;; LET and YIELD are tokens only in 1.7 and later | |
1990 (setq result 'js2-NAME)) | |
1991 (if (neq result js2-RESERVED) | |
1992 (throw 'return (js2-token-code result))) | |
1993 (js2-report-warning "msg.reserved.keyword" str))) | |
1994 | |
1995 ;; If we want to intern these as Rhino does, just use (intern str) | |
1996 (setq js2-ts-string str) | |
1997 (throw 'return js2-NAME)) ; end identifier/kwd check | |
1998 | |
1999 ;; is it a number? | |
2000 (when (or (js2-digit-p c) | |
2001 (and (eq c ?.) (js2-digit-p (js2-peek-char)))) | |
2002 (setq js2-ts-string-buffer nil | |
2003 base 10) | |
2004 (when (eq c ?0) | |
2005 (setq c (js2-get-char)) | |
2006 (cond | |
2007 ((or (eq c ?x) (eq c ?X)) | |
2008 (setq base 16) | |
2009 (setq c (js2-get-char))) | |
2010 ((js2-digit-p c) | |
2011 (setq base 8)) | |
2012 (t | |
2013 (js2-add-to-string ?0)))) | |
2014 | |
2015 (if (eq base 16) | |
2016 (while (<= 0 (js2-x-digit-to-int c 0)) | |
2017 (js2-add-to-string c) | |
2018 (setq c (js2-get-char))) | |
2019 (while (and (<= ?0 c) (<= c ?9)) | |
2020 ;; We permit 08 and 09 as decimal numbers, which | |
2021 ;; makes our behavior a superset of the ECMA | |
2022 ;; numeric grammar. We might not always be so | |
2023 ;; permissive, so we warn about it. | |
2024 (when (and (eq base 8) (>= c ?8)) | |
2025 (js2-report-warning "msg.bad.octal.literal" | |
2026 (if (eq c ?8) "8" "9")) | |
2027 (setq base 10)) | |
2028 (js2-add-to-string c) | |
2029 (setq c (js2-get-char)))) | |
2030 | |
2031 (setq is-integer t) | |
2032 | |
2033 (when (and (eq base 10) (memq c '(?. ?e ?E))) | |
2034 (setq is-integer nil) | |
2035 (when (eq c ?.) | |
2036 (loop do | |
2037 (js2-add-to-string c) | |
2038 (setq c (js2-get-char)) | |
2039 while (js2-digit-p c))) | |
2040 (when (memq c '(?e ?E)) | |
2041 (js2-add-to-string c) | |
2042 (setq c (js2-get-char)) | |
2043 (when (memq c '(?+ ?-)) | |
2044 (js2-add-to-string c) | |
2045 (setq c (js2-get-char))) | |
2046 (unless (js2-digit-p c) | |
2047 (js2-report-scan-error "msg.missing.exponent" t)) | |
2048 (loop do | |
2049 (js2-add-to-string c) | |
2050 (setq c (js2-get-char)) | |
2051 while (js2-digit-p c)))) | |
2052 | |
2053 (js2-unget-char) | |
2054 (setq js2-ts-string (js2-get-string-from-buffer) | |
2055 js2-ts-number | |
2056 (if (and (eq base 10) (not is-integer)) | |
2057 (string-to-number js2-ts-string) | |
2058 ;; TODO: call runtime number-parser. Some of it is in | |
2059 ;; js2-util.el, but I need to port ScriptRuntime.stringToNumber. | |
2060 (string-to-number js2-ts-string))) | |
2061 (throw 'return js2-NUMBER)) | |
2062 | |
2063 ;; is it a string? | |
2064 (when (memq c '(?\" ?\')) | |
2065 ;; We attempt to accumulate a string the fast way, by | |
2066 ;; building it directly out of the reader. But if there | |
2067 ;; are any escaped characters in the string, we revert to | |
2068 ;; building it out of a string buffer. | |
2069 (setq quote-char c | |
2070 js2-ts-string-buffer nil | |
2071 c (js2-get-char)) | |
2072 (catch 'break | |
2073 (while (/= c quote-char) | |
2074 (catch 'continue | |
2075 (when (or (eq c ?\n) (eq c js2-EOF_CHAR)) | |
2076 (js2-unget-char) | |
2077 (setq js2-token-end js2-ts-cursor) | |
2078 (js2-report-error "msg.unterminated.string.lit") | |
2079 (throw 'return js2-STRING)) | |
2080 | |
2081 (when (eq c ?\\) | |
2082 ;; We've hit an escaped character | |
2083 (setq c (js2-get-char)) | |
2084 (case c | |
2085 (?b (setq c ?\b)) | |
2086 (?f (setq c ?\f)) | |
2087 (?n (setq c ?\n)) | |
2088 (?r (setq c ?\r)) | |
2089 (?t (setq c ?\t)) | |
2090 (?v (setq c ?\v)) | |
2091 (?u | |
2092 (setq c1 (js2-read-unicode-escape)) | |
2093 (if js2-parse-ide-mode | |
2094 (if c1 | |
2095 (progn | |
2096 ;; just copy the string in IDE-mode | |
2097 (js2-add-to-string ?\\) | |
2098 (js2-add-to-string ?u) | |
2099 (dotimes (i 3) | |
2100 (js2-add-to-string (js2-get-char))) | |
2101 (setq c (js2-get-char))) ; added at end of loop | |
2102 ;; flag it as an invalid escape | |
2103 (js2-report-warning "msg.invalid.escape" | |
2104 nil (- js2-ts-cursor 2) 6)) | |
2105 ;; Get 4 hex digits; if the u escape is not | |
2106 ;; followed by 4 hex digits, use 'u' + the | |
2107 ;; literal character sequence that follows. | |
2108 (js2-add-to-string ?u) | |
2109 (setq escape-val 0) | |
2110 (dotimes (i 4) | |
2111 (setq c (js2-get-char) | |
2112 escape-val (js2-x-digit-to-int c escape-val)) | |
2113 (if (minusp escape-val) | |
2114 (throw 'continue nil)) | |
2115 (js2-add-to-string c)) | |
2116 ;; prepare for replace of stored 'u' sequence by escape value | |
2117 (setq js2-ts-string-buffer (nthcdr 5 js2-ts-string-buffer) | |
2118 c escape-val))) | |
2119 (?x | |
2120 ;; Get 2 hex digits, defaulting to 'x'+literal | |
2121 ;; sequence, as above. | |
2122 (setq c (js2-get-char) | |
2123 escape-val (js2-x-digit-to-int c 0)) | |
2124 (if (minusp escape-val) | |
2125 (progn | |
2126 (js2-add-to-string ?x) | |
2127 (throw 'continue nil)) | |
2128 (setq c1 c | |
2129 c (js2-get-char) | |
2130 escape-val (js2-x-digit-to-int c escape-val)) | |
2131 (if (minusp escape-val) | |
2132 (progn | |
2133 (js2-add-to-string ?x) | |
2134 (js2-add-to-string c1) | |
2135 (throw 'continue nil)) | |
2136 ;; got 2 hex digits | |
2137 (setq c escape-val)))) | |
2138 (?\n | |
2139 ;; Remove line terminator after escape to follow | |
2140 ;; SpiderMonkey and C/C++ | |
2141 (setq c (js2-get-char)) | |
2142 (throw 'continue nil)) | |
2143 (t | |
2144 (when (and (<= ?0 c) (< c ?8)) | |
2145 (setq val (- c ?0) | |
2146 c (js2-get-char)) | |
2147 (when (and (<= ?0 c) (< c ?8)) | |
2148 (setq val (- (+ (* 8 val) c) ?0) | |
2149 c (js2-get-char)) | |
2150 (when (and (<= ?0 c) | |
2151 (< c ?8) | |
2152 (< val #o37)) | |
2153 ;; c is 3rd char of octal sequence only | |
2154 ;; if the resulting val <= 0377 | |
2155 (setq val (- (+ (* 8 val) c) ?0) | |
2156 c (js2-get-char)))) | |
2157 (js2-unget-char) | |
2158 (setq c val))))) | |
2159 (js2-add-to-string c) | |
2160 (setq c (js2-get-char))))) | |
2161 (setq js2-ts-string (js2-get-string-from-buffer)) | |
2162 (throw 'return js2-STRING)) | |
2163 | |
2164 (case c | |
2165 (?\; | |
2166 (throw 'return js2-SEMI)) | |
2167 (?\[ | |
2168 (throw 'return js2-LB)) | |
2169 (?\] | |
2170 (throw 'return js2-RB)) | |
2171 (?{ | |
2172 (throw 'return js2-LC)) | |
2173 (?} | |
2174 (throw 'return js2-RC)) | |
2175 (?\( | |
2176 (throw 'return js2-LP)) | |
2177 (?\) | |
2178 (throw 'return js2-RP)) | |
2179 (?, | |
2180 (throw 'return js2-COMMA)) | |
2181 (?? | |
2182 (throw 'return js2-HOOK)) | |
2183 (?: | |
2184 (if (js2-match-char ?:) | |
2185 (js2-ts-return js2-COLONCOLON) | |
2186 (throw 'return js2-COLON))) | |
2187 (?. | |
2188 (if (js2-match-char ?.) | |
2189 (js2-ts-return js2-DOTDOT) | |
2190 (if (js2-match-char ?\() | |
2191 (js2-ts-return js2-DOTQUERY) | |
2192 (throw 'return js2-DOT)))) | |
2193 (?| | |
2194 (if (js2-match-char ?|) | |
2195 (throw 'return js2-OR) | |
2196 (if (js2-match-char ?=) | |
2197 (js2-ts-return js2-ASSIGN_BITOR) | |
2198 (throw 'return js2-BITOR)))) | |
2199 (?^ | |
2200 (if (js2-match-char ?=) | |
2201 (js2-ts-return js2-ASSIGN_BITOR) | |
2202 (throw 'return js2-BITXOR))) | |
2203 (?& | |
2204 (if (js2-match-char ?&) | |
2205 (throw 'return js2-AND) | |
2206 (if (js2-match-char ?=) | |
2207 (js2-ts-return js2-ASSIGN_BITAND) | |
2208 (throw 'return js2-BITAND)))) | |
2209 (?= | |
2210 (if (js2-match-char ?=) | |
2211 (if (js2-match-char ?=) | |
2212 (js2-ts-return js2-SHEQ) | |
2213 (throw 'return js2-EQ)) | |
2214 (throw 'return js2-ASSIGN))) | |
2215 (?! | |
2216 (if (js2-match-char ?=) | |
2217 (if (js2-match-char ?=) | |
2218 (js2-ts-return js2-SHNE) | |
2219 (js2-ts-return js2-NE)) | |
2220 (throw 'return js2-NOT))) | |
2221 (?< | |
2222 ;; NB:treat HTML begin-comment as comment-till-eol | |
2223 (when (js2-match-char ?!) | |
2224 (when (js2-match-char ?-) | |
2225 (when (js2-match-char ?-) | |
2226 (js2-skip-line) | |
2227 (setq js2-ts-comment-type 'html) | |
2228 (throw 'return js2-COMMENT))) | |
2229 (js2-unget-char)) | |
2230 | |
2231 (if (js2-match-char ?<) | |
2232 (if (js2-match-char ?=) | |
2233 (js2-ts-return js2-ASSIGN_LSH) | |
2234 (js2-ts-return js2-LSH)) | |
2235 (if (js2-match-char ?=) | |
2236 (js2-ts-return js2-LE) | |
2237 (throw 'return js2-LT)))) | |
2238 (?> | |
2239 (if (js2-match-char ?>) | |
2240 (if (js2-match-char ?>) | |
2241 (if (js2-match-char ?=) | |
2242 (js2-ts-return js2-ASSIGN_URSH) | |
2243 (js2-ts-return js2-URSH)) | |
2244 (if (js2-match-char ?=) | |
2245 (js2-ts-return js2-ASSIGN_RSH) | |
2246 (js2-ts-return js2-RSH))) | |
2247 (if (js2-match-char ?=) | |
2248 (js2-ts-return js2-GE) | |
2249 (throw 'return js2-GT)))) | |
2250 (?* | |
2251 (if (js2-match-char ?=) | |
2252 (js2-ts-return js2-ASSIGN_MUL) | |
2253 (throw 'return js2-MUL))) | |
2254 | |
2255 (?/ | |
2256 ;; is it a // comment? | |
2257 (when (js2-match-char ?/) | |
2258 (setq js2-token-beg (- js2-ts-cursor 2)) | |
2259 (js2-skip-line) | |
2260 (setq js2-ts-comment-type 'line) | |
2261 (throw 'return js2-COMMENT)) | |
2262 | |
2263 ;; is it a /* comment? | |
2264 (when (js2-match-char ?*) | |
2265 (setq look-for-slash nil | |
2266 js2-token-beg (- js2-ts-cursor 2) | |
2267 js2-ts-comment-type | |
2268 (if (js2-match-char ?*) | |
2269 (progn | |
2270 (setq look-for-slash t) | |
2271 'jsdoc) | |
2272 'block)) | |
2273 (while t | |
2274 (setq c (js2-get-char)) | |
2275 (cond | |
2276 ((eq c js2-EOF_CHAR) | |
2277 (setq js2-token-end (1- js2-ts-cursor)) | |
2278 (js2-report-error "msg.unterminated.comment") | |
2279 (throw 'return js2-COMMENT)) | |
2280 ((eq c ?*) | |
2281 (setq look-for-slash t)) | |
2282 ((eq c ?/) | |
2283 (if look-for-slash | |
2284 (js2-ts-return js2-COMMENT))) | |
2285 (t | |
2286 (setq look-for-slash nil | |
2287 js2-token-end js2-ts-cursor))))) | |
2288 | |
2289 (if (js2-match-char ?=) | |
2290 (js2-ts-return js2-ASSIGN_DIV) | |
2291 (throw 'return js2-DIV))) | |
2292 | |
2293 (?# | |
2294 (when js2-skip-preprocessor-directives | |
2295 (js2-skip-line) | |
2296 (setq js2-ts-comment-type 'preprocessor | |
2297 js2-token-end js2-ts-cursor) | |
2298 (throw 'return js2-COMMENT)) | |
2299 (throw 'return js2-ERROR)) | |
2300 | |
2301 (?% | |
2302 (if (js2-match-char ?=) | |
2303 (js2-ts-return js2-ASSIGN_MOD) | |
2304 (throw 'return js2-MOD))) | |
2305 (?~ | |
2306 (throw 'return js2-BITNOT)) | |
2307 (?+ | |
2308 (if (js2-match-char ?=) | |
2309 (js2-ts-return js2-ASSIGN_ADD) | |
2310 (if (js2-match-char ?+) | |
2311 (js2-ts-return js2-INC) | |
2312 (throw 'return js2-ADD)))) | |
2313 (?- | |
2314 (cond | |
2315 ((js2-match-char ?=) | |
2316 (setq c js2-ASSIGN_SUB)) | |
2317 ((js2-match-char ?-) | |
2318 (unless js2-ts-dirty-line | |
2319 ;; treat HTML end-comment after possible whitespace | |
2320 ;; after line start as comment-until-eol | |
2321 (when (js2-match-char ?>) | |
2322 (js2-skip-line) | |
2323 (setq js2-ts-comment-type 'html) | |
2324 (throw 'return js2-COMMENT))) | |
2325 (setq c js2-DEC)) | |
2326 (t | |
2327 (setq c js2-SUB))) | |
2328 (setq js2-ts-dirty-line t) | |
2329 (js2-ts-return c)) | |
2330 | |
2331 (otherwise | |
2332 (js2-report-scan-error "msg.illegal.character"))))))) | |
2333 | |
2334 (defun js2-read-regexp (start-token) | |
2335 "Called by parser when it gets / or /= in literal context." | |
2336 (let (c | |
2337 err | |
2338 in-class ; inside a '[' .. ']' character-class | |
2339 flags | |
2340 (continue t)) | |
2341 (setq js2-token-beg js2-ts-cursor | |
2342 js2-ts-string-buffer nil | |
2343 js2-ts-regexp-flags nil) | |
2344 | |
2345 (if (eq start-token js2-ASSIGN_DIV) | |
2346 ;; mis-scanned /= | |
2347 (js2-add-to-string ?=) | |
2348 (if (neq start-token js2-DIV) | |
2349 (error "failed assertion"))) | |
2350 | |
2351 (while (and (not err) | |
2352 (or (/= (setq c (js2-get-char)) ?/) | |
2353 in-class)) | |
2354 (cond | |
2355 ((or (= c ?\n) | |
2356 (= c js2-EOF_CHAR)) | |
2357 (setq js2-token-end (1- js2-ts-cursor) | |
2358 err t | |
2359 js2-ts-string (js2-collect-string js2-ts-string-buffer)) | |
2360 (js2-report-error "msg.unterminated.re.lit")) | |
2361 (t (cond | |
2362 ((= c ?\\) | |
2363 (js2-add-to-string c) | |
2364 (setq c (js2-get-char))) | |
2365 | |
2366 ((= c ?\[) | |
2367 (setq in-class t)) | |
2368 | |
2369 ((= c ?\]) | |
2370 (setq in-class nil))) | |
2371 (js2-add-to-string c)))) | |
2372 | |
2373 (unless err | |
2374 (while continue | |
2375 (cond | |
2376 ((js2-match-char ?g) | |
2377 (push ?g flags)) | |
2378 ((js2-match-char ?i) | |
2379 (push ?i flags)) | |
2380 ((js2-match-char ?m) | |
2381 (push ?m flags)) | |
2382 (t | |
2383 (setq continue nil)))) | |
2384 (if (js2-alpha-p (js2-peek-char)) | |
2385 (js2-report-scan-error "msg.invalid.re.flag" t | |
2386 js2-ts-cursor 1)) | |
2387 (setq js2-ts-string (js2-collect-string js2-ts-string-buffer) | |
2388 js2-ts-regexp-flags (js2-collect-string flags) | |
2389 js2-token-end js2-ts-cursor) | |
2390 ;; tell `parse-partial-sexp' to ignore this range of chars | |
2391 (put-text-property js2-token-beg js2-token-end 'syntax-class '(2))))) | |
2392 | |
2393 (defun js2-get-first-xml-token () | |
2394 (setq js2-ts-xml-open-tags-count 0 | |
2395 js2-ts-is-xml-attribute nil | |
2396 js2-ts-xml-is-tag-content nil) | |
2397 (js2-unget-char) | |
2398 (js2-get-next-xml-token)) | |
2399 | |
2400 (defsubst js2-xml-discard-string () | |
2401 "Throw away the string in progress and flag an XML parse error." | |
2402 (setq js2-ts-string-buffer nil | |
2403 js2-ts-string nil) | |
2404 (js2-report-scan-error "msg.XML.bad.form" t)) | |
2405 | |
2406 (defun js2-get-next-xml-token () | |
2407 (setq js2-ts-string-buffer nil ; for recording the XML | |
2408 js2-token-beg js2-ts-cursor) | |
2409 (let (c result) | |
2410 (setq result | |
2411 (catch 'return | |
2412 (while t | |
2413 (setq c (js2-get-char)) | |
2414 (cond | |
2415 ((= c js2-EOF_CHAR) | |
2416 (throw 'return js2-ERROR)) | |
2417 | |
2418 (js2-ts-xml-is-tag-content | |
2419 (case c | |
2420 (?> | |
2421 (js2-add-to-string c) | |
2422 (setq js2-ts-xml-is-tag-content nil | |
2423 js2-ts-is-xml-attribute nil)) | |
2424 (?/ | |
2425 (js2-add-to-string c) | |
2426 (when (eq ?> (js2-peek-char)) | |
2427 (setq c (js2-get-char)) | |
2428 (js2-add-to-string c) | |
2429 (setq js2-ts-xml-is-tag-content nil) | |
2430 (decf js2-ts-xml-open-tags-count))) | |
2431 (?{ | |
2432 (js2-unget-char) | |
2433 (setq js2-ts-string (js2-get-string-from-buffer)) | |
2434 (throw 'return js2-XML)) | |
2435 ((?\' ?\") | |
2436 (js2-add-to-string c) | |
2437 (unless (js2-read-quoted-string c) | |
2438 (throw 'return js2-ERROR))) | |
2439 (?= | |
2440 (js2-add-to-string c) | |
2441 (setq js2-ts-is-xml-attribute t)) | |
2442 ((? ?\t ?\r ?\n) | |
2443 (js2-add-to-string c)) | |
2444 (t | |
2445 (js2-add-to-string c) | |
2446 (setq js2-ts-is-xml-attribute nil))) | |
2447 (when (and (not js2-ts-xml-is-tag-content) | |
2448 (zerop js2-ts-xml-open-tags-count)) | |
2449 (setq js2-ts-string (js2-get-string-from-buffer)) | |
2450 (throw 'return js2-XMLEND))) | |
2451 | |
2452 (t | |
2453 ;; else not tag content | |
2454 (case c | |
2455 (?< | |
2456 (js2-add-to-string c) | |
2457 (setq c (js2-peek-char)) | |
2458 (case c | |
2459 (?! | |
2460 (setq c (js2-get-char)) ;; skip ! | |
2461 (js2-add-to-string c) | |
2462 (setq c (js2-peek-char)) | |
2463 (case c | |
2464 (?- | |
2465 (setq c (js2-get-char)) ;; skip - | |
2466 (js2-add-to-string c) | |
2467 (if (eq c ?-) | |
2468 (progn | |
2469 (js2-add-to-string c) | |
2470 (unless (js2-read-xml-comment) | |
2471 (throw 'return js2-ERROR))) | |
2472 (js2-xml-discard-string) | |
2473 (throw 'return js2-ERROR))) | |
2474 (?\[ | |
2475 (setq c (js2-get-char)) ;; skip [ | |
2476 (js2-add-to-string c) | |
2477 (if (and (= (js2-get-char) ?C) | |
2478 (= (js2-get-char) ?D) | |
2479 (= (js2-get-char) ?A) | |
2480 (= (js2-get-char) ?T) | |
2481 (= (js2-get-char) ?A) | |
2482 (= (js2-get-char) ?\[)) | |
2483 (progn | |
2484 (js2-add-to-string ?C) | |
2485 (js2-add-to-string ?D) | |
2486 (js2-add-to-string ?A) | |
2487 (js2-add-to-string ?T) | |
2488 (js2-add-to-string ?A) | |
2489 (js2-add-to-string ?\[) | |
2490 (unless (js2-read-cdata) | |
2491 (throw 'return js2-ERROR))) | |
2492 (js2-xml-discard-string) | |
2493 (throw 'return js2-ERROR))) | |
2494 (t | |
2495 (unless (js2-read-entity) | |
2496 (throw 'return js2-ERROR))))) | |
2497 (?? | |
2498 (setq c (js2-get-char)) ;; skip ? | |
2499 (js2-add-to-string c) | |
2500 (unless (js2-read-PI) | |
2501 (throw 'return js2-ERROR))) | |
2502 (?/ | |
2503 ;; end tag | |
2504 (setq c (js2-get-char)) ;; skip / | |
2505 (js2-add-to-string c) | |
2506 (when (zerop js2-ts-xml-open-tags-count) | |
2507 (js2-xml-discard-string) | |
2508 (throw 'return js2-ERROR)) | |
2509 (setq js2-ts-xml-is-tag-content t) | |
2510 (decf js2-ts-xml-open-tags-count)) | |
2511 (t | |
2512 ;; start tag | |
2513 (setq js2-ts-xml-is-tag-content t) | |
2514 (incf js2-ts-xml-open-tags-count)))) | |
2515 (?{ | |
2516 (js2-unget-char) | |
2517 (setq js2-ts-string (js2-get-string-from-buffer)) | |
2518 (throw 'return js2-XML)) | |
2519 (t | |
2520 (js2-add-to-string c)))))))) | |
2521 (setq js2-token-end js2-ts-cursor) | |
2522 result)) | |
2523 | |
2524 (defun js2-read-quoted-string (quote) | |
2525 (let (c) | |
2526 (catch 'return | |
2527 (while (/= (setq c (js2-get-char)) js2-EOF_CHAR) | |
2528 (js2-add-to-string c) | |
2529 (if (eq c quote) | |
2530 (throw 'return t))) | |
2531 (js2-xml-discard-string) ;; throw away string in progress | |
2532 nil))) | |
2533 | |
2534 (defun js2-read-xml-comment () | |
2535 (let ((c (js2-get-char))) | |
2536 (catch 'return | |
2537 (while (/= c js2-EOF_CHAR) | |
2538 (catch 'continue | |
2539 (js2-add-to-string c) | |
2540 (when (and (eq c ?-) (eq ?- (js2-peek-char))) | |
2541 (setq c (js2-get-char)) | |
2542 (js2-add-to-string c) | |
2543 (if (eq (js2-peek-char) ?>) | |
2544 (progn | |
2545 (setq c (js2-get-char)) ;; skip > | |
2546 (js2-add-to-string c) | |
2547 (throw 'return t)) | |
2548 (throw 'continue nil))) | |
2549 (setq c (js2-get-char)))) | |
2550 (js2-xml-discard-string) | |
2551 nil))) | |
2552 | |
2553 (defun js2-read-cdata () | |
2554 (let ((c (js2-get-char))) | |
2555 (catch 'return | |
2556 (while (/= c js2-EOF_CHAR) | |
2557 (catch 'continue | |
2558 (js2-add-to-string c) | |
2559 (when (and (eq c ?\]) (eq (js2-peek-char) ?\])) | |
2560 (setq c (js2-get-char)) | |
2561 (js2-add-to-string c) | |
2562 (if (eq (js2-peek-char) ?>) | |
2563 (progn | |
2564 (setq c (js2-get-char)) ;; Skip > | |
2565 (js2-add-to-string c) | |
2566 (throw 'return t)) | |
2567 (throw 'continue nil))) | |
2568 (setq c (js2-get-char)))) | |
2569 (js2-xml-discard-string) | |
2570 nil))) | |
2571 | |
2572 (defun js2-read-entity () | |
2573 (let ((decl-tags 1) | |
2574 c) | |
2575 (catch 'return | |
2576 (while (/= js2-EOF_CHAR (setq c (js2-get-char))) | |
2577 (js2-add-to-string c) | |
2578 (case c | |
2579 (?< | |
2580 (incf decl-tags)) | |
2581 (?> | |
2582 (decf decl-tags) | |
2583 (if (zerop decl-tags) | |
2584 (throw 'return t))))) | |
2585 (js2-xml-discard-string) | |
2586 nil))) | |
2587 | |
2588 (defun js2-read-PI () | |
2589 "Scan an XML processing instruction." | |
2590 (let (c) | |
2591 (catch 'return | |
2592 (while (/= js2-EOF_CHAR (setq c (js2-get-char))) | |
2593 (js2-add-to-string c) | |
2594 (when (and (eq c ??) (eq (js2-peek-char) ?>)) | |
2595 (setq c (js2-get-char)) ;; Skip > | |
2596 (js2-add-to-string c) | |
2597 (throw 'return t))) | |
2598 (js2-xml-discard-string) | |
2599 nil))) | |
2600 | |
2601 (defun js2-scanner-get-line () | |
2602 "Return the text of the current scan line." | |
2603 (buffer-substring (point-at-bol) (point-at-eol))) | |
2604 | |
2605 (provide 'js2-scan) | |
2606 | |
2607 ;;; js2-scan.el ends here | |
2608 ;;; js2-messages: localizable messages for js2-mode | |
2609 | |
2610 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
2611 ;; Keywords: javascript languages | |
2612 | |
2613 ;;; Commentary: | |
2614 | |
2615 ;; Messages are copied from Rhino's Messages.properties. | |
2616 ;; Many of the Java-specific messages have been elided. | |
2617 ;; Add any js2-specific ones at the end, so we can keep | |
2618 ;; this file synced with changes to Rhino's. | |
2619 ;; | |
2620 ;; TODO: | |
2621 ;; - move interpreter messages into separate file | |
2622 | |
2623 ;;; Code: | |
2624 | |
2625 (defvar js2-message-table | |
2626 (make-hash-table :test 'equal :size 250) | |
2627 "Contains localized messages for js2-mode.") | |
2628 | |
2629 ;; TODO: construct this hashtable at compile-time. | |
2630 (defmacro js2-msg (key &rest strings) | |
2631 `(puthash ,key (funcall #'concat ,@strings) | |
2632 js2-message-table)) | |
2633 | |
2634 (defun js2-get-msg (msg-key) | |
2635 "Look up a localized message. | |
2636 MSG-KEY is a list of (MSG ARGS). If the message takes parameters, | |
2637 the correct number of ARGS must be provided." | |
2638 (let* ((key (if (listp msg-key) (car msg-key) msg-key)) | |
2639 (args (if (listp msg-key) (cdr msg-key))) | |
2640 (msg (gethash key js2-message-table))) | |
2641 (if msg | |
2642 (apply #'format msg args) | |
2643 key))) ; default to showing the key | |
2644 | |
2645 (js2-msg "msg.dup.parms" | |
2646 "Duplicate parameter name '%s'.") | |
2647 | |
2648 (js2-msg "msg.too.big.jump" | |
2649 "Program too complex: jump offset too big.") | |
2650 | |
2651 (js2-msg "msg.too.big.index" | |
2652 "Program too complex: internal index exceeds 64K limit.") | |
2653 | |
2654 (js2-msg "msg.while.compiling.fn" | |
2655 "Encountered code generation error while compiling function '%s': %s") | |
2656 | |
2657 (js2-msg "msg.while.compiling.script" | |
2658 "Encountered code generation error while compiling script: %s") | |
2659 | |
2660 ;; Context | |
2661 (js2-msg "msg.ctor.not.found" | |
2662 "Constructor for '%s' not found.") | |
2663 | |
2664 (js2-msg "msg.not.ctor" | |
2665 "'%s' is not a constructor.") | |
2666 | |
2667 ;; FunctionObject | |
2668 (js2-msg "msg.varargs.ctor" | |
2669 "Method or constructor '%s' must be static " | |
2670 "with the signature (Context cx, Object[] args, " | |
2671 "Function ctorObj, boolean inNewExpr) " | |
2672 "to define a variable arguments constructor.") | |
2673 | |
2674 (js2-msg "msg.varargs.fun" | |
2675 "Method '%s' must be static with the signature " | |
2676 "(Context cx, Scriptable thisObj, Object[] args, Function funObj) " | |
2677 "to define a variable arguments function.") | |
2678 | |
2679 (js2-msg "msg.incompat.call" | |
2680 "Method '%s' called on incompatible object.") | |
2681 | |
2682 (js2-msg "msg.bad.parms" | |
2683 "Unsupported parameter type '%s' in method '%s'.") | |
2684 | |
2685 (js2-msg "msg.bad.method.return" | |
2686 "Unsupported return type '%s' in method '%s'.") | |
2687 | |
2688 (js2-msg "msg.bad.ctor.return" | |
2689 "Construction of objects of type '%s' is not supported.") | |
2690 | |
2691 (js2-msg "msg.no.overload" | |
2692 "Method '%s' occurs multiple times in class '%s'.") | |
2693 | |
2694 (js2-msg "msg.method.not.found" | |
2695 "Method '%s' not found in '%s'.") | |
2696 | |
2697 ;; IRFactory | |
2698 | |
2699 (js2-msg "msg.bad.for.in.lhs" | |
2700 "Invalid left-hand side of for..in loop.") | |
2701 | |
2702 (js2-msg "msg.mult.index" | |
2703 "Only one variable allowed in for..in loop.") | |
2704 | |
2705 (js2-msg "msg.bad.for.in.destruct" | |
2706 "Left hand side of for..in loop must be an array of " | |
2707 "length 2 to accept key/value pair.") | |
2708 | |
2709 (js2-msg "msg.cant.convert" | |
2710 "Can't convert to type '%s'.") | |
2711 | |
2712 (js2-msg "msg.bad.assign.left" | |
2713 "Invalid assignment left-hand side.") | |
2714 | |
2715 (js2-msg "msg.bad.decr" | |
2716 "Invalid decerement operand.") | |
2717 | |
2718 (js2-msg "msg.bad.incr" | |
2719 "Invalid increment operand.") | |
2720 | |
2721 (js2-msg "msg.bad.yield" | |
2722 "yield must be in a function.") | |
2723 | |
2724 (js2-msg "msg.yield.parenthesized" | |
2725 "yield expression must be parenthesized.") | |
2726 | |
2727 ;; NativeGlobal | |
2728 (js2-msg "msg.cant.call.indirect" | |
2729 "Function '%s' must be called directly, and not by way of a " | |
2730 "function of another name.") | |
2731 | |
2732 (js2-msg "msg.eval.nonstring" | |
2733 "Calling eval() with anything other than a primitive " | |
2734 "string value will simply return the value. " | |
2735 "Is this what you intended?") | |
2736 | |
2737 (js2-msg "msg.eval.nonstring.strict" | |
2738 "Calling eval() with anything other than a primitive " | |
2739 "string value is not allowed in strict mode.") | |
2740 | |
2741 (js2-msg "msg.bad.destruct.op" | |
2742 "Invalid destructuring assignment operator") | |
2743 | |
2744 ;; NativeCall | |
2745 (js2-msg "msg.only.from.new" | |
2746 "'%s' may only be invoked from a `new' expression.") | |
2747 | |
2748 (js2-msg "msg.deprec.ctor" | |
2749 "The '%s' constructor is deprecated.") | |
2750 | |
2751 ;; NativeFunction | |
2752 (js2-msg "msg.no.function.ref.found" | |
2753 "no source found to decompile function reference %s") | |
2754 | |
2755 (js2-msg "msg.arg.isnt.array" | |
2756 "second argument to Function.prototype.apply must be an array") | |
2757 | |
2758 ;; NativeGlobal | |
2759 (js2-msg "msg.bad.esc.mask" | |
2760 "invalid string escape mask") | |
2761 | |
2762 ;; NativeRegExp | |
2763 (js2-msg "msg.bad.quant" | |
2764 "Invalid quantifier %s") | |
2765 | |
2766 (js2-msg "msg.overlarge.backref" | |
2767 "Overly large back reference %s") | |
2768 | |
2769 (js2-msg "msg.overlarge.min" | |
2770 "Overly large minimum %s") | |
2771 | |
2772 (js2-msg "msg.overlarge.max" | |
2773 "Overly large maximum %s") | |
2774 | |
2775 (js2-msg "msg.zero.quant" | |
2776 "Zero quantifier %s") | |
2777 | |
2778 (js2-msg "msg.max.lt.min" | |
2779 "Maximum %s less than minimum") | |
2780 | |
2781 (js2-msg "msg.unterm.quant" | |
2782 "Unterminated quantifier %s") | |
2783 | |
2784 (js2-msg "msg.unterm.paren" | |
2785 "Unterminated parenthetical %s") | |
2786 | |
2787 (js2-msg "msg.unterm.class" | |
2788 "Unterminated character class %s") | |
2789 | |
2790 (js2-msg "msg.bad.range" | |
2791 "Invalid range in character class.") | |
2792 | |
2793 (js2-msg "msg.trail.backslash" | |
2794 "Trailing \\ in regular expression.") | |
2795 | |
2796 (js2-msg "msg.re.unmatched.right.paren" | |
2797 "unmatched ) in regular expression.") | |
2798 | |
2799 (js2-msg "msg.no.regexp" | |
2800 "Regular expressions are not available.") | |
2801 | |
2802 (js2-msg "msg.bad.backref" | |
2803 "back-reference exceeds number of capturing parentheses.") | |
2804 | |
2805 (js2-msg "msg.bad.regexp.compile" | |
2806 "Only one argument may be specified if the first " | |
2807 "argument to RegExp.prototype.compile is a RegExp object.") | |
2808 | |
2809 ;; Parser | |
2810 (js2-msg "msg.got.syntax.errors" | |
2811 "Compilation produced %s syntax errors.") | |
2812 | |
2813 (js2-msg "msg.var.redecl" | |
2814 "TypeError: redeclaration of var %s.") | |
2815 | |
2816 (js2-msg "msg.const.redecl" | |
2817 "TypeError: redeclaration of const %s.") | |
2818 | |
2819 (js2-msg "msg.let.redecl" | |
2820 "TypeError: redeclaration of variable %s.") | |
2821 | |
2822 (js2-msg "msg.parm.redecl" | |
2823 "TypeError: redeclaration of formal parameter %s.") | |
2824 | |
2825 (js2-msg "msg.fn.redecl" | |
2826 "TypeError: redeclaration of function %s.") | |
2827 | |
2828 (js2-msg "msg.let.decl.not.in.block" | |
2829 "SyntaxError: let declaration not directly within block") | |
2830 | |
2831 ;; NodeTransformer | |
2832 (js2-msg "msg.dup.label" | |
2833 "duplicated label") | |
2834 | |
2835 (js2-msg "msg.undef.label" | |
2836 "undefined label") | |
2837 | |
2838 (js2-msg "msg.bad.break" | |
2839 "unlabelled break must be inside loop or switch") | |
2840 | |
2841 (js2-msg "msg.continue.outside" | |
2842 "continue must be inside loop") | |
2843 | |
2844 (js2-msg "msg.continue.nonloop" | |
2845 "continue can only use labels of iteration statements") | |
2846 | |
2847 (js2-msg "msg.bad.throw.eol" | |
2848 "Line terminator is not allowed between the throw " | |
2849 "keyword and throw expression.") | |
2850 | |
2851 (js2-msg "msg.no.paren.parms" | |
2852 "missing ( before function parameters.") | |
2853 | |
2854 (js2-msg "msg.no.parm" | |
2855 "missing formal parameter") | |
2856 | |
2857 (js2-msg "msg.no.paren.after.parms" | |
2858 "missing ) after formal parameters") | |
2859 | |
2860 (js2-msg "msg.no.brace.body" | |
2861 "missing '{' before function body") | |
2862 | |
2863 (js2-msg "msg.no.brace.after.body" | |
2864 "missing } after function body") | |
2865 | |
2866 (js2-msg "msg.no.paren.cond" | |
2867 "missing ( before condition") | |
2868 | |
2869 (js2-msg "msg.no.paren.after.cond" | |
2870 "missing ) after condition") | |
2871 | |
2872 (js2-msg "msg.no.semi.stmt" | |
2873 "missing ; before statement") | |
2874 | |
2875 (js2-msg "msg.missing.semi" | |
2876 "missing ; after statement") | |
2877 | |
2878 (js2-msg "msg.no.name.after.dot" | |
2879 "missing name after . operator") | |
2880 | |
2881 (js2-msg "msg.no.name.after.coloncolon" | |
2882 "missing name after :: operator") | |
2883 | |
2884 (js2-msg "msg.no.name.after.dotdot" | |
2885 "missing name after .. operator") | |
2886 | |
2887 (js2-msg "msg.no.name.after.xmlAttr" | |
2888 "missing name after .@") | |
2889 | |
2890 (js2-msg "msg.no.bracket.index" | |
2891 "missing ] in index expression") | |
2892 | |
2893 (js2-msg "msg.no.paren.switch" | |
2894 "missing ( before switch expression") | |
2895 | |
2896 (js2-msg "msg.no.paren.after.switch" | |
2897 "missing ) after switch expression") | |
2898 | |
2899 (js2-msg "msg.no.brace.switch" | |
2900 "missing '{' before switch body") | |
2901 | |
2902 (js2-msg "msg.bad.switch" | |
2903 "invalid switch statement") | |
2904 | |
2905 (js2-msg "msg.no.colon.case" | |
2906 "missing : after case expression") | |
2907 | |
2908 (js2-msg "msg.double.switch.default" | |
2909 "double default label in the switch statement") | |
2910 | |
2911 (js2-msg "msg.no.while.do" | |
2912 "missing while after do-loop body") | |
2913 | |
2914 (js2-msg "msg.no.paren.for" | |
2915 "missing ( after for") | |
2916 | |
2917 (js2-msg "msg.no.semi.for" | |
2918 "missing ; after for-loop initializer") | |
2919 | |
2920 (js2-msg "msg.no.semi.for.cond" | |
2921 "missing ; after for-loop condition") | |
2922 | |
2923 (js2-msg "msg.in.after.for.name" | |
2924 "missing in after for") | |
2925 | |
2926 (js2-msg "msg.no.paren.for.ctrl" | |
2927 "missing ) after for-loop control") | |
2928 | |
2929 (js2-msg "msg.no.paren.with" | |
2930 "missing ( before with-statement object") | |
2931 | |
2932 (js2-msg "msg.no.paren.after.with" | |
2933 "missing ) after with-statement object") | |
2934 | |
2935 (js2-msg "msg.no.paren.after.let" | |
2936 "missing ( after let") | |
2937 | |
2938 (js2-msg "msg.no.paren.let" | |
2939 "missing ) after variable list") | |
2940 | |
2941 (js2-msg "msg.no.curly.let" | |
2942 "missing } after let statement") | |
2943 | |
2944 (js2-msg "msg.bad.return" | |
2945 "invalid return") | |
2946 | |
2947 (js2-msg "msg.no.brace.block" | |
2948 "missing } in compound statement") | |
2949 | |
2950 (js2-msg "msg.bad.label" | |
2951 "invalid label") | |
2952 | |
2953 (js2-msg "msg.bad.var" | |
2954 "missing variable name") | |
2955 | |
2956 (js2-msg "msg.bad.var.init" | |
2957 "invalid variable initialization") | |
2958 | |
2959 (js2-msg "msg.no.colon.cond" | |
2960 "missing : in conditional expression") | |
2961 | |
2962 (js2-msg "msg.no.paren.arg" | |
2963 "missing ) after argument list") | |
2964 | |
2965 (js2-msg "msg.no.bracket.arg" | |
2966 "missing ] after element list") | |
2967 | |
2968 (js2-msg "msg.bad.prop" | |
2969 "invalid property id") | |
2970 | |
2971 (js2-msg "msg.no.colon.prop" | |
2972 "missing : after property id") | |
2973 | |
2974 (js2-msg "msg.no.brace.prop" | |
2975 "missing } after property list") | |
2976 | |
2977 (js2-msg "msg.no.paren" | |
2978 "missing ) in parenthetical") | |
2979 | |
2980 (js2-msg "msg.reserved.id" | |
2981 "identifier is a reserved word") | |
2982 | |
2983 (js2-msg "msg.no.paren.catch" | |
2984 "missing ( before catch-block condition") | |
2985 | |
2986 (js2-msg "msg.bad.catchcond" | |
2987 "invalid catch block condition") | |
2988 | |
2989 (js2-msg "msg.catch.unreachable" | |
2990 "any catch clauses following an unqualified catch are unreachable") | |
2991 | |
2992 (js2-msg "msg.no.brace.try" | |
2993 "missing '{' before try block") | |
2994 | |
2995 (js2-msg "msg.no.brace.catchblock" | |
2996 "missing '{' before catch-block body") | |
2997 | |
2998 (js2-msg "msg.try.no.catchfinally" | |
2999 "'try' without 'catch' or 'finally'") | |
3000 | |
3001 (js2-msg "msg.no.return.value" | |
3002 "function %s does not always return a value") | |
3003 | |
3004 (js2-msg "msg.anon.no.return.value" | |
3005 "anonymous function does not always return a value") | |
3006 | |
3007 (js2-msg "msg.return.inconsistent" | |
3008 "return statement is inconsistent with previous usage") | |
3009 | |
3010 (js2-msg "msg.generator.returns" | |
3011 "TypeError: generator function '%s' returns a value") | |
3012 | |
3013 (js2-msg "msg.anon.generator.returns" | |
3014 "TypeError: anonymous generator function returns a value") | |
3015 | |
3016 (js2-msg "msg.syntax" | |
3017 "syntax error") | |
3018 | |
3019 (js2-msg "msg.unexpected.eof" | |
3020 "Unexpected end of file") | |
3021 | |
3022 (js2-msg "msg.XML.bad.form" | |
3023 "illegally formed XML syntax") | |
3024 | |
3025 (js2-msg "msg.XML.not.available" | |
3026 "XML runtime not available") | |
3027 | |
3028 (js2-msg "msg.too.deep.parser.recursion" | |
3029 "Too deep recursion while parsing") | |
3030 | |
3031 (js2-msg "msg.no.side.effects" | |
3032 "Code has no side effects") | |
3033 | |
3034 (js2-msg "msg.extra.trailing.comma" | |
3035 "Trailing comma is not legal in an ECMA-262 object initializer") | |
3036 | |
3037 (js2-msg "msg.array.trailing.comma" | |
3038 "Trailing comma yields different behavior across browsers") | |
3039 | |
3040 (js2-msg "msg.equal.as.assign" | |
3041 (concat "Test for equality (==) mistyped as assignment (=)?" | |
3042 " (parenthesize to suppress warning)")) | |
3043 | |
3044 (js2-msg "msg.var.hides.arg" | |
3045 "Variable %s hides argument") | |
3046 | |
3047 (js2-msg "msg.destruct.assign.no.init" | |
3048 "Missing = in destructuring declaration") | |
3049 | |
3050 ;; ScriptRuntime | |
3051 (js2-msg "msg.no.properties" | |
3052 "%s has no properties.") | |
3053 | |
3054 (js2-msg "msg.invalid.iterator" | |
3055 "Invalid iterator value") | |
3056 | |
3057 (js2-msg "msg.iterator.primitive" | |
3058 "__iterator__ returned a primitive value") | |
3059 | |
3060 (js2-msg "msg.assn.create.strict" | |
3061 "Assignment to undeclared variable %s") | |
3062 | |
3063 (js2-msg "msg.ref.undefined.prop" | |
3064 "Reference to undefined property '%s'") | |
3065 | |
3066 (js2-msg "msg.prop.not.found" | |
3067 "Property %s not found.") | |
3068 | |
3069 (js2-msg "msg.invalid.type" | |
3070 "Invalid JavaScript value of type %s") | |
3071 | |
3072 (js2-msg "msg.primitive.expected" | |
3073 "Primitive type expected (had %s instead)") | |
3074 | |
3075 (js2-msg "msg.namespace.expected" | |
3076 "Namespace object expected to left of :: (found %s instead)") | |
3077 | |
3078 (js2-msg "msg.null.to.object" | |
3079 "Cannot convert null to an object.") | |
3080 | |
3081 (js2-msg "msg.undef.to.object" | |
3082 "Cannot convert undefined to an object.") | |
3083 | |
3084 (js2-msg "msg.cyclic.value" | |
3085 "Cyclic %s value not allowed.") | |
3086 | |
3087 (js2-msg "msg.is.not.defined" | |
3088 "'%s' is not defined.") | |
3089 | |
3090 (js2-msg "msg.undef.prop.read" | |
3091 "Cannot read property '%s' from %s") | |
3092 | |
3093 (js2-msg "msg.undef.prop.write" | |
3094 "Cannot set property '%s' of %s to '%s'") | |
3095 | |
3096 (js2-msg "msg.undef.prop.delete" | |
3097 "Cannot delete property '%s' of %s") | |
3098 | |
3099 (js2-msg "msg.undef.method.call" | |
3100 "Cannot call method '%s' of %s") | |
3101 | |
3102 (js2-msg "msg.undef.with" | |
3103 "Cannot apply 'with' to %s") | |
3104 | |
3105 (js2-msg "msg.isnt.function" | |
3106 "%s is not a function, it is %s.") | |
3107 | |
3108 (js2-msg "msg.isnt.function.in" | |
3109 "Cannot call property %s in object %s. " | |
3110 "It is not a function, it is '%s'.") | |
3111 | |
3112 (js2-msg "msg.function.not.found" | |
3113 "Cannot find function %s.") | |
3114 | |
3115 (js2-msg "msg.function.not.found.in" | |
3116 "Cannot find function %s in object %s.") | |
3117 | |
3118 (js2-msg "msg.isnt.xml.object" | |
3119 "%s is not an xml object.") | |
3120 | |
3121 (js2-msg "msg.no.ref.to.get" | |
3122 "%s is not a reference to read reference value.") | |
3123 | |
3124 (js2-msg "msg.no.ref.to.set" | |
3125 "%s is not a reference to set reference value to %s.") | |
3126 | |
3127 (js2-msg "msg.no.ref.from.function" | |
3128 "Function %s can not be used as the left-hand " | |
3129 "side of assignment or as an operand of ++ or -- operator.") | |
3130 | |
3131 (js2-msg "msg.bad.default.value" | |
3132 "Object's getDefaultValue() method returned an object.") | |
3133 | |
3134 (js2-msg "msg.instanceof.not.object" | |
3135 "Can't use instanceof on a non-object.") | |
3136 | |
3137 (js2-msg "msg.instanceof.bad.prototype" | |
3138 "'prototype' property of %s is not an object.") | |
3139 | |
3140 (js2-msg "msg.bad.radix" | |
3141 "illegal radix %s.") | |
3142 | |
3143 ;; ScriptableObject | |
3144 (js2-msg "msg.default.value" | |
3145 "Cannot find default value for object.") | |
3146 | |
3147 (js2-msg "msg.zero.arg.ctor" | |
3148 "Cannot load class '%s' which has no zero-parameter constructor.") | |
3149 | |
3150 (js2-msg "msg.ctor.multiple.parms" | |
3151 "Can't define constructor or class %s since more than " | |
3152 "one constructor has multiple parameters.") | |
3153 | |
3154 (js2-msg "msg.extend.scriptable" | |
3155 "%s must extend ScriptableObject in order to define property %s.") | |
3156 | |
3157 (js2-msg "msg.bad.getter.parms" | |
3158 "In order to define a property, getter %s must have zero " | |
3159 "parameters or a single ScriptableObject parameter.") | |
3160 | |
3161 (js2-msg "msg.obj.getter.parms" | |
3162 "Expected static or delegated getter %s to take " | |
3163 "a ScriptableObject parameter.") | |
3164 | |
3165 (js2-msg "msg.getter.static" | |
3166 "Getter and setter must both be static or neither be static.") | |
3167 | |
3168 (js2-msg "msg.setter.return" | |
3169 "Setter must have void return type: %s") | |
3170 | |
3171 (js2-msg "msg.setter2.parms" | |
3172 "Two-parameter setter must take a ScriptableObject as " | |
3173 "its first parameter.") | |
3174 | |
3175 (js2-msg "msg.setter1.parms" | |
3176 "Expected single parameter setter for %s") | |
3177 | |
3178 (js2-msg "msg.setter2.expected" | |
3179 "Expected static or delegated setter %s to take two parameters.") | |
3180 | |
3181 (js2-msg "msg.setter.parms" | |
3182 "Expected either one or two parameters for setter.") | |
3183 | |
3184 (js2-msg "msg.setter.bad.type" | |
3185 "Unsupported parameter type '%s' in setter '%s'.") | |
3186 | |
3187 (js2-msg "msg.add.sealed" | |
3188 "Cannot add a property to a sealed object: %s.") | |
3189 | |
3190 (js2-msg "msg.remove.sealed" | |
3191 "Cannot remove a property from a sealed object: %s.") | |
3192 | |
3193 (js2-msg "msg.modify.sealed" | |
3194 "Cannot modify a property of a sealed object: %s.") | |
3195 | |
3196 (js2-msg "msg.modify.readonly" | |
3197 "Cannot modify readonly property: %s.") | |
3198 | |
3199 ;; TokenStream | |
3200 (js2-msg "msg.missing.exponent" | |
3201 "missing exponent") | |
3202 | |
3203 (js2-msg "msg.caught.nfe" | |
3204 "number format error") | |
3205 | |
3206 (js2-msg "msg.unterminated.string.lit" | |
3207 "unterminated string literal") | |
3208 | |
3209 (js2-msg "msg.unterminated.comment" | |
3210 "unterminated comment") | |
3211 | |
3212 (js2-msg "msg.unterminated.re.lit" | |
3213 "unterminated regular expression literal") | |
3214 | |
3215 (js2-msg "msg.invalid.re.flag" | |
3216 "invalid flag after regular expression") | |
3217 | |
3218 (js2-msg "msg.no.re.input.for" | |
3219 "no input for %s") | |
3220 | |
3221 (js2-msg "msg.illegal.character" | |
3222 "illegal character") | |
3223 | |
3224 (js2-msg "msg.invalid.escape" | |
3225 "invalid Unicode escape sequence") | |
3226 | |
3227 (js2-msg "msg.bad.namespace" | |
3228 "not a valid default namespace statement. " | |
3229 "Syntax is: default xml namespace = EXPRESSION;") | |
3230 | |
3231 ;; TokensStream warnings | |
3232 (js2-msg "msg.bad.octal.literal" | |
3233 "illegal octal literal digit %s; " | |
3234 "interpreting it as a decimal digit") | |
3235 | |
3236 (js2-msg "msg.reserved.keyword" | |
3237 "illegal usage of future reserved keyword %s; " | |
3238 "interpreting it as ordinary identifier") | |
3239 | |
3240 (js2-msg "msg.script.is.not.constructor" | |
3241 "Script objects are not constructors.") | |
3242 | |
3243 ;; Arrays | |
3244 (js2-msg "msg.arraylength.bad" | |
3245 "Inappropriate array length.") | |
3246 | |
3247 ;; Arrays | |
3248 (js2-msg "msg.arraylength.too.big" | |
3249 "Array length %s exceeds supported capacity limit.") | |
3250 | |
3251 ;; URI | |
3252 (js2-msg "msg.bad.uri" | |
3253 "Malformed URI sequence.") | |
3254 | |
3255 ;; Number | |
3256 (js2-msg "msg.bad.precision" | |
3257 "Precision %s out of range.") | |
3258 | |
3259 ;; NativeGenerator | |
3260 (js2-msg "msg.send.newborn" | |
3261 "Attempt to send value to newborn generator") | |
3262 | |
3263 (js2-msg "msg.already.exec.gen" | |
3264 "Already executing generator") | |
3265 | |
3266 (js2-msg "msg.StopIteration.invalid" | |
3267 "StopIteration may not be changed to an arbitrary object.") | |
3268 | |
3269 ;; Interpreter | |
3270 (js2-msg "msg.yield.closing" | |
3271 "Yield from closing generator") | |
3272 | |
3273 (provide 'js2-messages) | |
3274 ;;; js2-ast.el --- JavaScript syntax tree node definitions | |
3275 | |
3276 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
3277 ;; Keywords: javascript languages | |
3278 | |
3279 ;;; Code: | |
3280 | |
3281 (eval-and-compile | |
3282 (require 'cl)) | |
3283 | |
3284 | |
3285 ;; flags for ast node property 'member-type (used for e4x operators) | |
3286 (defvar js2-property-flag #x1 "property access: element is valid name") | |
3287 (defvar js2-attribute-flag #x2 "x.@y or x..@y") | |
3288 (defvar js2-descendants-flag #x4 "x..y or x..@i") | |
3289 | |
3290 (defsubst js2-relpos (pos anchor) | |
3291 "Convert POS to be relative to ANCHOR. | |
3292 If POS is nil, returns nil." | |
3293 (and pos (- pos anchor))) | |
3294 | |
3295 (defsubst js2-make-pad (indent) | |
3296 (if (zerop indent) | |
3297 "" | |
3298 (make-string (* indent js2-basic-offset) ? ))) | |
3299 | |
3300 (defsubst js2-visit-ast (node callback) | |
3301 "Visit every node in ast NODE with visitor CALLBACK. | |
3302 | |
3303 CALLBACK is a function that takes two arguments: (NODE END-P). It is | |
3304 called twice: once to visit the node, and again after all the node's | |
3305 children have been processed. The END-P argument is nil on the first | |
3306 call and non-nil on the second call. The return value of the callback | |
3307 affects the traversal: if non-nil, the children of NODE are processed. | |
3308 If the callback returns nil, or if the node has no children, then the | |
3309 callback is called immediately with a non-nil END-P argument. | |
3310 | |
3311 The node traversal is approximately lexical-order, although there | |
3312 are currently no guarantees around this." | |
3313 (let ((vfunc (get (aref node 0) 'js2-visitor))) | |
3314 ;; visit the node | |
3315 (when (funcall callback node nil) | |
3316 ;; visit the kids | |
3317 (cond | |
3318 ((eq vfunc 'js2-visit-none) | |
3319 nil) ; don't even bother calling it | |
3320 ;; Each AST node type has to define a `js2-visitor' function | |
3321 ;; that takes a node and a callback, and calls `js2-visit-ast' | |
3322 ;; on each child of the node. | |
3323 (vfunc | |
3324 (funcall vfunc node callback)) | |
3325 (t | |
3326 (error "%s does not define a visitor-traversal function" | |
3327 (aref node 0))))) | |
3328 ;; call the end-visit | |
3329 (funcall callback node t))) | |
3330 | |
3331 (defstruct (js2-node | |
3332 (:constructor nil)) ; abstract | |
3333 "Base AST node type." | |
3334 (type -1) ; token type | |
3335 (pos -1) ; start position of this AST node in parsed input | |
3336 (len 1) ; num characters spanned by the node | |
3337 props ; optional node property list (an alist) | |
3338 parent) ; link to parent node; null for root | |
3339 | |
3340 (defsubst js2-node-get-prop (node prop &optional default) | |
3341 (or (cadr (assoc prop (js2-node-props node))) default)) | |
3342 | |
3343 (defsubst js2-node-set-prop (node prop value) | |
3344 (setf (js2-node-props node) | |
3345 (cons (list prop value) (js2-node-props node)))) | |
3346 | |
3347 (defsubst js2-fixup-starts (n nodes) | |
3348 "Adjust the start positions of NODES to be relative to N. | |
3349 Any node in the list may be nil, for convenience." | |
3350 (dolist (node nodes) | |
3351 (when node | |
3352 (setf (js2-node-pos node) (- (js2-node-pos node) | |
3353 (js2-node-pos n)))))) | |
3354 | |
3355 (defsubst js2-node-add-children (parent &rest nodes) | |
3356 "Set parent node of NODES to PARENT, and return PARENT. | |
3357 Does nothing if we're not recording parent links. | |
3358 If any given node in NODES is nil, doesn't record that link." | |
3359 (js2-fixup-starts parent nodes) | |
3360 (dolist (node nodes) | |
3361 (and node | |
3362 (setf (js2-node-parent node) parent)))) | |
3363 | |
3364 ;; Non-recursive since it's called a frightening number of times. | |
3365 (defsubst js2-node-abs-pos (n) | |
3366 (let ((pos (js2-node-pos n))) | |
3367 (while (setq n (js2-node-parent n)) | |
3368 (setq pos (+ pos (js2-node-pos n)))) | |
3369 pos)) | |
3370 | |
3371 (defsubst js2-node-abs-end (n) | |
3372 "Return absolute buffer position of end of N." | |
3373 (+ (js2-node-abs-pos n) (js2-node-len n))) | |
3374 | |
3375 ;; It's important to make sure block nodes have a lisp list for the | |
3376 ;; child nodes, to limit printing recursion depth in an AST that | |
3377 ;; otherwise consists of defstruct vectors. Emacs will crash printing | |
3378 ;; a sufficiently large vector tree. | |
3379 | |
3380 (defstruct (js2-block-node | |
3381 (:include js2-node) | |
3382 (:constructor nil) | |
3383 (:constructor make-js2-block-node (&key (type js2-BLOCK) | |
3384 (pos js2-token-beg) | |
3385 len | |
3386 props | |
3387 kids))) | |
3388 "A block of statements." | |
3389 kids) ; a lisp list of the child statement nodes | |
3390 | |
3391 (put 'cl-struct-js2-block-node 'js2-visitor 'js2-visit-block) | |
3392 (put 'cl-struct-js2-block-node 'js2-printer 'js2-print-block) | |
3393 | |
3394 (defsubst js2-visit-block (ast callback) | |
3395 "Visit the `js2-block-node' children of AST." | |
3396 (dolist (kid (js2-block-node-kids ast)) | |
3397 (js2-visit-ast kid callback))) | |
3398 | |
3399 (defun js2-print-block (n i) | |
3400 (let ((pad (js2-make-pad i))) | |
3401 (insert pad "{\n") | |
3402 (dolist (kid (js2-block-node-kids n)) | |
3403 (js2-print-ast kid (1+ i))) | |
3404 (insert pad "}"))) | |
3405 | |
3406 (defstruct (js2-scope | |
3407 (:include js2-block-node) | |
3408 (:constructor nil) | |
3409 (:constructor make-js2-scope (&key (type js2-BLOCK) | |
3410 (pos js2-token-beg) | |
3411 len | |
3412 kids))) | |
3413 ;; The symbol-table is a LinkedHashMap<String,Symbol> in Rhino. | |
3414 ;; I don't have one of those handy, so I'll use an alist for now. | |
3415 ;; It's as fast as an emacs hashtable for up to about 50 elements, | |
3416 ;; and is much lighter-weight to construct (both CPU and mem). | |
3417 ;; The keys are interned strings (symbols) for faster lookup. | |
3418 ;; Should switch to hybrid alist/hashtable eventually. | |
3419 symbol-table ; an alist of (symbol . js2-symbol) | |
3420 parent-scope ; a `js2-scope' | |
3421 top) ; top-level `js2-scope' (script/function) | |
3422 | |
3423 (put 'cl-struct-js2-scope 'js2-visitor 'js2-visit-none) | |
3424 (put 'cl-struct-js2-scope 'js2-printer 'js2-print-none) | |
3425 | |
3426 (defun js2-scope-set-parent-scope (scope parent) | |
3427 (setf (js2-scope-parent-scope scope) parent | |
3428 (js2-scope-top scope) (if (null parent) | |
3429 scope | |
3430 (js2-scope-top parent)))) | |
3431 | |
3432 (defun js2-node-get-enclosing-scope (node) | |
3433 "Return the innermost `js2-scope' node surrounding NODE. | |
3434 Returns nil if there is no enclosing scope node." | |
3435 (let ((parent (js2-node-parent node))) | |
3436 (while (not (js2-scope-p parent)) | |
3437 (setq parent (js2-node-parent parent))) | |
3438 parent)) | |
3439 | |
3440 (defun js2-get-defining-scope (scope name) | |
3441 "Search up scope chain from SCOPE looking for NAME, a string or symbol. | |
3442 Returns `js2-scope' in which NAME is defined, or nil if not found." | |
3443 (let ((sym (if (symbolp name) | |
3444 name | |
3445 (intern name))) | |
3446 table | |
3447 result | |
3448 (continue t)) | |
3449 (while (and scope continue) | |
3450 (if (and (setq table (js2-scope-symbol-table scope)) | |
3451 (assq sym table)) | |
3452 (setq continue nil | |
3453 result scope) | |
3454 (setq scope (js2-scope-parent-scope scope)))) | |
3455 result)) | |
3456 | |
3457 (defsubst js2-scope-get-symbol (scope name) | |
3458 "Return symbol table entry for NAME in SCOPE. | |
3459 NAME can be a string or symbol. Returns a `js2-symbol' or nil if not found." | |
3460 (and (js2-scope-symbol-table scope) | |
3461 (cdr (assq (if (symbolp name) | |
3462 name | |
3463 (intern name)) | |
3464 (js2-scope-symbol-table scope))))) | |
3465 | |
3466 (defsubst js2-scope-put-symbol (scope name symbol) | |
3467 "Enter SYMBOL into symbol-table for SCOPE under NAME. | |
3468 NAME can be a lisp symbol or string. SYMBOL is a `js2-symbol'." | |
3469 (let* ((table (js2-scope-symbol-table scope)) | |
3470 (sym (if (symbolp name) name (intern name))) | |
3471 (entry (assq sym table))) | |
3472 (if entry | |
3473 (setcdr entry symbol) | |
3474 (push (cons sym symbol) | |
3475 (js2-scope-symbol-table scope))))) | |
3476 | |
3477 (defstruct (js2-symbol | |
3478 (:constructor nil) | |
3479 (:constructor make-js2-symbol (decl-type name &optional ast-node))) | |
3480 "A symbol table entry." | |
3481 ;; One of js2-FUNCTION, js2-LP (for parameters), js2-VAR, | |
3482 ;; js2-LET, or js2-CONST | |
3483 decl-type | |
3484 name ; string | |
3485 ast-node) ; a `js2-node' | |
3486 | |
3487 (defstruct (js2-error-node | |
3488 (:include js2-node) | |
3489 (:constructor nil) ; silence emacs21 byte-compiler | |
3490 (:constructor make-js2-error-node (&key (type js2-ERROR) | |
3491 (pos js2-token-beg) | |
3492 len))) | |
3493 "AST node representing a parse error.") | |
3494 | |
3495 (put 'cl-struct-js2-error-node 'js2-visitor 'js2-visit-none) | |
3496 (put 'cl-struct-js2-error-node 'js2-printer 'js2-print-none) | |
3497 | |
3498 (defstruct (js2-script-node | |
3499 (:include js2-scope) | |
3500 (:constructor nil) | |
3501 (:constructor make-js2-script-node (&key (type js2-SCRIPT) | |
3502 (pos js2-token-beg) | |
3503 len | |
3504 var-decls | |
3505 fun-decls))) | |
3506 functions ; lisp list of nested functions | |
3507 regexps ; lisp list of (string . flags) | |
3508 symbols ; alist (every symbol gets unique index) | |
3509 (param-count 0) | |
3510 var-names ; vector of string names | |
3511 consts ; bool-vector matching var-decls | |
3512 (temp-number 0)) ; for generating temp variables | |
3513 | |
3514 (put 'cl-struct-js2-script-node 'js2-visitor 'js2-visit-block) | |
3515 (put 'cl-struct-js2-script-node 'js2-printer 'js2-print-script) | |
3516 | |
3517 (defun js2-print-script (node indent) | |
3518 (dolist (kid (js2-block-node-kids node)) | |
3519 (js2-print-ast kid indent))) | |
3520 | |
3521 (defstruct (js2-ast-root | |
3522 (:include js2-script-node) | |
3523 (:constructor nil) | |
3524 (:constructor make-js2-ast-root (&key (type js2-SCRIPT) | |
3525 (pos js2-token-beg) | |
3526 len | |
3527 buffer))) | |
3528 "The root node of a js2 AST." | |
3529 buffer ; the source buffer from which the code was parsed | |
3530 comments ; a lisp list of comments, ordered by start position | |
3531 errors ; a lisp list of errors found during parsing | |
3532 warnings ; a lisp list of warnings found during parsing | |
3533 node-count) ; number of nodes in the tree, including the root | |
3534 | |
3535 (put 'cl-struct-js2-ast-root 'js2-visitor 'js2-visit-ast-root) | |
3536 (put 'cl-struct-js2-ast-root 'js2-printer 'js2-print-script) | |
3537 | |
3538 (defun js2-visit-ast-root (ast callback) | |
3539 (dolist (kid (js2-ast-root-kids ast)) | |
3540 (js2-visit-ast kid callback)) | |
3541 (dolist (comment (js2-ast-root-comments ast)) | |
3542 (js2-visit-ast comment callback))) | |
3543 | |
3544 (defstruct (js2-comment-node | |
3545 (:include js2-node) | |
3546 (:constructor nil) | |
3547 (:constructor make-js2-comment-node (&key (type js2-COMMENT) | |
3548 (pos js2-token-beg) | |
3549 len | |
3550 (format js2-ts-comment-type)))) | |
3551 format) ; 'line, 'block, 'jsdoc or 'html | |
3552 | |
3553 (put 'cl-struct-js2-comment-node 'js2-visitor 'js2-visit-none) | |
3554 (put 'cl-struct-js2-comment-node 'js2-printer 'js2-print-comment) | |
3555 | |
3556 (defun js2-print-comment (n i) | |
3557 ;; We really ought to link end-of-line comments to their nodes. | |
3558 ;; Or maybe we could add a new comment type, 'endline. | |
3559 (insert (js2-make-pad i) | |
3560 (js2-node-string n))) | |
3561 | |
3562 (defstruct (js2-expr-stmt-node | |
3563 (:include js2-node) | |
3564 (:constructor nil) | |
3565 (:constructor make-js2-expr-stmt-node (&key (type js2-EXPR_VOID) | |
3566 (pos js2-ts-cursor) | |
3567 len | |
3568 expr))) | |
3569 "An expression statement." | |
3570 expr) | |
3571 | |
3572 (defsubst js2-expr-stmt-node-set-has-result (node) | |
3573 "Change the node type to `js2-EXPR_RESULT'. Used for code generation." | |
3574 (setf (js2-node-type node) js2-EXPR_RESULT)) | |
3575 | |
3576 (put 'cl-struct-js2-expr-stmt-node 'js2-visitor 'js2-visit-expr-stmt-node) | |
3577 (put 'cl-struct-js2-expr-stmt-node 'js2-printer 'js2-print-expr-stmt-node) | |
3578 | |
3579 (defun js2-visit-expr-stmt-node (n v) | |
3580 (js2-visit-ast (js2-expr-stmt-node-expr n) v)) | |
3581 | |
3582 (defun js2-print-expr-stmt-node (n indent) | |
3583 (js2-print-ast (js2-expr-stmt-node-expr n) indent) | |
3584 (insert ";\n")) | |
3585 | |
3586 (defstruct (js2-loop-node | |
3587 (:include js2-scope) | |
3588 (:constructor nil)) | |
3589 "Abstract supertype of loop nodes." | |
3590 body ; a `js2-block-node' | |
3591 lp ; position of left-paren, nil if omitted | |
3592 rp) ; position of right-paren, nil if omitted | |
3593 | |
3594 (defstruct (js2-do-node | |
3595 (:include js2-loop-node) | |
3596 (:constructor nil) | |
3597 (:constructor make-js2-do-node (&key (type js2-DO) | |
3598 (pos js2-token-beg) | |
3599 len | |
3600 body | |
3601 condition | |
3602 while-pos | |
3603 lp | |
3604 rp))) | |
3605 "AST node for do-loop." | |
3606 condition ; while (expression) | |
3607 while-pos) ; buffer position of 'while' keyword | |
3608 | |
3609 (put 'cl-struct-js2-do-node 'js2-visitor 'js2-visit-do-node) | |
3610 (put 'cl-struct-js2-do-node 'js2-printer 'js2-print-do-node) | |
3611 | |
3612 (defun js2-visit-do-node (n v) | |
3613 (js2-visit-ast (js2-do-node-body n) v) | |
3614 (js2-visit-ast (js2-do-node-condition n) v)) | |
3615 | |
3616 (defun js2-print-do-node (n i) | |
3617 (let ((pad (js2-make-pad i))) | |
3618 (insert pad "do {\n") | |
3619 (dolist (kid (js2-block-node-kids (js2-do-node-body n))) | |
3620 (js2-print-ast kid (1+ i))) | |
3621 (insert pad "} while (") | |
3622 (js2-print-ast (js2-do-node-condition n) 0) | |
3623 (insert ");\n"))) | |
3624 | |
3625 (defstruct (js2-while-node | |
3626 (:include js2-loop-node) | |
3627 (:constructor nil) | |
3628 (:constructor make-js2-while-node (&key (type js2-WHILE) | |
3629 (pos js2-token-beg) | |
3630 len | |
3631 body | |
3632 condition | |
3633 lp | |
3634 rp))) | |
3635 "AST node for while-loop." | |
3636 condition) ; while-condition | |
3637 | |
3638 (put 'cl-struct-js2-while-node 'js2-visitor 'js2-visit-while-node) | |
3639 (put 'cl-struct-js2-while-node 'js2-printer 'js2-print-while-node) | |
3640 | |
3641 (defun js2-visit-while-node (n v) | |
3642 (js2-visit-ast (js2-while-node-condition n) v) | |
3643 (js2-visit-ast (js2-while-node-body n) v)) | |
3644 | |
3645 (defun js2-print-while-node (n i) | |
3646 (let ((pad (js2-make-pad i))) | |
3647 (insert pad "while (") | |
3648 (js2-print-ast (js2-while-node-condition n) 0) | |
3649 (insert ") {\n") | |
3650 (js2-print-body (js2-while-node-body n) (1+ i)) | |
3651 (insert pad "}\n"))) | |
3652 | |
3653 (defstruct (js2-for-node | |
3654 (:include js2-loop-node) | |
3655 (:constructor nil) | |
3656 (:constructor make-js2-for-node (&key (type js2-FOR) | |
3657 (pos js2-ts-cursor) | |
3658 len | |
3659 body | |
3660 init | |
3661 condition | |
3662 update | |
3663 lp | |
3664 rp))) | |
3665 "AST node for a C-style for-loop." | |
3666 init ; initialization expression | |
3667 condition ; loop condition | |
3668 update) ; update clause | |
3669 | |
3670 (put 'cl-struct-js2-for-node 'js2-visitor 'js2-visit-for-node) | |
3671 (put 'cl-struct-js2-for-node 'js2-printer 'js2-print-for-node) | |
3672 | |
3673 (defun js2-visit-for-node (n v) | |
3674 (js2-visit-ast (js2-for-node-init n) v) | |
3675 (js2-visit-ast (js2-for-node-condition n) v) | |
3676 (js2-visit-ast (js2-for-node-update n) v) | |
3677 (js2-visit-ast (js2-for-node-body n) v)) | |
3678 | |
3679 (defun js2-print-for-node (n i) | |
3680 (let ((pad (js2-make-pad i))) | |
3681 (insert pad "for (") | |
3682 (js2-print-ast (js2-for-node-init n) 0) | |
3683 (insert "; ") | |
3684 (js2-print-ast (js2-for-node-condition n) 0) | |
3685 (insert "; ") | |
3686 (js2-print-ast (js2-for-node-update n) 0) | |
3687 (insert ") {\n") | |
3688 (js2-print-body (js2-for-node-body n) (1+ i)) | |
3689 (insert pad "}\n"))) | |
3690 | |
3691 (defstruct (js2-for-in-node | |
3692 (:include js2-loop-node) | |
3693 (:constructor nil) | |
3694 (:constructor make-js2-for-in-node (&key (type js2-FOR) | |
3695 (pos js2-ts-cursor) | |
3696 len | |
3697 body | |
3698 iterator | |
3699 object | |
3700 in-pos | |
3701 each-pos | |
3702 foreach-p | |
3703 lp | |
3704 rp))) | |
3705 "AST node for a for..in loop." | |
3706 iterator ; [var] foo in ... | |
3707 object ; object over which we're iterating | |
3708 in-pos ; buffer position of 'in' keyword | |
3709 each-pos ; buffer position of 'each' keyword, if foreach-p | |
3710 foreach-p) ; t if it's a for-each loop | |
3711 | |
3712 (put 'cl-struct-js2-for-in-node 'js2-visitor 'js2-visit-for-in-node) | |
3713 (put 'cl-struct-js2-for-in-node 'js2-printer 'js2-print-for-in-node) | |
3714 | |
3715 (defun js2-visit-for-in-node (n v) | |
3716 (js2-visit-ast (js2-for-in-node-iterator n) v) | |
3717 (js2-visit-ast (js2-for-in-node-object n) v) | |
3718 (js2-visit-ast (js2-for-in-node-body n) v)) | |
3719 | |
3720 (defun js2-print-for-in-node (n i) | |
3721 (let ((pad (js2-make-pad i)) | |
3722 (foreach (js2-for-in-node-foreach-p n))) | |
3723 (insert pad "for ") | |
3724 (if foreach | |
3725 (insert "each ")) | |
3726 (insert "(") | |
3727 (js2-print-ast (js2-for-in-node-iterator n) 0) | |
3728 (insert " in ") | |
3729 (js2-print-ast (js2-for-in-node-object n) 0) | |
3730 (insert ") {\n") | |
3731 (js2-print-body (js2-for-in-node-body n) (1+ i)) | |
3732 (insert pad "}\n"))) | |
3733 | |
3734 (defstruct (js2-return-node | |
3735 (:include js2-node) | |
3736 (:constructor nil) | |
3737 (:constructor make-js2-return-node (&key (type js2-RETURN) | |
3738 (pos js2-ts-cursor) | |
3739 len | |
3740 retval))) | |
3741 "AST node for a return statement." | |
3742 retval) ; expression to return, or 'undefined | |
3743 | |
3744 (put 'cl-struct-js2-return-node 'js2-visitor 'js2-visit-return-node) | |
3745 (put 'cl-struct-js2-return-node 'js2-printer 'js2-print-return-node) | |
3746 | |
3747 (defun js2-visit-return-node (n v) | |
3748 (if (js2-return-node-retval n) | |
3749 (js2-visit-ast (js2-return-node-retval n) v))) | |
3750 | |
3751 (defun js2-print-return-node (n i) | |
3752 (insert (js2-make-pad i) "return") | |
3753 (when (js2-return-node-retval n) | |
3754 (insert " ") | |
3755 (js2-print-ast (js2-return-node-retval n) 0)) | |
3756 (insert ";\n")) | |
3757 | |
3758 (defstruct (js2-if-node | |
3759 (:include js2-node) | |
3760 (:constructor nil) | |
3761 (:constructor make-js2-if-node (&key (type js2-IF) | |
3762 (pos js2-ts-cursor) | |
3763 len | |
3764 condition | |
3765 then-part | |
3766 else-pos | |
3767 else-part | |
3768 lp | |
3769 rp))) | |
3770 "AST node for an if-statement." | |
3771 condition ; expression | |
3772 then-part ; statement or block | |
3773 else-pos ; optional buffer position of 'else' keyword | |
3774 else-part ; optional statement or block | |
3775 lp ; position of left-paren, nil if omitted | |
3776 rp) ; position of right-paren, nil if omitted | |
3777 | |
3778 (put 'cl-struct-js2-if-node 'js2-visitor 'js2-visit-if-node) | |
3779 (put 'cl-struct-js2-if-node 'js2-printer 'js2-print-if-node) | |
3780 | |
3781 (defun js2-visit-if-node (n v) | |
3782 (js2-visit-ast (js2-if-node-condition n) v) | |
3783 (js2-visit-ast (js2-if-node-then-part n) v) | |
3784 (if (js2-if-node-else-part n) | |
3785 (js2-visit-ast (js2-if-node-else-part n) v))) | |
3786 | |
3787 (defun js2-print-if-node (n i) | |
3788 (let ((pad (js2-make-pad i)) | |
3789 (then-part (js2-if-node-then-part n)) | |
3790 (else-part (js2-if-node-else-part n))) | |
3791 (insert pad "if (") | |
3792 (js2-print-ast (js2-if-node-condition n) 0) | |
3793 (insert ") {\n") | |
3794 (js2-print-body then-part (1+ i)) | |
3795 (insert pad "}") | |
3796 (cond | |
3797 ((not else-part) | |
3798 (insert "\n")) | |
3799 ((js2-if-node-p else-part) | |
3800 (insert " else ") | |
3801 (js2-print-body else-part i)) | |
3802 (t | |
3803 (insert " else {\n") | |
3804 (js2-print-body else-part (1+ i)) | |
3805 (insert pad "}\n"))))) | |
3806 | |
3807 (defstruct (js2-try-node | |
3808 (:include js2-node) | |
3809 (:constructor nil) | |
3810 (:constructor make-js2-try-node (&key (type js2-TRY) | |
3811 (pos js2-ts-cursor) | |
3812 len | |
3813 try-block | |
3814 catch-clauses | |
3815 finally-block))) | |
3816 "AST node for a try-statement." | |
3817 try-block | |
3818 catch-clauses ; a lisp list of `js2-catch-node' | |
3819 finally-block) ; a `js2-finally-node' | |
3820 | |
3821 (put 'cl-struct-js2-try-node 'js2-visitor 'js2-visit-try-node) | |
3822 (put 'cl-struct-js2-try-node 'js2-printer 'js2-print-try-node) | |
3823 | |
3824 (defun js2-visit-try-node (n v) | |
3825 (js2-visit-ast (js2-try-node-try-block n) v) | |
3826 (dolist (clause (js2-try-node-catch-clauses n)) | |
3827 (js2-visit-ast clause v)) | |
3828 (if (js2-try-node-finally-block n) | |
3829 (js2-visit-ast (js2-try-node-finally-block n) v))) | |
3830 | |
3831 (defun js2-print-try-node (n i) | |
3832 (let ((pad (js2-make-pad i)) | |
3833 (catches (js2-try-node-catch-clauses n)) | |
3834 (finally (js2-try-node-finally-block n))) | |
3835 (insert pad "try {\n") | |
3836 (js2-print-body (js2-try-node-try-block n) (1+ i)) | |
3837 (insert pad "}") | |
3838 (when catches | |
3839 (dolist (catch catches) | |
3840 (js2-print-ast catch i))) | |
3841 (if finally | |
3842 (js2-print-ast finally i) | |
3843 (insert "\n")))) | |
3844 | |
3845 (defstruct (js2-catch-node | |
3846 (:include js2-node) | |
3847 (:constructor nil) | |
3848 (:constructor make-js2-catch-node (&key (type js2-CATCH) | |
3849 (pos js2-ts-cursor) | |
3850 len | |
3851 var-name | |
3852 guard-kwd | |
3853 guard-expr | |
3854 block | |
3855 lp | |
3856 rp))) | |
3857 "AST node for a catch clause." | |
3858 var-name ; a `js2-name-node' | |
3859 guard-kwd ; relative buffer position of "if" in "catch (x if ...)" | |
3860 guard-expr ; catch condition, a `js2-node' | |
3861 block ; statements, a `js2-block-node' | |
3862 lp ; buffer position of left-paren, nil if omitted | |
3863 rp) ; buffer position of right-paren, nil if omitted | |
3864 | |
3865 (put 'cl-struct-js2-catch-node 'js2-visitor 'js2-visit-catch-node) | |
3866 (put 'cl-struct-js2-catch-node 'js2-printer 'js2-print-catch-node) | |
3867 | |
3868 (defun js2-visit-catch-node (n v) | |
3869 (js2-visit-ast (js2-catch-node-var-name n) v) | |
3870 (when (js2-catch-node-guard-kwd n) | |
3871 (js2-visit-ast (js2-catch-node-guard-expr n) v)) | |
3872 (js2-visit-ast (js2-catch-node-block n) v)) | |
3873 | |
3874 (defun js2-print-catch-node (n i) | |
3875 (let ((pad (js2-make-pad i)) | |
3876 (guard-kwd (js2-catch-node-guard-kwd n)) | |
3877 (guard-expr (js2-catch-node-guard-expr n))) | |
3878 (insert " catch (") | |
3879 (js2-print-ast (js2-catch-node-var-name n) 0) | |
3880 (when guard-kwd | |
3881 (insert " if ") | |
3882 (js2-print-ast guard-expr 0)) | |
3883 (insert ") {\n") | |
3884 (js2-print-body (js2-catch-node-block n) (1+ i)) | |
3885 (insert pad "}"))) | |
3886 | |
3887 (defstruct (js2-finally-node | |
3888 (:include js2-node) | |
3889 (:constructor nil) | |
3890 (:constructor make-js2-finally-node (&key (type js2-FINALLY) | |
3891 (pos js2-ts-cursor) | |
3892 len | |
3893 body))) | |
3894 "AST node for a finally clause." | |
3895 body) ; a `js2-node', often but not always a block node | |
3896 | |
3897 (put 'cl-struct-js2-finally-node 'js2-visitor 'js2-visit-finally-node) | |
3898 (put 'cl-struct-js2-finally-node 'js2-printer 'js2-print-finally-node) | |
3899 | |
3900 (defun js2-visit-finally-node (n v) | |
3901 (js2-visit-ast (js2-finally-node-body n) v)) | |
3902 | |
3903 (defun js2-print-finally-node (n i) | |
3904 (let ((pad (js2-make-pad i))) | |
3905 (insert " finally {\n") | |
3906 (js2-print-body (js2-finally-node-body n) (1+ i)) | |
3907 (insert pad "}\n"))) | |
3908 | |
3909 (defstruct (js2-switch-node | |
3910 (:include js2-node) | |
3911 (:constructor nil) | |
3912 (:constructor make-js2-switch-node (&key (type js2-SWITCH) | |
3913 (pos js2-ts-cursor) | |
3914 len | |
3915 discriminant | |
3916 cases | |
3917 lp | |
3918 rp))) | |
3919 "AST node for a switch statement." | |
3920 discriminant ; a `js2-node' (switch expression) | |
3921 cases ; a lisp list of `js2-case-node' | |
3922 lp ; position of open-paren for discriminant, nil if omitted | |
3923 rp) ; position of close-paren for discriminant, nil if omitted | |
3924 | |
3925 (put 'cl-struct-js2-switch-node 'js2-visitor 'js2-visit-switch-node) | |
3926 (put 'cl-struct-js2-switch-node 'js2-printer 'js2-print-switch-node) | |
3927 | |
3928 (defun js2-visit-switch-node (n v) | |
3929 (js2-visit-ast (js2-switch-node-discriminant n) v) | |
3930 (dolist (c (js2-switch-node-cases n)) | |
3931 (js2-visit-ast c v))) | |
3932 | |
3933 (defun js2-print-switch-node (n i) | |
3934 (let ((pad (js2-make-pad i)) | |
3935 (cases (js2-switch-node-cases n))) | |
3936 (insert pad "switch (") | |
3937 (js2-print-ast (js2-switch-node-discriminant n) 0) | |
3938 (insert ") {\n") | |
3939 (dolist (case cases) | |
3940 (js2-print-ast case i)) | |
3941 (insert pad "}\n"))) | |
3942 | |
3943 (defstruct (js2-case-node | |
3944 (:include js2-block-node) | |
3945 (:constructor nil) | |
3946 (:constructor make-js2-case-node (&key (type js2-CASE) | |
3947 (pos js2-ts-cursor) | |
3948 len | |
3949 kids | |
3950 expr))) | |
3951 "AST node for a case clause of a switch statement." | |
3952 expr) ; the case expression (nil for default) | |
3953 | |
3954 (put 'cl-struct-js2-case-node 'js2-visitor 'js2-visit-case-node) | |
3955 (put 'cl-struct-js2-case-node 'js2-printer 'js2-print-case-node) | |
3956 | |
3957 (defun js2-visit-case-node (n v) | |
3958 (if (js2-case-node-expr n) ; nil for default: case | |
3959 (js2-visit-ast (js2-case-node-expr n) v)) | |
3960 (js2-visit-block n v)) | |
3961 | |
3962 (defun js2-print-case-node (n i) | |
3963 (let ((pad (js2-make-pad i)) | |
3964 (expr (js2-case-node-expr n))) | |
3965 (insert pad) | |
3966 (if (null expr) | |
3967 (insert "default:\n") | |
3968 (insert "case ") | |
3969 (js2-print-ast expr 0) | |
3970 (insert ":\n")) | |
3971 (dolist (kid (js2-case-node-kids n)) | |
3972 (js2-print-ast kid (1+ i))))) | |
3973 | |
3974 (defstruct (js2-throw-node | |
3975 (:include js2-node) | |
3976 (:constructor nil) | |
3977 (:constructor make-js2-throw-node (&key (type js2-THROW) | |
3978 (pos js2-ts-cursor) | |
3979 len | |
3980 expr))) | |
3981 "AST node for a throw statement." | |
3982 expr) ; the expression to throw | |
3983 | |
3984 (put 'cl-struct-js2-throw-node 'js2-visitor 'js2-visit-throw-node) | |
3985 (put 'cl-struct-js2-throw-node 'js2-printer 'js2-print-throw-node) | |
3986 | |
3987 (defun js2-visit-throw-node (n v) | |
3988 (js2-visit-ast (js2-throw-node-expr n) v)) | |
3989 | |
3990 (defun js2-print-throw-node (n i) | |
3991 (insert (js2-make-pad i) "throw ") | |
3992 (js2-print-ast (js2-throw-node-expr n) 0) | |
3993 (insert ";\n")) | |
3994 | |
3995 (defstruct (js2-with-node | |
3996 (:include js2-node) | |
3997 (:constructor nil) | |
3998 (:constructor make-js2-with-node (&key (type js2-WITH) | |
3999 (pos js2-ts-cursor) | |
4000 len | |
4001 object | |
4002 body | |
4003 lp | |
4004 rp))) | |
4005 "AST node for a with-statement." | |
4006 object | |
4007 body | |
4008 lp ; buffer position of left-paren around object, nil if omitted | |
4009 rp) ; buffer position of right-paren around object, nil if omitted | |
4010 | |
4011 (put 'cl-struct-js2-with-node 'js2-visitor 'js2-visit-with-node) | |
4012 (put 'cl-struct-js2-with-node 'js2-printer 'js2-print-with-node) | |
4013 | |
4014 (defun js2-visit-with-node (n v) | |
4015 (js2-visit-ast (js2-with-node-object n) v) | |
4016 (js2-visit-ast (js2-with-node-body n) v)) | |
4017 | |
4018 (defun js2-print-with-node (n i) | |
4019 (let ((pad (js2-make-pad i))) | |
4020 (insert pad "with (") | |
4021 (js2-print-ast (js2-with-node-object n) 0) | |
4022 (insert ") {\n") | |
4023 (js2-print-body (js2-with-node-body n) (1+ i)) | |
4024 (insert pad "}\n"))) | |
4025 | |
4026 (defstruct (js2-label-node | |
4027 (:include js2-node) | |
4028 (:constructor nil) | |
4029 (:constructor make-js2-label-node (&key (type js2-LABEL) | |
4030 (pos js2-ts-cursor) | |
4031 len | |
4032 name))) | |
4033 "AST node for a statement label or case label." | |
4034 name ; a string | |
4035 loop) ; for validating and code-generating continue-to-label | |
4036 | |
4037 (put 'cl-struct-js2-label-node 'js2-visitor 'js2-visit-none) | |
4038 (put 'cl-struct-js2-label-node 'js2-printer 'js2-print-label) | |
4039 | |
4040 (defun js2-print-label (n i) | |
4041 (insert (js2-make-pad i) | |
4042 (js2-label-node-name n) | |
4043 ":\n")) | |
4044 | |
4045 (defstruct (js2-labeled-stmt-node | |
4046 (:include js2-node) | |
4047 (:constructor nil) | |
4048 ;; type needs to be in `js2-side-effecting-tokens' to avoid spurious | |
4049 ;; no-side-effects warnings, hence js2-EXPR_RESULT. | |
4050 (:constructor make-js2-labeled-stmt-node (&key (type js2-EXPR_RESULT) | |
4051 (pos js2-ts-cursor) | |
4052 len | |
4053 labels | |
4054 stmt))) | |
4055 "AST node for a statement with one or more labels. | |
4056 Multiple labels for a statement are collapsed into the labels field." | |
4057 labels ; lisp list of `js2-label-node' | |
4058 stmt) ; the statement these labels are for | |
4059 | |
4060 (put 'cl-struct-js2-labeled-stmt-node 'js2-visitor 'js2-visit-labeled-stmt) | |
4061 (put 'cl-struct-js2-labeled-stmt-node 'js2-printer 'js2-print-labeled-stmt) | |
4062 | |
4063 (defun js2-get-label-by-name (lbl-stmt name) | |
4064 "Return a `js2-label-node' by NAME from LBL-STMT's labels list. | |
4065 Returns nil if no such label is in the list." | |
4066 (let ((label-list (js2-labeled-stmt-node-labels lbl-stmt)) | |
4067 result) | |
4068 (while (and label-list (not result)) | |
4069 (if (string= (js2-label-node-name (car label-list)) name) | |
4070 (setq result (car label-list)) | |
4071 (setq label-list (cdr label-list)))) | |
4072 result)) | |
4073 | |
4074 (defun js2-visit-labeled-stmt (n v) | |
4075 (dolist (label (js2-labeled-stmt-node-labels n)) | |
4076 (js2-visit-ast label v)) | |
4077 (js2-visit-ast (js2-labeled-stmt-node-stmt n) v)) | |
4078 | |
4079 (defun js2-print-labeled-stmt (n i) | |
4080 (dolist (label (js2-labeled-stmt-node-labels n)) | |
4081 (js2-print-ast label i)) | |
4082 (js2-print-ast (js2-labeled-stmt-node-stmt n) (1+ i))) | |
4083 | |
4084 (defun js2-labeled-stmt-node-contains (node label) | |
4085 "Return t if NODE contains LABEL in its label set. | |
4086 NODE is a `js2-labels-node'. LABEL is an identifier." | |
4087 (loop for nl in (js2-labeled-stmt-node-labels node) | |
4088 if (string= label (js2-label-node-name nl)) | |
4089 return t | |
4090 finally return nil)) | |
4091 | |
4092 (defsubst js2-labeled-stmt-node-add-label (node label) | |
4093 "Add a `js2-label-node' to the label set for this statement." | |
4094 (setf (js2-labeled-stmt-node-labels node) | |
4095 (nconc (js2-labeled-stmt-node-labels node) (list label)))) | |
4096 | |
4097 (defstruct (js2-jump-node | |
4098 (:include js2-node) | |
4099 (:constructor nil)) | |
4100 "Abstract supertype of break and continue nodes." | |
4101 label ; `js2-name-node' for location of label identifier, if present | |
4102 target) ; target js2-labels-node or loop/switch statement | |
4103 | |
4104 (defun js2-visit-jump-node (n v) | |
4105 ;; we don't visit the target, since it's a back-link | |
4106 (if (js2-jump-node-label n) | |
4107 (js2-visit-ast (js2-jump-node-label n) v))) | |
4108 | |
4109 (defstruct (js2-break-node | |
4110 (:include js2-jump-node) | |
4111 (:constructor nil) | |
4112 (:constructor make-js2-break-node (&key (type js2-BREAK) | |
4113 (pos js2-ts-cursor) | |
4114 len | |
4115 label | |
4116 target))) | |
4117 "AST node for a break statement. | |
4118 The label field is a `js2-name-node', possibly nil, for the named label | |
4119 if provided. E.g. in 'break foo', it represents 'foo'. The target field | |
4120 is the target of the break - a label node or enclosing loop/switch statement.") | |
4121 | |
4122 (put 'cl-struct-js2-break-node 'js2-visitor 'js2-visit-jump-node) | |
4123 (put 'cl-struct-js2-break-node 'js2-printer 'js2-print-break-node) | |
4124 | |
4125 (defun js2-print-break-node (n i) | |
4126 (insert (js2-make-pad i) "break") | |
4127 (when (js2-break-node-label n) | |
4128 (insert " ") | |
4129 (js2-print-ast (js2-break-node-label n) 0)) | |
4130 (insert ";\n")) | |
4131 | |
4132 (defstruct (js2-continue-node | |
4133 (:include js2-jump-node) | |
4134 (:constructor nil) | |
4135 (:constructor make-js2-continue-node (&key (type js2-CONTINUE) | |
4136 (pos js2-ts-cursor) | |
4137 len | |
4138 label | |
4139 target))) | |
4140 "AST node for a continue statement. | |
4141 The label field is the user-supplied enclosing label name, a `js2-name-node'. | |
4142 It is nil if continue specifies no label. The target field is the jump target: | |
4143 a `js2-label-node' or the innermost enclosing loop.") | |
4144 | |
4145 (put 'cl-struct-js2-continue-node 'js2-visitor 'js2-visit-jump-node) | |
4146 (put 'cl-struct-js2-continue-node 'js2-printer 'js2-print-continue-node) | |
4147 | |
4148 (defun js2-print-continue-node (n i) | |
4149 (insert (js2-make-pad i) "continue") | |
4150 (when (js2-continue-node-label n) | |
4151 (insert " ") | |
4152 (js2-print-ast (js2-continue-node-label n) 0)) | |
4153 (insert ";\n")) | |
4154 | |
4155 (defstruct (js2-function-node | |
4156 (:include js2-script-node) | |
4157 (:constructor nil) | |
4158 (:constructor make-js2-function-node (&key (type js2-FUNCTION) | |
4159 (pos js2-ts-cursor) | |
4160 len | |
4161 (ftype 'FUNCTION) | |
4162 (form 'FUNCTION_STATEMENT) | |
4163 (name "") | |
4164 params | |
4165 body | |
4166 lp | |
4167 rp))) | |
4168 "AST node for a function declaration. | |
4169 The `params' field is a lisp list of nodes. Each node is either a simple | |
4170 `js2-name-node', or if it's a destructuring-assignment parameter, a | |
4171 `js2-array-node' or `js2-object-node'." | |
4172 ftype ; FUNCTION, GETTER or SETTER | |
4173 form ; FUNCTION_{STATEMENT|EXPRESSION|EXPRESSION_STATEMENT} | |
4174 name ; function name (a `js2-name-node', or nil if anonymous) | |
4175 params ; a lisp list of destructuring forms or simple name nodes | |
4176 body ; a `js2-block-node' | |
4177 lp ; position of arg-list open-paren, or nil if omitted | |
4178 rp ; position of arg-list close-paren, or nil if omitted | |
4179 ignore-dynamic ; ignore value of the dynamic-scope flag (interpreter only) | |
4180 needs-activation ; t if we need an activation object for this frame | |
4181 is-generator ; t if this function contains a yield | |
4182 member-expr) ; nonstandard Ecma extension from Rhino | |
4183 | |
4184 (put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node) | |
4185 (put 'cl-struct-js2-function-node 'js2-printer 'js2-print-function-node) | |
4186 | |
4187 (defun js2-visit-function-node (n v) | |
4188 (if (js2-function-node-name n) | |
4189 (js2-visit-ast (js2-function-node-name n) v)) | |
4190 (dolist (p (js2-function-node-params n)) | |
4191 (js2-visit-ast p v)) | |
4192 (js2-visit-ast (js2-function-node-body n) v)) | |
4193 | |
4194 (defun js2-print-function-node (n i) | |
4195 (let ((pad (js2-make-pad i)) | |
4196 (getter (js2-node-get-prop n 'GETTER_SETTER)) | |
4197 (name (js2-function-node-name n)) | |
4198 (params (js2-function-node-params n)) | |
4199 (body (js2-function-node-body n)) | |
4200 (expr (eq (js2-function-node-form n) 'FUNCTION_EXPRESSION))) | |
4201 (unless getter | |
4202 (insert pad "function")) | |
4203 (when name | |
4204 (insert " ") | |
4205 (js2-print-ast name 0)) | |
4206 (insert "(") | |
4207 (loop with len = (length params) | |
4208 for param in params | |
4209 for count from 1 | |
4210 do | |
4211 (js2-print-ast param 0) | |
4212 (if (< count len) | |
4213 (insert ", "))) | |
4214 (insert ") {") | |
4215 (unless expr | |
4216 (insert "\n")) | |
4217 ;; TODO: fix this to be smarter about indenting, etc. | |
4218 (js2-print-body body (1+ i)) | |
4219 (insert pad "}") | |
4220 (unless expr | |
4221 (insert "\n")))) | |
4222 | |
4223 (defsubst js2-function-name (node) | |
4224 "Return function name for NODE, a `js2-function-node', or nil if anonymous." | |
4225 (and (js2-function-node-name node) | |
4226 (js2-name-node-name (js2-function-node-name node)))) | |
4227 | |
4228 ;; Having this be an expression node makes it more flexible. | |
4229 ;; There are IDE contexts, such as indentation in a for-loop initializer, | |
4230 ;; that work better if you assume it's an expression. Whenever we have | |
4231 ;; a standalone var/const declaration, we just wrap with an expr stmt. | |
4232 ;; Eclipse apparently screwed this up and now has two versions, expr and stmt. | |
4233 (defstruct (js2-var-decl-node | |
4234 (:include js2-node) | |
4235 (:constructor nil) | |
4236 (:constructor make-js2-var-decl-node (&key (type js2-VAR) | |
4237 (pos js2-token-beg) | |
4238 len | |
4239 kids | |
4240 decl-type))) | |
4241 "AST node for a variable declaration list (VAR, CONST or LET). | |
4242 The node bounds differ depending on the declaration type. For VAR or | |
4243 CONST declarations, the bounds include the var/const keyword. For LET | |
4244 declarations, the node begins at the position of the first child." | |
4245 kids ; a lisp list of `js2-var-init-node' structs. | |
4246 decl-type) ; js2-VAR, js2-CONST or js2-LET | |
4247 | |
4248 (put 'cl-struct-js2-var-decl-node 'js2-visitor 'js2-visit-var-decl) | |
4249 (put 'cl-struct-js2-var-decl-node 'js2-printer 'js2-print-var-decl) | |
4250 | |
4251 (defun js2-visit-var-decl (n v) | |
4252 (dolist (kid (js2-var-decl-node-kids n)) | |
4253 (js2-visit-ast kid v))) | |
4254 | |
4255 (defun js2-print-var-decl (n i) | |
4256 (let ((pad (js2-make-pad i)) | |
4257 (tt (js2-var-decl-node-decl-type n))) | |
4258 (insert pad) | |
4259 (insert (cond | |
4260 ((= tt js2-VAR) "var ") | |
4261 ((= tt js2-LET) "") ; handled by parent let-{expr/stmt} | |
4262 ((= tt js2-CONST) "const ") | |
4263 (t | |
4264 (error "malformed var-decl node")))) | |
4265 (loop with kids = (js2-var-decl-node-kids n) | |
4266 with len = (length kids) | |
4267 for kid in kids | |
4268 for count from 1 | |
4269 do | |
4270 (js2-print-ast kid 0) | |
4271 (if (< count len) | |
4272 (insert ", "))))) | |
4273 | |
4274 (defstruct (js2-var-init-node | |
4275 (:include js2-node) | |
4276 (:constructor nil) | |
4277 (:constructor make-js2-var-init-node (&key (type js2-VAR) | |
4278 (pos js2-ts-cursor) | |
4279 len | |
4280 target | |
4281 initializer))) | |
4282 "AST node for a variable declaration. | |
4283 The type field will be js2-CONST for a const decl." | |
4284 target ; `js2-name-node', `js2-object-node', or `js2-array-node' | |
4285 initializer) ; initializer expression, a `js2-node' | |
4286 | |
4287 (put 'cl-struct-js2-var-init-node 'js2-visitor 'js2-visit-var-init-node) | |
4288 (put 'cl-struct-js2-var-init-node 'js2-printer 'js2-print-var-init-node) | |
4289 | |
4290 (defun js2-visit-var-init-node (n v) | |
4291 (js2-visit-ast (js2-var-init-node-target n) v) | |
4292 (if (js2-var-init-node-initializer n) | |
4293 (js2-visit-ast (js2-var-init-node-initializer n) v))) | |
4294 | |
4295 (defun js2-print-var-init-node (n i) | |
4296 (let ((pad (js2-make-pad i)) | |
4297 (name (js2-var-init-node-target n)) | |
4298 (init (js2-var-init-node-initializer n))) | |
4299 (insert pad) | |
4300 (js2-print-ast name 0) | |
4301 (when init | |
4302 (insert " = ") | |
4303 (js2-print-ast init 0)))) | |
4304 | |
4305 (defstruct (js2-cond-node | |
4306 (:include js2-node) | |
4307 (:constructor nil) | |
4308 (:constructor make-js2-cond-node (&key (type js2-HOOK) | |
4309 (pos js2-ts-cursor) | |
4310 len | |
4311 test-expr | |
4312 true-expr | |
4313 false-expr | |
4314 q-pos | |
4315 c-pos))) | |
4316 "AST node for the ternary operator" | |
4317 test-expr | |
4318 true-expr | |
4319 false-expr | |
4320 q-pos ; buffer position of ? | |
4321 c-pos) ; buffer position of : | |
4322 | |
4323 (put 'cl-struct-js2-cond-node 'js2-visitor 'js2-visit-cond-node) | |
4324 (put 'cl-struct-js2-cond-node 'js2-printer 'js2-print-cond-node) | |
4325 | |
4326 (defun js2-visit-cond-node (n v) | |
4327 (js2-visit-ast (js2-cond-node-test-expr n) v) | |
4328 (js2-visit-ast (js2-cond-node-true-expr n) v) | |
4329 (js2-visit-ast (js2-cond-node-false-expr n) v)) | |
4330 | |
4331 (defun js2-print-cond-node (n i) | |
4332 (let ((pad (js2-make-pad i))) | |
4333 (insert pad) | |
4334 (js2-print-ast (js2-cond-node-test-expr n) 0) | |
4335 (insert " ? ") | |
4336 (js2-print-ast (js2-cond-node-true-expr n) 0) | |
4337 (insert " : ") | |
4338 (js2-print-ast (js2-cond-node-false-expr n) 0))) | |
4339 | |
4340 (defstruct (js2-infix-node | |
4341 (:include js2-node) | |
4342 (:constructor nil) | |
4343 (:constructor make-js2-infix-node (&key type | |
4344 (pos js2-ts-cursor) | |
4345 len | |
4346 op-pos | |
4347 left | |
4348 right))) | |
4349 "Represents infix expressions. | |
4350 Includes assignment ops like `|=', and the comma operator. | |
4351 The type field inherited from `js2-node' holds the operator." | |
4352 op-pos ; buffer position where operator begins | |
4353 left ; any `js2-node' | |
4354 right) ; any `js2-node' | |
4355 | |
4356 (put 'cl-struct-js2-infix-node 'js2-visitor 'js2-visit-infix-node) | |
4357 (put 'cl-struct-js2-infix-node 'js2-printer 'js2-print-infix-node) | |
4358 | |
4359 (defun js2-visit-infix-node (n v) | |
4360 (when (js2-infix-node-left n) | |
4361 (js2-visit-ast (js2-infix-node-left n) v)) | |
4362 (when (js2-infix-node-right n) | |
4363 (js2-visit-ast (js2-infix-node-right n) v))) | |
4364 | |
4365 (defconst js2-operator-tokens | |
4366 (let ((table (make-hash-table :test 'eq)) | |
4367 (tokens | |
4368 (list (cons js2-IN "in") | |
4369 (cons js2-TYPEOF "typeof") | |
4370 (cons js2-INSTANCEOF "instanceof") | |
4371 (cons js2-DELPROP "delete") | |
4372 (cons js2-COMMA ",") | |
4373 (cons js2-COLON ":") | |
4374 (cons js2-OR "||") | |
4375 (cons js2-AND "&&") | |
4376 (cons js2-INC "++") | |
4377 (cons js2-DEC "--") | |
4378 (cons js2-BITOR "|") | |
4379 (cons js2-BITXOR "^") | |
4380 (cons js2-BITAND "&") | |
4381 (cons js2-EQ "==") | |
4382 (cons js2-NE "!=") | |
4383 (cons js2-LT "<") | |
4384 (cons js2-LE "<=") | |
4385 (cons js2-GT ">") | |
4386 (cons js2-GE ">=") | |
4387 (cons js2-LSH "<<") | |
4388 (cons js2-RSH ">>") | |
4389 (cons js2-URSH ">>>") | |
4390 (cons js2-ADD "+") ; infix plus | |
4391 (cons js2-SUB "-") ; infix minus | |
4392 (cons js2-MUL "*") | |
4393 (cons js2-DIV "/") | |
4394 (cons js2-MOD "%") | |
4395 (cons js2-NOT "!") | |
4396 (cons js2-BITNOT "~") | |
4397 (cons js2-POS "+") ; unary plus | |
4398 (cons js2-NEG "-") ; unary minus | |
4399 (cons js2-SHEQ "===") ; shallow equality | |
4400 (cons js2-SHNE "!==") ; shallow inequality | |
4401 (cons js2-ASSIGN "=") | |
4402 (cons js2-ASSIGN_BITOR "|=") | |
4403 (cons js2-ASSIGN_BITXOR "^=") | |
4404 (cons js2-ASSIGN_BITAND "&=") | |
4405 (cons js2-ASSIGN_LSH "<<=") | |
4406 (cons js2-ASSIGN_RSH ">>=") | |
4407 (cons js2-ASSIGN_URSH ">>>=") | |
4408 (cons js2-ASSIGN_ADD "+=") | |
4409 (cons js2-ASSIGN_SUB "-=") | |
4410 (cons js2-ASSIGN_MUL "*=") | |
4411 (cons js2-ASSIGN_DIV "/=") | |
4412 (cons js2-ASSIGN_MOD "%=")))) | |
4413 (loop for (k . v) in tokens do | |
4414 (puthash k v table)) | |
4415 table)) | |
4416 | |
4417 (defun js2-print-infix-node (n i) | |
4418 (let* ((tt (js2-node-type n)) | |
4419 (op (gethash tt js2-operator-tokens))) | |
4420 (unless op | |
4421 (error "unrecognized infix operator %s" (js2-node-type n))) | |
4422 (insert (js2-make-pad i)) | |
4423 (js2-print-ast (js2-infix-node-left n) 0) | |
4424 (unless (= tt js2-COMMA) | |
4425 (insert " ")) | |
4426 (insert op) | |
4427 (insert " ") | |
4428 (js2-print-ast (js2-infix-node-right n) 0))) | |
4429 | |
4430 (defstruct (js2-assign-node | |
4431 (:include js2-infix-node) | |
4432 (:constructor nil) | |
4433 (:constructor make-js2-assign-node (&key type | |
4434 (pos js2-ts-cursor) | |
4435 len | |
4436 op-pos | |
4437 left | |
4438 right))) | |
4439 "Represents any assignment. | |
4440 The type field holds the actual assignment operator.") | |
4441 | |
4442 (put 'cl-struct-js2-assign-node 'js2-visitor 'js2-visit-infix-node) | |
4443 (put 'cl-struct-js2-assign-node 'js2-printer 'js2-print-infix-node) | |
4444 | |
4445 (defstruct (js2-unary-node | |
4446 (:include js2-node) | |
4447 (:constructor nil) | |
4448 (:constructor make-js2-unary-node (&key type ; required | |
4449 (pos js2-ts-cursor) | |
4450 len | |
4451 operand))) | |
4452 "AST node type for unary operator nodes. | |
4453 The type field can be NOT, BITNOT, POS, NEG, INC, DEC, | |
4454 TYPEOF, or DELPROP. For INC or DEC, a 'postfix node | |
4455 property is added if the operator follows the operand." | |
4456 operand) ; a `js2-node' expression | |
4457 | |
4458 (put 'cl-struct-js2-unary-node 'js2-visitor 'js2-visit-unary-node) | |
4459 (put 'cl-struct-js2-unary-node 'js2-printer 'js2-print-unary-node) | |
4460 | |
4461 (defun js2-visit-unary-node (n v) | |
4462 (js2-visit-ast (js2-unary-node-operand n) v)) | |
4463 | |
4464 (defun js2-print-unary-node (n i) | |
4465 (let* ((tt (js2-node-type n)) | |
4466 (op (gethash tt js2-operator-tokens)) | |
4467 (postfix (js2-node-get-prop n 'postfix))) | |
4468 (unless op | |
4469 (error "unrecognized unary operator %s" tt)) | |
4470 (insert (js2-make-pad i)) | |
4471 (unless postfix | |
4472 (insert op)) | |
4473 (if (or (= tt js2-TYPEOF) | |
4474 (= tt js2-DELPROP)) | |
4475 (insert " ")) | |
4476 (js2-print-ast (js2-unary-node-operand n) 0) | |
4477 (when postfix | |
4478 (insert op)))) | |
4479 | |
4480 (defstruct (js2-let-node | |
4481 (:include js2-scope) | |
4482 (:constructor nil) | |
4483 (:constructor make-js2-let-node (&key (type js2-LETEXPR) | |
4484 (pos js2-token-beg) | |
4485 len | |
4486 vars | |
4487 body | |
4488 lp | |
4489 rp))) | |
4490 "AST node for a let expression or a let statement. | |
4491 Note that a let declaration such as let x=6, y=7 is a `js2-var-decl-node'." | |
4492 vars ; a `js2-var-decl-node' | |
4493 body ; a `js2-node' representing the expression or body block | |
4494 lp | |
4495 rp) | |
4496 | |
4497 (put 'cl-struct-js2-let-node 'js2-visitor 'js2-visit-let-node) | |
4498 (put 'cl-struct-js2-let-node 'js2-printer 'js2-print-let-node) | |
4499 | |
4500 (defun js2-visit-let-node (n v) | |
4501 (when (js2-let-node-vars n) | |
4502 (js2-visit-ast (js2-let-node-vars n) v)) | |
4503 (when (js2-let-node-body n) | |
4504 (js2-visit-ast (js2-let-node-body n) v))) | |
4505 | |
4506 (defun js2-print-let-node (n i) | |
4507 (insert (js2-make-pad i) "let (") | |
4508 (js2-print-ast (js2-let-node-vars n) 0) | |
4509 (insert ") ") | |
4510 (js2-print-ast (js2-let-node-body n) i)) | |
4511 | |
4512 (defstruct (js2-keyword-node | |
4513 (:include js2-node) | |
4514 (:constructor nil) | |
4515 (:constructor make-js2-keyword-node (&key type | |
4516 (pos js2-token-beg) | |
4517 (len (- js2-ts-cursor pos))))) | |
4518 "AST node representing a literal keyword such as `null'. | |
4519 Used for `null', `this', `true', `false' and `debugger'. | |
4520 The node type is set to js2-NULL, js2-THIS, etc.") | |
4521 | |
4522 (put 'cl-struct-js2-keyword-node 'js2-visitor 'js2-visit-none) | |
4523 (put 'cl-struct-js2-keyword-node 'js2-printer 'js2-print-keyword-node) | |
4524 | |
4525 (defun js2-print-keyword-node (n i) | |
4526 (insert (js2-make-pad i) | |
4527 (let ((tt (js2-node-type n))) | |
4528 (cond | |
4529 ((= tt 'js2-THIS) "this") | |
4530 ((= tt 'js2-NULL) "null") | |
4531 ((= tt 'js2-TRUE) "true") | |
4532 ((= tt 'js2-FALSE) "false") | |
4533 ((= tt 'js2-DEBUGGER) "debugger") | |
4534 (t (error "Invalid keyword literal type: %d" tt)))))) | |
4535 | |
4536 (defsubst js2-this-node-p (node) | |
4537 "Return t if this node is a `js2-literal-node' of type js2-THIS." | |
4538 (eq (js2-node-type node) js2-THIS)) | |
4539 | |
4540 (defstruct (js2-new-node | |
4541 (:include js2-node) | |
4542 (:constructor nil) | |
4543 (:constructor make-js2-new-node (&key (type js2-NEW) | |
4544 (pos js2-token-beg) | |
4545 len | |
4546 target | |
4547 args | |
4548 initializer | |
4549 lp | |
4550 rp))) | |
4551 "AST node for new-expression such as new Foo()." | |
4552 target ; an identifier or reference | |
4553 args ; a lisp list of argument nodes | |
4554 lp ; position of left-paren, nil if omitted | |
4555 rp ; position of right-paren, nil if omitted | |
4556 initializer) ; experimental Rhino syntax: optional `js2-object-node' | |
4557 | |
4558 (put 'cl-struct-js2-new-node 'js2-visitor 'js2-visit-new-node) | |
4559 (put 'cl-struct-js2-new-node 'js2-printer 'js2-print-new-node) | |
4560 | |
4561 (defun js2-visit-new-node (n v) | |
4562 (js2-visit-ast (js2-new-node-target n) v) | |
4563 (dolist (arg (js2-new-node-args n)) | |
4564 (js2-visit-ast arg v)) | |
4565 (when (js2-new-node-initializer n) | |
4566 (js2-visit-ast (js2-new-node-initializer n) v))) | |
4567 | |
4568 (defun js2-print-new-node (n i) | |
4569 (insert (js2-make-pad i) "new ") | |
4570 (js2-print-ast (js2-new-node-target n)) | |
4571 (insert "(") | |
4572 (js2-print-list (js2-new-node-args n)) | |
4573 (insert ")") | |
4574 (when (js2-new-node-initializer n) | |
4575 (insert " ") | |
4576 (js2-print-ast (js2-new-node-initializer n)))) | |
4577 | |
4578 (defstruct (js2-name-node | |
4579 (:include js2-node) | |
4580 (:constructor nil) | |
4581 (:constructor make-js2-name-node (&key (type js2-NAME) | |
4582 (pos js2-token-beg) | |
4583 (len (- js2-ts-cursor | |
4584 js2-token-beg)) | |
4585 (name js2-ts-string)))) | |
4586 "AST node for a JavaScript identifier" | |
4587 name ; a string | |
4588 scope) ; a `js2-scope' (optional, used for codegen) | |
4589 | |
4590 (put 'cl-struct-js2-name-node 'js2-visitor 'js2-visit-none) | |
4591 (put 'cl-struct-js2-name-node 'js2-printer 'js2-print-name-node) | |
4592 | |
4593 (defun js2-print-name-node (n i) | |
4594 (insert (js2-make-pad i) | |
4595 (js2-name-node-name n))) | |
4596 | |
4597 (defsubst js2-name-node-length (node) | |
4598 "Return identifier length of NODE, a `js2-name-node'. | |
4599 Returns 0 if NODE is nil or its identifier field is nil." | |
4600 (if node | |
4601 (length (js2-name-node-name node)) | |
4602 0)) | |
4603 | |
4604 (defstruct (js2-number-node | |
4605 (:include js2-node) | |
4606 (:constructor nil) | |
4607 (:constructor make-js2-number-node (&key (type js2-NUMBER) | |
4608 (pos js2-token-beg) | |
4609 (len (- js2-ts-cursor | |
4610 js2-token-beg)) | |
4611 (value js2-ts-string) | |
4612 (num-value js2-ts-number)))) | |
4613 "AST node for a number literal." | |
4614 value ; the original string, e.g. "6.02e23" | |
4615 num-value) ; the parsed number value | |
4616 | |
4617 (put 'cl-struct-js2-number-node 'js2-visitor 'js2-visit-none) | |
4618 (put 'cl-struct-js2-number-node 'js2-printer 'js2-print-number-node) | |
4619 | |
4620 (defun js2-print-number-node (n i) | |
4621 (insert (js2-make-pad i) | |
4622 (number-to-string (js2-number-node-value n)))) | |
4623 | |
4624 (defstruct (js2-regexp-node | |
4625 (:include js2-node) | |
4626 (:constructor nil) | |
4627 (:constructor make-js2-regexp-node (&key (type js2-REGEXP) | |
4628 (pos js2-token-beg) | |
4629 (len (- js2-ts-cursor | |
4630 js2-token-beg)) | |
4631 value | |
4632 flags))) | |
4633 "AST node for a regular expression literal." | |
4634 value ; the regexp string, without // delimiters | |
4635 flags) ; a string of flags, e.g. `mi'. | |
4636 | |
4637 (put 'cl-struct-js2-regexp-node 'js2-visitor 'js2-visit-none) | |
4638 (put 'cl-struct-js2-regexp-node 'js2-printer 'js2-print-regexp) | |
4639 | |
4640 (defun js2-print-regexp (n i) | |
4641 (insert (js2-make-pad i) | |
4642 "/" | |
4643 (js2-regexp-node-value n) | |
4644 "/") | |
4645 (if (js2-regexp-node-flags n) | |
4646 (insert (js2-regexp-node-flags n)))) | |
4647 | |
4648 (defstruct (js2-string-node | |
4649 (:include js2-node) | |
4650 (:constructor nil) | |
4651 (:constructor make-js2-string-node (&key (type js2-STRING) | |
4652 (pos js2-token-beg) | |
4653 (len (- js2-ts-cursor | |
4654 js2-token-beg)) | |
4655 (value js2-ts-string)))) | |
4656 "String literal. | |
4657 Escape characters are not evaluated; e.g. \n is 2 chars in value field. | |
4658 You can tell the quote type by looking at the first character." | |
4659 value) ; the characters of the string, including the quotes | |
4660 | |
4661 (put 'cl-struct-js2-string-node 'js2-visitor 'js2-visit-none) | |
4662 (put 'cl-struct-js2-string-node 'js2-printer 'js2-print-string-node) | |
4663 | |
4664 (defun js2-print-string-node (n i) | |
4665 (insert (js2-make-pad i) | |
4666 (js2-node-string n))) | |
4667 | |
4668 (defstruct (js2-array-node | |
4669 (:include js2-node) | |
4670 (:constructor nil) | |
4671 (:constructor make-js2-array-node (&key (type js2-ARRAYLIT) | |
4672 (pos js2-ts-cursor) | |
4673 len | |
4674 elems))) | |
4675 "AST node for an array literal." | |
4676 elems) ; list of expressions. [foo,,bar] yields a nil middle element. | |
4677 | |
4678 (put 'cl-struct-js2-array-node 'js2-visitor 'js2-visit-array-node) | |
4679 (put 'cl-struct-js2-array-node 'js2-printer 'js2-print-array-node) | |
4680 | |
4681 (defun js2-visit-array-node (n v) | |
4682 (dolist (e (js2-array-node-elems n)) | |
4683 (when e ; can be nil, e.g. [a, ,b] | |
4684 (js2-visit-ast e v)))) | |
4685 | |
4686 (defun js2-print-array-node (n i) | |
4687 (insert (js2-make-pad i) "[") | |
4688 (js2-print-list (js2-array-node-elems n)) | |
4689 (insert "]")) | |
4690 | |
4691 (defstruct (js2-object-node | |
4692 (:include js2-node) | |
4693 (:constructor nil) | |
4694 (:constructor make-js2-object-node (&key (type js2-OBJECTLIT) | |
4695 (pos js2-ts-cursor) | |
4696 len | |
4697 elems))) | |
4698 "AST node for an object literal expression." | |
4699 elems) ; a lisp list of `js2-object-prop-node' | |
4700 | |
4701 (put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node) | |
4702 (put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node) | |
4703 | |
4704 (defun js2-visit-object-node (n v) | |
4705 (dolist (e (js2-object-node-elems n)) | |
4706 (js2-visit-ast e v))) | |
4707 | |
4708 (defun js2-print-object-node (n i) | |
4709 (insert (js2-make-pad i) "{") | |
4710 (js2-print-list (js2-object-node-elems n)) | |
4711 (insert "}")) | |
4712 | |
4713 (defstruct (js2-object-prop-node | |
4714 (:include js2-infix-node) | |
4715 (:constructor nil) | |
4716 (:constructor make-js2-object-prop-node (&key (type js2-COLON) | |
4717 (pos js2-ts-cursor) | |
4718 len | |
4719 left | |
4720 right | |
4721 op-pos))) | |
4722 "AST node for an object literal prop:value entry. | |
4723 The `left' field is the property: a name node, string node or number node. | |
4724 The `right' field is a `js2-node' representing the initializer value.") | |
4725 | |
4726 (put 'cl-struct-js2-object-prop-node 'js2-visitor 'js2-visit-infix-node) | |
4727 (put 'cl-struct-js2-object-prop-node 'js2-printer 'js2-print-object-prop-node) | |
4728 | |
4729 (defun js2-print-object-prop-node (n i) | |
4730 (insert (js2-make-pad i)) | |
4731 (js2-print-ast (js2-object-prop-node-left n) 0) | |
4732 (insert ":") | |
4733 (js2-print-ast (js2-object-prop-node-right n) 0)) | |
4734 | |
4735 (defstruct (js2-getter-setter-node | |
4736 (:include js2-infix-node) | |
4737 (:constructor nil) | |
4738 (:constructor make-js2-getter-setter-node (&key type ; GET or SET | |
4739 (pos js2-ts-cursor) | |
4740 len | |
4741 left | |
4742 right))) | |
4743 "AST node for a getter/setter property in an object literal. | |
4744 The `left' field is the `js2-name-node' naming the getter/setter prop. | |
4745 The `right' field is always an anonymous `js2-function-node' with a node | |
4746 property `GETTER_SETTER' set to js2-GET or js2-SET. ") | |
4747 | |
4748 (put 'cl-struct-js2-getter-setter-node 'js2-visitor 'js2-visit-infix-node) | |
4749 (put 'cl-struct-js2-getter-setter-node 'js2-printer 'js2-print-getter-setter) | |
4750 | |
4751 (defun js2-print-getter-setter (n i) | |
4752 (let ((pad (js2-make-pad i)) | |
4753 (left (js2-getter-setter-node-left n)) | |
4754 (right (js2-getter-setter-node-right n))) | |
4755 (insert pad) | |
4756 (insert (if (= (js2-node-type n) js2-GET) "get " "set ")) | |
4757 (js2-print-ast left 0) | |
4758 (js2-print-ast right 0))) | |
4759 | |
4760 (defstruct (js2-prop-get-node | |
4761 (:include js2-infix-node) | |
4762 (:constructor nil) | |
4763 (:constructor make-js2-prop-get-node (&key (type js2-GETPROP) | |
4764 (pos js2-ts-cursor) | |
4765 len | |
4766 left | |
4767 right))) | |
4768 "AST node for a dotted property reference, e.g. foo.bar or foo().bar") | |
4769 | |
4770 (put 'cl-struct-js2-prop-get-node 'js2-visitor 'js2-visit-prop-get-node) | |
4771 (put 'cl-struct-js2-prop-get-node 'js2-printer 'js2-print-prop-get-node) | |
4772 | |
4773 (defun js2-visit-prop-get-node (n v) | |
4774 (when (js2-prop-get-node-left n) | |
4775 (js2-visit-ast (js2-prop-get-node-left n) v)) | |
4776 (when (js2-prop-get-node-right n) | |
4777 (js2-visit-ast (js2-prop-get-node-right n) v))) | |
4778 | |
4779 (defun js2-print-prop-get-node (n i) | |
4780 (insert (js2-make-pad i)) | |
4781 (js2-print-ast (js2-prop-get-node-left n) 0) | |
4782 (insert ".") | |
4783 (js2-print-ast (js2-prop-get-node-right n) 0)) | |
4784 | |
4785 (defstruct (js2-elem-get-node | |
4786 (:include js2-node) | |
4787 (:constructor nil) | |
4788 (:constructor make-js2-elem-get-node (&key (type js2-GETELEM) | |
4789 (pos js2-ts-cursor) | |
4790 len | |
4791 target | |
4792 element | |
4793 lb | |
4794 rb))) | |
4795 "AST node for an array index expression such as foo[bar]." | |
4796 target ; a `js2-node' - the expression preceding the "." | |
4797 element ; a `js2-node' - the expression in brackets | |
4798 lb ; position of left-bracket, nil if omitted | |
4799 rb) ; position of right-bracket, nil if omitted | |
4800 | |
4801 (put 'cl-struct-js2-elem-get-node 'js2-visitor 'js2-visit-elem-get-node) | |
4802 (put 'cl-struct-js2-elem-get-node 'js2-printer 'js2-print-elem-get-node) | |
4803 | |
4804 (defun js2-visit-elem-get-node (n v) | |
4805 (when (js2-elem-get-node-target n) | |
4806 (js2-visit-ast (js2-elem-get-node-target n) v)) | |
4807 (when (js2-elem-get-node-element n) | |
4808 (js2-visit-ast (js2-elem-get-node-element n) v))) | |
4809 | |
4810 (defun js2-print-elem-get-node (n i) | |
4811 (insert (js2-make-pad i)) | |
4812 (js2-print-ast (js2-elem-get-node-target n) 0) | |
4813 (insert "[") | |
4814 (js2-print-ast (js2-elem-get-node-element n) 0) | |
4815 (insert "]")) | |
4816 | |
4817 (defstruct (js2-call-node | |
4818 (:include js2-node) | |
4819 (:constructor nil) | |
4820 (:constructor make-js2-call-node (&key (type js2-CALL) | |
4821 (pos js2-ts-cursor) | |
4822 len | |
4823 target | |
4824 args | |
4825 lp | |
4826 rp))) | |
4827 "AST node for a JavaScript function call." | |
4828 target ; a `js2-node' evaluating to the function to call | |
4829 args ; a lisp list of `js2-node' arguments | |
4830 lp ; position of open-paren, or nil if missing | |
4831 rp) ; position of close-paren, or nil if missing | |
4832 | |
4833 (put 'cl-struct-js2-call-node 'js2-visitor 'js2-visit-call-node) | |
4834 (put 'cl-struct-js2-call-node 'js2-printer 'js2-print-call-node) | |
4835 | |
4836 (defun js2-visit-call-node (n v) | |
4837 (js2-visit-ast (js2-call-node-target n) v) | |
4838 (dolist (arg (js2-call-node-args n)) | |
4839 (js2-visit-ast arg v))) | |
4840 | |
4841 (defun js2-print-call-node (n i) | |
4842 (insert (js2-make-pad i)) | |
4843 (js2-print-ast (js2-call-node-target n) 0) | |
4844 (insert "(") | |
4845 (js2-print-list (js2-call-node-args n)) | |
4846 (insert ")")) | |
4847 | |
4848 (defstruct (js2-yield-node | |
4849 (:include js2-node) | |
4850 (:constructor nil) | |
4851 (:constructor make-js2-yield-node (&key (type js2-YIELD) | |
4852 (pos js2-ts-cursor) | |
4853 len | |
4854 value))) | |
4855 "AST node for yield statement or expression." | |
4856 value) ; optional: value to be yielded | |
4857 | |
4858 (put 'cl-struct-js2-yield-node 'js2-visitor 'js2-visit-yield-node) | |
4859 (put 'cl-struct-js2-yield-node 'js2-printer 'js2-print-yield-node) | |
4860 | |
4861 (defun js2-visit-yield-node (n v) | |
4862 (js2-visit-ast (js2-yield-node-value n) v)) | |
4863 | |
4864 (defun js2-print-yield-node (n i) | |
4865 (insert (js2-make-pad i)) | |
4866 (insert "yield") | |
4867 (when (js2-yield-node-value n) | |
4868 (insert " ") | |
4869 (js2-print-ast (js2-yield-node-value n) 0))) | |
4870 | |
4871 (defstruct (js2-paren-node | |
4872 (:include js2-node) | |
4873 (:constructor nil) | |
4874 (:constructor make-js2-paren-node (&key (type js2-LP) | |
4875 (pos js2-ts-cursor) | |
4876 len | |
4877 expr))) | |
4878 "AST node for a parenthesized expression. | |
4879 In particular, used when the parens are syntactically optional, | |
4880 as opposed to required parens such as those enclosing an if-conditional." | |
4881 expr) ; `js2-node' | |
4882 | |
4883 (put 'cl-struct-js2-paren-node 'js2-visitor 'js2-visit-paren-node) | |
4884 (put 'cl-struct-js2-paren-node 'js2-printer 'js2-print-paren-node) | |
4885 | |
4886 (defun js2-visit-paren-node (n v) | |
4887 (js2-visit-ast (js2-paren-node-expr n) v)) | |
4888 | |
4889 (defun js2-print-paren-node (n i) | |
4890 (insert (js2-make-pad i)) | |
4891 (insert "(") | |
4892 (js2-print-ast (js2-paren-node-expr n) 0) | |
4893 (insert ")")) | |
4894 | |
4895 (defstruct (js2-array-comp-node | |
4896 (:include js2-scope) | |
4897 (:constructor nil) | |
4898 (:constructor make-js2-array-comp-node (&key (type js2-ARRAYCOMP) | |
4899 (pos js2-ts-cursor) | |
4900 len | |
4901 result | |
4902 loops | |
4903 filter | |
4904 if-pos | |
4905 lp | |
4906 rp))) | |
4907 "AST node for an Array comprehension such as [[x,y] for (x in foo) for (y in bar)]." | |
4908 result ; result expression (just after left-bracket) | |
4909 loops ; a lisp list of `js2-array-comp-loop-node' | |
4910 filter ; guard/filter expression | |
4911 if-pos ; buffer pos of 'if' keyword, if present, else nil | |
4912 lp ; buffer position of if-guard left-paren, or nil if not present | |
4913 rp) ; buffer position of if-guard right-paren, or nil if not present | |
4914 | |
4915 (put 'cl-struct-js2-array-comp-node 'js2-visitor 'js2-visit-array-comp-node) | |
4916 (put 'cl-struct-js2-array-comp-node 'js2-printer 'js2-print-array-comp-node) | |
4917 | |
4918 (defun js2-visit-array-comp-node (n v) | |
4919 (js2-visit-ast (js2-array-comp-node-result n) v) | |
4920 (dolist (l (js2-array-comp-node-loops n)) | |
4921 (js2-visit-ast l v)) | |
4922 (if (js2-array-comp-node-filter n) | |
4923 (js2-visit-ast (js2-array-comp-node-filter n) v))) | |
4924 | |
4925 (defun js2-print-array-comp-node (n i) | |
4926 (let ((pad (js2-make-pad i)) | |
4927 (result (js2-array-comp-node-result n)) | |
4928 (loops (js2-array-comp-node-loops n)) | |
4929 (filter (js2-array-comp-node-filter n))) | |
4930 (insert pad "[") | |
4931 (js2-print-ast result 0) | |
4932 (dolist (l loops) | |
4933 (insert " ") | |
4934 (js2-print-ast l 0)) | |
4935 (when filter | |
4936 (insert " if (") | |
4937 (js2-print-ast filter 0)) | |
4938 (insert ")]"))) | |
4939 | |
4940 (defstruct (js2-array-comp-loop-node | |
4941 (:include js2-for-in-node) | |
4942 (:constructor nil) | |
4943 (:constructor make-js2-array-comp-loop-node (&key (type js2-FOR) | |
4944 (pos js2-ts-cursor) | |
4945 len | |
4946 iterator | |
4947 object | |
4948 in-pos | |
4949 foreach-p | |
4950 each-pos | |
4951 lp | |
4952 rp))) | |
4953 "AST subtree for each 'for (foo in bar)' loop in an array comprehension.") | |
4954 | |
4955 (put 'cl-struct-js2-array-comp-loop-node 'js2-visitor 'js2-visit-array-comp-loop) | |
4956 (put 'cl-struct-js2-array-comp-loop-node 'js2-printer 'js2-print-array-comp-loop) | |
4957 | |
4958 (defun js2-visit-array-comp-loop (n v) | |
4959 (js2-visit-ast (js2-array-comp-loop-node-iterator n) v) | |
4960 (js2-visit-ast (js2-array-comp-loop-node-object n) v)) | |
4961 | |
4962 (defun js2-print-array-comp-loop (n i) | |
4963 (insert "for (") | |
4964 (js2-print-ast (js2-array-comp-loop-node-iterator n) 0) | |
4965 (insert " in ") | |
4966 (js2-print-ast (js2-array-comp-loop-node-object n) 0) | |
4967 (insert ")")) | |
4968 | |
4969 (defstruct (js2-empty-expr-node | |
4970 (:include js2-node) | |
4971 (:constructor nil) | |
4972 (:constructor make-js2-empty-expr-node (&key (type js2-EMPTY) | |
4973 (pos js2-token-beg) | |
4974 len))) | |
4975 "AST node for an empty expression.") | |
4976 | |
4977 (put 'cl-struct-js2-empty-expr-node 'js2-visitor 'js2-visit-none) | |
4978 (put 'cl-struct-js2-empty-expr-node 'js2-printer 'js2-print-none) | |
4979 | |
4980 (defstruct (js2-xml-node | |
4981 (:include js2-block-node) | |
4982 (:constructor nil) | |
4983 (:constructor make-js2-xml-node (&key (type js2-XML) | |
4984 (pos js2-token-beg) | |
4985 len | |
4986 kids))) | |
4987 "AST node for initial parse of E4X literals. | |
4988 The kids field is a list of XML fragments, each a `js2-string-node' or | |
4989 a `js2-xml-js-expr-node'. Equivalent to Rhino's XmlLiteral node.") | |
4990 | |
4991 (put 'cl-struct-js2-xml-node 'js2-visitor 'js2-visit-block) | |
4992 (put 'cl-struct-js2-xml-node 'js2-printer 'js2-print-xml-node) | |
4993 | |
4994 (defun js2-print-xml-node (n i) | |
4995 (dolist (kid (js2-xml-node-kids n)) | |
4996 (js2-print-ast kid i))) | |
4997 | |
4998 (defstruct (js2-xml-js-expr-node | |
4999 (:include js2-xml-node) | |
5000 (:constructor nil) | |
5001 (:constructor make-js2-xml-js-expr-node (&key (type js2-XML) | |
5002 (pos js2-ts-cursor) | |
5003 len | |
5004 expr))) | |
5005 "AST node for an embedded JavaScript {expression} in an E4X literal. | |
5006 The start and end fields correspond to the curly-braces." | |
5007 expr) ; a `js2-expr-node' of some sort | |
5008 | |
5009 (put 'cl-struct-js2-xml-js-expr-node 'js2-visitor 'js2-visit-xml-js-expr) | |
5010 (put 'cl-struct-js2-xml-js-expr-node 'js2-printer 'js2-print-xml-js-expr) | |
5011 | |
5012 (defun js2-visit-xml-js-expr (n v) | |
5013 (js2-visit-ast (js2-xml-js-expr-node-expr n) v)) | |
5014 | |
5015 (defun js2-print-xml-js-expr (n i) | |
5016 (insert (js2-make-pad i)) | |
5017 (insert "{") | |
5018 (js2-print-ast (js2-xml-js-expr-node-expr n) 0) | |
5019 (insert "}")) | |
5020 | |
5021 (defstruct (js2-xml-dot-query-node | |
5022 (:include js2-infix-node) | |
5023 (:constructor nil) | |
5024 (:constructor make-js2-xml-dot-query-node (&key (type js2-DOTQUERY) | |
5025 (pos js2-ts-cursor) | |
5026 op-pos | |
5027 len | |
5028 left | |
5029 right | |
5030 rp))) | |
5031 "AST node for an E4X foo.(bar) filter expression. | |
5032 Note that the left-paren is automatically the character immediately | |
5033 following the dot (.) in the operator. No whitespace is permitted | |
5034 between the dot and the lp by the scanner." | |
5035 rp) | |
5036 | |
5037 (put 'cl-struct-js2-xml-dot-query-node 'js2-visitor 'js2-visit-infix-node) | |
5038 (put 'cl-struct-js2-xml-dot-query-node 'js2-printer 'js2-print-xml-dot-query) | |
5039 | |
5040 (defun js2-print-xml-dot-query (n i) | |
5041 (insert (js2-make-pad i)) | |
5042 (js2-print-ast (js2-xml-dot-query-node-left n) 0) | |
5043 (insert ".(") | |
5044 (js2-print-ast (js2-xml-dot-query-node-right n) 0) | |
5045 (insert ")")) | |
5046 | |
5047 (defstruct (js2-xml-ref-node | |
5048 (:include js2-node) | |
5049 (:constructor nil)) ; abstract | |
5050 "Base type for E4X XML attribute-access or property-get expressions. | |
5051 Such expressions can take a variety of forms. The general syntax has | |
5052 three parts: | |
5053 | |
5054 - (optional) an @ (specifying an attribute access) | |
5055 - (optional) a namespace (a `js2-name-node') and double-colon | |
5056 - (required) either a `js2-name-node' or a bracketed [expression] | |
5057 | |
5058 The property-name expressions (examples: ns::name, @name) are | |
5059 represented as `js2-xml-prop-ref' nodes. The bracketed-expression | |
5060 versions (examples: ns::[name], @[name]) become `js2-xml-elem-ref' nodes. | |
5061 | |
5062 This node type (or more specifically, its subclasses) will sometimes | |
5063 be the right-hand child of a `js2-prop-get-node' or a | |
5064 `js2-infix-node' of type `js2-DOTDOT', the .. xml-descendants operator. | |
5065 The `js2-xml-ref-node' may also be a standalone primary expression with | |
5066 no explicit target, which is valid in certain expression contexts such as | |
5067 | |
5068 company..employee.(@id < 100) | |
5069 | |
5070 in this case, the @id is a `js2-xml-ref' that is part of an infix '<' | |
5071 expression whose parent is a `js2-xml-dot-query-node'." | |
5072 namespace | |
5073 at-pos | |
5074 colon-pos) | |
5075 | |
5076 (defsubst js2-xml-ref-node-attr-access-p (node) | |
5077 "Return non-nil if this expression began with an @-token." | |
5078 (and (numberp (js2-xml-ref-node-at-pos node)) | |
5079 (plusp (js2-xml-ref-node-at-pos node)))) | |
5080 | |
5081 (defstruct (js2-xml-prop-ref-node | |
5082 (:include js2-xml-ref-node) | |
5083 (:constructor nil) | |
5084 (:constructor make-js2-xml-prop-ref-node (&key (type js2-REF_NAME) | |
5085 (pos js2-token-beg) | |
5086 len | |
5087 propname | |
5088 namespace | |
5089 at-pos | |
5090 colon-pos))) | |
5091 "AST node for an E4X XML [expr] property-ref expression. | |
5092 The JavaScript syntax is an optional @, an optional ns::, and a name. | |
5093 | |
5094 [ '@' ] [ name '::' ] name | |
5095 | |
5096 Examples include name, ns::name, ns::*, *::name, *::*, @attr, @ns::attr, | |
5097 @ns::*, @*::attr, @*::*, and @*. | |
5098 | |
5099 The node starts at the @ token, if present. Otherwise it starts at the | |
5100 namespace name. The node bounds extend through the closing right-bracket, | |
5101 or if it is missing due to a syntax error, through the end of the index | |
5102 expression." | |
5103 propname) | |
5104 | |
5105 (put 'cl-struct-js2-xml-prop-ref-node 'js2-visitor 'js2-visit-xml-prop-ref-node) | |
5106 (put 'cl-struct-js2-xml-prop-ref-node 'js2-printer 'js2-print-xml-prop-ref-node) | |
5107 | |
5108 (defun js2-visit-xml-prop-ref-node (n v) | |
5109 (if (js2-xml-prop-ref-node-namespace n) | |
5110 (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v)) | |
5111 (if (js2-xml-prop-ref-node-propname n) | |
5112 (js2-visit-ast (js2-xml-prop-ref-node-propname n) v))) | |
5113 | |
5114 (defun js2-print-xml-prop-ref-node (n i) | |
5115 (insert (js2-make-pad i)) | |
5116 (if (js2-xml-ref-node-attr-access-p n) | |
5117 (insert "@")) | |
5118 (when (js2-xml-prop-ref-node-namespace n) | |
5119 (js2-print-ast (js2-xml-prop-ref-node-namespace n) 0) | |
5120 (insert "::")) | |
5121 (if (js2-xml-prop-ref-node-propname n) | |
5122 (js2-print-ast (js2-xml-prop-ref-node-propname n) 0))) | |
5123 | |
5124 (defstruct (js2-xml-elem-ref-node | |
5125 (:include js2-xml-ref-node) | |
5126 (:constructor nil) | |
5127 (:constructor make-js2-xml-elem-ref-node (&key (type js2-REF_MEMBER) | |
5128 (pos js2-token-beg) | |
5129 len | |
5130 expr | |
5131 lb | |
5132 rb | |
5133 namespace | |
5134 at-pos | |
5135 colon-pos))) | |
5136 "AST node for an E4X XML [expr] member-ref expression. | |
5137 Syntax: | |
5138 | |
5139 [ '@' ] [ name '::' ] '[' expr ']' | |
5140 | |
5141 Examples include ns::[expr], @ns::[expr], @[expr], *::[expr] and @*::[expr]. | |
5142 | |
5143 Note that the form [expr] (i.e. no namespace or attribute-qualifier) | |
5144 is not a legal E4X XML element-ref expression, since it's already used | |
5145 for standard JavaScript element-get array indexing. Hence, a | |
5146 `js2-xml-elem-ref-node' always has either the attribute-qualifier, a | |
5147 non-nil namespace node, or both. | |
5148 | |
5149 The node starts at the @ token, if present. Otherwise it starts | |
5150 at the namespace name. The node bounds extend through the closing | |
5151 right-bracket, or if it is missing due to a syntax error, through the | |
5152 end of the index expression." | |
5153 expr ; the bracketed index expression | |
5154 lb | |
5155 rb) | |
5156 | |
5157 (put 'cl-struct-js2-xml-elem-ref-node 'js2-visitor 'js2-visit-xml-elem-ref-node) | |
5158 (put 'cl-struct-js2-xml-elem-ref-node 'js2-printer 'js2-print-xml-elem-ref-node) | |
5159 | |
5160 (defun js2-visit-xml-elem-ref-node (n v) | |
5161 (if (js2-xml-elem-ref-node-namespace n) | |
5162 (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v)) | |
5163 (if (js2-xml-elem-ref-node-expr n) | |
5164 (js2-visit-ast (js2-xml-elem-ref-node-expr n) v))) | |
5165 | |
5166 (defun js2-print-xml-elem-ref-node (n i) | |
5167 (insert (js2-make-pad i)) | |
5168 (if (js2-xml-ref-node-attr-access-p n) | |
5169 (insert "@")) | |
5170 (when (js2-xml-elem-ref-node-namespace n) | |
5171 (js2-print-ast (js2-xml-elem-ref-node-namespace n) 0) | |
5172 (insert "::")) | |
5173 (insert "[") | |
5174 (if (js2-xml-elem-ref-node-expr n) | |
5175 (js2-print-ast (js2-xml-elem-ref-node-expr n) 0)) | |
5176 (insert "]")) | |
5177 | |
5178 ;;; Placeholder nodes for when we try parsing the XML literals structurally. | |
5179 | |
5180 (defstruct (js2-xml-start-tag-node | |
5181 (:include js2-xml-node) | |
5182 (:constructor nil) | |
5183 (:constructor make-js2-xml-start-tag-node (&key (type js2-XML) | |
5184 (pos js2-ts-cursor) | |
5185 len | |
5186 name | |
5187 attrs | |
5188 kids | |
5189 empty-p))) | |
5190 "AST node for an XML start-tag. Not currently used. | |
5191 The `kids' field is a lisp list of child content nodes." | |
5192 name ; a `js2-xml-name-node' | |
5193 attrs ; a lisp list of `js2-xml-attr-node' | |
5194 empty-p) ; t if this is an empty element such as <foo bar="baz"/> | |
5195 | |
5196 (put 'cl-struct-js2-xml-start-tag-node 'js2-visitor 'js2-visit-xml-start-tag) | |
5197 (put 'cl-struct-js2-xml-start-tag-node 'js2-printer 'js2-print-xml-start-tag) | |
5198 | |
5199 (defun js2-visit-xml-start-tag (n v) | |
5200 (js2-visit-ast (js2-xml-start-tag-node-name n) v) | |
5201 (dolist (attr (js2-xml-start-tag-node-attrs n)) | |
5202 (js2-visit-ast attr v)) | |
5203 (js2-visit-block n v)) | |
5204 | |
5205 (defun js2-print-xml-start-tag (n i) | |
5206 (insert (js2-make-pad i) "<") | |
5207 (js2-print-ast (js2-xml-start-tag-node-name n) 0) | |
5208 (when (js2-xml-start-tag-node-attrs n) | |
5209 (insert " ") | |
5210 (js2-print-list (js2-xml-start-tag-node-attrs n) " ")) | |
5211 (insert ">")) | |
5212 | |
5213 ;; I -think- I'm going to make the parent node the corresponding start-tag, | |
5214 ;; and add the end-tag to the kids list of the parent as well. | |
5215 (defstruct (js2-xml-end-tag-node | |
5216 (:include js2-xml-node) | |
5217 (:constructor nil) | |
5218 (:constructor make-js2-xml-end-tag-node (&key (type js2-XML) | |
5219 (pos js2-ts-cursor) | |
5220 len | |
5221 name))) | |
5222 "AST node for an XML end-tag. Not currently used." | |
5223 name) ; a `js2-xml-name-node' | |
5224 | |
5225 (put 'cl-struct-js2-xml-end-tag-node 'js2-visitor 'js2-visit-xml-end-tag) | |
5226 (put 'cl-struct-js2-xml-end-tag-node 'js2-printer 'js2-print-xml-end-tag) | |
5227 | |
5228 (defun js2-visit-xml-end-tag (n v) | |
5229 (js2-visit-ast (js2-xml-end-tag-node-name n) v)) | |
5230 | |
5231 (defun js2-print-xml-end-tag (n i) | |
5232 (insert (js2-make-pad i)) | |
5233 (insert "</") | |
5234 (js2-print-ast (js2-xml-end-tag-node-name n) 0) | |
5235 (insert ">")) | |
5236 | |
5237 (defstruct (js2-xml-name-node | |
5238 (:include js2-xml-node) | |
5239 (:constructor nil) | |
5240 (:constructor make-js2-xml-name-node (&key (type js2-XML) | |
5241 (pos js2-ts-cursor) | |
5242 len | |
5243 namespace | |
5244 kids))) | |
5245 "AST node for an E4X XML name. Not currently used. | |
5246 Any XML name can be qualified with a namespace, hence the namespace field. | |
5247 Further, any E4X name can be comprised of arbitrary JavaScript {} expressions. | |
5248 The kids field is a list of `js2-name-node' and `js2-xml-js-expr-node'. | |
5249 For a simple name, the kids list has exactly one node, a `js2-name-node'." | |
5250 namespace) ; a `js2-string-node' | |
5251 | |
5252 (put 'cl-struct-js2-xml-name-node 'js2-visitor 'js2-visit-xml-name-node) | |
5253 (put 'cl-struct-js2-xml-name-node 'js2-printer 'js2-print-xml-name-node) | |
5254 | |
5255 (defun js2-visit-xml-name-node (n v) | |
5256 (js2-visit-ast (js2-xml-name-node-namespace n) v)) | |
5257 | |
5258 (defun js2-print-xml-name-node (n i) | |
5259 (insert (js2-make-pad i)) | |
5260 (when (js2-xml-name-node-namespace n) | |
5261 (js2-print-ast (js2-xml-name-node-namespace n) 0) | |
5262 (insert "::")) | |
5263 (dolist (kid (js2-xml-name-node-kids n)) | |
5264 (js2-print-ast kid 0))) | |
5265 | |
5266 (defstruct (js2-xml-pi-node | |
5267 (:include js2-xml-node) | |
5268 (:constructor nil) | |
5269 (:constructor make-js2-xml-pi-node (&key (type js2-XML) | |
5270 (pos js2-ts-cursor) | |
5271 len | |
5272 name | |
5273 attrs))) | |
5274 "AST node for an E4X XML processing instruction. Not currently used." | |
5275 name ; a `js2-xml-name-node' | |
5276 attrs) ; a list of `js2-xml-attr-node' | |
5277 | |
5278 (put 'cl-struct-js2-xml-pi-node 'js2-visitor 'js2-visit-xml-pi-node) | |
5279 (put 'cl-struct-js2-xml-pi-node 'js2-printer 'js2-print-xml-pi-node) | |
5280 | |
5281 (defun js2-visit-xml-pi-node (n v) | |
5282 (js2-visit-ast (js2-xml-pi-node-name n) v) | |
5283 (dolist (attr (js2-xml-pi-node-attrs n)) | |
5284 (js2-visit-ast attr v))) | |
5285 | |
5286 (defun js2-print-xml-pi-node (n i) | |
5287 (insert (js2-make-pad i) "<?") | |
5288 (js2-print-ast (js2-xml-pi-node-name n)) | |
5289 (when (js2-xml-pi-node-attrs n) | |
5290 (insert " ") | |
5291 (js2-print-list (js2-xml-pi-node-attrs n))) | |
5292 (insert "?>")) | |
5293 | |
5294 (defstruct (js2-xml-cdata-node | |
5295 (:include js2-xml-node) | |
5296 (:constructor nil) | |
5297 (:constructor make-js2-xml-cdata-node (&key (type js2-XML) | |
5298 (pos js2-ts-cursor) | |
5299 len | |
5300 content))) | |
5301 "AST node for a CDATA escape section. Not currently used." | |
5302 content) ; a `js2-string-node' with node-property 'quote-type 'cdata | |
5303 | |
5304 (put 'cl-struct-js2-xml-cdata-node 'js2-visitor 'js2-visit-xml-cdata-node) | |
5305 (put 'cl-struct-js2-xml-cdata-node 'js2-printer 'js2-print-xml-cdata-node) | |
5306 | |
5307 (defun js2-visit-xml-cdata-node (n v) | |
5308 (js2-visit-ast (js2-xml-cdata-node-content n) v)) | |
5309 | |
5310 (defun js2-print-xml-cdata-node (n i) | |
5311 (insert (js2-make-pad i)) | |
5312 (js2-print-ast (js2-xml-cdata-node-content n))) | |
5313 | |
5314 (defstruct (js2-xml-attr-node | |
5315 (:include js2-xml-node) | |
5316 (:constructor nil) | |
5317 (:constructor make-js2-attr-node (&key (type js2-XML) | |
5318 (pos js2-ts-cursor) | |
5319 len | |
5320 name | |
5321 value | |
5322 eq-pos | |
5323 quote-type))) | |
5324 "AST node representing a foo='bar' XML attribute value. Not yet used." | |
5325 name ; a `js2-xml-name-node' | |
5326 value ; a `js2-xml-name-node' | |
5327 eq-pos ; buffer position of "=" sign | |
5328 quote-type) ; 'single or 'double | |
5329 | |
5330 (put 'cl-struct-js2-xml-attr-node 'js2-visitor 'js2-visit-xml-attr-node) | |
5331 (put 'cl-struct-js2-xml-attr-node 'js2-printer 'js2-print-xml-attr-node) | |
5332 | |
5333 (defun js2-visit-xml-attr-node (n v) | |
5334 (js2-visit-ast (js2-xml-attr-node-name n) v) | |
5335 (js2-visit-ast (js2-xml-attr-node-value n) v)) | |
5336 | |
5337 (defun js2-print-xml-attr-node (n i) | |
5338 (let ((quote (if (eq (js2-xml-attr-node-quote-type n) 'single) | |
5339 "'" | |
5340 "\""))) | |
5341 (insert (js2-make-pad i)) | |
5342 (js2-print-ast (js2-xml-attr-node-name n) 0) | |
5343 (insert "=" quote) | |
5344 (js2-print-ast (js2-xml-attr-node-value n) 0) | |
5345 (insert quote))) | |
5346 | |
5347 (defstruct (js2-xml-text-node | |
5348 (:include js2-xml-node) | |
5349 (:constructor nil) | |
5350 (:constructor make-js2-text-node (&key (type js2-XML) | |
5351 (pos js2-ts-cursor) | |
5352 len | |
5353 content))) | |
5354 "AST node for an E4X XML text node. Not currently used." | |
5355 content) ; a lisp list of `js2-string-node' and `js2-xml-js-expr-node' | |
5356 | |
5357 (put 'cl-struct-js2-xml-text-node 'js2-visitor 'js2-visit-xml-text-node) | |
5358 (put 'cl-struct-js2-xml-text-node 'js2-printer 'js2-print-xml-text-node) | |
5359 | |
5360 (defun js2-visit-xml-text-node (n v) | |
5361 (js2-visit-ast (js2-xml-text-node-content n) v)) | |
5362 | |
5363 (defun js2-print-xml-text-node (n i) | |
5364 (insert (js2-make-pad i)) | |
5365 (dolist (kid (js2-xml-text-node-content n)) | |
5366 (js2-print-ast kid))) | |
5367 | |
5368 (defstruct (js2-xml-comment-node | |
5369 (:include js2-xml-node) | |
5370 (:constructor nil) | |
5371 (:constructor make-js2-xml-comment-node (&key (type js2-XML) | |
5372 (pos js2-ts-cursor) | |
5373 len))) | |
5374 "AST node for E4X XML comment. Not currently used.") | |
5375 | |
5376 (put 'cl-struct-js2-xml-comment-node 'js2-visitor 'js2-visit-none) | |
5377 (put 'cl-struct-js2-xml-comment-node 'js2-printer 'js2-print-xml-comment) | |
5378 | |
5379 (defun js2-print-xml-comment (n i) | |
5380 (insert (js2-make-pad i) | |
5381 (js2-node-string n))) | |
5382 | |
5383 ;;; Node utilities | |
5384 | |
5385 (defsubst js2-node-line (n) | |
5386 "Fetch the source line number at the start of node N. | |
5387 This is O(n) in the length of the source buffer; use prudently." | |
5388 (1+ (count-lines (point-min) (js2-node-abs-pos n)))) | |
5389 | |
5390 (defsubst js2-block-node-kid (n i) | |
5391 "Return child I of node N, or nil if there aren't that many." | |
5392 (nth i (js2-block-node-kids n))) | |
5393 | |
5394 (defsubst js2-block-node-first (n) | |
5395 "Return first child of block node N, or nil if there is none." | |
5396 (first (js2-block-node-kids n))) | |
5397 | |
5398 (defun js2-node-root (n) | |
5399 "Return the root of the AST containing N. | |
5400 If N has no parent pointer, returns N." | |
5401 (let ((parent (js2-node-parent n))) | |
5402 (if parent | |
5403 (js2-node-root parent) | |
5404 n))) | |
5405 | |
5406 (defun js2-node-position-in-parent (node &optional parent) | |
5407 "Return the position of NODE in parent's block-kids list. | |
5408 PARENT can be supplied if known. Positioned returned is zero-indexed. | |
5409 Returns 0 if NODE is not a child of a block statement, or if NODE | |
5410 is not a statement node." | |
5411 (let ((p (or parent (js2-node-parent node))) | |
5412 (i 0)) | |
5413 (if (not (js2-block-node-p p)) | |
5414 i | |
5415 (or (js2-position node (js2-block-node-kids p)) | |
5416 0)))) | |
5417 | |
5418 (defsubst js2-node-short-name (n) | |
5419 "Return the short name of node N as a string, e.g. `js2-if-node'." | |
5420 (substring (symbol-name (aref n 0)) | |
5421 (length "cl-struct-"))) | |
5422 | |
5423 (defsubst js2-node-child-list (node) | |
5424 "Return the child list for NODE, a lisp list of nodes. | |
5425 Works for block nodes, array nodes, obj literals, funarg lists, | |
5426 var decls and try nodes (for catch clauses). Note that you should call | |
5427 `js2-block-node-kids' on the function body for the body statements. | |
5428 Returns nil for zero-length child lists or unsupported nodes." | |
5429 (cond | |
5430 ((js2-function-node-p node) | |
5431 (js2-function-node-params node)) | |
5432 ((js2-block-node-p node) | |
5433 (js2-block-node-kids node)) | |
5434 ((js2-try-node-p node) | |
5435 (js2-try-node-catch-clauses node)) | |
5436 ((js2-array-node-p node) | |
5437 (js2-array-node-elems node)) | |
5438 ((js2-object-node-p node) | |
5439 (js2-object-node-elems node)) | |
5440 ((js2-call-node-p node) | |
5441 (js2-call-node-args node)) | |
5442 ((js2-new-node-p node) | |
5443 (js2-new-node-args node)) | |
5444 ((js2-var-decl-node-p node) | |
5445 (js2-var-decl-node-kids node)) | |
5446 (t | |
5447 nil))) | |
5448 | |
5449 (defsubst js2-node-set-child-list (node kids) | |
5450 "Set the child list for NODE to KIDS." | |
5451 (cond | |
5452 ((js2-function-node-p node) | |
5453 (setf (js2-function-node-params node) kids)) | |
5454 ((js2-block-node-p node) | |
5455 (setf (js2-block-node-kids node) kids)) | |
5456 ((js2-try-node-p node) | |
5457 (setf (js2-try-node-catch-clauses node) kids)) | |
5458 ((js2-array-node-p node) | |
5459 (setf (js2-array-node-elems node) kids)) | |
5460 ((js2-object-node-p node) | |
5461 (setf (js2-object-node-elems node) kids)) | |
5462 ((js2-call-node-p node) | |
5463 (setf (js2-call-node-args node) kids)) | |
5464 ((js2-new-node-p node) | |
5465 (setf (js2-new-node-args node) kids)) | |
5466 ((js2-var-decl-node-p node) | |
5467 (setf (js2-var-decl-node-kids node) kids)) | |
5468 (t | |
5469 (error "Unsupported node type: %s" (js2-node-short-name node)))) | |
5470 kids) | |
5471 | |
5472 ;; All because Common Lisp doesn't support multiple inheritance for defstructs. | |
5473 (defconst js2-paren-expr-nodes | |
5474 '(cl-struct-js2-array-comp-loop-node | |
5475 cl-struct-js2-array-comp-node | |
5476 cl-struct-js2-call-node | |
5477 cl-struct-js2-catch-node | |
5478 cl-struct-js2-do-node | |
5479 cl-struct-js2-elem-get-node | |
5480 cl-struct-js2-for-in-node | |
5481 cl-struct-js2-for-node | |
5482 cl-struct-js2-function-node | |
5483 cl-struct-js2-if-node | |
5484 cl-struct-js2-let-node | |
5485 cl-struct-js2-new-node | |
5486 cl-struct-js2-paren-node | |
5487 cl-struct-js2-switch-node | |
5488 cl-struct-js2-while-node | |
5489 cl-struct-js2-with-node | |
5490 cl-struct-js2-xml-dot-query-node) | |
5491 "Node types that can have a parenthesized child expression. | |
5492 In particular, nodes that respond to `js2-node-lp' and `js2-node-rp'.") | |
5493 | |
5494 (defsubst js2-paren-expr-node-p (node) | |
5495 "Return t for nodes that typically have a parenthesized child expression. | |
5496 Useful for computing the indentation anchors for arg-lists and conditions. | |
5497 Note that it may return a false positive, for instance when NODE is | |
5498 a `js2-new-node' and there are no arguments or parentheses." | |
5499 (memq (aref node 0) js2-paren-expr-nodes)) | |
5500 | |
5501 ;; Fake polymorphism... yech. | |
5502 (defsubst js2-node-lp (node) | |
5503 "Return relative left-paren position for NODE, if applicable. | |
5504 For `js2-elem-get-node' structs, returns left-bracket position. | |
5505 Note that the position may be nil in the case of a parse error." | |
5506 (cond | |
5507 ((js2-elem-get-node-p node) | |
5508 (js2-elem-get-node-lb node)) | |
5509 ((js2-loop-node-p node) | |
5510 (js2-loop-node-lp node)) | |
5511 ((js2-function-node-p node) | |
5512 (js2-function-node-lp node)) | |
5513 ((js2-if-node-p node) | |
5514 (js2-if-node-lp node)) | |
5515 ((js2-new-node-p node) | |
5516 (js2-new-node-lp node)) | |
5517 ((js2-call-node-p node) | |
5518 (js2-call-node-lp node)) | |
5519 ((js2-paren-node-p node) | |
5520 (js2-node-pos node)) | |
5521 ((js2-switch-node-p node) | |
5522 (js2-switch-node-lp node)) | |
5523 ((js2-catch-node-p node) | |
5524 (js2-catch-node-lp node)) | |
5525 ((js2-let-node-p node) | |
5526 (js2-let-node-lp node)) | |
5527 ((js2-array-comp-node-p node) | |
5528 (js2-array-comp-node-lp node)) | |
5529 ((js2-with-node-p node) | |
5530 (js2-with-node-lp node)) | |
5531 ((js2-xml-dot-query-node-p node) | |
5532 (1+ (js2-infix-node-op-pos node))) | |
5533 (t | |
5534 (error "Unsupported node type: %s" (js2-node-short-name node))))) | |
5535 | |
5536 ;; Fake polymorphism... blech. | |
5537 (defsubst js2-node-rp (node) | |
5538 "Return relative right-paren position for NODE, if applicable. | |
5539 For `js2-elem-get-node' structs, returns right-bracket position. | |
5540 Note that the position may be nil in the case of a parse error." | |
5541 (cond | |
5542 ((js2-elem-get-node-p node) | |
5543 (js2-elem-get-node-lb node)) | |
5544 ((js2-loop-node-p node) | |
5545 (js2-loop-node-rp node)) | |
5546 ((js2-function-node-p node) | |
5547 (js2-function-node-rp node)) | |
5548 ((js2-if-node-p node) | |
5549 (js2-if-node-rp node)) | |
5550 ((js2-new-node-p node) | |
5551 (js2-new-node-rp node)) | |
5552 ((js2-call-node-p node) | |
5553 (js2-call-node-rp node)) | |
5554 ((js2-paren-node-p node) | |
5555 (+ (js2-node-pos node) (js2-node-len node))) | |
5556 ((js2-switch-node-p node) | |
5557 (js2-switch-node-rp node)) | |
5558 ((js2-catch-node-p node) | |
5559 (js2-catch-node-rp node)) | |
5560 ((js2-let-node-p node) | |
5561 (js2-let-node-rp node)) | |
5562 ((js2-array-comp-node-p node) | |
5563 (js2-array-comp-node-rp node)) | |
5564 ((js2-with-node-p node) | |
5565 (js2-with-node-rp node)) | |
5566 ((js2-xml-dot-query-node-p node) | |
5567 (1+ (js2-xml-dot-query-node-rp node))) | |
5568 (t | |
5569 (error "Unsupported node type: %s" (js2-node-short-name node))))) | |
5570 | |
5571 (defsubst js2-node-first-child (node) | |
5572 "Returns the first element of `js2-node-child-list' for NODE." | |
5573 (car (js2-node-child-list node))) | |
5574 | |
5575 (defsubst js2-node-last-child (node) | |
5576 "Returns the last element of `js2-node-last-child' for NODE." | |
5577 (car (last (js2-node-child-list node)))) | |
5578 | |
5579 (defun js2-node-prev-sibling (node) | |
5580 "Return the previous statement in parent. | |
5581 Works for parents supported by `js2-node-child-list'. | |
5582 Returns nil if NODE is not in the parent, or PARENT is | |
5583 not a supported node, or if NODE is the first child." | |
5584 (let* ((p (js2-node-parent node)) | |
5585 (kids (js2-node-child-list p)) | |
5586 (sib (car kids))) | |
5587 (while (and kids | |
5588 (neq node (cadr kids))) | |
5589 (setq kids (cdr kids) | |
5590 sib (car kids))) | |
5591 sib)) | |
5592 | |
5593 (defun js2-node-next-sibling (node) | |
5594 "Return the next statement in parent block. | |
5595 Returns nil if NODE is not in the block, or PARENT is not | |
5596 a block node, or if NODE is the last statement." | |
5597 (let* ((p (js2-node-parent node)) | |
5598 (kids (js2-node-child-list p))) | |
5599 (while (and kids | |
5600 (neq node (car kids))) | |
5601 (setq kids (cdr kids))) | |
5602 (cadr kids))) | |
5603 | |
5604 (defun js2-node-find-child-before (pos parent &optional after) | |
5605 "Find the last child that starts before POS in parent. | |
5606 If AFTER is non-nil, returns first child starting after POS. | |
5607 POS is an absolute buffer position. PARENT is any node | |
5608 supported by `js2-node-child-list'. | |
5609 Returns nil if no applicable child is found." | |
5610 (let ((kids (if (js2-function-node-p parent) | |
5611 (js2-block-node-kids (js2-function-node-body parent)) | |
5612 (js2-node-child-list parent))) | |
5613 (beg (if (js2-function-node-p parent) | |
5614 (js2-node-abs-pos (js2-function-node-body parent)) | |
5615 (js2-node-abs-pos parent))) | |
5616 kid | |
5617 result | |
5618 fn | |
5619 (continue t)) | |
5620 (setq fn (if after '> '<)) | |
5621 (while (and kids continue) | |
5622 (setq kid (car kids)) | |
5623 (if (funcall fn (+ beg (js2-node-pos kid)) pos) | |
5624 (setq result kid | |
5625 continue (if after nil t)) | |
5626 (setq continue (if after t nil))) | |
5627 (setq kids (cdr kids))) | |
5628 result)) | |
5629 | |
5630 (defun js2-node-find-child-after (pos parent) | |
5631 "Find first child that starts after POS in parent. | |
5632 POS is an absolute buffer position. PARENT is any node | |
5633 supported by `js2-node-child-list'. | |
5634 Returns nil if no applicable child is found." | |
5635 (js2-node-find-child-before pos parent 'after)) | |
5636 | |
5637 (defun js2-node-replace-child (pos parent new-node) | |
5638 "Replace node at index POS in PARENT with NEW-NODE. | |
5639 Only works for parents supported by `js2-node-child-list'." | |
5640 (let ((kids (js2-node-child-list parent)) | |
5641 (i 0)) | |
5642 (while (< i pos) | |
5643 (setq kids (cdr kids) | |
5644 i (1+ i))) | |
5645 (setcar kids new-node) | |
5646 (js2-node-add-children parent new-node))) | |
5647 | |
5648 (defun js2-node-buffer (n) | |
5649 "Return the buffer associated with AST N. | |
5650 Returns nil if the buffer is not set as a property on the root | |
5651 node, or if parent links were not recorded during parsing." | |
5652 (let ((root (js2-node-root n))) | |
5653 (and root | |
5654 (js2-ast-root-p root) | |
5655 (js2-ast-root-buffer root)))) | |
5656 | |
5657 (defsubst js2-block-node-push (n kid) | |
5658 "Push js2-node KID onto the end of js2-block-node N's child list. | |
5659 KID is always added to the -end- of the kids list. | |
5660 Function also calls `js2-node-add-children' to add the parent link." | |
5661 (let ((kids (js2-node-child-list n))) | |
5662 (if kids | |
5663 (setcdr kids (nconc (cdr kids) (list kid))) | |
5664 (js2-node-set-child-list n (list kid))) | |
5665 (js2-node-add-children n kid))) | |
5666 | |
5667 (defun js2-node-string (node) | |
5668 (let ((buf (js2-node-buffer node)) | |
5669 pos) | |
5670 (unless buf | |
5671 (error "No buffer available for node %s" node)) | |
5672 (save-excursion | |
5673 (set-buffer buf) | |
5674 (buffer-substring-no-properties (setq pos (js2-node-abs-pos node)) | |
5675 (+ pos (js2-node-len node)))))) | |
5676 | |
5677 ;; Container for storing the node we're looking for in a traversal. | |
5678 (defvar js2-discovered-node nil) | |
5679 (make-variable-buffer-local 'js2-discovered-node) | |
5680 | |
5681 ;; Keep track of absolute node position during traversals. | |
5682 (defvar js2-visitor-offset nil) | |
5683 (make-variable-buffer-local 'js2-visitor-offset) | |
5684 | |
5685 (defvar js2-node-search-point nil) | |
5686 (make-variable-buffer-local 'js2-node-search-point) | |
5687 | |
5688 (when js2-mode-dev-mode-p | |
5689 (defun js2-find-node-at-point () | |
5690 (interactive) | |
5691 (let ((node (js2-node-at-point))) | |
5692 (message "%s" (or node "No node found at point")))) | |
5693 (defun js2-node-name-at-point () | |
5694 (interactive) | |
5695 (let ((node (js2-node-at-point))) | |
5696 (message "%s" (if node | |
5697 (js2-node-short-name node) | |
5698 "No node found at point."))))) | |
5699 | |
5700 (defun js2-node-at-point (&optional pos skip-comments) | |
5701 "Return AST node at POS, a buffer position, defaulting to current point. | |
5702 The `js2-mode-ast' variable must be set to the current parse tree. | |
5703 Signals an error if the AST (`js2-mode-ast') is nil. | |
5704 Always returns a node - if it can't find one, it returns the root. | |
5705 If SKIP-COMMENTS is non-nil, comment nodes are ignored." | |
5706 (let ((ast js2-mode-ast) | |
5707 result) | |
5708 (unless ast | |
5709 (error "No JavaScript AST available")) | |
5710 ;; Look through comments first, since they may be inside nodes that | |
5711 ;; would otherwise report a match. | |
5712 (setq pos (or pos (point)) | |
5713 result (if (> pos (js2-node-abs-end ast)) | |
5714 ast | |
5715 (if (not skip-comments) | |
5716 (js2-comment-at-point pos)))) | |
5717 (unless result | |
5718 (setq js2-discovered-node nil | |
5719 js2-visitor-offset 0 | |
5720 js2-node-search-point pos) | |
5721 (unwind-protect | |
5722 (catch 'js2-visit-done | |
5723 (js2-visit-ast ast #'js2-node-at-point-visitor)) | |
5724 (setq js2-visitor-offset nil | |
5725 js2-node-search-point nil)) | |
5726 (setq result js2-discovered-node)) | |
5727 ;; may have found a comment beyond end of last child node, | |
5728 ;; since visiting the ast-root looks at the comment-list last. | |
5729 (if (and skip-comments | |
5730 (js2-comment-node-p result)) | |
5731 (setq result nil)) | |
5732 (or result js2-mode-ast))) | |
5733 | |
5734 (defun js2-node-at-point-visitor (node end-p) | |
5735 (let ((rel-pos (js2-node-pos node)) | |
5736 abs-pos | |
5737 abs-end | |
5738 (point js2-node-search-point)) | |
5739 (cond | |
5740 (end-p | |
5741 ;; this evaluates to a non-nil return value, even if it's zero | |
5742 (decf js2-visitor-offset rel-pos)) | |
5743 ;; we already looked for comments before visiting, and don't want them now | |
5744 ((js2-comment-node-p node) | |
5745 nil) | |
5746 (t | |
5747 (setq abs-pos (incf js2-visitor-offset rel-pos) | |
5748 ;; we only want to use the node if the point is before | |
5749 ;; the last character position in the node, so we decrement | |
5750 ;; the absolute end by 1. | |
5751 abs-end (+ abs-pos (js2-node-len node) -1)) | |
5752 (cond | |
5753 ;; If this node starts after search-point, stop the search. | |
5754 ((> abs-pos point) | |
5755 (throw 'js2-visit-done nil)) | |
5756 ;; If this node ends before the search-point, don't check kids. | |
5757 ((> point abs-end) | |
5758 nil) | |
5759 (t | |
5760 ;; Otherwise point is within this node, possibly in a child. | |
5761 (setq js2-discovered-node node) | |
5762 t)))))) ; keep processing kids to look for more specific match | |
5763 | |
5764 (defsubst js2-block-comment-p (node) | |
5765 "Return non-nil if NODE is a comment node of format `jsdoc' or `block'." | |
5766 (and (js2-comment-node-p node) | |
5767 (memq (js2-comment-node-format node) '(jsdoc block)))) | |
5768 | |
5769 ;; TODO: put the comments in a vector and binary-search them instead | |
5770 (defun js2-comment-at-point (&optional pos) | |
5771 "Look through scanned comment nodes for one containing POS. | |
5772 POS is a buffer position that defaults to current point. | |
5773 Function returns nil if POS was not in any comment node." | |
5774 (let ((ast js2-mode-ast) | |
5775 (x (or pos (point))) | |
5776 beg | |
5777 end) | |
5778 (unless ast | |
5779 (error "No JavaScript AST available")) | |
5780 (catch 'done | |
5781 ;; Comments are stored in lexical order. | |
5782 (dolist (comment (js2-ast-root-comments ast) nil) | |
5783 (setq beg (js2-node-abs-pos comment) | |
5784 end (+ beg (js2-node-len comment))) | |
5785 (if (and (>= x beg) | |
5786 (<= x end)) | |
5787 (throw 'done comment)))))) | |
5788 | |
5789 (defun js2-mode-find-parent-fn (node) | |
5790 "Find function enclosing NODE. | |
5791 Returns nil if NODE is not inside a function." | |
5792 (setq node (js2-node-parent node)) | |
5793 (while (and node (not (js2-function-node-p node))) | |
5794 (setq node (js2-node-parent node))) | |
5795 (and (js2-function-node-p node) node)) | |
5796 | |
5797 (defun js2-mode-find-enclosing-fn (node) | |
5798 "Find function or root enclosing NODE." | |
5799 (if (js2-ast-root-p node) | |
5800 node | |
5801 (setq node (js2-node-parent node)) | |
5802 (while (not (or (js2-ast-root-p node) | |
5803 (js2-function-node-p node))) | |
5804 (setq node (js2-node-parent node))) | |
5805 node)) | |
5806 | |
5807 (defun js2-mode-find-enclosing-node (beg end) | |
5808 "Find script or function fully enclosing BEG and END." | |
5809 (let ((node (js2-node-at-point beg)) | |
5810 pos | |
5811 (continue t)) | |
5812 (while continue | |
5813 (if (or (js2-ast-root-p node) | |
5814 (and (js2-function-node-p node) | |
5815 (<= (setq pos (js2-node-abs-pos node)) beg) | |
5816 (>= (+ pos (js2-node-len node)) end))) | |
5817 (setq continue nil) | |
5818 (setq node (js2-node-parent node)))) | |
5819 node)) | |
5820 | |
5821 (defun js2-node-parent-script-or-fn (node) | |
5822 "Find script or function immediately enclosing NODE. | |
5823 If NODE is the ast-root, returns nil." | |
5824 (if (js2-ast-root-p node) | |
5825 nil | |
5826 (setq node (js2-node-parent node)) | |
5827 (while (and node (not (or (js2-function-node-p node) | |
5828 (js2-script-node-p node)))) | |
5829 (setq node (js2-node-parent node))) | |
5830 node)) | |
5831 | |
5832 (defsubst js2-nested-function-p (node) | |
5833 "Return t if NODE is a nested function, or is inside a nested function." | |
5834 (js2-function-node-p (if (js2-function-node-p node) | |
5835 (js2-node-parent-script-or-fn node) | |
5836 (js2-node-parent-script-or-fn | |
5837 (js2-node-parent-script-or-fn node))))) | |
5838 | |
5839 (defsubst js2-mode-shift-kids (kids start offset) | |
5840 (dolist (kid kids) | |
5841 (if (> (js2-node-pos kid) start) | |
5842 (incf (js2-node-pos kid) offset)))) | |
5843 | |
5844 (defsubst js2-mode-shift-children (parent start offset) | |
5845 "Update start-positions of all children of PARENT beyond START." | |
5846 (let ((root (js2-node-root parent))) | |
5847 (js2-mode-shift-kids (js2-node-child-list parent) start offset) | |
5848 (js2-mode-shift-kids (js2-ast-root-comments root) start offset))) | |
5849 | |
5850 (defsubst js2-node-is-descendant (node ancestor) | |
5851 "Return t if NODE is a descendant of ANCESTOR." | |
5852 (while (and node | |
5853 (neq node ancestor)) | |
5854 (setq node (js2-node-parent node))) | |
5855 node) | |
5856 | |
5857 ;;; visitor infrastructure | |
5858 | |
5859 (defun js2-visit-none (node callback) | |
5860 "Visitor for AST node that have no node children." | |
5861 nil) | |
5862 | |
5863 (defun js2-print-none (node indent) | |
5864 "Visitor for AST node with no printed representation.") | |
5865 | |
5866 (defun js2-print-body (node indent) | |
5867 "Print a statement, or a block without braces." | |
5868 (if (js2-block-node-p node) | |
5869 (dolist (kid (js2-block-node-kids node)) | |
5870 (js2-print-ast kid indent)) | |
5871 (js2-print-ast node indent))) | |
5872 | |
5873 (defun js2-print-list (args &optional delimiter) | |
5874 (loop with len = (length args) | |
5875 for arg in args | |
5876 for count from 1 | |
5877 do | |
5878 (js2-print-ast arg 0) | |
5879 (if (< count len) | |
5880 (insert (or delimiter ", "))))) | |
5881 | |
5882 (defun js2-print-tree (ast) | |
5883 "Prints an AST to the current buffer. | |
5884 Makes `js2-ast-parent-nodes' available to the printer functions." | |
5885 (let ((max-lisp-eval-depth (max max-lisp-eval-depth 1500))) | |
5886 (js2-print-ast ast))) | |
5887 | |
5888 (defun js2-print-ast (node &optional indent) | |
5889 "Helper function for printing AST nodes. | |
5890 Requires `js2-ast-parent-nodes' to be non-nil. | |
5891 You should use `js2-print-tree' instead of this function." | |
5892 (let ((printer (get (aref node 0) 'js2-printer)) | |
5893 (i (or indent 0)) | |
5894 (pos (js2-node-abs-pos node))) | |
5895 ;; TODO: wedge comments in here somewhere | |
5896 (if printer | |
5897 (funcall printer node i)))) | |
5898 | |
5899 (defconst js2-side-effecting-tokens | |
5900 (let ((tokens (make-bool-vector js2-num-tokens nil))) | |
5901 (dolist (tt (list js2-ASSIGN | |
5902 js2-ASSIGN_ADD | |
5903 js2-ASSIGN_BITAND | |
5904 js2-ASSIGN_BITOR | |
5905 js2-ASSIGN_BITXOR | |
5906 js2-ASSIGN_DIV | |
5907 js2-ASSIGN_LSH | |
5908 js2-ASSIGN_MOD | |
5909 js2-ASSIGN_MUL | |
5910 js2-ASSIGN_RSH | |
5911 js2-ASSIGN_SUB | |
5912 js2-ASSIGN_URSH | |
5913 js2-BLOCK | |
5914 js2-BREAK | |
5915 js2-CALL | |
5916 js2-CATCH | |
5917 js2-CATCH_SCOPE | |
5918 js2-CONST | |
5919 js2-CONTINUE | |
5920 js2-DEBUGGER | |
5921 js2-DEC | |
5922 js2-DELPROP | |
5923 js2-DEL_REF | |
5924 js2-DO | |
5925 js2-ELSE | |
5926 js2-EMPTY | |
5927 js2-ENTERWITH | |
5928 js2-EXPORT | |
5929 js2-EXPR_RESULT | |
5930 js2-FINALLY | |
5931 js2-FOR | |
5932 js2-FUNCTION | |
5933 js2-GOTO | |
5934 js2-IF | |
5935 js2-IFEQ | |
5936 js2-IFNE | |
5937 js2-IMPORT | |
5938 js2-INC | |
5939 js2-JSR | |
5940 js2-LABEL | |
5941 js2-LEAVEWITH | |
5942 js2-LET | |
5943 js2-LETEXPR | |
5944 js2-LOCAL_BLOCK | |
5945 js2-LOOP | |
5946 js2-NEW | |
5947 js2-REF_CALL | |
5948 js2-RETHROW | |
5949 js2-RETURN | |
5950 js2-RETURN_RESULT | |
5951 js2-SEMI | |
5952 js2-SETELEM | |
5953 js2-SETELEM_OP | |
5954 js2-SETNAME | |
5955 js2-SETPROP | |
5956 js2-SETPROP_OP | |
5957 js2-SETVAR | |
5958 js2-SET_REF | |
5959 js2-SET_REF_OP | |
5960 js2-SWITCH | |
5961 js2-TARGET | |
5962 js2-THROW | |
5963 js2-TRY | |
5964 js2-VAR | |
5965 js2-WHILE | |
5966 js2-WITH | |
5967 js2-WITHEXPR | |
5968 js2-YIELD)) | |
5969 (aset tokens tt t)) | |
5970 (if js2-instanceof-has-side-effects | |
5971 (aset tokens js2-INSTANCEOF t)) | |
5972 tokens)) | |
5973 | |
5974 (defun js2-node-has-side-effects (node) | |
5975 "Return t if NODE has side effects." | |
5976 (when node ; makes it easier to handle malformed expressions | |
5977 (let ((tt (js2-node-type node))) | |
5978 (cond | |
5979 ;; This doubtless needs some work, since EXPR_VOID is used | |
5980 ;; in several ways in Rhino, and I may not have caught them all. | |
5981 ;; I'll wait for people to notice incorrect warnings. | |
5982 ((and (= tt js2-EXPR_VOID) | |
5983 (js2-expr-stmt-node-p node)) ; but not if EXPR_RESULT | |
5984 (js2-node-has-side-effects (js2-expr-stmt-node-expr node))) | |
5985 | |
5986 ((= tt js2-COMMA) | |
5987 (js2-node-has-side-effects (js2-infix-node-right node))) | |
5988 | |
5989 ((or (= tt js2-AND) | |
5990 (= tt js2-OR)) | |
5991 (or (js2-node-has-side-effects (js2-infix-node-right node)) | |
5992 (js2-node-has-side-effects (js2-infix-node-left node)))) | |
5993 | |
5994 ((= tt js2-HOOK) | |
5995 (and (js2-node-has-side-effects (js2-cond-node-true-expr node)) | |
5996 (js2-node-has-side-effects (js2-cond-node-false-expr node)))) | |
5997 | |
5998 ((js2-paren-node-p node) | |
5999 (js2-node-has-side-effects (js2-paren-node-expr node))) | |
6000 | |
6001 ((= tt js2-ERROR) ; avoid cascaded error messages | |
6002 nil) | |
6003 (t | |
6004 (aref js2-side-effecting-tokens tt)))))) | |
6005 | |
6006 (defun js2-member-expr-leftmost-name (node) | |
6007 "For an expr such as foo.bar.baz, return leftmost node foo. | |
6008 NODE is any `js2-node' object. If it represents a member expression, | |
6009 which is any sequence of property gets, element-gets, function calls, | |
6010 or xml descendants/filter operators, then we look at the lexically | |
6011 leftmost (first) node in the chain. If it is a name-node we return it. | |
6012 Note that NODE can be a raw name-node and it will be returned as well. | |
6013 If NODE is not a name-node or member expression, or if it is a member | |
6014 expression whose leftmost target is not a name node, returns nil." | |
6015 (let ((continue t) | |
6016 result) | |
6017 (while (and continue (not result)) | |
6018 (cond | |
6019 ((js2-name-node-p node) | |
6020 (setq result node)) | |
6021 ((js2-prop-get-node-p node) | |
6022 (setq node (js2-prop-get-node-left node))) | |
6023 ;; TODO: handle call-nodes, xml-nodes, others? | |
6024 (t | |
6025 (setq continue nil)))) | |
6026 result)) | |
6027 | |
6028 (defconst js2-stmt-node-types | |
6029 (list js2-BLOCK | |
6030 js2-BREAK | |
6031 js2-CONTINUE | |
6032 js2-DEFAULT ; e4x "default xml namespace" statement | |
6033 js2-DO | |
6034 js2-EXPR_RESULT | |
6035 js2-EXPR_VOID | |
6036 js2-FOR | |
6037 js2-IF | |
6038 js2-RETURN | |
6039 js2-SWITCH | |
6040 js2-THROW | |
6041 js2-TRY | |
6042 js2-WHILE | |
6043 js2-WITH) | |
6044 "Node types that only appear in statement contexts. | |
6045 The list does not include nodes that always appear as the child | |
6046 of another specific statement type, such as switch-cases, | |
6047 catch and finally blocks, and else-clauses. The list also excludes | |
6048 nodes like yield, let and var, which may appear in either expression | |
6049 or statement context, and in the latter context always have a | |
6050 `js2-expr-stmt-node' parent. Finally, the list does not include | |
6051 functions or scripts, which are treated separately from statements | |
6052 by the JavaScript parser and runtime.") | |
6053 | |
6054 (defun js2-stmt-node-p (node) | |
6055 "Heuristic for figuring out if NODE is a statement. | |
6056 Some node types can appear in either an expression context or a | |
6057 statement context, e.g. let-nodes, yield-nodes, and var-decl nodes. | |
6058 For these node types in a statement context, the parent will be a | |
6059 `js2-expr-stmt-node'. | |
6060 Functions aren't included in the check." | |
6061 (memq (js2-node-type node) js2-stmt-node-types)) | |
6062 | |
6063 (defsubst js2-mode-find-first-stmt (node) | |
6064 "Search upward starting from NODE looking for a statement. | |
6065 For purposes of this function, a `js2-function-node' counts." | |
6066 (while (not (or (js2-stmt-node-p node) | |
6067 (js2-function-node-p node))) | |
6068 (setq node (js2-node-parent node))) | |
6069 node) | |
6070 | |
6071 (defun js2-node-parent-stmt (node) | |
6072 "Return the node's first ancestor that is a statement. | |
6073 Returns nil if NODE is a `js2-ast-root'. Note that any expression | |
6074 appearing in a statement context will have a parent that is a | |
6075 `js2-expr-stmt-node' that will be returned by this function." | |
6076 (let ((parent (js2-node-parent node))) | |
6077 (if (or (null parent) | |
6078 (js2-stmt-node-p parent) | |
6079 (and (js2-function-node-p parent) | |
6080 (neq (js2-function-node-form parent) 'FUNCTION_EXPRESSION))) | |
6081 parent | |
6082 (js2-node-parent-stmt parent)))) | |
6083 | |
6084 ;; Roshan James writes: | |
6085 ;; Does consistent-return analysis on the function body when strict mode is | |
6086 ;; enabled. | |
6087 ;; | |
6088 ;; function (x) { return (x+1) } | |
6089 ;; | |
6090 ;; is ok, but | |
6091 ;; | |
6092 ;; function (x) { if (x < 0) return (x+1); } | |
6093 ;; | |
6094 ;; is not because the function can potentially return a value when the | |
6095 ;; condition is satisfied and if not, the function does not explicitly | |
6096 ;; return a value. | |
6097 ;; | |
6098 ;; This extends to checking mismatches such as "return" and "return <value>" | |
6099 ;; used in the same function. Warnings are not emitted if inconsistent | |
6100 ;; returns exist in code that can be statically shown to be unreachable. | |
6101 ;; Ex. | |
6102 ;; function (x) { while (true) { ... if (..) { return value } ... } } | |
6103 ;; | |
6104 ;; emits no warning. However if the loop had a break statement, then a | |
6105 ;; warning would be emitted. | |
6106 ;; | |
6107 ;; The consistency analysis looks at control structures such as loops, ifs, | |
6108 ;; switch, try-catch-finally blocks, examines the reachable code paths and | |
6109 ;; warns the user about an inconsistent set of termination possibilities. | |
6110 ;; | |
6111 ;; These flags enumerate the possible ways a statement/function can | |
6112 ;; terminate. These flags are used by endCheck() and by the Parser to | |
6113 ;; detect inconsistent return usage. | |
6114 ;; | |
6115 ;; END_UNREACHED is reserved for code paths that are assumed to always be | |
6116 ;; able to execute (example: throw, continue) | |
6117 ;; | |
6118 ;; END_DROPS_OFF indicates if the statement can transfer control to the | |
6119 ;; next one. Statement such as return dont. A compound statement may have | |
6120 ;; some branch that drops off control to the next statement. | |
6121 ;; | |
6122 ;; END_RETURNS indicates that the statement can return with no value. | |
6123 ;; END_RETURNS_VALUE indicates that the statement can return a value. | |
6124 ;; | |
6125 ;; A compound statement such as | |
6126 ;; if (condition) { | |
6127 ;; return value; | |
6128 ;; } | |
6129 ;; Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck() | |
6130 | |
6131 (defconst js2-END_UNREACHED 0) | |
6132 (defconst js2-END_DROPS_OFF 1) | |
6133 (defconst js2-END_RETURNS 2) | |
6134 (defconst js2-END_RETURNS_VALUE 4) | |
6135 (defconst js2-END_YIELDS 8) | |
6136 | |
6137 (defun js2-has-consistent-return-usage (node) | |
6138 "Check that every return usage in a function body is consistent. | |
6139 Returns t if the function satisfies strict mode requirement." | |
6140 (let ((n (js2-end-check node))) | |
6141 ;; either it doesn't return a value in any branch... | |
6142 (or (js2-flag-not-set-p n js2-END_RETURNS_VALUE) | |
6143 ;; or it returns a value (or is unreached) at every branch | |
6144 (js2-flag-not-set-p n (logior js2-END_DROPS_OFF | |
6145 js2-END_RETURNS | |
6146 js2-END_YIELDS))))) | |
6147 | |
6148 (defun js2-end-check-if (node) | |
6149 "Returns in the then and else blocks must be consistent with each other. | |
6150 If there is no else block, then the return statement can fall through. | |
6151 Returns logical OR of END_* flags" | |
6152 (let ((th (js2-if-node-then-part node)) | |
6153 (el (js2-if-node-else-part node))) | |
6154 (if (null th) | |
6155 js2-END_UNREACHED | |
6156 (logior (js2-end-check th) (if el | |
6157 (js2-end-check el) | |
6158 js2-END_DROPS_OFF))))) | |
6159 | |
6160 (defun js2-end-check-switch (node) | |
6161 "Consistency of return statements is checked between the case statements. | |
6162 If there is no default, then the switch can fall through. If there is a | |
6163 default, we check to see if all code paths in the default return or if | |
6164 there is a code path that can fall through. | |
6165 Returns logical OR of END_* flags." | |
6166 (let ((rv js2-END_UNREACHED) | |
6167 default-case) | |
6168 ;; examine the cases | |
6169 (catch 'break | |
6170 (dolist (c (js2-switch-node-cases node)) | |
6171 (if (js2-case-node-expr c) | |
6172 (js2-set-flag rv (js2-end-check-block c)) | |
6173 (setq default-case c) | |
6174 (throw 'break nil)))) | |
6175 | |
6176 ;; we don't care how the cases drop into each other | |
6177 (js2-clear-flag rv js2-END_DROPS_OFF) | |
6178 | |
6179 ;; examine the default | |
6180 (js2-set-flag rv (if default-case | |
6181 (js2-end-check default-case) | |
6182 js2-END_DROPS_OFF)) | |
6183 rv)) | |
6184 | |
6185 (defun js2-end-check-try (node) | |
6186 "If the block has a finally, return consistency is checked in the | |
6187 finally block. If all code paths in the finally return, then the | |
6188 returns in the try-catch blocks don't matter. If there is a code path | |
6189 that does not return or if there is no finally block, the returns | |
6190 of the try and catch blocks are checked for mismatch. | |
6191 Returns logical OR of END_* flags." | |
6192 (let ((finally (js2-try-node-finally-block node)) | |
6193 rv) | |
6194 ;; check the finally if it exists | |
6195 (setq rv (if finally | |
6196 (js2-end-check (js2-finally-node-body finally)) | |
6197 js2-END_DROPS_OFF)) | |
6198 | |
6199 ;; If the finally block always returns, then none of the returns | |
6200 ;; in the try or catch blocks matter. | |
6201 (when (js2-flag-set-p rv js2-END_DROPS_OFF) | |
6202 (js2-clear-flag rv js2-END_DROPS_OFF) | |
6203 | |
6204 ;; examine the try block | |
6205 (js2-set-flag rv (js2-end-check (js2-try-node-try-block node))) | |
6206 | |
6207 ;; check each catch block | |
6208 (dolist (cb (js2-try-node-catch-clauses node)) | |
6209 (js2-set-flag rv (js2-end-check (js2-catch-node-block cb))))) | |
6210 rv)) | |
6211 | |
6212 (defun js2-end-check-loop (node) | |
6213 "Return statement in the loop body must be consistent. The default | |
6214 assumption for any kind of a loop is that it will eventually terminate. | |
6215 The only exception is a loop with a constant true condition. Code that | |
6216 follows such a loop is examined only if one can statically determine | |
6217 that there is a break out of the loop. | |
6218 | |
6219 for(... ; ... ; ...) {} | |
6220 for(... in ... ) {} | |
6221 while(...) { } | |
6222 do { } while(...) | |
6223 | |
6224 Returns logical OR of END_* flags." | |
6225 (let ((rv (js2-end-check (js2-loop-node-body node))) | |
6226 (condition (cond | |
6227 ((js2-while-node-p node) | |
6228 (js2-while-node-condition node)) | |
6229 ((js2-do-node-p node) | |
6230 (js2-do-node-condition node)) | |
6231 ((js2-for-node-p node) | |
6232 (js2-for-node-condition node))))) | |
6233 | |
6234 ;; check to see if the loop condition is always true | |
6235 (if (and condition | |
6236 (eq (js2-always-defined-boolean-p condition) 'ALWAYS_TRUE)) | |
6237 (js2-clear-flag rv js2-END_DROPS_OFF)) | |
6238 | |
6239 ;; look for effect of breaks | |
6240 (js2-set-flag rv (js2-node-get-prop node | |
6241 'CONTROL_BLOCK_PROP | |
6242 js2-END_UNREACHED)) | |
6243 rv)) | |
6244 | |
6245 (defun js2-end-check-block (node) | |
6246 "A general block of code is examined statement by statement. | |
6247 If any statement (even a compound one) returns in all branches, then | |
6248 subsequent statements are not examined. | |
6249 Returns logical OR of END_* flags." | |
6250 (let* ((rv js2-END_DROPS_OFF) | |
6251 (kids (js2-block-node-kids node)) | |
6252 (n (car kids))) | |
6253 ;; Check each statment. If the statement can continue onto the next | |
6254 ;; one (i.e. END_DROPS_OFF is set), then check the next statement. | |
6255 (while (and n (js2-flag-set-p rv js2-END_DROPS_OFF)) | |
6256 (js2-clear-flag rv js2-END_DROPS_OFF) | |
6257 (js2-set-flag rv (js2-end-check n)) | |
6258 (setq kids (cdr kids) | |
6259 n (car kids))) | |
6260 rv)) | |
6261 | |
6262 (defun js2-end-check-label (node) | |
6263 "A labeled statement implies that there may be a break to the label. | |
6264 The function processes the labeled statement and then checks the | |
6265 CONTROL_BLOCK_PROP property to see if there is ever a break to the | |
6266 particular label. | |
6267 Returns logical OR of END_* flags." | |
6268 (let ((rv (js2-end-check (js2-labeled-stmt-node-stmt node)))) | |
6269 (logior rv (js2-node-get-prop node | |
6270 'CONTROL_BLOCK_PROP | |
6271 js2-END_UNREACHED)))) | |
6272 | |
6273 (defun js2-end-check-break (node) | |
6274 "When a break is encountered annotate the statement being broken | |
6275 out of by setting its CONTROL_BLOCK_PROP property. | |
6276 Returns logical OR of END_* flags." | |
6277 (and (js2-break-node-target node) | |
6278 (js2-node-set-prop (js2-break-node-target node) | |
6279 'CONTROL_BLOCK_PROP | |
6280 js2-END_DROPS_OFF)) | |
6281 js2-END_UNREACHED) | |
6282 | |
6283 (defun js2-end-check (node) | |
6284 "Examine the body of a function, doing a basic reachability analysis. | |
6285 Returns a combination of flags END_* flags that indicate | |
6286 how the function execution can terminate. These constitute only the | |
6287 pessimistic set of termination conditions. It is possible that at | |
6288 runtime certain code paths will never be actually taken. Hence this | |
6289 analysis will flag errors in cases where there may not be errors. | |
6290 Returns logical OR of END_* flags" | |
6291 (let (kid) | |
6292 (cond | |
6293 ((js2-break-node-p node) | |
6294 (js2-end-check-break node)) | |
6295 | |
6296 ((js2-expr-stmt-node-p node) | |
6297 (if (setq kid (js2-expr-stmt-node-expr node)) | |
6298 (js2-end-check kid) | |
6299 js2-END_DROPS_OFF)) | |
6300 | |
6301 ((or (js2-continue-node-p node) | |
6302 (js2-throw-node-p node)) | |
6303 js2-END_UNREACHED) | |
6304 | |
6305 ((js2-return-node-p node) | |
6306 (if (setq kid (js2-return-node-retval node)) | |
6307 js2-END_RETURNS_VALUE | |
6308 js2-END_RETURNS)) | |
6309 | |
6310 ((js2-loop-node-p node) | |
6311 (js2-end-check-loop node)) | |
6312 | |
6313 ((js2-switch-node-p node) | |
6314 (js2-end-check-switch node)) | |
6315 | |
6316 ((js2-labeled-stmt-node-p node) | |
6317 (js2-end-check-label node)) | |
6318 | |
6319 ((js2-if-node-p node) | |
6320 (js2-end-check-if node)) | |
6321 | |
6322 ((js2-try-node-p node) | |
6323 (js2-end-check-try node)) | |
6324 | |
6325 ((js2-block-node-p node) | |
6326 (if (null (js2-block-node-kids node)) | |
6327 js2-END_DROPS_OFF | |
6328 (js2-end-check-block node))) | |
6329 | |
6330 ((js2-yield-node-p node) | |
6331 js2-END_YIELDS) | |
6332 | |
6333 (t | |
6334 js2-END_DROPS_OFF)))) | |
6335 | |
6336 (defun js2-always-defined-boolean-p (node) | |
6337 "Check if NODE always evaluates to true or false in boolean context. | |
6338 Returns 'ALWAYS_TRUE, 'ALWAYS_FALSE, or nil if it's neither always true | |
6339 nor always false." | |
6340 (let ((tt (js2-node-type node)) | |
6341 num) | |
6342 (cond | |
6343 ((or (= tt js2-FALSE) (= tt js2-NULL)) | |
6344 'ALWAYS_FALSE) | |
6345 ((= tt js2-TRUE) | |
6346 'ALWAYS_TRUE) | |
6347 ((= tt js2-NUMBER) | |
6348 (setq num (js2-number-node-num-value node)) | |
6349 (if (and (not (eq num 0.0e+NaN)) | |
6350 (not (zerop num))) | |
6351 'ALWAYS_TRUE | |
6352 'ALWAYS_FALSE)) | |
6353 (t | |
6354 nil)))) | |
6355 | |
6356 (provide 'js2-ast) | |
6357 | |
6358 ;;; js2-ast.el ends here | |
6359 ;;; js2-highlight.el --- JavaScript syntax coloring support | |
6360 | |
6361 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
6362 ;; Keywords: javascript languages | |
6363 | |
6364 ;;; Code: | |
6365 | |
6366 | |
6367 (defsubst js2-set-face (beg end face &optional record) | |
6368 "Fontify a region. If RECORD is non-nil, record for later." | |
6369 (when (plusp js2-highlight-level) | |
6370 (setq beg (min (point-max) beg) | |
6371 beg (max (point-min) beg) | |
6372 end (min (point-max) end) | |
6373 end (max (point-min) end)) | |
6374 (if record | |
6375 (push (list beg end face) js2-mode-fontifications) | |
6376 (put-text-property beg end 'face face)))) | |
6377 | |
6378 (defsubst js2-set-kid-face (pos kid len face) | |
6379 "Set-face on a child node. | |
6380 POS is absolute buffer position of parent. | |
6381 KID is the child node. | |
6382 LEN is the length to fontify. | |
6383 FACE is the face to fontify with." | |
6384 (js2-set-face (+ pos (js2-node-pos kid)) | |
6385 (+ pos (js2-node-pos kid) (js2-node-len kid)) | |
6386 face)) | |
6387 | |
6388 (defsubst js2-fontify-kwd (start length) | |
6389 (js2-set-face start (+ start length) 'font-lock-keyword-face)) | |
6390 | |
6391 (defsubst js2-clear-face (beg end) | |
6392 (remove-text-properties beg end '(face nil | |
6393 help-echo nil | |
6394 point-entered nil | |
6395 c-in-sws nil))) | |
6396 | |
6397 (defsubst js2-record-text-property (beg end prop value) | |
6398 "Record a text property to set when parsing finishes." | |
6399 (push (list beg end prop value) js2-mode-deferred-properties)) | |
6400 | |
6401 (defconst js2-ecma-global-props | |
6402 (concat "^" | |
6403 (regexp-opt | |
6404 '("Infinity" "NaN" "undefined" "arguments") t) | |
6405 "$") | |
6406 "Value properties of the Ecma-262 Global Object. | |
6407 Shown at or above `js2-highlight-level' 2.") | |
6408 | |
6409 ;; might want to add the name "arguments" to this list? | |
6410 (defconst js2-ecma-object-props | |
6411 (concat "^" | |
6412 (regexp-opt | |
6413 '("prototype" "__proto__" "__parent__") t) | |
6414 "$") | |
6415 "Value properties of the Ecma-262 Object constructor. | |
6416 Shown at or above `js2-highlight-level' 2.") | |
6417 | |
6418 (defconst js2-ecma-global-funcs | |
6419 (concat | |
6420 "^" | |
6421 (regexp-opt | |
6422 '("decodeURI" "decodeURIComponent" "encodeURI" "encodeURIComponent" | |
6423 "eval" "isFinite" "isNaN" "parseFloat" "parseInt") t) | |
6424 "$") | |
6425 "Function properties of the Ecma-262 Global object. | |
6426 Shown at or above `js2-highlight-level' 2.") | |
6427 | |
6428 (defconst js2-ecma-number-props | |
6429 (concat "^" | |
6430 (regexp-opt '("MAX_VALUE" "MIN_VALUE" "NaN" | |
6431 "NEGATIVE_INFINITY" | |
6432 "POSITIVE_INFINITY") t) | |
6433 "$") | |
6434 "Properties of the Ecma-262 Number constructor. | |
6435 Shown at or above `js2-highlight-level' 2.") | |
6436 | |
6437 (defconst js2-ecma-date-props "^\\(parse\\|UTC\\)$" | |
6438 "Properties of the Ecma-262 Date constructor. | |
6439 Shown at or above `js2-highlight-level' 2.") | |
6440 | |
6441 | |
6442 (defconst js2-ecma-math-props | |
6443 (concat "^" | |
6444 (regexp-opt | |
6445 '("E" "LN10" "LN2" "LOG2E" "LOG10E" "PI" "SQRT1_2" "SQRT2") | |
6446 t) | |
6447 "$") | |
6448 "Properties of the Ecma-262 Math object. | |
6449 Shown at or above `js2-highlight-level' 2.") | |
6450 | |
6451 | |
6452 (defconst js2-ecma-math-funcs | |
6453 (concat "^" | |
6454 (regexp-opt | |
6455 '("abs" "acos" "asin" "atan" "atan2" "ceil" "cos" "exp" "floor" | |
6456 "log" "max" "min" "pow" "random" "round" "sin" "sqrt" "tan") t) | |
6457 "$") | |
6458 "Function properties of the Ecma-262 Math object. | |
6459 Shown at or above `js2-highlight-level' 2.") | |
6460 | |
6461 (defconst js2-ecma-function-props | |
6462 (concat | |
6463 "^" | |
6464 (regexp-opt | |
6465 '(;; properties of the Object prototype object | |
6466 "hasOwnProperty" "isPrototypeOf" "propertyIsEnumerable" | |
6467 "toLocaleString" "toString" "valueOf" | |
6468 ;; properties of the Function prototype object | |
6469 "apply" "call" | |
6470 ;; properties of the Array prototype object | |
6471 "concat" "join" "pop" "push" "reverse" "shift" "slice" "sort" | |
6472 "splice" "unshift" | |
6473 ;; properties of the String prototype object | |
6474 "charAt" "charCodeAt" "fromCharCode" "indexOf" "lastIndexOf" | |
6475 "localeCompare" "match" "replace" "search" "split" "substring" | |
6476 "toLocaleLowerCase" "toLocaleUpperCase" "toLowerCase" | |
6477 "toUpperCase" | |
6478 ;; properties of the Number prototype object | |
6479 "toExponential" "toFixed" "toPrecision" | |
6480 ;; properties of the Date prototype object | |
6481 "getDate" "getDay" "getFullYear" "getHours" "getMilliseconds" | |
6482 "getMinutes" "getMonth" "getSeconds" "getTime" | |
6483 "getTimezoneOffset" "getUTCDate" "getUTCDay" "getUTCFullYear" | |
6484 "getUTCHours" "getUTCMilliseconds" "getUTCMinutes" "getUTCMonth" | |
6485 "getUTCSeconds" "setDate" "setFullYear" "setHours" | |
6486 "setMilliseconds" "setMinutes" "setMonth" "setSeconds" "setTime" | |
6487 "setUTCDate" "setUTCFullYear" "setUTCHours" "setUTCMilliseconds" | |
6488 "setUTCMinutes" "setUTCMonth" "setUTCSeconds" "toDateString" | |
6489 "toLocaleDateString" "toLocaleString" "toLocaleTimeString" | |
6490 "toTimeString" "toUTCString" | |
6491 ;; properties of the RegExp prototype object | |
6492 "exec" "test" | |
6493 ;; SpiderMonkey/Rhino extensions, versions 1.5+ | |
6494 "toSource" "__defineGetter__" "__defineSetter__" | |
6495 "__lookupGetter__" "__lookupSetter__" "__noSuchMethod__" | |
6496 "every" "filter" "forEach" "lastIndexOf" "map" "some") | |
6497 t) | |
6498 "$") | |
6499 "Built-in functions defined by Ecma-262 and SpiderMonkey extensions. | |
6500 Shown at or above `js2-highlight-level' 3.") | |
6501 | |
6502 (defsubst js2-parse-highlight-prop-get (parent target prop call-p) | |
6503 (let ((target-name (and target | |
6504 (js2-name-node-p target) | |
6505 (js2-name-node-name target))) | |
6506 (prop-name (if prop (js2-name-node-name prop))) | |
6507 (level1 (>= js2-highlight-level 1)) | |
6508 (level2 (>= js2-highlight-level 2)) | |
6509 (level3 (>= js2-highlight-level 3)) | |
6510 pos | |
6511 face) | |
6512 (when level2 | |
6513 (if call-p | |
6514 (cond | |
6515 ((and target prop) | |
6516 (cond | |
6517 ((and level3 (string-match js2-ecma-function-props prop-name)) | |
6518 (setq face 'font-lock-builtin-face)) | |
6519 ((and target-name prop) | |
6520 (cond | |
6521 ((string= target-name "Date") | |
6522 (if (string-match js2-ecma-date-props prop-name) | |
6523 (setq face 'font-lock-builtin-face))) | |
6524 ((string= target-name "Math") | |
6525 (if (string-match js2-ecma-math-funcs prop-name) | |
6526 (setq face 'font-lock-builtin-face))))))) | |
6527 (prop | |
6528 (if (string-match js2-ecma-global-funcs prop-name) | |
6529 (setq face 'font-lock-builtin-face)))) | |
6530 (cond | |
6531 ((and target prop) | |
6532 (cond | |
6533 ((string= target-name "Number") | |
6534 (if (string-match js2-ecma-number-props prop-name) | |
6535 (setq face 'font-lock-constant-face))) | |
6536 ((string= target-name "Math") | |
6537 (if (string-match js2-ecma-math-props prop-name) | |
6538 (setq face 'font-lock-constant-face))))) | |
6539 (prop | |
6540 (if (string-match js2-ecma-object-props prop-name) | |
6541 (setq face 'font-lock-constant-face))))) | |
6542 (when face | |
6543 (js2-set-face (setq pos (+ (js2-node-pos parent) ; absolute | |
6544 (js2-node-pos prop))) ; relative | |
6545 (+ pos (js2-node-len prop)) | |
6546 face))))) | |
6547 | |
6548 (defun js2-parse-highlight-member-expr-node (node) | |
6549 "Perform syntax highlighting of EcmaScript built-in properties. | |
6550 The variable `js2-highlight-level' governs this highighting." | |
6551 (let (face target prop name pos end parent call-p callee) | |
6552 (cond | |
6553 ;; case 1: simple name, e.g. foo | |
6554 ((js2-name-node-p node) | |
6555 (setq name (js2-name-node-name node)) | |
6556 ;; possible for name to be nil in rare cases - saw it when | |
6557 ;; running js2-mode on an elisp buffer. Might as well try to | |
6558 ;; make it so js2-mode never barfs. | |
6559 (when name | |
6560 (setq face (if (string-match js2-ecma-global-props name) | |
6561 'font-lock-constant-face)) | |
6562 (when face | |
6563 (setq pos (js2-node-pos node) | |
6564 end (+ pos (js2-node-len node))) | |
6565 (js2-set-face pos end face)))) | |
6566 | |
6567 ;; case 2: property access or function call | |
6568 ((or (js2-prop-get-node-p node) | |
6569 ;; highlight function call if expr is a prop-get node | |
6570 ;; or a plain name (i.e. unqualified function call) | |
6571 (and (setq call-p (js2-call-node-p node)) | |
6572 (setq callee (js2-call-node-target node)) ; separate setq! | |
6573 (or (js2-prop-get-node-p callee) | |
6574 (js2-name-node-p callee)))) | |
6575 (setq parent node | |
6576 node (if call-p callee node)) | |
6577 (if (and call-p (js2-name-node-p callee)) | |
6578 (setq prop callee) | |
6579 (setq target (js2-prop-get-node-left node) | |
6580 prop (js2-prop-get-node-right node))) | |
6581 (cond | |
6582 ((js2-name-node-p target) | |
6583 (if (js2-name-node-p prop) | |
6584 ;; case 2a: simple target, simple prop name, e.g. foo.bar | |
6585 (js2-parse-highlight-prop-get parent target prop call-p) | |
6586 ;; case 2b: simple target, complex name, e.g. foo.x[y] | |
6587 (js2-parse-highlight-prop-get parent target nil call-p))) | |
6588 ((js2-name-node-p prop) | |
6589 ;; case 2c: complex target, simple name, e.g. x[y].bar | |
6590 (js2-parse-highlight-prop-get parent target prop call-p))))))) | |
6591 | |
6592 (defun js2-parse-highlight-member-expr-fn-name (expr) | |
6593 "Highlight the `baz' in function foo.bar.baz(args) {...}. | |
6594 This is experimental Rhino syntax. EXPR is the foo.bar.baz member expr. | |
6595 We currently only handle the case where the last component is a prop-get | |
6596 of a simple name. Called before EXPR has a parent node." | |
6597 (let (pos | |
6598 (name (and (js2-prop-get-node-p expr) | |
6599 (js2-prop-get-node-right expr)))) | |
6600 (when (js2-name-node-p name) | |
6601 (js2-set-face (setq pos (+ (js2-node-pos expr) ; parent is absolute | |
6602 (js2-node-pos name))) | |
6603 (+ pos (js2-node-len name)) | |
6604 'font-lock-function-name-face | |
6605 'record)))) | |
6606 | |
6607 ;; source: http://jsdoc.sourceforge.net/ | |
6608 ;; Note - this syntax is for Google's enhanced jsdoc parser that | |
6609 ;; allows type specifications, and needs work before entering the wild. | |
6610 | |
6611 (defconst js2-jsdoc-param-tag-regexp | |
6612 (concat "^\\s-*\\*+\\s-*\\(@" | |
6613 "\\(?:param\\|argument\\)" | |
6614 "\\)" | |
6615 "\\s-*\\({[^}]+}\\)?" ; optional type | |
6616 "\\s-*\\([a-zA-Z0-9_$]+\\)?" ; name | |
6617 "\\>") | |
6618 "Matches jsdoc tags with optional type and optional param name.") | |
6619 | |
6620 (defconst js2-jsdoc-typed-tag-regexp | |
6621 (concat "^\\s-*\\*+\\s-*\\(@\\(?:" | |
6622 (regexp-opt | |
6623 '("requires" "return" "returns" "throw" "throws")) | |
6624 "\\)\\)\\s-*\\({[^}]+}\\)?") | |
6625 "Matches jsdoc tags with optional type.") | |
6626 | |
6627 (defconst js2-jsdoc-arg-tag-regexp | |
6628 (concat "^\\s-*\\*+\\s-*\\(@\\(?:" | |
6629 (regexp-opt | |
6630 '("base" "extends" "member" "type" "version")) | |
6631 "\\)\\)\\s-+\\([^ \t]+\\)") | |
6632 "Matches jsdoc tags with a single argument.") | |
6633 | |
6634 (defconst js2-jsdoc-empty-tag-regexp | |
6635 (concat "^\\s-*\\*+\\s-*\\(@\\(?:" | |
6636 (regexp-opt | |
6637 '("addon" "author" "class" "constructor" "deprecated" "exec" | |
6638 "exception" "fileoverview" "final" "ignore" "private")) | |
6639 "\\)\\)\\s-*") | |
6640 "Matches empty jsdoc tags.") | |
6641 | |
6642 (defconst js2-jsdoc-link-tag-regexp | |
6643 "{\\(@link\\)\\s-+\\([^#}\n]+\\)\\(#.+\\)?}" | |
6644 "Matches a jsdoc link tag.") | |
6645 | |
6646 (defconst js2-jsdoc-see-tag-regexp | |
6647 "^\\s-*\\*+\\s-*\\(@see\\)\\s-+\\([^#}\n]+\\)\\(#.+\\)?" | |
6648 "Matches a jsdoc @see tag.") | |
6649 | |
6650 (defconst js2-jsdoc-html-tag-regexp | |
6651 "\\(</?\\)\\([a-zA-Z]+\\)\\s-*\\(/?>\\)" | |
6652 "Matches a simple (no attributes) html start- or end-tag.") | |
6653 | |
6654 (defsubst js2-jsdoc-highlight-helper () | |
6655 (js2-set-face (match-beginning 1) | |
6656 (match-end 1) | |
6657 'js2-jsdoc-tag-face) | |
6658 (if (match-beginning 2) | |
6659 (if (save-excursion | |
6660 (goto-char (match-beginning 2)) | |
6661 (= (char-after) ?{)) | |
6662 (js2-set-face (1+ (match-beginning 2)) | |
6663 (1- (match-end 2)) | |
6664 'js2-jsdoc-type-face) | |
6665 (js2-set-face (match-beginning 2) | |
6666 (match-end 2) | |
6667 'js2-jsdoc-value-face))) | |
6668 (if (match-beginning 3) | |
6669 (js2-set-face (match-beginning 3) | |
6670 (match-end 3) | |
6671 'js2-jsdoc-value-face))) | |
6672 | |
6673 (defun js2-highlight-jsdoc (ast) | |
6674 "Highlight doc comment tags." | |
6675 (let ((comments (js2-ast-root-comments ast)) | |
6676 beg end) | |
6677 (save-excursion | |
6678 (dolist (node comments) | |
6679 (when (eq (js2-comment-node-format node) 'jsdoc) | |
6680 (setq beg (js2-node-abs-pos node) | |
6681 end (+ beg (js2-node-len node))) | |
6682 (save-restriction | |
6683 (narrow-to-region beg end) | |
6684 (dolist (re (list js2-jsdoc-param-tag-regexp | |
6685 js2-jsdoc-typed-tag-regexp | |
6686 js2-jsdoc-arg-tag-regexp | |
6687 js2-jsdoc-link-tag-regexp | |
6688 js2-jsdoc-see-tag-regexp | |
6689 js2-jsdoc-empty-tag-regexp)) | |
6690 (goto-char beg) | |
6691 (while (re-search-forward re nil t) | |
6692 (js2-jsdoc-highlight-helper))) | |
6693 ;; simple highlighting for html tags | |
6694 (goto-char beg) | |
6695 (while (re-search-forward js2-jsdoc-html-tag-regexp nil t) | |
6696 (js2-set-face (match-beginning 1) | |
6697 (match-end 1) | |
6698 'js2-jsdoc-html-tag-delimiter-face) | |
6699 (js2-set-face (match-beginning 2) | |
6700 (match-end 2) | |
6701 'js2-jsdoc-html-tag-name-face) | |
6702 (js2-set-face (match-beginning 3) | |
6703 (match-end 3) | |
6704 'js2-jsdoc-html-tag-delimiter-face)))))))) | |
6705 | |
6706 (defun js2-highlight-assign-targets (node left right) | |
6707 "Highlight function properties and external variables." | |
6708 (let (leftpos end name) | |
6709 ;; highlight vars and props assigned function values | |
6710 (when (js2-function-node-p right) | |
6711 (cond | |
6712 ;; var foo = function() {...} | |
6713 ((js2-name-node-p left) | |
6714 (setq name left)) | |
6715 | |
6716 ;; foo.bar.baz = function() {...} | |
6717 ((and (js2-prop-get-node-p left) | |
6718 (js2-name-node-p (js2-prop-get-node-right left))) | |
6719 (setq name (js2-prop-get-node-right left)))) | |
6720 | |
6721 (when name | |
6722 (js2-set-face (setq leftpos (js2-node-abs-pos name)) | |
6723 (+ leftpos (js2-node-len name)) | |
6724 'font-lock-function-name-face | |
6725 'record))) | |
6726 | |
6727 ;; save variable assignments so we can check for undeclared later | |
6728 ;; (can't do it here since var decls can come at end of script) | |
6729 (when (and js2-highlight-external-variables | |
6730 (setq name (js2-member-expr-leftmost-name left))) | |
6731 (push (list name js2-current-scope | |
6732 (setq leftpos (js2-node-abs-pos name)) | |
6733 (setq end (+ leftpos (js2-node-len name)))) | |
6734 js2-recorded-assignments)))) | |
6735 | |
6736 (defun js2-highlight-undeclared-vars () | |
6737 "After entire parse is finished, look for undeclared variable assignments. | |
6738 Have to wait until entire buffer is parsed, since JavaScript permits var | |
6739 decls to occur after they're used. | |
6740 | |
6741 We currently use a simple heuristic to rule out complaining about built-ins: | |
6742 if the name is capitalized we don't highlight it. This could be improved a | |
6743 bit by declaring all the Ecma global object, constructor and function names | |
6744 in a hashtable, but we'd still wind up complaining about all the DHTML | |
6745 builtins, the Mozilla builtins, etc." | |
6746 (let (name first-char) | |
6747 (dolist (entry js2-recorded-assignments) | |
6748 (destructuring-bind (name-node scope pos end) entry | |
6749 (setq name (js2-name-node-name name-node) | |
6750 first-char (aref name 0)) | |
6751 (unless (or (and (>= first-char ?A) (<= first-char ?Z)) | |
6752 (js2-get-defining-scope scope name)) | |
6753 (js2-set-face pos end 'js2-external-variable-face 'record) | |
6754 (js2-record-text-property pos end 'help-echo "Undeclared variable") | |
6755 (js2-record-text-property pos end 'point-entered #'js2-echo-help)))) | |
6756 (setq js2-recorded-assignments nil))) | |
6757 | |
6758 (provide 'js2-highlight) | |
6759 | |
6760 ;;; js2-highlight.el ends here | |
6761 ;;; js2-browse.el --- browsing/hierarchy support for js2-mode | |
6762 | |
6763 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
6764 ;; Keywords: javascript languages | |
6765 | |
6766 ;; Commentary: | |
6767 ;; | |
6768 ;; We currently only support imenu, but eventually should support speedbar and | |
6769 ;; possibly other browsing mechanisms. | |
6770 ;; | |
6771 ;; The basic strategy is to identify function assignment targets of the form | |
6772 ;; `foo.bar.baz', convert them to (list foo bar baz <position>), and push the | |
6773 ;; list into `js2-imenu-recorder'. The lists are merged into a trie-like tree | |
6774 ;; for imenu after parsing is finished. | |
6775 ;; | |
6776 ;; A `foo.bar.baz' assignment target may be expressed in many ways in | |
6777 ;; JavaScript, and the general problem is undecidable. However, several forms | |
6778 ;; are readily recognizable at parse-time; the forms we attempt to recognize | |
6779 ;; include: | |
6780 ;; | |
6781 ;; function foo() -- function declaration | |
6782 ;; foo = function() -- function expression assigned to variable | |
6783 ;; foo.bar.baz = function() -- function expr assigned to nested property-get | |
6784 ;; foo = {bar: function()} -- fun prop in object literal assigned to var | |
6785 ;; foo = {bar: {baz: function()}} -- inside nested object literal | |
6786 ;; foo.bar = {baz: function()}} -- obj lit assigned to nested prop get | |
6787 ;; a.b = {c: {d: function()}} -- nested obj lit assigned to nested prop get | |
6788 ;; foo = {get bar() {...}} -- getter/setter in obj literal | |
6789 ;; function foo() {function bar() {...}} -- nested function | |
6790 ;; foo['a'] = function() -- fun expr assigned to deterministic element-get | |
6791 ;; | |
6792 ;; This list boils down to a few forms that can be combined recursively. | |
6793 ;; Top-level named function declarations include both the left-hand (name) | |
6794 ;; and the right-hand (function value) expressions needed to produce an imenu | |
6795 ;; entry. The other "right-hand" forms we need to look for are: | |
6796 ;; - functions declared as props/getters/setters in object literals | |
6797 ;; - nested named function declarations | |
6798 ;; The "left-hand" expressions that functions can be assigned to include: | |
6799 ;; - local/global variables | |
6800 ;; - nested property-get expressions like a.b.c.d | |
6801 ;; - element gets like foo[10] or foo['bar'] where the index | |
6802 ;; expression can be trivially converted to a property name. They | |
6803 ;; effectively then become property gets. | |
6804 ;; | |
6805 ;; All the different definition types are canonicalized into the form | |
6806 ;; foo.bar.baz = position-of-function-keyword | |
6807 ;; | |
6808 ;; We need to build a trie-like structure for imenu. As an example, | |
6809 ;; consider the following JavaScript code: | |
6810 ;; | |
6811 ;; a = function() {...} // function at position 5 | |
6812 ;; b = function() {...} // function at position 25 | |
6813 ;; foo = function() {...} // function at position 100 | |
6814 ;; foo.bar = function() {...} // function at position 200 | |
6815 ;; foo.bar.baz = function() {...} // function at position 300 | |
6816 ;; foo.bar.zab = function() {...} // function at position 400 | |
6817 ;; | |
6818 ;; During parsing we accumulate an entry for each definition in | |
6819 ;; the variable `js2-imenu-recorder', like so: | |
6820 ;; | |
6821 ;; '((a 5) | |
6822 ;; (b 25) | |
6823 ;; (foo 100) | |
6824 ;; (foo bar 200) | |
6825 ;; (foo bar baz 300) | |
6826 ;; (foo bar zab 400)) | |
6827 ;; | |
6828 ;; After parsing these entries are merged into this alist-trie: | |
6829 ;; | |
6830 ;; '((a . 1) | |
6831 ;; (b . 2) | |
6832 ;; (foo (<definition> . 3) | |
6833 ;; (bar (<definition> . 6) | |
6834 ;; (baz . 100) | |
6835 ;; (zab . 200)))) | |
6836 ;; | |
6837 ;; Note the wacky need for a <definition> name. The token can be anything | |
6838 ;; that isn't a valid JavaScript identifier, because you might make foo | |
6839 ;; a function and then start setting properties on it that are also functions. | |
6840 | |
6841 ;;; Code: | |
6842 | |
6843 | |
6844 (defsubst js2-prop-node-name (node) | |
6845 "Return the name of a node that may be a property-get/property-name. | |
6846 If NODE is not a valid name-node, string-node or integral number-node, | |
6847 returns nil. Otherwise returns the string name/value of the node." | |
6848 (cond | |
6849 ((js2-name-node-p node) | |
6850 (js2-name-node-name node)) | |
6851 ((js2-string-node-p node) | |
6852 (js2-string-node-value node)) | |
6853 ((and (js2-number-node-p node) | |
6854 (string-match "^[0-9]+$" (js2-number-node-value node))) | |
6855 (js2-number-node-value node)) | |
6856 ((js2-this-node-p node) | |
6857 "this"))) | |
6858 | |
6859 (defsubst js2-node-qname-component (node) | |
6860 "Test function: return the name of this node, if it contributes to a qname. | |
6861 Returns nil if the node doesn't contribute." | |
6862 (copy-sequence | |
6863 (or (js2-prop-node-name node) | |
6864 (if (and (js2-function-node-p node) | |
6865 (js2-function-node-name node)) | |
6866 (js2-name-node-name (js2-function-node-name node)))))) | |
6867 | |
6868 (defsubst js2-record-function-qname (fn-node qname) | |
6869 "Associate FN-NODE with its QNAME for later lookup. | |
6870 This is used in postprocessing the chain list. When we find a chain | |
6871 whose first element is a js2-THIS keyword node, we look up the parent | |
6872 function and see (using this map) whether it is the tail of a chain. | |
6873 If so, we replace the this-node with a copy of the parent's qname." | |
6874 (unless js2-imenu-function-map | |
6875 (setq js2-imenu-function-map (make-hash-table :test 'eq))) | |
6876 (puthash fn-node qname js2-imenu-function-map)) | |
6877 | |
6878 (defun js2-record-imenu-functions (node &optional var) | |
6879 "Record function definitions for imenu. | |
6880 NODE is a function node or an object literal. | |
6881 VAR, if non-nil, is the expression that NODE is being assigned to." | |
6882 (when js2-parse-ide-mode | |
6883 (let ((fun-p (js2-function-node-p node)) | |
6884 qname left fname-node pos) | |
6885 (cond | |
6886 ;; non-anonymous function declaration? | |
6887 ((and fun-p | |
6888 (not var) | |
6889 (setq fname-node (js2-function-node-name node))) | |
6890 (push (setq qname (list fname-node (js2-node-pos node))) | |
6891 js2-imenu-recorder) | |
6892 (js2-record-function-qname node qname)) | |
6893 | |
6894 ;; for remaining forms, compute left-side tree branch first | |
6895 ((and var (setq qname (js2-compute-nested-prop-get var))) | |
6896 (cond | |
6897 ;; foo.bar.baz = function | |
6898 (fun-p | |
6899 (push (nconc qname (list (js2-node-pos node))) | |
6900 js2-imenu-recorder) | |
6901 (js2-record-function-qname node qname)) | |
6902 ;; foo.bar.baz = object-literal | |
6903 ;; look for nested functions: {a: {b: function() {...} }} | |
6904 ((js2-object-node-p node) | |
6905 (js2-record-object-literal node qname)))))))) | |
6906 | |
6907 (defun js2-compute-nested-prop-get (node) | |
6908 "If NODE is of form foo.bar.baz, return component nodes as a list. | |
6909 Otherwise returns nil. Element-gets can be treated as property-gets | |
6910 if the index expression is a name, a string, or a positive integer." | |
6911 (let (left right head) | |
6912 (cond | |
6913 ((or (js2-name-node-p node) | |
6914 (js2-this-node-p node)) | |
6915 (list node)) | |
6916 ;; foo.bar.baz is parenthesized as (foo.bar).baz => right operand is a leaf | |
6917 ((js2-prop-get-node-p node) ; includes elem-get nodes | |
6918 (setq left (js2-prop-get-node-left node) | |
6919 right (js2-prop-get-node-right node)) | |
6920 (if (and (or (js2-prop-get-node-p left) ; left == foo.bar | |
6921 (js2-name-node-p left) | |
6922 (js2-this-node-p left)) ; or left == foo | |
6923 (or (js2-name-node-p right) ; .bar | |
6924 (js2-string-node-p right) ; ['bar'] | |
6925 (and (js2-number-node-p right) ; [10] | |
6926 (string-match "^[0-9]+$" | |
6927 (js2-number-node-value right))))) | |
6928 (if (setq head (js2-compute-nested-prop-get left)) | |
6929 (nconc head (list right)))))))) | |
6930 | |
6931 (defun js2-record-object-literal (node qname) | |
6932 "Recursively process an object literal looking for functions. | |
6933 NODE is an object literal that is the right-hand child of an assignment | |
6934 expression. QNAME is a list of nodes representing the assignment target, | |
6935 e.g. for foo.bar.baz = {...}, QNAME is (foo-node bar-node baz-node). | |
6936 We do a depth-first traversal of NODE. Any functions we find are prefixed | |
6937 with QNAME plus the property name of the function and appended to the | |
6938 variable `js2-imenu-recorder'." | |
6939 ;; Elements are relative to parent position, which is still absolute, | |
6940 ;; since the parser passes the assignment target and value expressions | |
6941 ;; to us before they are added as children of the assignment node. | |
6942 (let ((pos (js2-node-pos node)) | |
6943 left right) | |
6944 (dolist (e (js2-object-node-elems node)) ; e is a `js2-object-prop-node' | |
6945 (setq left (js2-infix-node-left e)) | |
6946 (cond | |
6947 ;; foo: function() {...} | |
6948 ((js2-function-node-p (setq right (js2-infix-node-right e))) | |
6949 (when (js2-prop-node-name left) | |
6950 ;; As a policy decision, we record the position of the property, | |
6951 ;; not the position of the `function' keyword, since the property | |
6952 ;; is effectively the name of the function. | |
6953 (push (append qname (list left) (list (+ pos (js2-node-pos e)))) | |
6954 js2-imenu-recorder) | |
6955 (js2-record-function-qname right qname))) | |
6956 ;; foo: {object-literal} -- add foo to qname and recurse | |
6957 ((js2-object-node-p right) | |
6958 (js2-record-object-literal right | |
6959 (append qname (list (js2-infix-node-left e))))))))) | |
6960 | |
6961 (defsubst js2-node-top-level-decl-p (node) | |
6962 "Return t if NODE's name is defined in the top-level scope. | |
6963 Also returns t if NODE's name is not defined in any scope, since it implies | |
6964 that it's an external variable, which must also be in the top-level scope." | |
6965 (let* ((name (js2-prop-node-name node)) | |
6966 (this-scope (js2-node-get-enclosing-scope node)) | |
6967 defining-scope) | |
6968 (cond | |
6969 ((js2-this-node-p node) | |
6970 nil) | |
6971 ((null this-scope) | |
6972 t) | |
6973 ((setq defining-scope (js2-get-defining-scope this-scope name)) | |
6974 (js2-ast-root-p defining-scope)) | |
6975 (t t)))) | |
6976 | |
6977 (defun js2-browse-postprocess-chains (chains) | |
6978 "Modify function-declaration name chains after parsing finishes. | |
6979 Some of the information is only available after the parse tree is complete. | |
6980 For instance, following a 'this' reference requires a parent function node." | |
6981 (let (result head fn parent-chain p elem) | |
6982 (dolist (chain chains) | |
6983 ;; examine the head of each node to get its defining scope | |
6984 (setq head (car chain)) | |
6985 (cond | |
6986 ;; if top-level/external, keep as-is | |
6987 ((js2-node-top-level-decl-p head) | |
6988 (push chain result)) | |
6989 ;; check for a this-reference | |
6990 ((eq (js2-node-type head) js2-THIS) | |
6991 (setq fn (js2-node-parent-script-or-fn head)) | |
6992 ;; if there is no parent function, or if the parent function | |
6993 ;; is nested, discard the head node and keep the rest of the chain. | |
6994 (if (or (null fn) (js2-nested-function-p fn)) | |
6995 (push (cdr chain) result) | |
6996 ;; else look up parent in function-map. If not found, discard chain. | |
6997 (when (setq parent-chain (and js2-imenu-function-map | |
6998 (gethash fn js2-imenu-function-map))) | |
6999 ;; else discard head node and prefix parent fn qname, which is | |
7000 ;; the parent-chain sans tail, to this chain. | |
7001 (push (append (butlast parent-chain) (cdr chain)) result)))))) | |
7002 ;; finally replace each node in each chain with its name. | |
7003 (dolist (chain result) | |
7004 (setq p chain) | |
7005 (while p | |
7006 (if (js2-node-p (setq elem (car p))) | |
7007 (setcar p (js2-node-qname-component elem))) | |
7008 (setq p (cdr p)))) | |
7009 result)) | |
7010 | |
7011 ;; Merge name chains into a trie-like tree structure of nested lists. | |
7012 ;; To simplify construction of the trie, we first build it out using the rule | |
7013 ;; that the trie consists of lists of pairs. Each pair is a 2-element array: | |
7014 ;; [key, num-or-list]. The second element can be a number; if so, this key | |
7015 ;; is a leaf-node with only one value. (I.e. there is only one declaration | |
7016 ;; associated with the key at this level.) Otherwise the second element is | |
7017 ;; a list of pairs, with the rule applied recursively. This symmetry permits | |
7018 ;; a simple recursive formulation. | |
7019 ;; | |
7020 ;; js2-mode is building the data structure for imenu. The imenu documentation | |
7021 ;; claims that it's the structure above, but in practice it wants the children | |
7022 ;; at the same list level as the key for that level, which is how I've drawn | |
7023 ;; the "Expected final result" above. We'll postprocess the trie to remove the | |
7024 ;; list wrapper around the children at each level. | |
7025 ;; | |
7026 ;; A completed nested imenu-alist entry looks like this: | |
7027 ;; '(("foo" | |
7028 ;; ("<definition>" . 7) | |
7029 ;; ("bar" | |
7030 ;; ("a" . 40) | |
7031 ;; ("b" . 60)))) | |
7032 ;; | |
7033 ;; In particular, the documentation for `imenu--index-alist' says that | |
7034 ;; a nested sub-alist element looks like (INDEX-NAME SUB-ALIST). | |
7035 ;; The sub-alist entries immediately follow INDEX-NAME, the head of the list. | |
7036 | |
7037 (defsubst js2-treeify (lst) | |
7038 "Convert (a b c d) to (a ((b ((c d)))))" | |
7039 (if (null (cddr lst)) ; list length <= 2 | |
7040 lst | |
7041 (list (car lst) (list (js2-treeify (cdr lst)))))) | |
7042 | |
7043 (defun js2-build-alist-trie (chains trie) | |
7044 "Merge declaration name chains into a trie-like alist structure for imenu. | |
7045 CHAINS is the qname chain list produced during parsing. TRIE is a | |
7046 list of elements built up so far." | |
7047 (let (head tail pos branch kids) | |
7048 (dolist (chain chains) | |
7049 (setq head (car chain) | |
7050 tail (cdr chain) | |
7051 pos (if (numberp (car tail)) (car tail)) | |
7052 branch (js2-find-if (lambda (n) | |
7053 (string= (car n) head)) | |
7054 trie) | |
7055 kids (second branch)) | |
7056 (cond | |
7057 ;; case 1: this key isn't in the trie yet | |
7058 ((null branch) | |
7059 (if trie | |
7060 (setcdr (last trie) (list (js2-treeify chain))) | |
7061 (setq trie (list (js2-treeify chain))))) | |
7062 | |
7063 ;; case 2: key is present with a single number entry: replace w/ list | |
7064 ;; ("a1" 10) + ("a1" 20) => ("a1" (("<definition>" 10) | |
7065 ;; ("<definition>" 20))) | |
7066 ((numberp kids) | |
7067 (setcar (cdr branch) | |
7068 (list (list "<definition-1>" kids) | |
7069 (if pos | |
7070 (list "<definition-2>" pos) | |
7071 (js2-treeify tail))))) | |
7072 | |
7073 ;; case 3: key is there (with kids), and we're a number entry | |
7074 (pos | |
7075 (setcdr (last kids) | |
7076 (list | |
7077 (list (format "<definition-%d>" | |
7078 (1+ (loop for kid in kids | |
7079 count (eq ?< (aref (car kid) 0))))) | |
7080 pos)))) | |
7081 | |
7082 ;; case 4: key is there with kids, need to merge in our chain | |
7083 (t | |
7084 (js2-build-alist-trie (list tail) kids)))) | |
7085 trie)) | |
7086 | |
7087 (defun js2-flatten-trie (trie) | |
7088 "Convert TRIE to imenu-format. | |
7089 Recurses through nodes, and for each one whose second element is a list, | |
7090 appends the list's flattened elements to the current element. Also | |
7091 changes the tails into conses. For instance, this pre-flattened trie | |
7092 | |
7093 '(a ((b 20) | |
7094 (c ((d 30) | |
7095 (e 40))))) | |
7096 | |
7097 becomes | |
7098 | |
7099 '(a (b . 20) | |
7100 (c (d . 30) | |
7101 (e . 40))) | |
7102 | |
7103 Note that the root of the trie has no key, just a list of chains. | |
7104 This is also true for the value of any key with multiple children, | |
7105 e.g. key 'c' in the example above." | |
7106 (cond | |
7107 ((listp (car trie)) | |
7108 (mapcar #'js2-flatten-trie trie)) | |
7109 (t | |
7110 (if (numberp (second trie)) | |
7111 (cons (car trie) (second trie)) | |
7112 ;; else pop list and append its kids | |
7113 (apply #'append (list (car trie)) (js2-flatten-trie (cdr trie))))))) | |
7114 | |
7115 (defun js2-build-imenu-index () | |
7116 "Turn `js2-imenu-recorder' into an imenu data structure." | |
7117 (unless (eq js2-imenu-recorder 'empty) | |
7118 (let* ((chains (js2-browse-postprocess-chains js2-imenu-recorder)) | |
7119 (result (js2-build-alist-trie chains nil))) | |
7120 (js2-flatten-trie result)))) | |
7121 | |
7122 (defun js2-test-print-chains (chains) | |
7123 "Print a list of qname chains. | |
7124 Each element of CHAINS is a list of the form (NODE [NODE *] pos); | |
7125 i.e. one or more nodes, and an integer position as the list tail." | |
7126 (mapconcat (lambda (chain) | |
7127 (concat "(" | |
7128 (mapconcat (lambda (elem) | |
7129 (if (js2-node-p elem) | |
7130 (or (js2-node-qname-component elem) | |
7131 "nil") | |
7132 (number-to-string elem))) | |
7133 chain | |
7134 " ") | |
7135 ")")) | |
7136 chains | |
7137 "\n")) | |
7138 | |
7139 | |
7140 (provide 'js2-browse) | |
7141 | |
7142 ;;; js2-browse.el ends here | |
7143 ;;; js2-parse.el --- JavaScript parser | |
7144 | |
7145 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
7146 ;; Keywords: javascript languages | |
7147 | |
7148 ;; Commentary: | |
7149 | |
7150 ;; This is based on Rhino's parser and tries to follow its code | |
7151 ;; structure as closely as practical, so that changes to the Rhino | |
7152 ;; parser can easily be propagated into this code. However, Rhino | |
7153 ;; does not currently generate a usable AST representation, at least | |
7154 ;; from an IDE perspective, so we build our own more suitable AST. | |
7155 | |
7156 ;; The AST node structures are defined in `js2-ast.el'. | |
7157 ;; Every parser function that creates and returns an AST node has | |
7158 ;; the following responsibilities: | |
7159 | |
7160 ;; 1) set the node start to the absolute buffer start position | |
7161 ;; 2) set the node length to include any closing chars (RC, SEMI) | |
7162 ;; 3) fix up any child-node starts to be relative to this node | |
7163 ;; 4) set any field positions (e.g. keywords) relative to this node | |
7164 ;; 5) report any child nodes with `js2-node-add-children' | |
7165 ;; (note that this call fixes up start positions by default) | |
7166 | |
7167 ;; The resulting AST has all node start positions relative to the | |
7168 ;; parent nodes; only the root has an absolute start position. | |
7169 | |
7170 ;; Note: fontification is done inline while parsing. It used to be | |
7171 ;; done in a second pass over the AST, but doing it inline is about | |
7172 ;; twice as fast. Most of the fontification happens when tokens are | |
7173 ;; scanned, and the parser has a few spots that perform extra | |
7174 ;; fontification. In addition to speed, a second benefit of inline | |
7175 ;; parsing is that if a long parse is interrupted, everything parsed | |
7176 ;; so far is still fontified. | |
7177 | |
7178 ;; The editing mode that uses this parser, `js2-mode', directs the | |
7179 ;; parser to check periodically for user input. If user input | |
7180 ;; arrives, the parse is abandoned, except for the highlighting that | |
7181 ;; has occurred so far, and a re-parse is rescheduled for when Emacs | |
7182 ;; becomes idle again. This works pretty well, but could be better. | |
7183 ;; In particular, when the user input has not resulted in changes to | |
7184 ;; the buffer (for instance, navigation input), the parse tree built | |
7185 ;; so far should not be discarded, and the parse should continue where | |
7186 ;; it left off. It will be some work to create what amounts to a | |
7187 ;; continuation, but it should not be unreasonably difficult. | |
7188 | |
7189 ;; TODO: | |
7190 ;; - make non-editing input restart parse at previous continuation | |
7191 ;; - in Eclipse, sibling nodes never overlap start/end ranges | |
7192 ;; - for getters, prop name and function nodes overlap | |
7193 ;; - should write a debug tree visitor to look for overlaps | |
7194 ;; - mark array and object literals as "destructuring" (node prop?) | |
7195 ;; so we can syntax-highlight them properly. | |
7196 ;; - figure out a way not to store value in string/name nodes | |
7197 ;; - needs a solution for synthetic nodes | |
7198 | |
7199 ;;; Code | |
7200 | |
7201 (eval-and-compile | |
7202 (require 'cl)) ; for delete-if | |
7203 | |
7204 | |
7205 (defconst js2-version "1.7.0" | |
7206 "Version of JavaScript supported, plus minor js2 version.") | |
7207 | |
7208 (defmacro js2-record-face (face) | |
7209 "Record a style run of FACE for the current token." | |
7210 `(js2-set-face js2-token-beg js2-token-end ,face 'record)) | |
7211 | |
7212 (defsubst js2-node-end (n) | |
7213 "Computes the absolute end of node N. | |
7214 Use with caution! Assumes `js2-node-pos' is -absolute-, which | |
7215 is only true until the node is added to its parent; i.e., while parsing." | |
7216 (+ (js2-node-pos n) | |
7217 (js2-node-len n))) | |
7218 | |
7219 (defsubst js2-record-comment () | |
7220 (push (make-js2-comment-node :len (- js2-token-end js2-token-beg) | |
7221 :format js2-ts-comment-type) | |
7222 js2-scanned-comments) | |
7223 (when js2-parse-ide-mode | |
7224 (js2-record-face 'font-lock-comment-face) | |
7225 (when (memq js2-ts-comment-type '(html preprocessor)) | |
7226 ;; Tell cc-engine the bounds of the comment. | |
7227 (put-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t)))) | |
7228 | |
7229 ;; This function is called depressingly often, so it should be fast. | |
7230 ;; Most of the time it's looking at the same token it peeked before. | |
7231 (defsubst js2-peek-token () | |
7232 "Returns the next token without consuming it. | |
7233 If previous token was consumed, calls scanner to get new token. | |
7234 If previous token was -not- consumed, returns it (idempotent). | |
7235 | |
7236 This function will not return a newline (js2-EOL) - instead, it | |
7237 gobbles newlines until it finds a non-newline token, and flags | |
7238 that token as appearing just after a newline. | |
7239 | |
7240 This function will also not return a js2-COMMENT. Instead, it | |
7241 records comments found in `js2-scanned-comments'. If the token | |
7242 returned by this function immediately follows a jsdoc comment, | |
7243 the token is flagged as such. | |
7244 | |
7245 Note that this function always returned the un-flagged token! | |
7246 The flags, if any, are saved in `js2-current-flagged-token'." | |
7247 (if (/= js2-current-flagged-token js2-EOF) ; last token not consumed | |
7248 js2-current-token ; most common case - return already-peeked token | |
7249 (let ((tt (js2-get-token)) ; call scanner | |
7250 saw-eol | |
7251 face) | |
7252 ;; process comments and whitespace | |
7253 (while (or (= tt js2-EOL) | |
7254 (= tt js2-COMMENT)) | |
7255 (if (= tt js2-EOL) | |
7256 (setq saw-eol t) | |
7257 (setq saw-eol nil) | |
7258 (if js2-record-comments | |
7259 (js2-record-comment))) | |
7260 (setq tt (js2-get-token))) ; call scanner | |
7261 | |
7262 (setq js2-current-token tt | |
7263 js2-current-flagged-token (if saw-eol | |
7264 (logior tt js2-ti-after-eol) | |
7265 tt)) | |
7266 ;; perform lexical fontification as soon as token is scanned | |
7267 (when js2-parse-ide-mode | |
7268 (cond | |
7269 ((minusp tt) | |
7270 (js2-record-face 'js2-error-face)) | |
7271 ((setq face (aref js2-kwd-tokens tt)) | |
7272 (js2-record-face face)) | |
7273 ((and (= tt js2-NAME) | |
7274 (equal js2-ts-string "undefined")) | |
7275 (js2-record-face 'font-lock-constant-face)))) | |
7276 tt))) ; return unflagged token | |
7277 | |
7278 (defsubst js2-peek-flagged-token () | |
7279 "Returns the current token along with any flags set for it." | |
7280 (js2-peek-token) | |
7281 js2-current-flagged-token) | |
7282 | |
7283 (defsubst js2-consume-token () | |
7284 (setq js2-current-flagged-token js2-EOF)) | |
7285 | |
7286 (defsubst js2-next-token () | |
7287 (prog1 | |
7288 (js2-peek-token) | |
7289 (js2-consume-token))) | |
7290 | |
7291 (defsubst js2-next-flagged-token () | |
7292 (js2-peek-token) | |
7293 (prog1 js2-current-flagged-token | |
7294 (js2-consume-token))) | |
7295 | |
7296 (defsubst js2-match-token (match) | |
7297 "Consume and return t if next token matches MATCH, a bytecode. | |
7298 Returns nil and consumes nothing if MATCH is not the next token." | |
7299 (if (/= (js2-peek-token) match) | |
7300 nil | |
7301 (js2-consume-token) | |
7302 t)) | |
7303 | |
7304 (defsubst js2-valid-prop-name-token (tt) | |
7305 (or (= tt js2-NAME) | |
7306 (and js2-allow-keywords-as-property-names | |
7307 (plusp tt) | |
7308 (aref js2-kwd-tokens tt)))) | |
7309 | |
7310 (defsubst js2-match-prop-name () | |
7311 "Consume token and return t if next token is a valid property name. | |
7312 It's valid if it's a js2-NAME, or `js2-allow-keywords-as-property-names' | |
7313 is non-nil and it's a keyword token." | |
7314 (if (js2-valid-prop-name-token (js2-peek-token)) | |
7315 (progn | |
7316 (js2-consume-token) | |
7317 t) | |
7318 nil)) | |
7319 | |
7320 (defsubst js2-must-match-prop-name (msg-id &optional pos len) | |
7321 (if (js2-match-prop-name) | |
7322 t | |
7323 (js2-report-error msg-id nil pos len) | |
7324 nil)) | |
7325 | |
7326 (defsubst js2-peek-token-or-eol () | |
7327 "Return js2-EOL if the current token immediately follows a newline. | |
7328 Else returns the current token. Used in situations where we don't | |
7329 consider certain token types valid if they are preceded by a newline. | |
7330 One example is the postfix ++ or -- operator, which has to be on the | |
7331 same line as its operand." | |
7332 (let ((tt (js2-peek-token))) | |
7333 ;; Check for last peeked token flags | |
7334 (if (js2-flag-set-p js2-current-flagged-token js2-ti-after-eol) | |
7335 js2-EOL | |
7336 tt))) | |
7337 | |
7338 (defsubst js2-set-check-for-label () | |
7339 (assert (= (logand js2-current-flagged-token js2-clear-ti-mask) js2-NAME)) | |
7340 (js2-set-flag js2-current-flagged-token js2-ti-check-label)) | |
7341 | |
7342 (defsubst js2-must-match (token msg-id &optional pos len) | |
7343 "Match next token to token code TOKEN, or record a syntax error. | |
7344 MSG-ID is the error message to report if the match fails. | |
7345 Returns t on match, nil if no match." | |
7346 (if (js2-match-token token) | |
7347 t | |
7348 (js2-report-error msg-id nil pos len) | |
7349 nil)) | |
7350 | |
7351 (defsubst js2-inside-function () | |
7352 (plusp js2-nesting-of-function)) | |
7353 | |
7354 (defsubst js2-set-requires-activation () | |
7355 (if (js2-function-node-p js2-current-script-or-fn) | |
7356 (setf (js2-function-node-needs-activation js2-current-script-or-fn) t))) | |
7357 | |
7358 (defsubst js2-check-activation-name (name token) | |
7359 (when (js2-inside-function) | |
7360 ;; skip language-version 1.2 check from Rhino | |
7361 (if (or (string= "arguments" name) | |
7362 (and js2-compiler-activation-names ; only used in codegen | |
7363 (gethash name js2-compiler-activation-names))) | |
7364 (js2-set-requires-activation)))) | |
7365 | |
7366 (defsubst js2-set-is-generator () | |
7367 (if (js2-function-node-p js2-current-script-or-fn) | |
7368 (setf (js2-function-node-is-generator js2-current-script-or-fn) t))) | |
7369 | |
7370 (defsubst js2-must-have-xml () | |
7371 (unless js2-compiler-xml-available | |
7372 (js2-report-error "msg.XML.not.available"))) | |
7373 | |
7374 (defsubst js2-push-scope (scope) | |
7375 "Push SCOPE, a `js2-scope', onto the lexical scope chain." | |
7376 (assert (js2-scope-p scope)) | |
7377 (assert (null (js2-scope-parent-scope scope))) | |
7378 (assert (neq js2-current-scope scope)) | |
7379 (setf (js2-scope-parent-scope scope) js2-current-scope | |
7380 js2-current-scope scope)) | |
7381 | |
7382 (defsubst js2-pop-scope () | |
7383 (setq js2-current-scope | |
7384 (js2-scope-parent-scope js2-current-scope))) | |
7385 | |
7386 (defsubst js2-enter-loop (loop-node) | |
7387 (push loop-node js2-loop-set) | |
7388 (push loop-node js2-loop-and-switch-set) | |
7389 (js2-push-scope loop-node) | |
7390 ;; Tell the current labeled statement (if any) its statement, | |
7391 ;; and set the jump target of the first label to the loop. | |
7392 ;; These are used in `js2-parse-continue' to verify that the | |
7393 ;; continue target is an actual labeled loop. (And for codegen.) | |
7394 (when js2-labeled-stmt | |
7395 (setf (js2-labeled-stmt-node-stmt js2-labeled-stmt) loop-node | |
7396 (js2-label-node-loop (car (js2-labeled-stmt-node-labels | |
7397 js2-labeled-stmt))) loop-node))) | |
7398 | |
7399 (defsubst js2-exit-loop () | |
7400 (pop js2-loop-set) | |
7401 (pop js2-loop-and-switch-set) | |
7402 (js2-pop-scope)) | |
7403 | |
7404 (defsubst js2-enter-switch (switch-node) | |
7405 (push switch-node js2-loop-and-switch-set)) | |
7406 | |
7407 (defsubst js2-exit-switch () | |
7408 (pop js2-loop-and-switch-set)) | |
7409 | |
7410 (defun js2-parse (&optional buf cb) | |
7411 "Tells the js2 parser to parse a region of JavaScript. | |
7412 | |
7413 BUF is a buffer or buffer name containing the code to parse. | |
7414 Call `narrow-to-region' first to parse only part of the buffer. | |
7415 | |
7416 The returned AST root node is given some additional properties: | |
7417 `node-count' - total number of nodes in the AST | |
7418 `buffer' - BUF. The buffer it refers to may change or be killed, | |
7419 so the value is not necessarily reliable. | |
7420 | |
7421 An optional callback CB can be specified to report parsing | |
7422 progress. If `(functionp CB)' returns t, it will be called with | |
7423 the current line number once before parsing begins, then again | |
7424 each time the lexer reaches a new line number. | |
7425 | |
7426 CB can also be a list of the form `(symbol cb ...)' to specify | |
7427 multiple callbacks with different criteria. Each symbol is a | |
7428 criterion keyword, and the following element is the callback to | |
7429 call | |
7430 | |
7431 :line - called whenever the line number changes | |
7432 :token - called for each new token consumed | |
7433 | |
7434 The list of criteria could be extended to include entering or | |
7435 leaving a statement, an expression, or a function definition." | |
7436 (if (and cb (not (functionp cb))) | |
7437 (error "criteria callbacks not yet implemented")) | |
7438 (let ((inhibit-point-motion-hooks t) | |
7439 (js2-compiler-xml-available (>= js2-language-version 160)) | |
7440 ;; This is a recursive-descent parser, so give it a big stack. | |
7441 (max-lisp-eval-depth (max max-lisp-eval-depth 3000)) | |
7442 (max-specpdl-size (max max-specpdl-size 3000)) | |
7443 (case-fold-search nil) | |
7444 ast) | |
7445 (or buf (setq buf (current-buffer))) | |
7446 (save-excursion | |
7447 (set-buffer buf) | |
7448 (setq js2-scanned-comments nil | |
7449 js2-parsed-errors nil | |
7450 js2-parsed-warnings nil | |
7451 js2-imenu-recorder nil | |
7452 js2-imenu-function-map nil | |
7453 js2-label-set nil) | |
7454 (js2-init-scanner) | |
7455 (setq ast (js2-with-unmodifying-text-property-changes | |
7456 (js2-do-parse))) | |
7457 (unless js2-ts-hit-eof | |
7458 (js2-report-error "msg.got.syntax.errors" (length js2-parsed-errors))) | |
7459 (setf (js2-ast-root-errors ast) js2-parsed-errors | |
7460 (js2-ast-root-warnings ast) js2-parsed-warnings) | |
7461 ;; if we didn't find any declarations, put a dummy in this list so we | |
7462 ;; don't end up re-parsing the buffer in `js2-mode-create-imenu-index' | |
7463 (unless js2-imenu-recorder | |
7464 (setq js2-imenu-recorder 'empty)) | |
7465 (run-hooks 'js2-parse-finished-hook) | |
7466 ast))) | |
7467 | |
7468 ;; Corresponds to Rhino's Parser.parse() method. | |
7469 (defun js2-do-parse () | |
7470 "Parse current buffer starting from current point. | |
7471 Scanner should be initialized." | |
7472 (let ((pos js2-ts-cursor) | |
7473 (end js2-ts-cursor) ; in case file is empty | |
7474 root n tt) | |
7475 ;; initialize buffer-local parsing vars | |
7476 (setf root (make-js2-ast-root :buffer (buffer-name) :pos pos) | |
7477 js2-current-script-or-fn root | |
7478 js2-current-scope root | |
7479 js2-current-flagged-token js2-EOF | |
7480 js2-nesting-of-function 0 | |
7481 js2-labeled-stmt nil | |
7482 js2-recorded-assignments nil) ; for js2-highlight | |
7483 | |
7484 (while (/= (setq tt (js2-peek-token)) js2-EOF) | |
7485 (if (= tt js2-FUNCTION) | |
7486 (progn | |
7487 (js2-consume-token) | |
7488 (setq n (js2-parse-function (if js2-called-by-compile-function | |
7489 'FUNCTION_EXPRESSION | |
7490 'FUNCTION_STATEMENT))) | |
7491 (js2-record-imenu-functions n)) | |
7492 ;; not a function - parse a statement | |
7493 (setq n (js2-parse-statement))) | |
7494 ;; add function or statement to script | |
7495 (setq end (js2-node-end n)) | |
7496 (js2-block-node-push root n)) | |
7497 | |
7498 ;; add comments to root in lexical order | |
7499 (when js2-scanned-comments | |
7500 ;; if we find a comment beyond end of normal kids, use its end | |
7501 (setq end (max end (js2-node-end (first js2-scanned-comments)))) | |
7502 (dolist (comment js2-scanned-comments) | |
7503 (push comment (js2-ast-root-comments root)) | |
7504 (js2-node-add-children root comment))) | |
7505 | |
7506 (setf (js2-node-len root) (- end pos)) | |
7507 (js2-highlight-undeclared-vars) | |
7508 root)) | |
7509 | |
7510 (defun js2-function-parser () | |
7511 (js2-consume-token) | |
7512 (js2-parse-function 'FUNCTION_EXPRESSION_STATEMENT)) | |
7513 | |
7514 (defun js2-parse-function-body (fn-node) | |
7515 (js2-must-match js2-LC "msg.no.brace.body") | |
7516 (let ((pos js2-token-beg) ; LC position | |
7517 (pn (make-js2-block-node)) ; starts at LC position | |
7518 tt | |
7519 end) | |
7520 (incf js2-nesting-of-function) | |
7521 (unwind-protect | |
7522 (while (not (or (= (setq tt (js2-peek-token)) js2-ERROR) | |
7523 (= tt js2-EOF) | |
7524 (= tt js2-RC))) | |
7525 (js2-block-node-push pn (if (/= tt js2-FUNCTION) | |
7526 (js2-parse-statement) | |
7527 (js2-consume-token) | |
7528 (js2-parse-function 'FUNCTION_STATEMENT)))) | |
7529 (decf js2-nesting-of-function)) | |
7530 (setq end js2-token-end) ; assume no curly and leave at current token | |
7531 (if (js2-must-match js2-RC "msg.no.brace.after.body" pos) | |
7532 (setq end js2-token-end)) | |
7533 (setf (js2-node-pos pn) pos | |
7534 (js2-node-len pn) (- end pos)) | |
7535 (setf (js2-function-node-body fn-node) pn) | |
7536 (js2-node-add-children fn-node pn) | |
7537 pn)) | |
7538 | |
7539 (defun js2-parse-function-params (fn-node pos) | |
7540 (if (js2-match-token js2-RP) | |
7541 (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)) | |
7542 (let (params len param) | |
7543 (loop for tt = (js2-peek-token) | |
7544 do | |
7545 (cond | |
7546 ;; destructuring param | |
7547 ((or (= tt js2-LB) (= tt js2-LC)) | |
7548 (push (js2-parse-primary-expr) params)) | |
7549 ;; simple name | |
7550 (t | |
7551 (js2-must-match js2-NAME "msg.no.parm") | |
7552 (js2-record-face 'js2-function-param-face) | |
7553 (setq param (js2-create-name-node)) | |
7554 (js2-define-symbol js2-LP js2-ts-string param) | |
7555 (push param params))) | |
7556 while | |
7557 (js2-match-token js2-COMMA)) | |
7558 (if (js2-must-match js2-RP "msg.no.paren.after.parms") | |
7559 (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))) | |
7560 (dolist (p params) | |
7561 (js2-node-add-children fn-node p) | |
7562 (push p (js2-function-node-params fn-node)))))) | |
7563 | |
7564 (defsubst js2-check-inconsistent-return-warning (fn-node name) | |
7565 "Possibly show inconsistent-return warning. | |
7566 Last token scanned is the close-curly for the function body." | |
7567 (when (and js2-mode-show-strict-warnings | |
7568 js2-strict-inconsistent-return-warning | |
7569 (not (js2-has-consistent-return-usage | |
7570 (js2-function-node-body fn-node)))) | |
7571 ;; Have it extend from close-curly to bol or beginning of block. | |
7572 (let ((pos (save-excursion | |
7573 (goto-char js2-token-end) | |
7574 (max (js2-node-abs-pos (js2-function-node-body fn-node)) | |
7575 (point-at-bol)))) | |
7576 (end js2-token-end)) | |
7577 (if (plusp (js2-name-node-length name)) | |
7578 (js2-add-strict-warning "msg.no.return.value" | |
7579 (js2-name-node-name name) pos end) | |
7580 (js2-add-strict-warning "msg.anon.no.return.value" nil pos end))))) | |
7581 | |
7582 (defun js2-parse-function (function-type) | |
7583 "Function parser. FUNCTION-TYPE is a symbol." | |
7584 (let ((pos js2-token-beg) ; start of 'function' keyword | |
7585 name | |
7586 name-beg | |
7587 name-end | |
7588 fn-node | |
7589 lp | |
7590 (synthetic-type function-type) | |
7591 member-expr-node) | |
7592 | |
7593 ;; parse function name, expression, or non-name (anonymous) | |
7594 (cond | |
7595 ;; function foo(...) | |
7596 ((js2-match-token js2-NAME) | |
7597 (setq name (js2-create-name-node t) | |
7598 name-beg js2-token-beg | |
7599 name-end js2-token-end) | |
7600 (unless (js2-match-token js2-LP) | |
7601 (when js2-allow-member-expr-as-function-name | |
7602 ;; function foo.bar(...) | |
7603 (setq member-expr-node name | |
7604 name nil | |
7605 member-expr-node (js2-parse-member-expr-tail | |
7606 nil member-expr-node))) | |
7607 (js2-must-match js2-LP "msg.no.paren.parms"))) | |
7608 | |
7609 ((js2-match-token js2-LP) | |
7610 nil) ; anonymous function: leave name as null | |
7611 | |
7612 (t | |
7613 ;; function random-member-expr(...) | |
7614 (when js2-allow-member-expr-as-function-name | |
7615 ;; Note that memberExpr can not start with '(' like | |
7616 ;; in function (1+2).toString(), because 'function (' already | |
7617 ;; processed as anonymous function | |
7618 (setq member-expr-node (js2-parse-member-expr))) | |
7619 (js2-must-match js2-LP "msg.no.paren.parms"))) | |
7620 | |
7621 (if (= js2-current-token js2-LP) ; eventually matched LP? | |
7622 (setq lp js2-token-beg)) | |
7623 | |
7624 (if member-expr-node | |
7625 (progn | |
7626 (setq synthetic-type 'FUNCTION_EXPRESSION) | |
7627 (js2-parse-highlight-member-expr-fn-name member-expr-node)) | |
7628 (if name | |
7629 (js2-set-face name-beg name-end | |
7630 'font-lock-function-name-face 'record))) | |
7631 | |
7632 (if (and (neq synthetic-type 'FUNCTION_EXPRESSION) | |
7633 (plusp (js2-name-node-length name))) | |
7634 ;; Function statements define a symbol in the enclosing scope | |
7635 (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node)) | |
7636 | |
7637 (setf fn-node (make-js2-function-node :pos pos | |
7638 :name name | |
7639 :form function-type | |
7640 :lp (if lp (- lp pos)))) | |
7641 | |
7642 (if (or (js2-inside-function) (plusp js2-nesting-of-with)) | |
7643 ;; 1. Nested functions are not affected by the dynamic scope flag | |
7644 ;; as dynamic scope is already a parent of their scope. | |
7645 ;; 2. Functions defined under the with statement also immune to | |
7646 ;; this setup, in which case dynamic scope is ignored in favor | |
7647 ;; of the with object. | |
7648 (setf (js2-function-node-ignore-dynamic fn-node) t)) | |
7649 | |
7650 ;; dynamically bind all the per-function variables | |
7651 (let ((js2-current-script-or-fn fn-node) | |
7652 (js2-current-scope fn-node) | |
7653 (js2-nesting-of-with 0) | |
7654 (js2-end-flags 0) | |
7655 js2-label-set | |
7656 js2-loop-set | |
7657 js2-loop-and-switch-set) | |
7658 | |
7659 ;; parse params and function body | |
7660 (js2-parse-function-params fn-node pos) | |
7661 (js2-parse-function-body fn-node) | |
7662 (if name | |
7663 (js2-node-add-children fn-node name)) | |
7664 | |
7665 (js2-check-inconsistent-return-warning fn-node name) | |
7666 | |
7667 ;; Function expressions define a name only in the body of the | |
7668 ;; function, and only if not hidden by a parameter name | |
7669 (if (and name | |
7670 (eq synthetic-type 'FUNCTION_EXPRESSION) | |
7671 (null (js2-scope-get-symbol js2-current-scope | |
7672 (js2-name-node-name name)))) | |
7673 (js2-define-symbol js2-FUNCTION | |
7674 (js2-name-node-name name) | |
7675 fn-node)) | |
7676 (if (and name | |
7677 (eq function-type 'FUNCTION_EXPRESSION_STATEMENT)) | |
7678 (js2-record-imenu-functions fn-node))) | |
7679 | |
7680 (setf (js2-node-len fn-node) (- js2-ts-cursor pos) | |
7681 (js2-function-node-member-expr fn-node) member-expr-node) ; may be nil | |
7682 | |
7683 ;; Rhino doesn't do this, but we need it for finding undeclared vars. | |
7684 ;; We wait until after parsing the function to set its parent scope, | |
7685 ;; since `js2-define-symbol' needs the defining-scope check to stop | |
7686 ;; at the function boundary when checking for redeclarations. | |
7687 (setf (js2-scope-parent-scope fn-node) js2-current-scope) | |
7688 | |
7689 fn-node)) | |
7690 | |
7691 (defun js2-parse-statements (&optional parent) | |
7692 "Parse a statement list. Last token consumed must be js2-LC. | |
7693 | |
7694 PARENT can be a `js2-block-node', in which case the statements are | |
7695 appended to PARENT. Otherwise a new `js2-block-node' is created | |
7696 and returned. | |
7697 | |
7698 This function does not match the closing js2-RC: the caller | |
7699 matches the RC so it can provide a suitable error message if not | |
7700 matched. This means it's up to the caller to set the length of | |
7701 the node to include the closing RC. The node start pos is set to | |
7702 the absolute buffer start position, and the caller should fix it | |
7703 up to be relative to the parent node. All children of this block | |
7704 node are given relative start positions and correct lengths." | |
7705 (let ((pn (or parent (make-js2-block-node))) | |
7706 tt) | |
7707 (setf (js2-node-pos pn) js2-token-beg) | |
7708 (while (and (> (setq tt (js2-peek-token)) js2-EOF) | |
7709 (/= tt js2-RC)) | |
7710 (js2-block-node-push pn (js2-parse-statement))) | |
7711 pn)) | |
7712 | |
7713 (defun js2-parse-statement () | |
7714 (let (tt pn beg end) | |
7715 | |
7716 ;; coarse-grained user-interrupt check - needs work | |
7717 (and js2-parse-interruptable-p | |
7718 (zerop (% (incf js2-parse-stmt-count) | |
7719 js2-statements-per-pause)) | |
7720 (input-pending-p) | |
7721 (throw 'interrupted t)) | |
7722 | |
7723 (setq pn (js2-statement-helper)) | |
7724 | |
7725 ;; no-side-effects warning check | |
7726 (unless (js2-node-has-side-effects pn) | |
7727 (setq end (js2-node-end pn)) | |
7728 (save-excursion | |
7729 (goto-char end) | |
7730 (setq beg (max (js2-node-pos pn) (point-at-bol)))) | |
7731 (js2-add-strict-warning "msg.no.side.effects" nil beg end)) | |
7732 | |
7733 pn)) | |
7734 | |
7735 ;; These correspond to the switch cases in Parser.statementHelper | |
7736 (defconst js2-parsers | |
7737 (let ((parsers (make-vector js2-num-tokens | |
7738 #'js2-parse-expr-stmt))) | |
7739 (aset parsers js2-BREAK #'js2-parse-break) | |
7740 (aset parsers js2-CONST #'js2-parse-const-var) | |
7741 (aset parsers js2-CONTINUE #'js2-parse-continue) | |
7742 (aset parsers js2-DEBUGGER #'js2-parse-debugger) | |
7743 (aset parsers js2-DEFAULT #'js2-parse-default-xml-namespace) | |
7744 (aset parsers js2-DO #'js2-parse-do) | |
7745 (aset parsers js2-FOR #'js2-parse-for) | |
7746 (aset parsers js2-FUNCTION #'js2-function-parser) | |
7747 (aset parsers js2-IF #'js2-parse-if) | |
7748 (aset parsers js2-LC #'js2-parse-block) | |
7749 (aset parsers js2-LET #'js2-parse-let-stmt) | |
7750 (aset parsers js2-NAME #'js2-parse-name-or-label) | |
7751 (aset parsers js2-RETURN #'js2-parse-ret-yield) | |
7752 (aset parsers js2-SEMI #'js2-parse-semi) | |
7753 (aset parsers js2-SWITCH #'js2-parse-switch) | |
7754 (aset parsers js2-THROW #'js2-parse-throw) | |
7755 (aset parsers js2-TRY #'js2-parse-try) | |
7756 (aset parsers js2-VAR #'js2-parse-const-var) | |
7757 (aset parsers js2-WHILE #'js2-parse-while) | |
7758 (aset parsers js2-WITH #'js2-parse-with) | |
7759 (aset parsers js2-YIELD #'js2-parse-ret-yield) | |
7760 parsers) | |
7761 "A vector mapping token types to parser functions.") | |
7762 | |
7763 (defsubst js2-parse-warn-missing-semi (beg end) | |
7764 (and js2-mode-show-strict-warnings | |
7765 js2-strict-missing-semi-warning | |
7766 (js2-add-strict-warning | |
7767 "msg.missing.semi" nil | |
7768 ;; back up to beginning of statement or line | |
7769 (max beg (save-excursion | |
7770 (goto-char end) | |
7771 (point-at-bol))) | |
7772 end))) | |
7773 | |
7774 (defconst js2-no-semi-insertion | |
7775 (list js2-IF | |
7776 js2-SWITCH | |
7777 js2-WHILE | |
7778 js2-DO | |
7779 js2-FOR | |
7780 js2-TRY | |
7781 js2-WITH | |
7782 js2-LC | |
7783 js2-ERROR | |
7784 js2-SEMI | |
7785 js2-FUNCTION) | |
7786 "List of tokens that don't do automatic semicolon insertion.") | |
7787 | |
7788 (defconst js2-autoinsert-semi-and-warn | |
7789 (list js2-ERROR js2-EOF js2-RC)) | |
7790 | |
7791 (defun js2-statement-helper () | |
7792 (let* ((tt (js2-peek-token)) | |
7793 (first-tt tt) | |
7794 (beg js2-token-beg) | |
7795 (parser (if (= tt js2-ERROR) | |
7796 #'js2-parse-semi | |
7797 (aref js2-parsers tt))) | |
7798 pn | |
7799 tt-flagged) | |
7800 ;; If the statement is set, then it's been told its label by now. | |
7801 (and js2-labeled-stmt | |
7802 (js2-labeled-stmt-node-stmt js2-labeled-stmt) | |
7803 (setq js2-labeled-stmt nil)) | |
7804 | |
7805 (setq pn (funcall parser) | |
7806 tt-flagged (js2-peek-flagged-token) | |
7807 tt (logand tt-flagged js2-clear-ti-mask)) | |
7808 | |
7809 ;; Don't do auto semi insertion for certain statement types. | |
7810 (unless (or (memq first-tt js2-no-semi-insertion) | |
7811 (js2-labeled-stmt-node-p pn)) | |
7812 (cond | |
7813 ((= tt js2-SEMI) | |
7814 ;; Consume ';' as a part of expression | |
7815 (js2-consume-token) | |
7816 ;; extend the node bounds to include the semicolon. | |
7817 (setf (js2-node-len pn) (- js2-token-end beg))) | |
7818 ((memq tt js2-autoinsert-semi-and-warn) | |
7819 ;; Autoinsert ; | |
7820 (js2-parse-warn-missing-semi beg (js2-node-end pn))) | |
7821 (t | |
7822 (if (js2-flag-not-set-p tt-flagged js2-ti-after-eol) | |
7823 ;; Report error if no EOL or autoinsert ';' otherwise | |
7824 (js2-report-error "msg.no.semi.stmt") | |
7825 (js2-parse-warn-missing-semi beg (js2-node-end pn)))))) | |
7826 pn)) | |
7827 | |
7828 (defun js2-parse-condition () | |
7829 "Parse a parenthesized boolean expression, e.g. in an if- or while-stmt. | |
7830 The parens are discarded and the expression node is returned. | |
7831 The `pos' field of the return value is set to an absolute position | |
7832 that must be fixed up by the caller. | |
7833 Return value is a list (EXPR LP RP), with absolute paren positions." | |
7834 (let (pn lp rp) | |
7835 (if (js2-must-match js2-LP "msg.no.paren.cond") | |
7836 (setq lp js2-token-beg)) | |
7837 (setq pn (js2-parse-expr)) | |
7838 (if (js2-must-match js2-RP "msg.no.paren.after.cond") | |
7839 (setq rp js2-token-beg)) | |
7840 ;; Report strict warning on code like "if (a = 7) ..." | |
7841 (if (and js2-strict-cond-assign-warning | |
7842 (js2-assign-node-p pn)) | |
7843 (js2-add-strict-warning "msg.equal.as.assign" nil | |
7844 (js2-node-pos pn) | |
7845 (+ (js2-node-pos pn) | |
7846 (js2-node-len pn)))) | |
7847 (list pn lp rp))) | |
7848 | |
7849 (defun js2-parse-if () | |
7850 "Parser for if-statement. Last matched token must be js2-IF." | |
7851 (let ((pos js2-token-beg) | |
7852 cond | |
7853 if-true | |
7854 if-false | |
7855 else-pos | |
7856 end | |
7857 pn) | |
7858 (js2-consume-token) | |
7859 (setq cond (js2-parse-condition) | |
7860 if-true (js2-parse-statement) | |
7861 if-false (if (js2-match-token js2-ELSE) | |
7862 (progn | |
7863 (setq else-pos (- js2-token-beg pos)) | |
7864 (js2-parse-statement))) | |
7865 end (js2-node-end (or if-false if-true)) | |
7866 pn (make-js2-if-node :pos pos | |
7867 :len (- end pos) | |
7868 :condition (car cond) | |
7869 :then-part if-true | |
7870 :else-part if-false | |
7871 :else-pos else-pos | |
7872 :lp (js2-relpos (second cond) pos) | |
7873 :rp (js2-relpos (third cond) pos))) | |
7874 (js2-node-add-children pn (car cond) if-true if-false) | |
7875 pn)) | |
7876 | |
7877 (defun js2-parse-switch () | |
7878 "Parser for if-statement. Last matched token must be js2-SWITCH." | |
7879 (let ((pos js2-token-beg) | |
7880 tt | |
7881 pn | |
7882 discriminant | |
7883 has-default | |
7884 case-expr | |
7885 case-node | |
7886 case-pos | |
7887 cases | |
7888 stmt | |
7889 lp | |
7890 rp) | |
7891 (js2-consume-token) | |
7892 (if (js2-must-match js2-LP "msg.no.paren.switch") | |
7893 (setq lp js2-token-beg)) | |
7894 (setq discriminant (js2-parse-expr) | |
7895 pn (make-js2-switch-node :discriminant discriminant | |
7896 :pos pos | |
7897 :lp (js2-relpos lp pos))) | |
7898 (js2-node-add-children pn discriminant) | |
7899 (js2-enter-switch pn) | |
7900 (unwind-protect | |
7901 (progn | |
7902 (if (js2-must-match js2-RP "msg.no.paren.after.switch") | |
7903 (setf (js2-switch-node-rp pn) (- js2-token-beg pos))) | |
7904 (js2-must-match js2-LC "msg.no.brace.switch") | |
7905 (catch 'break | |
7906 (while t | |
7907 (setq tt (js2-next-token) | |
7908 case-pos js2-token-beg) | |
7909 (cond | |
7910 ((= tt js2-RC) | |
7911 (setf (js2-node-len pn) (- js2-token-end pos)) | |
7912 (throw 'break nil)) ; done | |
7913 | |
7914 ((= tt js2-CASE) | |
7915 (setq case-expr (js2-parse-expr)) | |
7916 (js2-must-match js2-COLON "msg.no.colon.case")) | |
7917 | |
7918 ((= tt js2-DEFAULT) | |
7919 (if has-default | |
7920 (js2-report-error "msg.double.switch.default")) | |
7921 (setq has-default t | |
7922 case-expr nil) | |
7923 (js2-must-match js2-COLON "msg.no.colon.case")) | |
7924 | |
7925 (t | |
7926 (js2-report-error "msg.bad.switch") | |
7927 (throw 'break nil))) | |
7928 | |
7929 (setq case-node (make-js2-case-node :pos case-pos | |
7930 :len (- js2-token-end case-pos) | |
7931 :expr case-expr)) | |
7932 (js2-node-add-children case-node case-expr) | |
7933 (while (and (/= (setq tt (js2-peek-token)) js2-RC) | |
7934 (/= tt js2-CASE) | |
7935 (/= tt js2-DEFAULT) | |
7936 (/= tt js2-EOF)) | |
7937 (setf stmt (js2-parse-statement) | |
7938 (js2-node-len case-node) (- (js2-node-end stmt) case-pos)) | |
7939 (js2-block-node-push case-node stmt)) | |
7940 (push case-node cases))) | |
7941 ;; add cases last, as pushing reverses the order to be correct | |
7942 (dolist (kid cases) | |
7943 (js2-node-add-children pn kid) | |
7944 (push kid (js2-switch-node-cases pn))) | |
7945 pn) ; return value | |
7946 (js2-exit-switch)))) | |
7947 | |
7948 (defun js2-parse-while () | |
7949 "Parser for while-statement. Last matched token must be js2-WHILE." | |
7950 (let ((pos js2-token-beg) | |
7951 (pn (make-js2-while-node)) | |
7952 cond | |
7953 body) | |
7954 (js2-consume-token) | |
7955 (js2-enter-loop pn) | |
7956 (unwind-protect | |
7957 (progn | |
7958 (setf cond (js2-parse-condition) | |
7959 (js2-while-node-condition pn) (car cond) | |
7960 body (js2-parse-statement) | |
7961 (js2-while-node-body pn) body | |
7962 (js2-node-len pn) (- (js2-node-end body) pos) | |
7963 (js2-while-node-lp pn) (js2-relpos (second cond) pos) | |
7964 (js2-while-node-rp pn) (js2-relpos (third cond) pos)) | |
7965 (js2-node-add-children pn body (car cond))) | |
7966 (js2-exit-loop)) | |
7967 pn)) | |
7968 | |
7969 (defun js2-parse-do () | |
7970 "Parser for do-statement. Last matched token must be js2-DO." | |
7971 (let ((pos js2-token-beg) | |
7972 (pn (make-js2-do-node)) | |
7973 cond | |
7974 body | |
7975 end) | |
7976 (js2-consume-token) | |
7977 (js2-enter-loop pn) | |
7978 (unwind-protect | |
7979 (progn | |
7980 (setq body (js2-parse-statement)) | |
7981 (js2-must-match js2-WHILE "msg.no.while.do") | |
7982 (setf (js2-do-node-while-pos pn) (- js2-token-beg pos) | |
7983 cond (js2-parse-condition) | |
7984 (js2-do-node-condition pn) (car cond) | |
7985 (js2-do-node-body pn) body | |
7986 end js2-ts-cursor | |
7987 (js2-do-node-lp pn) (js2-relpos (second cond) pos) | |
7988 (js2-do-node-rp pn) (js2-relpos (third cond) pos)) | |
7989 (js2-node-add-children pn (car cond) body)) | |
7990 (js2-exit-loop)) | |
7991 ;; Always auto-insert semicolon to follow SpiderMonkey: | |
7992 ;; It is required by ECMAScript but is ignored by the rest of | |
7993 ;; world; see bug 238945 | |
7994 (if (js2-match-token js2-SEMI) | |
7995 (setq end js2-ts-cursor)) | |
7996 (setf (js2-node-len pn) (- end pos)) | |
7997 pn)) | |
7998 | |
7999 (defun js2-parse-for () | |
8000 "Parser for for-statement. Last matched token must be js2-FOR. | |
8001 Parses for, for-in, and for each-in statements." | |
8002 (let ((for-pos js2-token-beg) | |
8003 pn | |
8004 is-for-each | |
8005 is-for-in | |
8006 in-pos | |
8007 each-pos | |
8008 tmp-pos | |
8009 init ; Node init is also foo in 'foo in object' | |
8010 cond ; Node cond is also object in 'foo in object' | |
8011 incr ; 3rd section of for-loop initializer | |
8012 body | |
8013 tt | |
8014 lp | |
8015 rp) | |
8016 (js2-consume-token) | |
8017 ;; See if this is a for each () instead of just a for () | |
8018 (when (js2-match-token js2-NAME) | |
8019 (if (string= "each" js2-ts-string) | |
8020 (progn | |
8021 (setq is-for-each t | |
8022 each-pos (- js2-token-beg for-pos)) ; relative | |
8023 (js2-record-face 'font-lock-keyword-face)) | |
8024 (js2-report-error "msg.no.paren.for"))) | |
8025 | |
8026 (if (js2-must-match js2-LP "msg.no.paren.for") | |
8027 (setq lp (- js2-token-beg for-pos))) | |
8028 (setq tt (js2-peek-token)) | |
8029 | |
8030 ;; parse init clause | |
8031 (let ((js2-in-for-init t)) ; set as dynamic variable | |
8032 (cond | |
8033 ((= tt js2-SEMI) | |
8034 (setq init (make-js2-empty-expr-node))) | |
8035 ((or (= tt js2-VAR) (= tt js2-LET)) | |
8036 (js2-consume-token) | |
8037 (setq init (js2-parse-variables tt js2-token-beg))) | |
8038 (t | |
8039 (setq init (js2-parse-expr))))) | |
8040 | |
8041 (if (js2-match-token js2-IN) | |
8042 (setq is-for-in t | |
8043 in-pos (- js2-token-beg for-pos) | |
8044 cond (js2-parse-expr)) ; object over which we're iterating | |
8045 ;; else ordinary for loop - parse cond and incr | |
8046 (js2-must-match js2-SEMI "msg.no.semi.for") | |
8047 (setq cond (if (= (js2-peek-token) js2-SEMI) | |
8048 (make-js2-empty-expr-node) ; no loop condition | |
8049 (js2-parse-expr))) | |
8050 (js2-must-match js2-SEMI "msg.no.semi.for.cond") | |
8051 (setq tmp-pos js2-token-end | |
8052 incr (if (= (js2-peek-token) js2-RP) | |
8053 (make-js2-empty-expr-node :pos tmp-pos) | |
8054 (js2-parse-expr)))) | |
8055 | |
8056 (if (js2-must-match js2-RP "msg.no.paren.for.ctrl") | |
8057 (setq rp (- js2-token-beg for-pos))) | |
8058 (if (not is-for-in) | |
8059 (setq pn (make-js2-for-node :init init | |
8060 :condition cond | |
8061 :update incr | |
8062 :lp lp | |
8063 :rp rp)) | |
8064 ;; cond could be null if 'in obj' got eaten by the init node. | |
8065 (if (js2-infix-node-p init) | |
8066 ;; it was (foo in bar) instead of (var foo in bar) | |
8067 (setq cond (js2-infix-node-right init) | |
8068 init (js2-infix-node-left init)) | |
8069 (if (and (js2-var-decl-node-p init) | |
8070 (> (length (js2-var-decl-node-kids init)) 1)) | |
8071 (js2-report-error "msg.mult.index"))) | |
8072 | |
8073 (setq pn (make-js2-for-in-node :iterator init | |
8074 :object cond | |
8075 :in-pos in-pos | |
8076 :foreach-p is-for-each | |
8077 :each-pos each-pos | |
8078 :lp lp | |
8079 :rp rp))) | |
8080 (unwind-protect | |
8081 (progn | |
8082 (js2-enter-loop pn) | |
8083 ;; We have to parse the body -after- creating the loop node, | |
8084 ;; so that the loop node appears in the js2-loop-set, allowing | |
8085 ;; break/continue statements to find the enclosing loop. | |
8086 (setf body (js2-parse-statement) | |
8087 (js2-loop-node-body pn) body | |
8088 (js2-node-pos pn) for-pos | |
8089 (js2-node-len pn) (- (js2-node-end body) for-pos)) | |
8090 (js2-node-add-children pn init cond incr body)) | |
8091 ;; finally | |
8092 (js2-exit-loop)) | |
8093 pn)) | |
8094 | |
8095 (defun js2-parse-try () | |
8096 "Parser for try-statement. Last matched token must be js2-TRY." | |
8097 (let ((try-pos js2-token-beg) | |
8098 try-end | |
8099 try-block | |
8100 catch-blocks | |
8101 finally-block | |
8102 saw-default-catch | |
8103 peek | |
8104 var-name | |
8105 catch-cond | |
8106 catch-node | |
8107 guard-kwd | |
8108 catch-pos | |
8109 finally-pos | |
8110 pn | |
8111 block | |
8112 lp | |
8113 rp) | |
8114 (js2-consume-token) | |
8115 (if (/= (js2-peek-token) js2-LC) | |
8116 (js2-report-error "msg.no.brace.try")) | |
8117 (setq try-block (js2-parse-statement) | |
8118 try-end (js2-node-end try-block) | |
8119 peek (js2-peek-token)) | |
8120 (cond | |
8121 ((= peek js2-CATCH) | |
8122 (while (js2-match-token js2-CATCH) | |
8123 (setq catch-pos js2-token-beg | |
8124 guard-kwd nil | |
8125 catch-cond nil | |
8126 lp nil | |
8127 rp nil) | |
8128 (if saw-default-catch | |
8129 (js2-report-error "msg.catch.unreachable")) | |
8130 (if (js2-must-match js2-LP "msg.no.paren.catch") | |
8131 (setq lp (- js2-token-beg catch-pos))) | |
8132 | |
8133 (js2-must-match js2-NAME "msg.bad.catchcond") | |
8134 (setq var-name (js2-create-name-node)) | |
8135 | |
8136 (if (js2-match-token js2-IF) | |
8137 (setq guard-kwd (- js2-token-beg catch-pos) | |
8138 catch-cond (js2-parse-expr)) | |
8139 (setq saw-default-catch t)) | |
8140 | |
8141 (if (js2-must-match js2-RP "msg.bad.catchcond") | |
8142 (setq rp (- js2-token-beg catch-pos))) | |
8143 (js2-must-match js2-LC "msg.no.brace.catchblock") | |
8144 | |
8145 (setq block (js2-parse-statements) | |
8146 try-end (js2-node-end block) | |
8147 catch-node (make-js2-catch-node :pos catch-pos | |
8148 :var-name var-name | |
8149 :guard-expr catch-cond | |
8150 :guard-kwd guard-kwd | |
8151 :block block | |
8152 :lp lp | |
8153 :rp rp)) | |
8154 (if (js2-must-match js2-RC "msg.no.brace.after.body") | |
8155 (setq try-end js2-token-beg)) | |
8156 (setf (js2-node-len block) (- try-end (js2-node-pos block)) | |
8157 (js2-node-len catch-node) (- try-end catch-pos)) | |
8158 (js2-node-add-children catch-node var-name catch-cond block) | |
8159 (push catch-node catch-blocks))) | |
8160 | |
8161 ((/= peek js2-FINALLY) | |
8162 (js2-must-match js2-FINALLY "msg.try.no.catchfinally" | |
8163 (js2-node-pos try-block) | |
8164 (- (setq try-end (js2-node-end try-block)) | |
8165 (js2-node-pos try-block))))) | |
8166 | |
8167 (when (js2-match-token js2-FINALLY) | |
8168 (setq finally-pos js2-token-beg | |
8169 block (js2-parse-statement) | |
8170 try-end (js2-node-end block) | |
8171 finally-block (make-js2-finally-node :pos finally-pos | |
8172 :len (- try-end finally-pos) | |
8173 :body block)) | |
8174 (js2-node-add-children finally-block block)) | |
8175 | |
8176 (setq pn (make-js2-try-node :pos try-pos | |
8177 :len (- try-end try-pos) | |
8178 :try-block try-block | |
8179 :finally-block finally-block)) | |
8180 (js2-node-add-children pn try-block finally-block) | |
8181 | |
8182 ;; push them onto the try-node, which reverses and corrects their order | |
8183 (dolist (cb catch-blocks) | |
8184 (js2-node-add-children pn cb) | |
8185 (push cb (js2-try-node-catch-clauses pn))) | |
8186 pn)) | |
8187 | |
8188 (defun js2-parse-throw () | |
8189 "Parser for throw-statement. Last matched token must be js2-THROW." | |
8190 (let ((pos js2-token-beg) | |
8191 expr | |
8192 pn) | |
8193 (js2-consume-token) | |
8194 (if (= (js2-peek-token-or-eol) js2-EOL) | |
8195 ;; ECMAScript does not allow new lines before throw expression, | |
8196 ;; see bug 256617 | |
8197 (js2-report-error "msg.bad.throw.eol")) | |
8198 (setq expr (js2-parse-expr) | |
8199 pn (make-js2-throw-node :pos pos | |
8200 :len (- (js2-node-end expr) pos) | |
8201 :expr expr)) | |
8202 (js2-node-add-children pn expr) | |
8203 pn)) | |
8204 | |
8205 (defsubst js2-match-jump-label-name (label-name) | |
8206 "If break/continue specified a label, return that label's labeled stmt. | |
8207 Returns the corresponding `js2-labeled-stmt-node', or if LABEL-NAME | |
8208 does not match an existing label, reports an error and returns nil." | |
8209 (let ((bundle (cdr (assoc label-name js2-label-set)))) | |
8210 (if (null bundle) | |
8211 (js2-report-error "msg.undef.label")) | |
8212 bundle)) | |
8213 | |
8214 (defun js2-parse-break () | |
8215 "Parser for break-statement. Last matched token must be js2-BREAK." | |
8216 (let ((pos js2-token-beg) | |
8217 (end js2-token-end) | |
8218 break-target ; statement to break from | |
8219 break-label ; in "break foo", name-node representing the foo | |
8220 labels ; matching labeled statement to break to | |
8221 pn) | |
8222 (js2-consume-token) ; `break' | |
8223 (when (eq (js2-peek-token-or-eol) js2-NAME) | |
8224 (js2-consume-token) | |
8225 (setq break-label (js2-create-name-node) | |
8226 end (js2-node-end break-label) | |
8227 ;; matchJumpLabelName only matches if there is one | |
8228 labels (js2-match-jump-label-name js2-ts-string) | |
8229 break-target (if labels (car (js2-labeled-stmt-node-labels labels))))) | |
8230 | |
8231 (unless (or break-target break-label) | |
8232 ;; no break target specified - try for innermost enclosing loop/switch | |
8233 (if (null js2-loop-and-switch-set) | |
8234 (unless break-label | |
8235 (js2-report-error "msg.bad.break" nil pos (length "break"))) | |
8236 (setq break-target (car js2-loop-and-switch-set)))) | |
8237 | |
8238 (setq pn (make-js2-break-node :pos pos | |
8239 :len (- end pos) | |
8240 :label break-label | |
8241 :target break-target)) | |
8242 (js2-node-add-children pn break-label) ; but not break-target | |
8243 pn)) | |
8244 | |
8245 (defun js2-parse-continue () | |
8246 "Parser for continue-statement. Last matched token must be js2-CONTINUE." | |
8247 (let ((pos js2-token-beg) | |
8248 (end js2-token-end) | |
8249 label ; optional user-specified label, a `js2-name-node' | |
8250 labels ; current matching labeled stmt, if any | |
8251 target ; the `js2-loop-node' target of this continue stmt | |
8252 pn) | |
8253 (js2-consume-token) ; `continue' | |
8254 (when (= (js2-peek-token-or-eol) js2-NAME) | |
8255 (js2-consume-token) | |
8256 (setq label (js2-create-name-node) | |
8257 end (js2-node-end label) | |
8258 ;; matchJumpLabelName only matches if there is one | |
8259 labels (js2-match-jump-label-name js2-ts-string))) | |
8260 (cond | |
8261 ((null labels) ; no current label to go to | |
8262 (if (null js2-loop-set) ; no loop to continue to | |
8263 (js2-report-error "msg.continue.outside" nil pos | |
8264 (length "continue")) | |
8265 (setq target (car js2-loop-set)))) ; innermost enclosing loop | |
8266 (t | |
8267 (if (js2-loop-node-p (js2-labeled-stmt-node-stmt labels)) | |
8268 (setq target (js2-labeled-stmt-node-stmt labels)) | |
8269 (js2-report-error "msg.continue.nonloop" nil pos (- end pos))))) | |
8270 | |
8271 (setq pn (make-js2-continue-node :pos pos | |
8272 :len (- end pos) | |
8273 :label label | |
8274 :target target)) | |
8275 (js2-node-add-children pn label) ; but not target - it's not our child | |
8276 pn)) | |
8277 | |
8278 (defun js2-parse-with () | |
8279 "Parser for with-statement. Last matched token must be js2-WITH." | |
8280 (js2-consume-token) | |
8281 (let ((pos js2-token-beg) | |
8282 obj body pn lp rp) | |
8283 | |
8284 (if (js2-must-match js2-LP "msg.no.paren.with") | |
8285 (setq lp js2-token-beg)) | |
8286 | |
8287 (setq obj (js2-parse-expr)) | |
8288 | |
8289 (if (js2-must-match js2-RP "msg.no.paren.after.with") | |
8290 (setq rp js2-token-beg)) | |
8291 | |
8292 (let ((js2-nesting-of-with (1+ js2-nesting-of-with))) | |
8293 (setq body (js2-parse-statement))) | |
8294 | |
8295 (setq pn (make-js2-with-node :pos pos | |
8296 :len (- (js2-node-end body) pos) | |
8297 :object obj | |
8298 :body body | |
8299 :lp (js2-relpos lp pos) | |
8300 :rp (js2-relpos rp pos))) | |
8301 (js2-node-add-children pn obj body) | |
8302 pn)) | |
8303 | |
8304 (defun js2-parse-const-var () | |
8305 "Parser for var- or const-statement. | |
8306 Last matched token must be js2-CONST or js2-VAR." | |
8307 (let ((tt (js2-peek-token)) | |
8308 (pos js2-token-beg) | |
8309 expr | |
8310 pn) | |
8311 (js2-consume-token) | |
8312 (setq expr (js2-parse-variables tt js2-token-beg) | |
8313 pn (make-js2-expr-stmt-node :pos pos | |
8314 :len (- (js2-node-end expr) pos) | |
8315 :expr expr)) | |
8316 (js2-node-add-children pn expr) | |
8317 pn)) | |
8318 | |
8319 (defsubst js2-wrap-with-expr-stmt (pos expr &optional add-child) | |
8320 (let ((pn (make-js2-expr-stmt-node :pos pos | |
8321 :len (js2-node-len expr) | |
8322 :type (if (js2-inside-function) | |
8323 js2-EXPR_VOID | |
8324 js2-EXPR_RESULT) | |
8325 :expr expr))) | |
8326 (if add-child | |
8327 (js2-node-add-children pn expr)) | |
8328 pn)) | |
8329 | |
8330 (defun js2-parse-let-stmt () | |
8331 "Parser for let-statement. Last matched token must be js2-LET." | |
8332 (js2-consume-token) | |
8333 (let ((pos js2-token-beg) | |
8334 expr | |
8335 pn) | |
8336 (if (= (js2-peek-token) js2-LP) | |
8337 ;; let expression in statement context | |
8338 (setq expr (js2-parse-let pos 'statement) | |
8339 pn (js2-wrap-with-expr-stmt pos expr t)) | |
8340 ;; else we're looking at a statement like let x=6, y=7; | |
8341 (setf expr (js2-parse-variables js2-LET pos) | |
8342 pn (js2-wrap-with-expr-stmt pos expr t) | |
8343 (js2-node-type pn) js2-EXPR_RESULT)) | |
8344 pn)) | |
8345 | |
8346 (defun js2-parse-ret-yield () | |
8347 (js2-parse-return-or-yield (js2-peek-token) nil)) | |
8348 | |
8349 (defconst js2-parse-return-stmt-enders | |
8350 (list js2-SEMI js2-RC js2-EOF js2-EOL js2-ERROR js2-RB js2-RP js2-YIELD)) | |
8351 | |
8352 (defsubst js2-now-all-set (before after mask) | |
8353 "Return whether or not the bits in the mask have changed to all set. | |
8354 BEFORE is bits before change, AFTER is bits after change, and MASK is | |
8355 the mask for bits. Returns t if all the bits in the mask are set in AFTER | |
8356 but not BEFORE." | |
8357 (and (/= (logand before mask) mask) | |
8358 (= (logand after mask) mask))) | |
8359 | |
8360 (defun js2-parse-return-or-yield (tt expr-context) | |
8361 (let ((pos js2-token-beg) | |
8362 (end js2-token-end) | |
8363 (before js2-end-flags) | |
8364 (inside-function (js2-inside-function)) | |
8365 e | |
8366 ret | |
8367 name) | |
8368 (unless inside-function | |
8369 (js2-report-error (if (eq tt js2-RETURN) | |
8370 "msg.bad.return" | |
8371 "msg.bad.yield"))) | |
8372 (js2-consume-token) | |
8373 ;; This is ugly, but we don't want to require a semicolon. | |
8374 (unless (memq (js2-peek-token-or-eol) js2-parse-return-stmt-enders) | |
8375 (setq e (js2-parse-expr) | |
8376 end (js2-node-end e))) | |
8377 (cond | |
8378 ((eq tt js2-RETURN) | |
8379 (js2-set-flag js2-end-flags (if (null e) | |
8380 js2-end-returns | |
8381 js2-end-returns-value)) | |
8382 (setq ret (make-js2-return-node :pos pos | |
8383 :len (- end pos) | |
8384 :retval e)) | |
8385 (js2-node-add-children ret e) | |
8386 ;; See if we need a strict mode warning. | |
8387 ;; TODO: The analysis done by `js2-has-consistent-return-usage' is | |
8388 ;; more thorough and accurate than this before/after flag check. | |
8389 ;; E.g. if there's a finally-block that always returns, we shouldn't | |
8390 ;; show a warning generated by inconsistent returns in the catch blocks. | |
8391 ;; Basically `js2-has-consistent-return-usage' needs to keep more state, | |
8392 ;; so we know which returns/yields to highlight, and we should get rid of | |
8393 ;; all the checking in `js2-parse-return-or-yield'. | |
8394 (if (and js2-strict-inconsistent-return-warning | |
8395 (js2-now-all-set before js2-end-flags | |
8396 (logior js2-end-returns js2-end-returns-value))) | |
8397 (js2-add-strict-warning "msg.return.inconsistent" nil pos end))) | |
8398 (t | |
8399 (unless (js2-inside-function) | |
8400 (js2-report-error "msg.bad.yield")) | |
8401 (js2-set-flag js2-end-flags js2-end-yields) | |
8402 (setq ret (make-js2-yield-node :pos pos | |
8403 :len (- end pos) | |
8404 :value e)) | |
8405 (js2-node-add-children ret e) | |
8406 (unless expr-context | |
8407 (setq e ret | |
8408 ret (js2-wrap-with-expr-stmt pos e t)) | |
8409 (js2-set-requires-activation) | |
8410 (js2-set-is-generator)))) | |
8411 | |
8412 ;; see if we are mixing yields and value returns. | |
8413 (when (and inside-function | |
8414 (js2-now-all-set before js2-end-flags | |
8415 (logior js2-end-yields js2-end-returns-value))) | |
8416 (setq name (js2-function-name js2-current-script-or-fn)) | |
8417 (if (zerop (length name)) | |
8418 (js2-report-error "msg.anon.generator.returns" nil pos (- end pos)) | |
8419 (js2-report-error "msg.generator.returns" name pos (- end pos)))) | |
8420 | |
8421 ret)) | |
8422 | |
8423 (defun js2-parse-debugger () | |
8424 (js2-consume-token) | |
8425 (make-js2-keyword-node :type js2-DEBUGGER)) | |
8426 | |
8427 (defun js2-parse-block () | |
8428 "Parser for a curly-delimited statement block. | |
8429 Last token matched must be js2-LC." | |
8430 (let ((pos js2-token-beg) | |
8431 (pn (make-js2-scope))) | |
8432 (js2-consume-token) | |
8433 (js2-push-scope pn) | |
8434 (unwind-protect | |
8435 (progn | |
8436 (js2-parse-statements pn) | |
8437 (js2-must-match js2-RC "msg.no.brace.block") | |
8438 (setf (js2-node-len pn) (- js2-token-end pos))) | |
8439 (js2-pop-scope)) | |
8440 pn)) | |
8441 | |
8442 ;; for js2-ERROR too, to have a node for error recovery to work on | |
8443 (defun js2-parse-semi () | |
8444 "Parse a statement or handle an error. | |
8445 Last matched token is js-SEMI or js-ERROR." | |
8446 (let ((tt (js2-peek-token)) pos len) | |
8447 (js2-consume-token) | |
8448 (if (eq tt js2-SEMI) | |
8449 (make-js2-empty-expr-node :len 1) | |
8450 (setq pos js2-token-beg | |
8451 len (- js2-token-beg pos)) | |
8452 (js2-report-error "msg.syntax" nil pos len) | |
8453 (make-js2-error-node :pos pos :len len)))) | |
8454 | |
8455 (defun js2-parse-default-xml-namespace () | |
8456 "Parse a `default xml namespace = <expr>' e4x statement." | |
8457 (let ((pos js2-token-beg) | |
8458 end len expr unary es) | |
8459 (js2-consume-token) | |
8460 (js2-must-have-xml) | |
8461 (js2-set-requires-activation) | |
8462 (setq len (- js2-ts-cursor pos)) | |
8463 (unless (and (js2-match-token js2-NAME) | |
8464 (string= js2-ts-string "xml")) | |
8465 (js2-report-error "msg.bad.namespace" nil pos len)) | |
8466 (unless (and (js2-match-token js2-NAME) | |
8467 (string= js2-ts-string "namespace")) | |
8468 (js2-report-error "msg.bad.namespace" nil pos len)) | |
8469 (unless (js2-match-token js2-ASSIGN) | |
8470 (js2-report-error "msg.bad.namespace" nil pos len)) | |
8471 (setq expr (js2-parse-expr) | |
8472 end (js2-node-end expr) | |
8473 unary (make-js2-unary-node :type js2-DEFAULTNAMESPACE | |
8474 :pos pos | |
8475 :len (- end pos) | |
8476 :operand expr)) | |
8477 (js2-node-add-children unary expr) | |
8478 (make-js2-expr-stmt-node :pos pos | |
8479 :len (- end pos) | |
8480 :expr unary))) | |
8481 | |
8482 (defun js2-record-label (label bundle) | |
8483 ;; current token should be colon that `js2-parse-primary-expr' left untouched | |
8484 (js2-consume-token) | |
8485 (let ((name (js2-label-node-name label)) | |
8486 labeled-stmt | |
8487 dup) | |
8488 (when (setq labeled-stmt (cdr (assoc name js2-label-set))) | |
8489 ;; flag both labels if possible when used in editing mode | |
8490 (if (and js2-parse-ide-mode | |
8491 (setq dup (js2-get-label-by-name labeled-stmt name))) | |
8492 (js2-report-error "msg.dup.label" nil | |
8493 (js2-node-abs-pos dup) (js2-node-len dup))) | |
8494 (js2-report-error "msg.dup.label" nil | |
8495 (js2-node-pos label) (js2-node-len label))) | |
8496 (js2-labeled-stmt-node-add-label bundle label) | |
8497 (js2-node-add-children bundle label) | |
8498 ;; Add one reference to the bundle per label in `js2-label-set' | |
8499 (push (cons name bundle) js2-label-set))) | |
8500 | |
8501 (defun js2-parse-name-or-label () | |
8502 "Parser for identifier or label. Last token matched must be js2-NAME. | |
8503 Called when we found a name in a statement context. If it's a label, we gather | |
8504 up any following labels and the next non-label statement into a | |
8505 `js2-labeled-stmt-node' bundle and return that. Otherwise we parse an | |
8506 expression and return it wrapped in a `js2-expr-stmt-node'." | |
8507 (let ((pos js2-token-beg) | |
8508 (end js2-token-end) | |
8509 expr | |
8510 stmt | |
8511 pn | |
8512 bundle | |
8513 (continue t)) | |
8514 ;; set check for label and call down to `js2-parse-primary-expr' | |
8515 (js2-set-check-for-label) | |
8516 (setq expr (js2-parse-expr)) | |
8517 | |
8518 (if (/= (js2-node-type expr) js2-LABEL) | |
8519 ;; Parsed non-label expression - wrap with expression stmt. | |
8520 (setq pn (js2-wrap-with-expr-stmt pos expr t)) | |
8521 | |
8522 ;; else parsed a label | |
8523 (setq bundle (make-js2-labeled-stmt-node :pos pos)) | |
8524 (js2-record-label expr bundle) | |
8525 | |
8526 ;; look for more labels | |
8527 (while (and continue (= (js2-peek-token) js2-NAME)) | |
8528 (js2-set-check-for-label) | |
8529 (setq expr (js2-parse-expr)) | |
8530 (if (/= (js2-node-type expr) js2-LABEL) | |
8531 (setq stmt (js2-wrap-with-expr-stmt pos expr t) | |
8532 continue nil) | |
8533 (js2-record-label expr bundle))) | |
8534 | |
8535 ;; no more labels; now parse the labeled statement | |
8536 (unwind-protect | |
8537 (unless stmt | |
8538 (let ((js2-labeled-stmt bundle)) ; bind dynamically | |
8539 (setq stmt (js2-statement-helper)))) | |
8540 ;; remove the labels for this statement from the global set | |
8541 (dolist (label (js2-labeled-stmt-node-labels bundle)) | |
8542 (setq js2-label-set (remove label js2-label-set)))) | |
8543 | |
8544 (setf (js2-labeled-stmt-node-stmt bundle) stmt) | |
8545 (js2-node-add-children bundle stmt) | |
8546 bundle))) | |
8547 | |
8548 (defun js2-parse-expr-stmt () | |
8549 "Default parser in statement context, if no recognized statement found." | |
8550 (js2-wrap-with-expr-stmt js2-token-beg (js2-parse-expr) t)) | |
8551 | |
8552 (defun js2-parse-variables (decl-type pos) | |
8553 "Parse a comma-separated list of variable declarations. | |
8554 Could be a 'var', 'const' or 'let' expression, possibly in a for-loop initializer. | |
8555 | |
8556 DECL-TYPE is a token value: either VAR, CONST, or LET depending on context. | |
8557 For 'var' or 'const', the keyword should be the token last scanned. | |
8558 | |
8559 POS is the position where the node should start. It's sometimes the | |
8560 var/const/let keyword, and other times the beginning of the first token | |
8561 in the first variable declaration. | |
8562 | |
8563 Returns the parsed `js2-var-decl-node' expression node." | |
8564 (let* ((result (make-js2-var-decl-node :decl-type decl-type | |
8565 :pos pos)) | |
8566 destructuring | |
8567 kid-pos | |
8568 tt | |
8569 init | |
8570 name | |
8571 end | |
8572 nbeg nend | |
8573 vi | |
8574 (continue t)) | |
8575 ;; Example: | |
8576 ;; var foo = {a: 1, b: 2}, bar = [3, 4]; | |
8577 ;; var {b: s2, a: s1} = foo, x = 6, y, [s3, s4] = bar; | |
8578 (while continue | |
8579 (setq destructuring nil | |
8580 name nil | |
8581 tt (js2-peek-token) | |
8582 kid-pos js2-token-beg | |
8583 end js2-token-end | |
8584 init nil) | |
8585 (if (or (= tt js2-LB) (= tt js2-LC)) | |
8586 ;; Destructuring assignment, e.g., var [a, b] = ... | |
8587 (setq destructuring (js2-parse-primary-expr) | |
8588 end (js2-node-end destructuring)) | |
8589 ;; Simple variable name | |
8590 (when (js2-must-match js2-NAME "msg.bad.var") | |
8591 (setq name (js2-create-name-node) | |
8592 nbeg js2-token-beg | |
8593 nend js2-token-end | |
8594 end nend) | |
8595 (js2-define-symbol decl-type js2-ts-string name js2-in-for-init))) | |
8596 | |
8597 (when (js2-match-token js2-ASSIGN) | |
8598 (setq init (js2-parse-assign-expr) | |
8599 end (js2-node-end init)) | |
8600 (if (and js2-parse-ide-mode | |
8601 (or (js2-object-node-p init) | |
8602 (js2-function-node-p init))) | |
8603 (js2-record-imenu-functions init name))) | |
8604 | |
8605 (when name | |
8606 (js2-set-face nbeg nend (if (js2-function-node-p init) | |
8607 'font-lock-function-name-face | |
8608 'font-lock-variable-name-face) | |
8609 'record)) | |
8610 | |
8611 (setq vi (make-js2-var-init-node :pos kid-pos | |
8612 :len (- end kid-pos) | |
8613 :type decl-type)) | |
8614 (if destructuring | |
8615 (progn | |
8616 (if (and (null init) (not js2-in-for-init)) | |
8617 (js2-report-error "msg.destruct.assign.no.init")) | |
8618 (setf (js2-var-init-node-target vi) destructuring)) | |
8619 (setf (js2-var-init-node-target vi) name)) | |
8620 (setf (js2-var-init-node-initializer vi) init) | |
8621 (js2-node-add-children vi name destructuring init) | |
8622 | |
8623 (js2-block-node-push result vi) | |
8624 (unless (js2-match-token js2-COMMA) | |
8625 (setq continue nil))) | |
8626 | |
8627 (setf (js2-node-len result) (- end pos)) | |
8628 result)) | |
8629 | |
8630 (defun js2-parse-let (pos &optional stmt-p) | |
8631 "Parse a let expression or statement. | |
8632 A let-expression is of the form `let (vars) expr'. | |
8633 A let-statment is of the form `let (vars) {statements}'. | |
8634 The third form of let is a variable declaration list, handled | |
8635 by `js2-parse-variables'." | |
8636 (let ((pn (make-js2-let-node :pos pos)) | |
8637 beg vars body) | |
8638 (if (js2-must-match js2-LP "msg.no.paren.after.let") | |
8639 (setf (js2-let-node-lp pn) (- js2-token-beg pos))) | |
8640 (js2-push-scope pn) | |
8641 (unwind-protect | |
8642 (progn | |
8643 (setq vars (js2-parse-variables js2-LET js2-token-beg)) | |
8644 (if (js2-must-match js2-RP "msg.no.paren.let") | |
8645 (setf (js2-let-node-rp pn) (- js2-token-beg pos))) | |
8646 (if (and stmt-p (eq (js2-peek-token) js2-LC)) | |
8647 ;; let statement | |
8648 (progn | |
8649 (js2-consume-token) | |
8650 (setf beg js2-token-beg ; position stmt at LC | |
8651 body (js2-parse-statements)) | |
8652 (js2-must-match js2-RC "msg.no.curly.let") | |
8653 (setf (js2-node-len body) (- js2-token-end beg) | |
8654 (js2-node-len pn) (- js2-token-end pos) | |
8655 (js2-let-node-body pn) body | |
8656 (js2-node-type pn) js2-LET)) | |
8657 ;; let expression | |
8658 (setf body (js2-parse-expr) | |
8659 (js2-node-len pn) (- (js2-node-end body) pos) | |
8660 (js2-let-node-body pn) body)) | |
8661 (js2-node-add-children pn vars body)) | |
8662 (js2-pop-scope)) | |
8663 pn)) | |
8664 | |
8665 (defsubst js2-define-new-symbol (decl-type name node) | |
8666 (js2-scope-put-symbol js2-current-scope | |
8667 name | |
8668 (make-js2-symbol decl-type name node))) | |
8669 | |
8670 (defun js2-define-symbol (decl-type name &optional node ignore-not-in-block) | |
8671 "Define a symbol in the current scope. | |
8672 If NODE is non-nil, it is the AST node associated with the symbol." | |
8673 (let* ((defining-scope (js2-get-defining-scope js2-current-scope name)) | |
8674 (symbol (if defining-scope | |
8675 (js2-scope-get-symbol defining-scope name))) | |
8676 (sdt (if symbol (js2-symbol-decl-type symbol) -1))) | |
8677 (cond | |
8678 ((and symbol ; already defined | |
8679 (or (= sdt js2-CONST) ; old version is const | |
8680 (= decl-type js2-CONST) ; new version is const | |
8681 ;; two let-bound vars in this block have same name | |
8682 (and (= sdt js2-LET) | |
8683 (eq defining-scope js2-current-scope)))) | |
8684 (js2-report-error | |
8685 (cond | |
8686 ((= sdt js2-CONST) "msg.const.redecl") | |
8687 ((= sdt js2-LET) "msg.let.redecl") | |
8688 ((= sdt js2-VAR) "msg.var.redecl") | |
8689 ((= sdt js2-FUNCTION) "msg.function.redecl") | |
8690 (t "msg.parm.redecl")) | |
8691 name)) | |
8692 | |
8693 ((= decl-type js2-LET) | |
8694 (if (and (not ignore-not-in-block) | |
8695 (or (= (js2-node-type js2-current-scope) js2-IF) | |
8696 (js2-loop-node-p js2-current-scope))) | |
8697 (js2-report-error "msg.let.decl.not.in.block") | |
8698 (js2-define-new-symbol decl-type name node))) | |
8699 | |
8700 ((or (= decl-type js2-VAR) | |
8701 (= decl-type js2-CONST) | |
8702 (= decl-type js2-FUNCTION)) | |
8703 (if symbol | |
8704 (if (and js2-strict-var-redeclaration-warning (= sdt js2-VAR)) | |
8705 (js2-add-strict-warning "msg.var.redecl" name) | |
8706 (if (and js2-strict-var-hides-function-arg-warning (= sdt js2-LP)) | |
8707 (js2-add-strict-warning "msg.var.hides.arg" name))) | |
8708 (js2-define-new-symbol decl-type name node))) | |
8709 | |
8710 ((= decl-type js2-LP) | |
8711 (if symbol | |
8712 ;; must be duplicate parameter. Second parameter hides the | |
8713 ;; first, so go ahead and add the second pararameter | |
8714 (js2-report-warning "msg.dup.parms" name)) | |
8715 (js2-define-new-symbol decl-type name node)) | |
8716 | |
8717 (t (js2-code-bug))))) | |
8718 | |
8719 (defun js2-parse-expr () | |
8720 (let* ((pn (js2-parse-assign-expr)) | |
8721 (pos (js2-node-pos pn)) | |
8722 left | |
8723 right | |
8724 op-pos) | |
8725 (while (js2-match-token js2-COMMA) | |
8726 (setq op-pos (- js2-token-beg pos)) ; relative | |
8727 (if (= (js2-peek-token) js2-YIELD) | |
8728 (js2-report-error "msg.yield.parenthesized")) | |
8729 (setq right (js2-parse-assign-expr) | |
8730 left pn | |
8731 pn (make-js2-infix-node :type js2-COMMA | |
8732 :pos pos | |
8733 :len (- js2-ts-cursor pos) | |
8734 :op-pos op-pos | |
8735 :left left | |
8736 :right right)) | |
8737 (js2-node-add-children pn left right)) | |
8738 pn)) | |
8739 | |
8740 (defun js2-parse-assign-expr () | |
8741 (let ((tt (js2-peek-token)) | |
8742 (pos js2-token-beg) | |
8743 pn | |
8744 left | |
8745 right | |
8746 op-pos) | |
8747 (if (= tt js2-YIELD) | |
8748 (js2-parse-return-or-yield tt t) | |
8749 ;; not yield - parse assignment expression | |
8750 (setq pn (js2-parse-cond-expr) | |
8751 tt (js2-peek-token)) | |
8752 (when (and (<= js2-first-assign tt) | |
8753 (<= tt js2-last-assign)) | |
8754 (js2-consume-token) | |
8755 (setq op-pos (- js2-token-beg pos) ; relative | |
8756 left pn | |
8757 right (js2-parse-assign-expr) | |
8758 pn (make-js2-assign-node :type tt | |
8759 :pos pos | |
8760 :len (- (js2-node-end right) pos) | |
8761 :op-pos op-pos | |
8762 :left left | |
8763 :right right)) | |
8764 (when js2-parse-ide-mode | |
8765 (js2-highlight-assign-targets pn left right) | |
8766 (if (or (js2-function-node-p right) | |
8767 (js2-object-node-p right)) | |
8768 (js2-record-imenu-functions right left))) | |
8769 ;; do this last so ide checks above can use absolute positions | |
8770 (js2-node-add-children pn left right)) | |
8771 pn))) | |
8772 | |
8773 (defun js2-parse-cond-expr () | |
8774 (let ((pos js2-token-beg) | |
8775 (pn (js2-parse-or-expr)) | |
8776 test-expr | |
8777 if-true | |
8778 if-false | |
8779 q-pos | |
8780 c-pos) | |
8781 (when (js2-match-token js2-HOOK) | |
8782 (setq q-pos (- js2-token-beg pos) | |
8783 if-true (js2-parse-assign-expr)) | |
8784 (js2-must-match js2-COLON "msg.no.colon.cond") | |
8785 (setq c-pos (- js2-token-beg pos) | |
8786 if-false (js2-parse-assign-expr) | |
8787 test-expr pn | |
8788 pn (make-js2-cond-node :pos pos | |
8789 :len (- (js2-node-end if-false) pos) | |
8790 :test-expr test-expr | |
8791 :true-expr if-true | |
8792 :false-expr if-false | |
8793 :q-pos q-pos | |
8794 :c-pos c-pos)) | |
8795 (js2-node-add-children pn test-expr if-true if-false)) | |
8796 pn)) | |
8797 | |
8798 (defun js2-make-binary (type left parser) | |
8799 "Helper for constructing a binary-operator AST node. | |
8800 LEFT is the left-side-expression, already parsed, and the | |
8801 binary operator should have just been matched. | |
8802 PARSER is a function to call to parse the right operand, | |
8803 or a `js2-node' struct if it has already been parsed." | |
8804 (let* ((pos (js2-node-pos left)) | |
8805 (op-pos (- js2-token-beg pos)) | |
8806 (right (if (js2-node-p parser) | |
8807 parser | |
8808 (funcall parser))) | |
8809 (pn (make-js2-infix-node :type type | |
8810 :pos pos | |
8811 :len (- (js2-node-end right) pos) | |
8812 :op-pos op-pos | |
8813 :left left | |
8814 :right right))) | |
8815 (js2-node-add-children pn left right) | |
8816 pn)) | |
8817 | |
8818 (defun js2-parse-or-expr () | |
8819 (let ((pn (js2-parse-and-expr))) | |
8820 (when (js2-match-token js2-OR) | |
8821 (setq pn (js2-make-binary js2-OR | |
8822 pn | |
8823 'js2-parse-or-expr))) | |
8824 pn)) | |
8825 | |
8826 (defun js2-parse-and-expr () | |
8827 (let ((pn (js2-parse-bit-or-expr))) | |
8828 (when (js2-match-token js2-AND) | |
8829 (setq pn (js2-make-binary js2-AND | |
8830 pn | |
8831 'js2-parse-and-expr))) | |
8832 pn)) | |
8833 | |
8834 (defun js2-parse-bit-or-expr () | |
8835 (let ((pn (js2-parse-bit-xor-expr))) | |
8836 (while (js2-match-token js2-BITOR) | |
8837 (setq pn (js2-make-binary js2-BITOR | |
8838 pn | |
8839 'js2-parse-bit-xor-expr))) | |
8840 pn)) | |
8841 | |
8842 (defun js2-parse-bit-xor-expr () | |
8843 (let ((pn (js2-parse-bit-and-expr))) | |
8844 (while (js2-match-token js2-BITXOR) | |
8845 (setq pn (js2-make-binary js2-BITXOR | |
8846 pn | |
8847 'js2-parse-bit-and-expr))) | |
8848 pn)) | |
8849 | |
8850 (defun js2-parse-bit-and-expr () | |
8851 (let ((pn (js2-parse-eq-expr))) | |
8852 (while (js2-match-token js2-BITAND) | |
8853 (setq pn (js2-make-binary js2-BITAND | |
8854 pn | |
8855 'js2-parse-eq-expr))) | |
8856 pn)) | |
8857 | |
8858 (defconst js2-parse-eq-ops | |
8859 (list js2-EQ js2-NE js2-SHEQ js2-SHNE)) | |
8860 | |
8861 (defun js2-parse-eq-expr () | |
8862 (let ((pn (js2-parse-rel-expr)) | |
8863 tt) | |
8864 (while (memq (setq tt (js2-peek-token)) js2-parse-eq-ops) | |
8865 (js2-consume-token) | |
8866 (setq pn (js2-make-binary tt | |
8867 pn | |
8868 'js2-parse-rel-expr))) | |
8869 pn)) | |
8870 | |
8871 (defconst js2-parse-rel-ops | |
8872 (list js2-IN js2-INSTANCEOF js2-LE js2-LT js2-GE js2-GT)) | |
8873 | |
8874 (defun js2-parse-rel-expr () | |
8875 (let ((pn (js2-parse-shift-expr)) | |
8876 (continue t) | |
8877 tt) | |
8878 (while continue | |
8879 (setq tt (js2-peek-token)) | |
8880 (cond | |
8881 ((and js2-in-for-init (= tt js2-IN)) | |
8882 (setq continue nil)) | |
8883 ((memq tt js2-parse-rel-ops) | |
8884 (js2-consume-token) | |
8885 (setq pn (js2-make-binary tt pn 'js2-parse-shift-expr))) | |
8886 (t | |
8887 (setq continue nil)))) | |
8888 pn)) | |
8889 | |
8890 (defconst js2-parse-shift-ops | |
8891 (list js2-LSH js2-URSH js2-RSH)) | |
8892 | |
8893 (defun js2-parse-shift-expr () | |
8894 (let ((pn (js2-parse-add-expr)) | |
8895 tt | |
8896 (continue t)) | |
8897 (while continue | |
8898 (setq tt (js2-peek-token)) | |
8899 (if (memq tt js2-parse-shift-ops) | |
8900 (progn | |
8901 (js2-consume-token) | |
8902 (setq pn (js2-make-binary tt pn 'js2-parse-add-expr))) | |
8903 (setq continue nil))) | |
8904 pn)) | |
8905 | |
8906 (defun js2-parse-add-expr () | |
8907 (let ((pn (js2-parse-mul-expr)) | |
8908 tt | |
8909 (continue t)) | |
8910 (while continue | |
8911 (setq tt (js2-peek-token)) | |
8912 (if (or (= tt js2-ADD) (= tt js2-SUB)) | |
8913 (progn | |
8914 (js2-consume-token) | |
8915 (setq pn (js2-make-binary tt pn 'js2-parse-mul-expr))) | |
8916 (setq continue nil))) | |
8917 pn)) | |
8918 | |
8919 (defconst js2-parse-mul-ops | |
8920 (list js2-MUL js2-DIV js2-MOD)) | |
8921 | |
8922 (defun js2-parse-mul-expr () | |
8923 (let ((pn (js2-parse-unary-expr)) | |
8924 tt | |
8925 (continue t)) | |
8926 (while continue | |
8927 (setq tt (js2-peek-token)) | |
8928 (if (memq tt js2-parse-mul-ops) | |
8929 (progn | |
8930 (js2-consume-token) | |
8931 (setq pn (js2-make-binary tt pn 'js2-parse-unary-expr))) | |
8932 (setq continue nil))) | |
8933 pn)) | |
8934 | |
8935 (defsubst js2-make-unary (type parser &rest args) | |
8936 "Make a unary node of type TYPE. | |
8937 PARSER is either a node (for postfix operators) or a function to call | |
8938 to parse the operand (for prefix operators)." | |
8939 (let* ((pos js2-token-beg) | |
8940 (postfix (js2-node-p parser)) | |
8941 (expr (if postfix | |
8942 parser | |
8943 (apply parser args))) | |
8944 end | |
8945 pn) | |
8946 (if postfix ; e.g. i++ | |
8947 (setq pos (js2-node-pos expr) | |
8948 end js2-token-end) | |
8949 (setq end (js2-node-end expr))) | |
8950 (setq pn (make-js2-unary-node :type type | |
8951 :pos pos | |
8952 :len (- end pos) | |
8953 :operand expr)) | |
8954 (js2-node-add-children pn expr) | |
8955 pn)) | |
8956 | |
8957 (defconst js2-incrementable-node-types | |
8958 (list js2-NAME js2-GETPROP js2-GETELEM js2-GET_REF js2-CALL) | |
8959 "Node types that can be the operand of a ++ or -- operator.") | |
8960 | |
8961 (defsubst js2-check-bad-inc-dec (tt beg end unary) | |
8962 (unless (memq (js2-node-type (js2-unary-node-operand unary)) | |
8963 js2-incrementable-node-types) | |
8964 (js2-report-error (if (= tt js2-INC) | |
8965 "msg.bad.incr" | |
8966 "msg.bad.decr") | |
8967 nil beg (- end beg)))) | |
8968 | |
8969 (defun js2-parse-unary-expr () | |
8970 (let ((tt (js2-peek-token)) | |
8971 pn expr beg end) | |
8972 (cond | |
8973 ((or (= tt js2-VOID) | |
8974 (= tt js2-NOT) | |
8975 (= tt js2-BITNOT) | |
8976 (= tt js2-TYPEOF)) | |
8977 (js2-consume-token) | |
8978 (js2-make-unary tt 'js2-parse-unary-expr)) | |
8979 | |
8980 ((= tt js2-ADD) | |
8981 (js2-consume-token) | |
8982 ;; Convert to special POS token in decompiler and parse tree | |
8983 (js2-make-unary js2-POS 'js2-parse-unary-expr)) | |
8984 | |
8985 ((= tt js2-SUB) | |
8986 (js2-consume-token) | |
8987 ;; Convert to special NEG token in decompiler and parse tree | |
8988 (js2-make-unary js2-NEG 'js2-parse-unary-expr)) | |
8989 | |
8990 ((or (= tt js2-INC) | |
8991 (= tt js2-DEC)) | |
8992 (js2-consume-token) | |
8993 (prog1 | |
8994 (setq beg js2-token-beg | |
8995 end js2-token-end | |
8996 expr (js2-make-unary tt 'js2-parse-member-expr t)) | |
8997 (js2-check-bad-inc-dec tt beg end expr))) | |
8998 | |
8999 ((= tt js2-DELPROP) | |
9000 (js2-consume-token) | |
9001 (js2-make-unary js2-DELPROP 'js2-parse-unary-expr)) | |
9002 | |
9003 ((= tt js2-ERROR) | |
9004 (js2-consume-token) | |
9005 (make-js2-error-node)) ; try to continue | |
9006 | |
9007 ((and (= tt js2-LT) | |
9008 js2-compiler-xml-available) | |
9009 ;; XML stream encountered in expression. | |
9010 (js2-consume-token) | |
9011 (js2-parse-member-expr-tail t (js2-parse-xml-initializer))) | |
9012 (t | |
9013 (setq pn (js2-parse-member-expr t) | |
9014 ;; Don't look across a newline boundary for a postfix incop. | |
9015 tt (js2-peek-token-or-eol)) | |
9016 (when (or (= tt js2-INC) (= tt js2-DEC)) | |
9017 (js2-consume-token) | |
9018 (setf expr pn | |
9019 pn (js2-make-unary tt expr)) | |
9020 (js2-node-set-prop pn 'postfix t) | |
9021 (js2-check-bad-inc-dec tt js2-token-beg js2-token-end pn)) | |
9022 pn)))) | |
9023 | |
9024 (defun js2-parse-xml-initializer () | |
9025 "Parse an E4X XML initializer. | |
9026 I'm parsing it the way Rhino parses it, but without the tree-rewriting. | |
9027 Then I'll postprocess the result, depending on whether we're in IDE | |
9028 mode or codegen mode, and generate the appropriate rewritten AST. | |
9029 IDE mode uses a rich AST that models the XML structure. Codegen mode | |
9030 just concatenates everything and makes a new XML or XMLList out of it." | |
9031 (let ((tt (js2-get-first-xml-token)) | |
9032 pn-xml | |
9033 pn | |
9034 expr | |
9035 kids | |
9036 expr-pos | |
9037 (continue t) | |
9038 (first-token t)) | |
9039 (when (not (or (= tt js2-XML) (= tt js2-XMLEND))) | |
9040 (js2-report-error "msg.syntax")) | |
9041 (setq pn-xml (make-js2-xml-node)) | |
9042 (while continue | |
9043 (if first-token | |
9044 (setq first-token nil) | |
9045 (setq tt (js2-get-next-xml-token))) | |
9046 (cond | |
9047 ;; js2-XML means we found a {expr} in the XML stream. | |
9048 ;; The js2-ts-string is the XML up to the left-curly. | |
9049 ((= tt js2-XML) | |
9050 (push (make-js2-string-node :pos js2-token-beg | |
9051 :len (- js2-ts-cursor js2-token-beg)) | |
9052 kids) | |
9053 (js2-must-match js2-LC "msg.syntax") | |
9054 (setq expr-pos js2-ts-cursor | |
9055 expr (if (eq (js2-peek-token) js2-RC) | |
9056 (make-js2-empty-expr-node :pos expr-pos) | |
9057 (js2-parse-expr))) | |
9058 (js2-must-match js2-RC "msg.syntax") | |
9059 (setq pn (make-js2-xml-js-expr-node :pos (js2-node-pos expr) | |
9060 :len (js2-node-len expr) | |
9061 :expr expr)) | |
9062 (js2-node-add-children pn expr) | |
9063 (push pn kids)) | |
9064 | |
9065 ;; a js2-XMLEND token means we hit the final close-tag. | |
9066 ((= tt js2-XMLEND) | |
9067 (push (make-js2-string-node :pos js2-token-beg | |
9068 :len (- js2-ts-cursor js2-token-beg)) | |
9069 kids) | |
9070 (dolist (kid (nreverse kids)) | |
9071 (js2-block-node-push pn-xml kid)) | |
9072 (setf (js2-node-len pn-xml) (- js2-ts-cursor | |
9073 (js2-node-pos pn-xml)) | |
9074 continue nil)) | |
9075 (t | |
9076 (js2-report-error "msg.syntax") | |
9077 (setq continue nil)))) | |
9078 pn-xml)) | |
9079 | |
9080 | |
9081 (defun js2-parse-argument-list () | |
9082 "Parse an argument list and return it as a lisp list of nodes. | |
9083 Returns the list in reverse order. Consumes the right-paren token." | |
9084 (let (result) | |
9085 (unless (js2-match-token js2-RP) | |
9086 (loop do | |
9087 (if (= (js2-peek-token) js2-YIELD) | |
9088 (js2-report-error "msg.yield.parenthesized")) | |
9089 (push (js2-parse-assign-expr) result) | |
9090 while | |
9091 (js2-match-token js2-COMMA)) | |
9092 (js2-must-match js2-RP "msg.no.paren.arg") | |
9093 result))) | |
9094 | |
9095 (defun js2-parse-member-expr (&optional allow-call-syntax) | |
9096 (let ((tt (js2-peek-token)) | |
9097 pn | |
9098 pos | |
9099 target | |
9100 args | |
9101 beg | |
9102 end | |
9103 init | |
9104 tail) | |
9105 (if (/= tt js2-NEW) | |
9106 (setq pn (js2-parse-primary-expr)) | |
9107 ;; parse a 'new' expression | |
9108 (js2-consume-token) | |
9109 (setq pos js2-token-beg | |
9110 beg pos | |
9111 target (js2-parse-member-expr) | |
9112 end (js2-node-end target) | |
9113 pn (make-js2-new-node :pos pos | |
9114 :target target | |
9115 :len (- end pos))) | |
9116 (js2-node-add-children pn target) | |
9117 (when (js2-match-token js2-LP) | |
9118 ;; Add the arguments to pn, if any are supplied. | |
9119 (setf beg pos ; start of "new" keyword | |
9120 pos js2-token-beg | |
9121 args (nreverse (js2-parse-argument-list)) | |
9122 (js2-new-node-args pn) args | |
9123 end js2-token-end | |
9124 (js2-new-node-lp pn) (- pos beg) | |
9125 (js2-new-node-rp pn) (- end 1 beg)) | |
9126 (apply #'js2-node-add-children pn args)) | |
9127 | |
9128 (when (and js2-allow-rhino-new-expr-initializer | |
9129 (js2-match-token js2-LC)) | |
9130 (setf init (js2-parse-object-literal) | |
9131 end (js2-node-end init) | |
9132 (js2-new-node-initializer pn) init) | |
9133 (js2-node-add-children pn init)) | |
9134 | |
9135 (setf (js2-node-len pn) (- beg pos))) ; end outer if | |
9136 | |
9137 (js2-parse-member-expr-tail allow-call-syntax pn))) | |
9138 | |
9139 (defun js2-parse-member-expr-tail (allow-call-syntax pn) | |
9140 "Parse a chain of property/array accesses or function calls. | |
9141 Includes parsing for E4X operators like `..' and `.@'. | |
9142 If ALLOW-CALL-SYNTAX is nil, stops when we encounter a left-paren. | |
9143 Returns an expression tree that includes PN, the parent node." | |
9144 (let ((beg (js2-node-pos pn)) | |
9145 tt | |
9146 (continue t)) | |
9147 (while continue | |
9148 (setq tt (js2-peek-token)) | |
9149 (cond | |
9150 ((or (= tt js2-DOT) (= tt js2-DOTDOT)) | |
9151 (setq pn (js2-parse-property-access tt pn))) | |
9152 | |
9153 ((= tt js2-DOTQUERY) | |
9154 (setq pn (js2-parse-dot-query pn))) | |
9155 | |
9156 ((= tt js2-LB) | |
9157 (setq pn (js2-parse-element-get pn))) | |
9158 | |
9159 ((= tt js2-LP) | |
9160 (if allow-call-syntax | |
9161 (setq pn (js2-parse-function-call pn)) | |
9162 (setq continue nil))) | |
9163 (t | |
9164 (setq continue nil)))) | |
9165 (if (>= js2-highlight-level 2) | |
9166 (js2-parse-highlight-member-expr-node pn)) | |
9167 pn)) | |
9168 | |
9169 (defun js2-parse-dot-query (pn) | |
9170 "Parse a dot-query expression, e.g. foo.bar.(@name == 2) | |
9171 Last token parsed must be `js2-DOTQUERY'." | |
9172 (let ((pos (js2-node-pos pn)) | |
9173 op-pos | |
9174 expr | |
9175 end) | |
9176 (js2-consume-token) | |
9177 (js2-must-have-xml) | |
9178 (js2-set-requires-activation) | |
9179 (setq op-pos js2-token-beg | |
9180 expr (js2-parse-expr) | |
9181 end (js2-node-end expr) | |
9182 pn (make-js2-xml-dot-query-node :left pn | |
9183 :pos pos | |
9184 :op-pos op-pos | |
9185 :right expr)) | |
9186 (js2-node-add-children pn | |
9187 (js2-xml-dot-query-node-left pn) | |
9188 (js2-xml-dot-query-node-right pn)) | |
9189 (if (js2-must-match js2-RP "msg.no.paren") | |
9190 (setf (js2-xml-dot-query-node-rp pn) js2-token-beg | |
9191 end js2-token-end)) | |
9192 (setf (js2-node-len pn) (- end pos)) | |
9193 pn)) | |
9194 | |
9195 (defun js2-parse-element-get (pn) | |
9196 "Parse an element-get expression, e.g. foo[bar]. | |
9197 Last token parsed must be `js2-RB'." | |
9198 (let ((lb js2-token-beg) | |
9199 (pos (js2-node-pos pn)) | |
9200 rb | |
9201 expr) | |
9202 (js2-consume-token) | |
9203 (setq expr (js2-parse-expr)) | |
9204 (if (js2-must-match js2-RB "msg.no.bracket.index") | |
9205 (setq rb js2-token-beg)) | |
9206 (setq pn (make-js2-elem-get-node :target pn | |
9207 :pos pos | |
9208 :element expr | |
9209 :lb (js2-relpos lb pos) | |
9210 :rb (js2-relpos rb pos) | |
9211 :len (- js2-token-end pos))) | |
9212 (js2-node-add-children pn | |
9213 (js2-elem-get-node-target pn) | |
9214 (js2-elem-get-node-element pn)) | |
9215 pn)) | |
9216 | |
9217 (defun js2-parse-function-call (pn) | |
9218 (let (args | |
9219 (pos (js2-node-pos pn))) | |
9220 (js2-consume-token) | |
9221 (setq pn (make-js2-call-node :pos pos | |
9222 :target pn | |
9223 :lp (- js2-token-beg pos))) | |
9224 (js2-node-add-children pn (js2-call-node-target pn)) | |
9225 | |
9226 ;; Add the arguments to pn, if any are supplied. | |
9227 (setf args (nreverse (js2-parse-argument-list)) | |
9228 (js2-call-node-rp pn) (- js2-token-beg pos) | |
9229 (js2-call-node-args pn) args) | |
9230 (apply #'js2-node-add-children pn args) | |
9231 | |
9232 (setf (js2-node-len pn) (- js2-ts-cursor pos)) | |
9233 pn)) | |
9234 | |
9235 (defun js2-parse-property-access (tt pn) | |
9236 "Parse a property access, XML descendants access, or XML attr access." | |
9237 (let ((member-type-flags 0) | |
9238 (dot-pos js2-token-beg) | |
9239 (dot-len (if (= tt js2-DOTDOT) 2 1)) | |
9240 name | |
9241 ref ; right side of . or .. operator | |
9242 result) | |
9243 (js2-consume-token) | |
9244 (when (= tt js2-DOTDOT) | |
9245 (js2-must-have-xml) | |
9246 (setq member-type-flags js2-descendants-flag)) | |
9247 (if (not js2-compiler-xml-available) | |
9248 (progn | |
9249 (js2-must-match-prop-name "msg.no.name.after.dot") | |
9250 (setq name (js2-create-name-node t js2-GETPROP) | |
9251 result (make-js2-prop-get-node :left pn | |
9252 :pos js2-token-beg | |
9253 :right name | |
9254 :len (- js2-token-end | |
9255 js2-token-beg))) | |
9256 (js2-node-add-children result pn name) | |
9257 result) | |
9258 ;; otherwise look for XML operators | |
9259 (setf result (if (= tt js2-DOT) | |
9260 (make-js2-prop-get-node) | |
9261 (make-js2-infix-node :type js2-DOTDOT)) | |
9262 (js2-node-pos result) (js2-node-pos pn) | |
9263 (js2-infix-node-op-pos result) dot-pos | |
9264 (js2-infix-node-left result) pn ; do this after setting position | |
9265 tt (js2-next-token)) | |
9266 (cond | |
9267 ;; needed for generator.throw() | |
9268 ((= tt js2-THROW) | |
9269 (js2-save-name-token-data js2-token-beg "throw") | |
9270 (setq ref (js2-parse-property-name nil js2-ts-string member-type-flags))) | |
9271 | |
9272 ;; handles: name, ns::name, ns::*, ns::[expr] | |
9273 ((js2-valid-prop-name-token tt) | |
9274 (setq ref (js2-parse-property-name -1 js2-ts-string member-type-flags))) | |
9275 | |
9276 ;; handles: *, *::name, *::*, *::[expr] | |
9277 ((= tt js2-MUL) | |
9278 (js2-save-name-token-data js2-token-beg "*") | |
9279 (setq ref (js2-parse-property-name nil "*" member-type-flags))) | |
9280 | |
9281 ;; handles: '@attr', '@ns::attr', '@ns::*', '@ns::[expr]', etc. | |
9282 ((= tt js2-XMLATTR) | |
9283 (setq result (js2-parse-attribute-access))) | |
9284 | |
9285 (t | |
9286 (js2-report-error "msg.no.name.after.dot" nil dot-pos dot-len))) | |
9287 | |
9288 (if ref | |
9289 (setf (js2-node-len result) (- (js2-node-end ref) | |
9290 (js2-node-pos result)) | |
9291 (js2-infix-node-right result) ref)) | |
9292 (if (js2-infix-node-p result) | |
9293 (js2-node-add-children result | |
9294 (js2-infix-node-left result) | |
9295 (js2-infix-node-right result))) | |
9296 result))) | |
9297 | |
9298 (defun js2-parse-attribute-access () | |
9299 "Parse an E4X XML attribute expression. | |
9300 This includes expressions of the forms: | |
9301 | |
9302 @attr @ns::attr @ns::* | |
9303 @* @*::attr @*::* | |
9304 @[expr] @*::[expr] @ns::[expr] | |
9305 | |
9306 Called if we peeked an '@' token." | |
9307 (let ((tt (js2-next-token)) | |
9308 (at-pos js2-token-beg)) | |
9309 (cond | |
9310 ;; handles: @name, @ns::name, @ns::*, @ns::[expr] | |
9311 ((js2-valid-prop-name-token tt) | |
9312 (js2-parse-property-name at-pos js2-ts-string 0)) | |
9313 | |
9314 ;; handles: @*, @*::name, @*::*, @*::[expr] | |
9315 ((= tt js2-MUL) | |
9316 (js2-save-name-token-data js2-token-beg "*") | |
9317 (js2-parse-property-name js2-token-beg "*" 0)) | |
9318 | |
9319 ;; handles @[expr] | |
9320 ((= tt js2-LB) | |
9321 (js2-parse-xml-elem-ref at-pos)) | |
9322 | |
9323 (t | |
9324 (js2-report-error "msg.no.name.after.xmlAttr") | |
9325 ;; Avoid cascaded errors that happen if we make an error node here. | |
9326 (js2-save-name-token-data js2-token-beg "") | |
9327 (js2-parse-property-name js2-token-beg "" 0))))) | |
9328 | |
9329 (defun js2-parse-property-name (at-pos s member-type-flags) | |
9330 "Check if :: follows name in which case it becomes qualified name. | |
9331 | |
9332 AT-POS is a natural number if we just read an '@' token, else nil. | |
9333 S is the name or string that was matched: an identifier, 'throw' or '*'. | |
9334 MEMBER-TYPE-FLAGS is a bit set tracking whether we're a '.' or '..' child. | |
9335 | |
9336 Returns a `js2-xml-ref-node' if it's an attribute access, a child of a '..' | |
9337 operator, or the name is followed by ::. For a plain name, returns a | |
9338 `js2-name-node'. Returns a `js2-error-node' for malformed XML expressions." | |
9339 (let ((pos (or at-pos js2-token-beg)) | |
9340 colon-pos | |
9341 (name (js2-create-name-node t js2-current-token)) | |
9342 ns | |
9343 tt | |
9344 ref | |
9345 pn) | |
9346 (catch 'return | |
9347 (when (js2-match-token js2-COLONCOLON) | |
9348 (setq ns name | |
9349 colon-pos js2-token-beg | |
9350 tt (js2-next-token)) | |
9351 (cond | |
9352 ;; handles name::name | |
9353 ((js2-valid-prop-name-token tt) | |
9354 (setq name (js2-create-name-node))) | |
9355 | |
9356 ;; handles name::* | |
9357 ((= tt js2-MUL) | |
9358 (js2-save-name-token-data js2-token-beg "*") | |
9359 (setq name (js2-create-name-node))) | |
9360 | |
9361 ;; handles name::[expr] | |
9362 ((= tt js2-LB) | |
9363 (throw 'return (js2-parse-xml-elem-ref at-pos ns colon-pos))) | |
9364 | |
9365 (t | |
9366 (js2-report-error "msg.no.name.after.coloncolon")))) | |
9367 | |
9368 (if (and (null ns) (zerop member-type-flags)) | |
9369 name | |
9370 (prog1 | |
9371 (setq pn | |
9372 (make-js2-xml-prop-ref-node :pos pos | |
9373 :len (- (js2-node-end name) pos) | |
9374 :at-pos at-pos | |
9375 :colon-pos colon-pos | |
9376 :propname name)) | |
9377 (js2-node-add-children pn name)))))) | |
9378 | |
9379 (defun js2-parse-xml-elem-ref (at-pos &optional namespace colon-pos) | |
9380 "Parse the [expr] portion of an xml element reference. | |
9381 For instance, @[expr], @*::[expr], or ns::[expr]." | |
9382 (let* ((lb js2-token-beg) | |
9383 (pos (or at-pos lb)) | |
9384 rb | |
9385 (expr (js2-parse-expr)) | |
9386 (end (js2-node-end expr)) | |
9387 pn) | |
9388 (if (js2-must-match js2-RB "msg.no.bracket.index") | |
9389 (setq rb js2-token-beg | |
9390 end js2-token-end)) | |
9391 (prog1 | |
9392 (setq pn | |
9393 (make-js2-xml-elem-ref-node :pos pos | |
9394 :len (- end pos) | |
9395 :namespace namespace | |
9396 :colon-pos colon-pos | |
9397 :at-pos at-pos | |
9398 :expr expr | |
9399 :lb (js2-relpos lb pos) | |
9400 :rb (js2-relpos rb pos))) | |
9401 (js2-node-add-children pn namespace expr)))) | |
9402 | |
9403 (defun js2-parse-primary-expr () | |
9404 "Parses a literal (leaf) expression of some sort. | |
9405 Includes complex literals such as functions, object-literals, | |
9406 array-literals, array comprehensions and regular expressions." | |
9407 (let ((tt-flagged (js2-next-flagged-token)) | |
9408 pn ; parent node (usually return value) | |
9409 tt | |
9410 px-pos ; paren-expr pos | |
9411 len | |
9412 flags ; regexp flags | |
9413 expr) | |
9414 (setq tt js2-current-token) | |
9415 (cond | |
9416 ((= tt js2-FUNCTION) | |
9417 (js2-parse-function 'FUNCTION_EXPRESSION)) | |
9418 | |
9419 ((= tt js2-LB) | |
9420 (js2-parse-array-literal)) | |
9421 | |
9422 ((= tt js2-LC) | |
9423 (js2-parse-object-literal)) | |
9424 | |
9425 ((= tt js2-LET) | |
9426 (js2-parse-let js2-token-beg)) | |
9427 | |
9428 ((= tt js2-LP) | |
9429 (setq px-pos js2-token-beg | |
9430 expr (js2-parse-expr)) | |
9431 (js2-must-match js2-RP "msg.no.paren") | |
9432 (setq pn (make-js2-paren-node :pos px-pos | |
9433 :expr expr | |
9434 :len (- js2-token-end px-pos))) | |
9435 (js2-node-add-children pn (js2-paren-node-expr pn)) | |
9436 pn) | |
9437 | |
9438 ((= tt js2-XMLATTR) | |
9439 (js2-must-have-xml) | |
9440 (js2-parse-attribute-access)) | |
9441 | |
9442 ((= tt js2-NAME) | |
9443 (js2-parse-name tt-flagged tt)) | |
9444 | |
9445 ((= tt js2-NUMBER) | |
9446 (make-js2-number-node)) | |
9447 | |
9448 ((= tt js2-STRING) | |
9449 (prog1 | |
9450 (make-js2-string-node) | |
9451 (js2-record-face 'font-lock-string-face))) | |
9452 | |
9453 ((or (= tt js2-DIV) (= tt js2-ASSIGN_DIV)) | |
9454 ;; Got / or /= which in this context means a regexp literal | |
9455 (setq px-pos js2-token-beg) | |
9456 (js2-read-regexp tt) | |
9457 (setq flags js2-ts-regexp-flags | |
9458 js2-ts-regexp-flags nil) | |
9459 (prog1 | |
9460 (make-js2-regexp-node :pos px-pos | |
9461 :len (- js2-ts-cursor px-pos) | |
9462 :value js2-ts-string | |
9463 :flags flags) | |
9464 (js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record))) | |
9465 | |
9466 ((or (= tt js2-NULL) | |
9467 (= tt js2-THIS) | |
9468 (= tt js2-FALSE) | |
9469 (= tt js2-TRUE)) | |
9470 (make-js2-keyword-node :type tt)) | |
9471 | |
9472 ((= tt js2-RESERVED) | |
9473 (js2-report-error "msg.reserved.id") | |
9474 (make-js2-name-node)) | |
9475 | |
9476 ((= tt js2-ERROR) | |
9477 ;; the scanner or one of its subroutines reported the error. | |
9478 (make-js2-error-node)) | |
9479 | |
9480 ((= tt js2-EOF) | |
9481 (setq px-pos (point-at-bol) | |
9482 len (- js2-ts-cursor px-pos)) | |
9483 (js2-report-error "msg.unexpected.eof" nil px-pos len) | |
9484 (make-js2-error-node :pos px-pos :len len)) | |
9485 | |
9486 (t | |
9487 (js2-report-error "msg.syntax") | |
9488 (make-js2-error-node))))) | |
9489 | |
9490 (defun js2-parse-name (tt-flagged tt) | |
9491 (let ((name js2-ts-string) | |
9492 (name-pos js2-token-beg)) | |
9493 (if (and (js2-flag-set-p tt-flagged js2-ti-check-label) | |
9494 (= (js2-peek-token) js2-COLON)) | |
9495 (prog1 | |
9496 ;; Do not consume colon, it is used as unwind indicator | |
9497 ;; to return to statementHelper. | |
9498 (make-js2-label-node :pos name-pos | |
9499 :len (- js2-token-end name-pos) | |
9500 :name name) | |
9501 (js2-set-face name-pos | |
9502 js2-token-end | |
9503 'font-lock-variable-name-face 'record)) | |
9504 ;; Otherwise not a label, just a name. Unfortunately peeking | |
9505 ;; the next token to check for a colon has biffed js2-token-beg | |
9506 ;; and js2-token-end. We store the name's bounds in buffer vars | |
9507 ;; and `js2-create-name-node' uses them. | |
9508 (js2-save-name-token-data name-pos name) | |
9509 (if js2-compiler-xml-available | |
9510 (js2-parse-property-name nil name 0) | |
9511 (js2-create-name-node 'check-activation))))) | |
9512 | |
9513 (defsubst js2-parse-warn-trailing-comma (msg pos elems comma-pos) | |
9514 (js2-add-strict-warning | |
9515 msg nil | |
9516 ;; back up from comma to beginning of line or array/objlit | |
9517 (max (if elems | |
9518 (js2-node-pos (car elems)) | |
9519 pos) | |
9520 (save-excursion | |
9521 (goto-char comma-pos) | |
9522 (back-to-indentation) | |
9523 (point))) | |
9524 comma-pos)) | |
9525 | |
9526 (defun js2-parse-array-literal () | |
9527 (let ((pos js2-token-beg) | |
9528 (end js2-token-end) | |
9529 (after-lb-or-comma t) | |
9530 after-comma | |
9531 tt | |
9532 elems | |
9533 pn | |
9534 (continue t)) | |
9535 (while continue | |
9536 (setq tt (js2-peek-token)) | |
9537 (cond | |
9538 ;; comma | |
9539 ((= tt js2-COMMA) | |
9540 (js2-consume-token) | |
9541 (setq after-comma js2-token-end) | |
9542 (if (not after-lb-or-comma) | |
9543 (setq after-lb-or-comma t) | |
9544 (push nil elems))) | |
9545 | |
9546 ;; end of array | |
9547 ((or (= tt js2-RB) | |
9548 (= tt js2-EOF)) ; prevent infinite loop | |
9549 (if (= tt js2-EOF) | |
9550 (js2-report-error "msg.no.bracket.arg" nil pos) | |
9551 (js2-consume-token)) | |
9552 (setq continue nil | |
9553 end js2-token-end | |
9554 pn (make-js2-array-node :pos pos | |
9555 :len (- js2-ts-cursor pos) | |
9556 :elems (nreverse elems))) | |
9557 (apply #'js2-node-add-children pn (js2-array-node-elems pn)) | |
9558 (when after-comma | |
9559 (js2-parse-warn-trailing-comma "msg.array.trailing.comma" | |
9560 pos elems after-comma))) | |
9561 | |
9562 ;; array comp | |
9563 ((and (>= js2-language-version 170) | |
9564 (= tt js2-FOR) ; check for array comprehension | |
9565 (not after-lb-or-comma) ; "for" can't follow a comma | |
9566 elems ; must have at least 1 element | |
9567 (not (cdr elems))) ; but no 2nd element | |
9568 (setf continue nil | |
9569 pn (js2-parse-array-comprehension (car elems) pos))) | |
9570 | |
9571 ;; another element | |
9572 (t | |
9573 (unless after-lb-or-comma | |
9574 (js2-report-error "msg.no.bracket.arg")) | |
9575 (push (js2-parse-assign-expr) elems) | |
9576 (setq after-lb-or-comma nil | |
9577 after-comma nil)))) | |
9578 pn)) | |
9579 | |
9580 (defun js2-parse-array-comprehension (expr pos) | |
9581 "Parse a JavaScript 1.7 Array Comprehension. | |
9582 EXPR is the first expression after the opening left-bracket. | |
9583 POS is the beginning of the LB token preceding EXPR. | |
9584 We should have just parsed the 'for' keyword before calling this function." | |
9585 (let (loops | |
9586 filter | |
9587 if-pos | |
9588 result) | |
9589 (while (= (js2-peek-token) js2-FOR) | |
9590 (push (js2-parse-array-comp-loop) loops)) | |
9591 (when (= (js2-peek-token) js2-IF) | |
9592 (js2-consume-token) | |
9593 (setq if-pos (- js2-token-beg pos) ; relative | |
9594 filter (js2-parse-condition))) | |
9595 (js2-must-match js2-RB "msg.no.bracket.arg" pos) | |
9596 (setq result (make-js2-array-comp-node :pos pos | |
9597 :len (- js2-ts-cursor pos) | |
9598 :result expr | |
9599 :loops (nreverse loops) | |
9600 :filter (car filter) | |
9601 :lp (js2-relpos (second filter) pos) | |
9602 :rp (js2-relpos (third filter) pos) | |
9603 :if-pos if-pos)) | |
9604 (apply #'js2-node-add-children result expr (car filter) | |
9605 (js2-array-comp-node-loops result)) | |
9606 result)) | |
9607 | |
9608 (defun js2-parse-array-comp-loop () | |
9609 "Parse a 'for [each] (foo in bar)' expression in an Array comprehension. | |
9610 Last token peeked should be the initial FOR." | |
9611 (let ((pos js2-token-beg) | |
9612 (pn (make-js2-array-comp-loop-node)) | |
9613 tt | |
9614 iter | |
9615 obj | |
9616 foreach-p | |
9617 in-pos | |
9618 each-pos | |
9619 lp | |
9620 rp) | |
9621 (assert (= (js2-next-token) js2-FOR)) ; consumes token | |
9622 (js2-push-scope pn) | |
9623 (unwind-protect | |
9624 (progn | |
9625 (when (js2-match-token js2-NAME) | |
9626 (if (string= js2-ts-string "each") | |
9627 (progn | |
9628 (setq foreach-p t | |
9629 each-pos (- js2-token-beg pos)) ; relative | |
9630 (js2-record-face 'font-lock-keyword-face)) | |
9631 (js2-report-error "msg.no.paren.for"))) | |
9632 | |
9633 (if (js2-must-match js2-LP "msg.no.paren.for") | |
9634 (setq lp (- js2-token-beg pos))) | |
9635 | |
9636 (setq tt (js2-peek-token)) | |
9637 (cond | |
9638 ((or (= tt js2-LB) | |
9639 (= tt js2-LC)) | |
9640 ;; handle destructuring assignment | |
9641 (setq iter (js2-parse-primary-expr))) | |
9642 | |
9643 ((js2-valid-prop-name-token tt) | |
9644 (js2-consume-token) | |
9645 (setq iter (js2-create-name-node))) | |
9646 | |
9647 (t | |
9648 (js2-report-error "msg.bad.var"))) | |
9649 | |
9650 ;; Define as a let since we want the scope of the variable to | |
9651 ;; be restricted to the array comprehension | |
9652 (if (js2-name-node-p iter) | |
9653 (js2-define-symbol js2-LET (js2-name-node-name iter) pn t)) | |
9654 | |
9655 (if (js2-must-match js2-IN "msg.in.after.for.name") | |
9656 (setq in-pos (- js2-token-beg pos))) | |
9657 | |
9658 (setq obj (js2-parse-expr)) | |
9659 (if (js2-must-match js2-RP "msg.no.paren.for.ctrl") | |
9660 (setq rp (- js2-token-beg pos))) | |
9661 | |
9662 (setf (js2-node-pos pn) pos | |
9663 (js2-node-len pn) (- js2-ts-cursor pos) | |
9664 (js2-array-comp-loop-node-iterator pn) iter | |
9665 (js2-array-comp-loop-node-object pn) obj | |
9666 (js2-array-comp-loop-node-in-pos pn) in-pos | |
9667 (js2-array-comp-loop-node-each-pos pn) each-pos | |
9668 (js2-array-comp-loop-node-foreach-p pn) foreach-p | |
9669 (js2-array-comp-loop-node-lp pn) lp | |
9670 (js2-array-comp-loop-node-rp pn) rp) | |
9671 (js2-node-add-children pn iter obj)) | |
9672 (js2-pop-scope)) | |
9673 pn)) | |
9674 | |
9675 (defun js2-parse-object-literal () | |
9676 (let ((pos js2-token-beg) | |
9677 tt | |
9678 elems | |
9679 result | |
9680 after-comma | |
9681 (continue t)) | |
9682 (while continue | |
9683 (setq tt (js2-peek-token)) | |
9684 (cond | |
9685 ;; {foo: ...}, {'foo': ...}, {get foo() {...}}, or {set foo(x) {...}} | |
9686 ((or (js2-valid-prop-name-token tt) | |
9687 (= tt js2-STRING)) | |
9688 (setq after-comma nil | |
9689 result (js2-parse-named-prop tt)) | |
9690 (if (and (null result) | |
9691 (not js2-recover-from-parse-errors)) | |
9692 (setq continue nil) | |
9693 (push result elems))) | |
9694 | |
9695 ;; {12: x} or {10.7: x} | |
9696 ((= tt js2-NUMBER) | |
9697 (js2-consume-token) | |
9698 (setq after-comma nil) | |
9699 (push (js2-parse-plain-property (make-js2-number-node)) elems)) | |
9700 | |
9701 ;; trailing comma | |
9702 ((= tt js2-RC) | |
9703 (setq continue nil) | |
9704 (if after-comma | |
9705 (js2-parse-warn-trailing-comma "msg.extra.trailing.comma" | |
9706 pos elems after-comma))) | |
9707 (t | |
9708 (js2-report-error "msg.bad.prop") | |
9709 (unless js2-recover-from-parse-errors | |
9710 (setq continue nil)))) ; end switch | |
9711 | |
9712 (if (js2-match-token js2-COMMA) | |
9713 (setq after-comma js2-token-end) | |
9714 (setq continue nil))) ; end loop | |
9715 | |
9716 (js2-must-match js2-RC "msg.no.brace.prop") | |
9717 (setq result (make-js2-object-node :pos pos | |
9718 :len (- js2-ts-cursor pos) | |
9719 :elems (nreverse elems))) | |
9720 (apply #'js2-node-add-children result (js2-object-node-elems result)) | |
9721 result)) | |
9722 | |
9723 (defun js2-parse-named-prop (tt) | |
9724 "Parse a name, string, or getter/setter object property." | |
9725 (js2-consume-token) | |
9726 (let ((string-prop (and (= tt js2-STRING) | |
9727 (make-js2-string-node))) | |
9728 expr | |
9729 (ppos js2-token-beg) | |
9730 (pend js2-token-end) | |
9731 (name (js2-create-name-node)) | |
9732 (prop js2-ts-string)) | |
9733 | |
9734 (if (and (= tt js2-NAME) | |
9735 (= (js2-peek-token) js2-NAME) | |
9736 (or (string= prop "get") | |
9737 (string= prop "set"))) | |
9738 (progn | |
9739 ;; getter/setter prop | |
9740 (js2-consume-token) | |
9741 (js2-set-face ppos pend 'font-lock-keyword-face 'record) ; get/set | |
9742 (js2-record-face 'font-lock-function-name-face) ; for peeked name | |
9743 (setq name (js2-create-name-node)) ; discard get/set & use peeked name | |
9744 (js2-parse-getter-setter-prop ppos name (string= prop "get"))) | |
9745 | |
9746 ;; regular prop | |
9747 (prog1 | |
9748 (setq expr (js2-parse-plain-property (or string-prop name))) | |
9749 (js2-set-face ppos pend | |
9750 (if (js2-function-node-p | |
9751 (js2-object-prop-node-right expr)) | |
9752 'font-lock-function-name-face | |
9753 'font-lock-variable-name-face) | |
9754 'record))))) | |
9755 | |
9756 (defun js2-parse-plain-property (prop) | |
9757 "Parse a non-getter/setter property in an object literal. | |
9758 PROP is the node representing the property: a number, name or string." | |
9759 (js2-must-match js2-COLON "msg.no.colon.prop") | |
9760 (let* ((pos (js2-node-pos prop)) | |
9761 (colon (- js2-token-beg pos)) | |
9762 (expr (js2-parse-assign-expr)) | |
9763 (result (make-js2-object-prop-node | |
9764 :pos pos | |
9765 ;; don't include last consumed token in length | |
9766 :len (- (+ (js2-node-pos expr) | |
9767 (js2-node-len expr)) | |
9768 pos) | |
9769 :left prop | |
9770 :right expr | |
9771 :op-pos colon))) | |
9772 (js2-node-add-children result prop expr) | |
9773 result)) | |
9774 | |
9775 (defun js2-parse-getter-setter-prop (pos prop get-p) | |
9776 "Parse getter or setter property in an object literal. | |
9777 JavaScript syntax is: | |
9778 | |
9779 { get foo() {...}, set foo(x) {...} } | |
9780 | |
9781 POS is the start position of the `get' or `set' keyword. | |
9782 PROP is the `js2-name-node' representing the property name. | |
9783 GET-P is non-nil if the keyword was `get'." | |
9784 (let ((type (if get-p js2-GET js2-SET)) | |
9785 result | |
9786 end | |
9787 (fn (js2-parse-function 'FUNCTION_EXPRESSION))) | |
9788 | |
9789 ;; it has to be an anonymous function, as we already parsed the name | |
9790 (if (/= (js2-node-type fn) js2-FUNCTION) | |
9791 (js2-report-error "msg.bad.prop") | |
9792 (if (plusp (length (js2-function-name fn))) | |
9793 (js2-report-error "msg.bad.prop"))) | |
9794 | |
9795 (js2-node-set-prop fn 'GETTER_SETTER type) ; for codegen | |
9796 (setq end (js2-node-end fn) | |
9797 result (make-js2-getter-setter-node :type type | |
9798 :pos pos | |
9799 :len (- end pos) | |
9800 :left prop | |
9801 :right fn)) | |
9802 (js2-node-add-children result prop fn) | |
9803 result)) | |
9804 | |
9805 (defun js2-create-name-node (&optional check-activation-p token) | |
9806 "Create a name node using the token info from last scanned name. | |
9807 In some cases we need to either synthesize a name node, or we lost | |
9808 the name token information by peeking. If the TOKEN parameter is | |
9809 not `js2-NAME', then we use the token info saved in instance vars." | |
9810 (let ((beg js2-token-beg) | |
9811 (s js2-ts-string) | |
9812 name) | |
9813 (when (/= js2-current-token js2-NAME) | |
9814 (setq beg (or js2-prev-name-token-start js2-ts-cursor) | |
9815 s js2-prev-name-token-string | |
9816 js2-prev-name-token-start nil | |
9817 js2-prev-name-token-string nil)) | |
9818 (setq name (make-js2-name-node :pos beg | |
9819 :name s | |
9820 :len (length s))) | |
9821 (if check-activation-p | |
9822 (js2-check-activation-name s (or token js2-NAME))) | |
9823 name)) | |
9824 | |
9825 (provide 'js2-parse) | |
9826 | |
9827 ;;; js2-parse.el ends here | |
9828 ;;; js2-indent.el --- indentation for js2-mode | |
9829 ;; | |
9830 ;; Copyright (C) 2008 Steve Yegge | |
9831 ;; Author: Steve Yegge (steve.yegge@gmail.com) | |
9832 ;; Maintainer: Steve Yegge (steve.yegge@gmail.com) | |
9833 | |
9834 ;; Commentary: | |
9835 ;; | |
9836 ;; This indenter is based on Karl Landström's "javascript.el" indenter. | |
9837 ;; Karl cleverly deduces that the desired indentation level is often a | |
9838 ;; function of paren/bracket/brace nesting depth, which can be determined | |
9839 ;; quickly via the built-in `parse-partial-sexp' function. His indenter | |
9840 ;; then does some equally clever checks to see if we're in the context of a | |
9841 ;; substatement of a possibly braceless statement keyword such as if, while, | |
9842 ;; or finally. This approach yields pretty good results. | |
9843 ;; | |
9844 ;; The indenter is often "wrong", however, and needs to be overridden. | |
9845 ;; The right long-term solution is probably to emulate (or modify) | |
9846 ;; cc-engine, but it's thousands upon thousands of lines of code. Even | |
9847 ;; if you were to assume the accurate parse tree from `js2-parse' is | |
9848 ;; present, indentation is still thousands of lines of code (I've been | |
9849 ;; down that path) to handle every possible syntactic edge case, and in | |
9850 ;; any case, relying on the parse tree is undesirable because parsing is | |
9851 ;; slow. So you might as well go the cc-engine approach, but it's a | |
9852 ;; huge pile of work that I'm just not up for any time soon. | |
9853 ;; | |
9854 ;; In the meantime, the compromise solution is that we offer a | |
9855 ;; "bounce indenter", configured with `js2-bounce-indent-flag', which | |
9856 ;; cycles the current line indent among various likely guess points. | |
9857 ;; This approach is far from perfect, but should at least make it | |
9858 ;; slightly easier to move the line towards its desired indentation | |
9859 ;; when manually overriding Karl's heuristic nesting guesser. | |
9860 ;; | |
9861 ;; I've made miscellaneous tweaks to Karl's code to handle some Ecma | |
9862 ;; extensions such as `let' and Array comprehensions, and will likely | |
9863 ;; make further tweaks to it, but major kudos to Karl for coming up with | |
9864 ;; the initial approach, which packs a lot of punch for so little code. | |
9865 | |
9866 ;;; Code: | |
9867 | |
9868 (defconst js-possibly-braceless-keyword-re | |
9869 (regexp-opt | |
9870 '("catch" "do" "else" "finally" "for" "if" "try" "while" "with" "let") | |
9871 'words) | |
9872 "Regular expression matching keywords that are optionally | |
9873 followed by an opening brace.") | |
9874 | |
9875 (defconst js-indent-operator-re | |
9876 (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|" | |
9877 (regexp-opt '("in" "instanceof") 'words)) | |
9878 "Regular expression matching operators that affect indentation | |
9879 of continued expressions.") | |
9880 | |
9881 ;; This function has horrible results if you're typing an array | |
9882 ;; such as [[1, 2], [3, 4], [5, 6]]. Bounce indenting -really- sucks | |
9883 ;; in conjunction with electric-indent, so just disabling it. | |
9884 (defsubst js2-code-at-bol-p () | |
9885 "Return t if the first character on line is non-whitespace." | |
9886 nil) | |
9887 ;; (not (memq (char-after (point-at-bol)) | |
9888 ;; '(? ?\t))))) | |
9889 | |
9890 (defun js2-insert-and-indent (key) | |
9891 "Run command bound to key and indent current line. Runs the command | |
9892 bound to KEY in the global keymap and indents the current line." | |
9893 (interactive (list (this-command-keys))) | |
9894 (let ((cmd (lookup-key (current-global-map) key))) | |
9895 (if (commandp cmd) | |
9896 (call-interactively cmd))) | |
9897 ;; don't do the electric keys inside comments or strings, | |
9898 ;; and don't do bounce-indent with them. | |
9899 (let ((parse-state (parse-partial-sexp (point-min) (point))) | |
9900 (js2-bounce-indent-flag (js2-code-at-bol-p))) | |
9901 (unless (or (nth 3 parse-state) | |
9902 (nth 4 parse-state)) | |
9903 (indent-according-to-mode)))) | |
9904 | |
9905 (defun js-re-search-forward-inner (regexp &optional bound count) | |
9906 "Auxiliary function for `js-re-search-forward'." | |
9907 (let ((parse) | |
9908 (saved-point (point-min))) | |
9909 (while (> count 0) | |
9910 (re-search-forward regexp bound) | |
9911 (setq parse (parse-partial-sexp saved-point (point))) | |
9912 (cond ((nth 3 parse) | |
9913 (re-search-forward | |
9914 (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) | |
9915 (save-excursion (end-of-line) (point)) t)) | |
9916 ((nth 7 parse) | |
9917 (forward-line)) | |
9918 ((or (nth 4 parse) | |
9919 (and (eq (char-before) ?\/) (eq (char-after) ?\*))) | |
9920 (re-search-forward "\\*/")) | |
9921 (t | |
9922 (setq count (1- count)))) | |
9923 (setq saved-point (point)))) | |
9924 (point)) | |
9925 | |
9926 (defun js-re-search-forward (regexp &optional bound noerror count) | |
9927 "Search forward but ignore strings and comments. Invokes | |
9928 `re-search-forward' but treats the buffer as if strings and | |
9929 comments have been removed." | |
9930 (let ((saved-point (point)) | |
9931 (search-expr | |
9932 (cond ((null count) | |
9933 '(js-re-search-forward-inner regexp bound 1)) | |
9934 ((< count 0) | |
9935 '(js-re-search-backward-inner regexp bound (- count))) | |
9936 ((> count 0) | |
9937 '(js-re-search-forward-inner regexp bound count))))) | |
9938 (condition-case err | |
9939 (eval search-expr) | |
9940 (search-failed | |
9941 (goto-char saved-point) | |
9942 (unless noerror | |
9943 (error (error-message-string err))))))) | |
9944 | |
9945 (defun js-re-search-backward-inner (regexp &optional bound count) | |
9946 "Auxiliary function for `js-re-search-backward'." | |
9947 (let ((parse) | |
9948 (saved-point (point-min))) | |
9949 (while (> count 0) | |
9950 (re-search-backward regexp bound) | |
9951 (setq parse (parse-partial-sexp saved-point (point))) | |
9952 (cond ((nth 3 parse) | |
9953 (re-search-backward | |
9954 (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) | |
9955 (save-excursion (beginning-of-line) (point)) t)) | |
9956 ((nth 7 parse) | |
9957 (goto-char (nth 8 parse))) | |
9958 ((or (nth 4 parse) | |
9959 (and (eq (char-before) ?/) (eq (char-after) ?*))) | |
9960 (re-search-backward "/\\*")) | |
9961 (t | |
9962 (setq count (1- count)))))) | |
9963 (point)) | |
9964 | |
9965 (defun js-re-search-backward (regexp &optional bound noerror count) | |
9966 "Search backward but ignore strings and comments. Invokes | |
9967 `re-search-backward' but treats the buffer as if strings and | |
9968 comments have been removed." | |
9969 (let ((saved-point (point)) | |
9970 (search-expr | |
9971 (cond ((null count) | |
9972 '(js-re-search-backward-inner regexp bound 1)) | |
9973 ((< count 0) | |
9974 '(js-re-search-forward-inner regexp bound (- count))) | |
9975 ((> count 0) | |
9976 '(js-re-search-backward-inner regexp bound count))))) | |
9977 (condition-case err | |
9978 (eval search-expr) | |
9979 (search-failed | |
9980 (goto-char saved-point) | |
9981 (unless noerror | |
9982 (error (error-message-string err))))))) | |
9983 | |
9984 (defun js-looking-at-operator-p () | |
9985 "Return non-nil if text after point is an operator (that is not | |
9986 a comma)." | |
9987 (save-match-data | |
9988 (and (looking-at js-indent-operator-re) | |
9989 (or (not (looking-at ":")) | |
9990 (save-excursion | |
9991 (and (js-re-search-backward "[?:{]\\|\\<case\\>" nil t) | |
9992 (looking-at "?"))))))) | |
9993 | |
9994 (defun js-continued-expression-p () | |
9995 "Returns non-nil if the current line continues an expression." | |
9996 (save-excursion | |
9997 (back-to-indentation) | |
9998 (or (js-looking-at-operator-p) | |
9999 (and (js-re-search-backward "\n" nil t) | |
10000 (progn | |
10001 (skip-chars-backward " \t") | |
10002 (backward-char) | |
10003 (and (js-looking-at-operator-p) | |
10004 (and (progn (backward-char) | |
10005 (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))))) | |
10006 | |
10007 (defun js-end-of-do-while-loop-p () | |
10008 "Returns non-nil if word after point is `while' of a do-while | |
10009 statement, else returns nil. A braceless do-while statement | |
10010 spanning several lines requires that the start of the loop is | |
10011 indented to the same column as the current line." | |
10012 (interactive) | |
10013 (save-excursion | |
10014 (save-match-data | |
10015 (when (looking-at "\\s-*\\<while\\>") | |
10016 (if (save-excursion | |
10017 (skip-chars-backward "[ \t\n]*}") | |
10018 (looking-at "[ \t\n]*}")) | |
10019 (save-excursion | |
10020 (backward-list) (backward-word 1) (looking-at "\\<do\\>")) | |
10021 (js-re-search-backward "\\<do\\>" (point-at-bol) t) | |
10022 (or (looking-at "\\<do\\>") | |
10023 (let ((saved-indent (current-indentation))) | |
10024 (while (and (js-re-search-backward "^[ \t]*\\<" nil t) | |
10025 (/= (current-indentation) saved-indent))) | |
10026 (and (looking-at "[ \t]*\\<do\\>") | |
10027 (not (js-re-search-forward | |
10028 "\\<while\\>" (point-at-eol) t)) | |
10029 (= (current-indentation) saved-indent))))))))) | |
10030 | |
10031 (defun js-ctrl-statement-indentation () | |
10032 "Returns the proper indentation of the current line if it | |
10033 starts the body of a control statement without braces, else | |
10034 returns nil." | |
10035 (let (forward-sexp-function) ; temporarily unbind it | |
10036 (save-excursion | |
10037 (back-to-indentation) | |
10038 (when (save-excursion | |
10039 (and (not (js2-same-line (point-min))) | |
10040 (not (looking-at "{")) | |
10041 (js-re-search-backward "[[:graph:]]" nil t) | |
10042 (not (looking-at "[{([]")) | |
10043 (progn | |
10044 (forward-char) | |
10045 ;; scan-sexps sometimes throws an error | |
10046 (ignore-errors (backward-sexp)) | |
10047 (when (looking-at "(") (backward-word 1)) | |
10048 (and (save-excursion | |
10049 (skip-chars-backward " \t}" (point-at-bol)) | |
10050 (bolp)) | |
10051 (looking-at js-possibly-braceless-keyword-re) | |
10052 (not (js-end-of-do-while-loop-p)))))) | |
10053 (save-excursion | |
10054 (goto-char (match-beginning 0)) | |
10055 (+ (current-indentation) js2-basic-offset)))))) | |
10056 | |
10057 (defun js2-indent-in-array-comp (parse-status) | |
10058 "Return non-nil if we think we're in an array comprehension. | |
10059 In particular, return the buffer position of the first `for' kwd." | |
10060 (let ((end (point))) | |
10061 (when (nth 1 parse-status) | |
10062 (save-excursion | |
10063 (goto-char (nth 1 parse-status)) | |
10064 (when (looking-at "\\[") | |
10065 (forward-char 1) | |
10066 (js2-forward-sws) | |
10067 (if (looking-at "[[{]") | |
10068 (let (forward-sexp-function) ; use lisp version | |
10069 (forward-sexp) ; skip destructuring form | |
10070 (js2-forward-sws) | |
10071 (if (and (/= (char-after) ?,) ; regular array | |
10072 (looking-at "for")) | |
10073 (match-beginning 0))) | |
10074 ;; to skip arbitrary expressions we need the parser, | |
10075 ;; so we'll just guess at it. | |
10076 (if (re-search-forward "[^,]* \\(for\\) " end t) | |
10077 (match-beginning 1)))))))) | |
10078 | |
10079 (defun js2-array-comp-indentation (parse-status for-kwd) | |
10080 (if (js2-same-line for-kwd) | |
10081 ;; first continuation line | |
10082 (save-excursion | |
10083 (goto-char (nth 1 parse-status)) | |
10084 (forward-char 1) | |
10085 (skip-chars-forward " \t") | |
10086 (current-column)) | |
10087 (save-excursion | |
10088 (goto-char for-kwd) | |
10089 (current-column)))) | |
10090 | |
10091 (defun js-proper-indentation (parse-status) | |
10092 "Return the proper indentation for the current line." | |
10093 (save-excursion | |
10094 (back-to-indentation) | |
10095 (let ((ctrl-stmt-indent (js-ctrl-statement-indentation)) | |
10096 (same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>")) | |
10097 (continued-expr-p (js-continued-expression-p)) | |
10098 (bracket (nth 1 parse-status)) | |
10099 beg) | |
10100 (cond | |
10101 ;; indent array comprehension continuation lines specially | |
10102 ((and bracket | |
10103 (not (js2-same-line bracket)) | |
10104 (setq beg (js2-indent-in-array-comp parse-status)) | |
10105 (>= (point) (save-excursion | |
10106 (goto-char beg) | |
10107 (point-at-bol)))) ; at or after first loop? | |
10108 (js2-array-comp-indentation parse-status beg)) | |
10109 | |
10110 (ctrl-stmt-indent) | |
10111 | |
10112 (bracket | |
10113 (goto-char bracket) | |
10114 (cond | |
10115 ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)") | |
10116 (let ((p (parse-partial-sexp (point-at-bol) (point)))) | |
10117 (when (save-excursion (skip-chars-backward " \t)") | |
10118 (looking-at ")")) | |
10119 (backward-list)) | |
10120 (if (nth 1 p) | |
10121 (progn (goto-char (1+ (nth 1 p))) | |
10122 (skip-chars-forward " \t")) | |
10123 (back-to-indentation)) | |
10124 (cond (same-indent-p | |
10125 (current-column)) | |
10126 (continued-expr-p | |
10127 (+ (current-column) (* 2 js2-basic-offset))) | |
10128 (t | |
10129 (+ (current-column) js2-basic-offset))))) | |
10130 (t | |
10131 (unless same-indent-p | |
10132 (forward-char) | |
10133 (skip-chars-forward " \t")) | |
10134 (current-column)))) | |
10135 | |
10136 (continued-expr-p js2-basic-offset) | |
10137 (t 0))))) | |
10138 | |
10139 (defun js2-lineup-comment (parse-status) | |
10140 "Indent a multi-line block comment continuation line." | |
10141 (let* ((beg (nth 8 parse-status)) | |
10142 (first-line (js2-same-line beg)) | |
10143 (offset (save-excursion | |
10144 (goto-char beg) | |
10145 (if (looking-at "/\\*") | |
10146 (+ 1 (current-column)) | |
10147 0)))) | |
10148 (unless first-line | |
10149 (indent-line-to offset)))) | |
10150 | |
10151 (defun js2-backward-sws () | |
10152 "Move backward through whitespace and comments." | |
10153 (interactive) | |
10154 (while (forward-comment -1))) | |
10155 | |
10156 (defun js2-forward-sws () | |
10157 "Move forward through whitespace and comments." | |
10158 (interactive) | |
10159 (while (forward-comment 1))) | |
10160 | |
10161 (defsubst js2-current-indent (&optional pos) | |
10162 "Return column of indentation on current line. | |
10163 If POS is non-nil, go to that point and return indentation for that line." | |
10164 (save-excursion | |
10165 (if pos | |
10166 (goto-char pos)) | |
10167 (back-to-indentation) | |
10168 (current-column))) | |
10169 | |
10170 (defsubst js2-arglist-close () | |
10171 "Return non-nil if we're on a line beginning with a close-paren/brace." | |
10172 (save-match-data | |
10173 (save-excursion | |
10174 (goto-char (point-at-bol)) | |
10175 (js2-forward-sws) | |
10176 (looking-at "[])}]")))) | |
10177 | |
10178 (defsubst js2-indent-looks-like-label-p () | |
10179 (goto-char (point-at-bol)) | |
10180 (js2-forward-sws) | |
10181 (looking-at (concat js2-mode-identifier-re ":"))) | |
10182 | |
10183 (defun js2-indent-in-objlit-p (parse-status) | |
10184 "Return non-nil if this looks like an object-literal entry." | |
10185 (let ((start (nth 1 parse-status))) | |
10186 (and | |
10187 start | |
10188 (save-excursion | |
10189 (and (zerop (forward-line -1)) | |
10190 (not (< (point) start)) ; crossed a {} boundary | |
10191 (js2-indent-looks-like-label-p))) | |
10192 (save-excursion | |
10193 (js2-indent-looks-like-label-p))))) | |
10194 | |
10195 ;; if prev line looks like foobar({ then we're passing an object | |
10196 ;; literal to a function call, and people pretty much always want to | |
10197 ;; de-dent back to the previous line, so move the 'basic-offset' | |
10198 ;; position to the front. | |
10199 (defsubst js2-indent-objlit-arg-p (parse-status) | |
10200 (save-excursion | |
10201 (back-to-indentation) | |
10202 (js2-backward-sws) | |
10203 (and (eq (1- (point)) (nth 1 parse-status)) | |
10204 (eq (char-before) ?{) | |
10205 (progn | |
10206 (forward-char -1) | |
10207 (skip-chars-backward " \t") | |
10208 (eq (char-before) ?\())))) | |
10209 | |
10210 (defsubst js2-indent-case-block-p () | |
10211 (save-excursion | |
10212 (back-to-indentation) | |
10213 (js2-backward-sws) | |
10214 (goto-char (point-at-bol)) | |
10215 (skip-chars-forward " \t") | |
10216 (save-match-data | |
10217 (looking-at "case\\s-.+:")))) | |
10218 | |
10219 (defsubst js2-syntax-bol () | |
10220 "Return the point at the first non-whitespace char on the line. | |
10221 Returns `point-at-bol' if the line is empty." | |
10222 (save-excursion | |
10223 (beginning-of-line) | |
10224 (skip-chars-forward " \t") | |
10225 (point))) | |
10226 | |
10227 (defun js2-bounce-indent (normal-col parse-status) | |
10228 "Cycle among alternate computed indentation positions. | |
10229 PARSE-STATUS is the result of `parse-partial-sexp' from the beginning | |
10230 of the buffer to the current point. NORMAL-COL is the indentation | |
10231 column computed by the heuristic guesser based on current paren, | |
10232 bracket, brace and statement nesting." | |
10233 (let ((cur-indent (js2-current-indent)) | |
10234 (old-buffer-undo-list buffer-undo-list) | |
10235 ;; Emacs 21 only has `count-lines', not `line-number-at-pos' | |
10236 (current-line (save-excursion | |
10237 (forward-line 0) ; move to bol | |
10238 (1+ (count-lines (point-min) (point))))) | |
10239 positions | |
10240 pos | |
10241 anchor | |
10242 arglist-cont | |
10243 same-indent | |
10244 prev-line-col | |
10245 basic-offset | |
10246 computed-pos) | |
10247 ;; temporarily don't record undo info, if user requested this | |
10248 (if js2-mode-indent-inhibit-undo | |
10249 (setq buffer-undo-list t)) | |
10250 (unwind-protect | |
10251 (progn | |
10252 ;; first likely point: indent from beginning of previous code line | |
10253 (push (setq basic-offset | |
10254 (+ (save-excursion | |
10255 (back-to-indentation) | |
10256 (js2-backward-sws) | |
10257 (back-to-indentation) | |
10258 (setq prev-line-col (current-column))) | |
10259 js2-basic-offset)) | |
10260 positions) | |
10261 | |
10262 ;; second likely point: indent from assign-expr RHS. This | |
10263 ;; is just a crude guess based on finding " = " on the previous | |
10264 ;; line containing actual code. | |
10265 (setq pos (save-excursion | |
10266 (save-match-data | |
10267 (forward-line -1) | |
10268 (goto-char (point-at-bol)) | |
10269 (when (re-search-forward "\\s-+\\(=\\)\\s-+" | |
10270 (point-at-eol) t) | |
10271 (goto-char (match-end 1)) | |
10272 (skip-chars-forward " \t\r\n") | |
10273 (current-column))))) | |
10274 (when pos | |
10275 (incf pos js2-basic-offset) | |
10276 (unless (member pos positions) | |
10277 (push pos positions))) | |
10278 | |
10279 ;; third likely point: same indent as previous line of code. | |
10280 ;; Make it the first likely point if we're not on an | |
10281 ;; arglist-close line and previous line ends in a comma, or | |
10282 ;; both this line and prev line look like object-literal | |
10283 ;; elements. | |
10284 (setq pos (save-excursion | |
10285 (goto-char (point-at-bol)) | |
10286 (js2-backward-sws) | |
10287 (back-to-indentation) | |
10288 (prog1 | |
10289 (current-column) | |
10290 ;; while we're here, look for trailing comma | |
10291 (if (save-excursion | |
10292 (goto-char (point-at-eol)) | |
10293 (js2-backward-sws) | |
10294 (eq (char-before) ?,)) | |
10295 (setq arglist-cont (1- (point))))))) | |
10296 (when pos | |
10297 (if (and (or arglist-cont | |
10298 (js2-indent-in-objlit-p parse-status)) | |
10299 (not (js2-arglist-close))) | |
10300 (setq same-indent pos)) | |
10301 (unless (member pos positions) | |
10302 (push pos positions))) | |
10303 | |
10304 ;; fourth likely position: first preceding code with less indentation | |
10305 ;; than the immediately preceding code line. | |
10306 (setq pos (save-excursion | |
10307 (js2-backward-sws) | |
10308 (back-to-indentation) | |
10309 (setq anchor (current-column)) | |
10310 (while (and (zerop (forward-line -1)) | |
10311 (>= (progn | |
10312 (back-to-indentation) | |
10313 (current-column)) | |
10314 anchor))) | |
10315 (setq pos (current-column)))) | |
10316 (unless (member pos positions) | |
10317 (push pos positions)) | |
10318 | |
10319 ;; put nesting-heuristic position first in list, sort rest | |
10320 (setq positions (nreverse (sort positions '<))) | |
10321 (setq positions (cons normal-col (delete normal-col positions))) | |
10322 | |
10323 ;; comma-list continuation lines: prev line indent takes precedence | |
10324 (if same-indent | |
10325 (setq positions | |
10326 (cons same-indent | |
10327 (sort (delete same-indent positions) '<)))) | |
10328 | |
10329 ;; common special cases where we want to indent in from previous line | |
10330 (if (or (js2-indent-case-block-p) | |
10331 (js2-indent-objlit-arg-p parse-status)) | |
10332 (setq positions | |
10333 (cons basic-offset | |
10334 (delete basic-offset positions)))) | |
10335 | |
10336 ;; record whether we're already sitting on one of the alternatives | |
10337 (setq pos (member cur-indent positions)) | |
10338 (cond | |
10339 ;; case 0: we're one one of the alternatives and this is the | |
10340 ;; first time they've pressed TAB on this line (best-guess). | |
10341 ((and js2-mode-indent-ignore-first-tab | |
10342 pos | |
10343 ;; first time pressing TAB on this line? | |
10344 (not (eq js2-mode-last-indented-line current-line))) | |
10345 ;; do nothing | |
10346 (setq computed-pos nil)) | |
10347 ;; case 1: only one computed position => use it | |
10348 ((null (cdr positions)) | |
10349 (setq computed-pos 0)) | |
10350 ;; case 2: not on any of the computed spots => use main spot | |
10351 ((not pos) | |
10352 (setq computed-pos 0)) | |
10353 ;; case 3: on last position: cycle to first position | |
10354 ((null (cdr pos)) | |
10355 (setq computed-pos 0)) | |
10356 ;; case 4: on intermediate position: cycle to next position | |
10357 (t | |
10358 (setq computed-pos (js2-position (second pos) positions)))) | |
10359 | |
10360 ;; see if any hooks want to indent; otherwise we do it | |
10361 (loop with result = nil | |
10362 for hook in js2-indent-hook | |
10363 while (null result) | |
10364 do | |
10365 (setq result (funcall hook positions computed-pos)) | |
10366 finally do | |
10367 (unless (or result (null computed-pos)) | |
10368 (indent-line-to (nth computed-pos positions))))) | |
10369 | |
10370 ;; finally | |
10371 (if js2-mode-indent-inhibit-undo | |
10372 (setq buffer-undo-list old-buffer-undo-list)) | |
10373 ;; see commentary for `js2-mode-last-indented-line' | |
10374 (setq js2-mode-last-indented-line current-line)))) | |
10375 | |
10376 (defsubst js2-1-line-comment-continuation-p () | |
10377 "Return t if we're in a 1-line comment continuation. | |
10378 If so, we don't ever want to use bounce-indent." | |
10379 (save-excursion | |
10380 (save-match-data | |
10381 (and (progn | |
10382 (forward-line 0) | |
10383 (looking-at "\\s-*//")) | |
10384 (progn | |
10385 (forward-line -1) | |
10386 (forward-line 0) | |
10387 (when (looking-at "\\s-*$") | |
10388 (js2-backward-sws) | |
10389 (forward-line 0)) | |
10390 (looking-at "\\s-*//")))))) | |
10391 | |
10392 (defun js2-indent-line () | |
10393 "Indent the current line as JavaScript source text." | |
10394 (interactive) | |
10395 (let (parse-status | |
10396 current-indent | |
10397 offset | |
10398 indent-col | |
10399 moved | |
10400 ;; don't whine about errors/warnings when we're indenting. | |
10401 ;; This has to be set before calling parse-partial-sexp below. | |
10402 (inhibit-point-motion-hooks t)) | |
10403 (setq parse-status (save-excursion | |
10404 (parse-partial-sexp (point-min) | |
10405 (point-at-bol))) | |
10406 offset (- (point) (save-excursion | |
10407 (back-to-indentation) | |
10408 (setq current-indent (current-column)) | |
10409 (point)))) | |
10410 (js2-with-underscore-as-word-syntax | |
10411 (if (nth 4 parse-status) | |
10412 (js2-lineup-comment parse-status) | |
10413 (setq indent-col (js-proper-indentation parse-status)) | |
10414 ;; see comments below about js2-mode-last-indented-line | |
10415 (when | |
10416 (cond | |
10417 ;; bounce-indenting is disabled during electric-key indent. | |
10418 ;; It doesn't work well on first line of buffer. | |
10419 ((and js2-bounce-indent-flag | |
10420 (not (js2-same-line (point-min))) | |
10421 (not (js2-1-line-comment-continuation-p))) | |
10422 (js2-bounce-indent indent-col parse-status) | |
10423 (setq moved t)) | |
10424 ;; just indent to the guesser's likely spot | |
10425 ((/= current-indent indent-col) | |
10426 (indent-line-to indent-col) | |
10427 (setq moved t))) | |
10428 (when (and moved (plusp offset)) | |
10429 (forward-char offset))))))) | |
10430 | |
10431 (defun js2-indent-region (start end) | |
10432 "Indent the region, but don't use bounce indenting." | |
10433 (let ((js2-bounce-indent-flag nil) | |
10434 (indent-region-function nil)) | |
10435 (indent-region start end nil))) ; nil for byte-compiler | |
10436 | |
10437 (provide 'js2-indent) | |
10438 | |
10439 ;;; js2-indent.el ends here | |
10440 | |
10441 (eval-when-compile | |
10442 (require 'cl)) | |
10443 | |
10444 (require 'imenu) | |
10445 (require 'cc-cmds) ; for `c-fill-paragraph' | |
10446 | |
10447 | |
10448 ;;;###autoload (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) | |
10449 | |
10450 ;;;###autoload | |
10451 (defun js2-mode () | |
10452 "Major mode for editing JavaScript code." | |
10453 (interactive) | |
10454 (js2-mode-check-compat) | |
10455 (kill-all-local-variables) | |
10456 (set-syntax-table js2-mode-syntax-table) | |
10457 (use-local-map js2-mode-map) | |
10458 (setq major-mode 'js2-mode | |
10459 mode-name "JavaScript-IDE" | |
10460 comment-start "//" ; used by comment-region; don't change it | |
10461 comment-end "") | |
10462 (setq local-abbrev-table js2-mode-abbrev-table) | |
10463 (set (make-local-variable 'max-lisp-eval-depth) | |
10464 (max max-lisp-eval-depth 3000)) | |
10465 (set (make-local-variable 'indent-line-function) #'js2-indent-line) | |
10466 (set (make-local-variable 'indent-region-function) #'js2-indent-region) | |
10467 (set (make-local-variable 'fill-paragraph-function) #'js2-fill-paragraph) | |
10468 (set (make-local-variable 'before-save-hook) #'js2-before-save) | |
10469 (set (make-local-variable 'next-error-function) #'js2-next-error) | |
10470 (set (make-local-variable 'beginning-of-defun-function) #'js2-beginning-of-defun) | |
10471 (set (make-local-variable 'end-of-defun-function) #'js2-end-of-defun) | |
10472 ;; We un-confuse `parse-partial-sexp' by setting syntax-table properties | |
10473 ;; for characters inside regexp literals. | |
10474 (set (make-local-variable 'parse-sexp-lookup-properties) t) | |
10475 ;; this is necessary to make `show-paren-function' work properly | |
10476 (set (make-local-variable 'parse-sexp-ignore-comments) t) | |
10477 ;; needed for M-x rgrep, among other things | |
10478 (put 'js2-mode 'find-tag-default-function #'js2-mode-find-tag) | |
10479 | |
10480 ;; some variables needed by cc-engine for paragraph-fill, etc. | |
10481 (setq c-buffer-is-cc-mode t | |
10482 c-comment-prefix-regexp js2-comment-prefix-regexp | |
10483 c-paragraph-start js2-paragraph-start | |
10484 c-paragraph-separate "$" | |
10485 comment-start-skip js2-comment-start-skip | |
10486 c-syntactic-ws-start js2-syntactic-ws-start | |
10487 c-syntactic-ws-end js2-syntactic-ws-end | |
10488 c-syntactic-eol js2-syntactic-eol) | |
10489 (if js2-emacs22 | |
10490 (c-setup-paragraph-variables)) | |
10491 | |
10492 ;; We do our own syntax highlighting based on the parse tree. | |
10493 ;; However, we want minor modes that add keywords to highlight properly | |
10494 ;; (examples: doxymacs, column-marker). We do this by not letting | |
10495 ;; font-lock unfontify anything, and telling it to fontify after we | |
10496 ;; re-parse and re-highlight the buffer. (We currently don't do any | |
10497 ;; work with regions other than the whole buffer.) | |
10498 (dolist (var '(font-lock-unfontify-buffer-function | |
10499 font-lock-unfontify-region-function)) | |
10500 (set (make-local-variable var) (lambda (&rest args) t))) | |
10501 | |
10502 ;; Don't let font-lock do syntactic (string/comment) fontification. | |
10503 (set (make-local-variable #'font-lock-syntactic-face-function) | |
10504 (lambda (state) nil)) | |
10505 | |
10506 ;; Experiment: make reparse-delay longer for longer files. | |
10507 (if (plusp js2-dynamic-idle-timer-adjust) | |
10508 (setq js2-idle-timer-delay | |
10509 (* js2-idle-timer-delay | |
10510 (/ (point-max) js2-dynamic-idle-timer-adjust)))) | |
10511 | |
10512 (add-hook 'change-major-mode-hook #'js2-mode-exit nil t) | |
10513 (add-hook 'after-change-functions #'js2-mode-edit nil t) | |
10514 (setq imenu-create-index-function #'js2-mode-create-imenu-index) | |
10515 (imenu-add-to-menubar (concat "IM-" mode-name)) | |
10516 (when js2-mirror-mode | |
10517 (js2-enter-mirror-mode)) | |
10518 (add-to-invisibility-spec '(js2-outline . t)) | |
10519 (set (make-local-variable 'line-move-ignore-invisible) t) | |
10520 (set (make-local-variable 'forward-sexp-function) #'js2-mode-forward-sexp) | |
10521 (setq js2-mode-functions-hidden nil | |
10522 js2-mode-comments-hidden nil | |
10523 js2-mode-buffer-dirty-p t | |
10524 js2-mode-parsing nil) | |
10525 (js2-reparse) | |
10526 (run-hooks 'js2-mode-hook)) | |
10527 | |
10528 (defun js2-mode-check-compat () | |
10529 "Signal an error if we can't run with this version of Emacs." | |
10530 (if (and js2-mode-must-byte-compile | |
10531 (not (byte-code-function-p (symbol-function 'js2-mode)))) | |
10532 (error "You must byte-compile js2-mode before using it.")) | |
10533 (if (and (boundp 'running-xemacs) | |
10534 running-xemacs) | |
10535 (error "js2-mode is not compatible with XEmacs")) | |
10536 (unless (>= emacs-major-version 21) | |
10537 (error "js2-mode requires GNU Emacs version 21 or higher"))) | |
10538 | |
10539 (defun js2-mode-exit () | |
10540 (interactive) | |
10541 (when js2-mode-node-overlay | |
10542 (delete-overlay js2-mode-node-overlay) | |
10543 (setq js2-mode-node-overlay nil)) | |
10544 (js2-remove-overlays) | |
10545 (setq js2-mode-ast nil) | |
10546 (remove-hook 'change-major-mode-hook #'js2-mode-exit t) | |
10547 (remove-from-invisibility-spec '(js2-outline . t)) | |
10548 (js2-mode-show-all) | |
10549 (js2-with-unmodifying-text-property-changes | |
10550 (js2-clear-face (point-min) (point-max)))) | |
10551 | |
10552 (defun js2-before-save () | |
10553 "Clean up whitespace before saving file. | |
10554 You can disable this by customizing `js2-cleanup-whitespace'." | |
10555 (when js2-cleanup-whitespace | |
10556 (let ((col (current-column))) | |
10557 (delete-trailing-whitespace) | |
10558 ;; don't change trailing whitespace on current line | |
10559 (unless (eq (current-column) col) | |
10560 (indent-to col))))) | |
10561 | |
10562 (defsubst js2-mode-reset-timer () | |
10563 (if js2-mode-parse-timer | |
10564 (cancel-timer js2-mode-parse-timer)) | |
10565 (setq js2-mode-parsing nil) | |
10566 (setq js2-mode-parse-timer | |
10567 (run-with-idle-timer js2-idle-timer-delay nil #'js2-reparse))) | |
10568 | |
10569 (defun js2-mode-edit (beg end len) | |
10570 "Schedule a new parse after buffer is edited." | |
10571 (setq js2-mode-buffer-dirty-p t) | |
10572 (js2-mode-hide-overlay) | |
10573 (js2-mode-reset-timer)) | |
10574 | |
10575 (defun js2-mode-run-font-lock () | |
10576 "Run `font-lock-fontify-buffer' after parsing/highlighting. | |
10577 This is intended to allow modes that install their own font-lock keywords | |
10578 to work with js2-mode. In practice it never seems to work for long. | |
10579 Hopefully the Emacs maintainers can help figure out a way to make it work." | |
10580 (when (and (boundp 'font-lock-keywords) | |
10581 font-lock-keywords | |
10582 (boundp 'font-lock-mode) | |
10583 font-lock-mode) | |
10584 ;; TODO: font-lock and jit-lock really really REALLY don't want to | |
10585 ;; play nicely with js2-mode. They go out of their way to fail to | |
10586 ;; provide any option for saying "look, fontify the goddamn buffer | |
10587 ;; with just the keywords already". Argh. | |
10588 (setq font-lock-defaults (list font-lock-keywords 'keywords-only)) | |
10589 (let (font-lock-verbose) | |
10590 (font-lock-default-fontify-buffer)))) | |
10591 | |
10592 (defun js2-reparse (&optional force) | |
10593 "Re-parse current buffer after user finishes some data entry. | |
10594 If we get any user input while parsing, including cursor motion, | |
10595 we discard the parse and reschedule it. If FORCE is nil, then the | |
10596 buffer will only rebuild its `js2-mode-ast' if the buffer is dirty." | |
10597 (let (time | |
10598 interrupted-p | |
10599 (js2-compiler-strict-mode js2-mode-show-strict-warnings)) | |
10600 (unless js2-mode-parsing | |
10601 (setq js2-mode-parsing t) | |
10602 (unwind-protect | |
10603 (when (or js2-mode-buffer-dirty-p force) | |
10604 (js2-remove-overlays) | |
10605 (js2-with-unmodifying-text-property-changes | |
10606 (setq js2-mode-buffer-dirty-p nil | |
10607 js2-mode-fontifications nil | |
10608 js2-mode-deferred-properties nil) | |
10609 (if js2-mode-verbose-parse-p | |
10610 (message "parsing...")) | |
10611 (setq time | |
10612 (js2-time | |
10613 (setq interrupted-p | |
10614 (catch 'interrupted | |
10615 (setq js2-mode-ast (js2-parse)) | |
10616 (js2-mode-fontify-regions) | |
10617 (js2-mode-remove-suppressed-warnings) | |
10618 (js2-mode-show-warnings) | |
10619 (js2-mode-show-errors) | |
10620 (js2-mode-run-font-lock) ; note: doesn't work | |
10621 (if (>= js2-highlight-level 1) | |
10622 (js2-highlight-jsdoc js2-mode-ast)) | |
10623 nil)))) | |
10624 (if interrupted-p | |
10625 (progn | |
10626 ;; unfinished parse => try again | |
10627 (setq js2-mode-buffer-dirty-p t) | |
10628 (js2-mode-reset-timer)) | |
10629 (if js2-mode-verbose-parse-p | |
10630 (message "Parse time: %s" time))))) | |
10631 ;; finally | |
10632 (setq js2-mode-parsing nil) | |
10633 (unless interrupted-p | |
10634 (setq js2-mode-parse-timer nil)))))) | |
10635 | |
10636 (defun js2-mode-show-node () | |
10637 "Debugging aid: highlight selected AST node on mouse click." | |
10638 (interactive) | |
10639 (let ((node (js2-node-at-point)) | |
10640 beg | |
10641 end) | |
10642 (when js2-mode-show-overlay | |
10643 (if (null node) | |
10644 (message "No node found at location %s" (point)) | |
10645 (setq beg (js2-node-abs-pos node) | |
10646 end (+ beg (js2-node-len node))) | |
10647 (if js2-mode-node-overlay | |
10648 (move-overlay js2-mode-node-overlay beg end) | |
10649 (setq js2-mode-node-overlay (make-overlay beg end)) | |
10650 (overlay-put js2-mode-node-overlay 'face 'highlight)) | |
10651 (js2-with-unmodifying-text-property-changes | |
10652 (put-text-property beg end 'point-left #'js2-mode-hide-overlay)) | |
10653 (message "%s, parent: %s" | |
10654 (js2-node-short-name node) | |
10655 (if (js2-node-parent node) | |
10656 (js2-node-short-name (js2-node-parent node)) | |
10657 "nil")))))) | |
10658 | |
10659 (defun js2-mode-hide-overlay (&optional p1 p2) | |
10660 "Remove the debugging overlay when the point moves." | |
10661 (when js2-mode-node-overlay | |
10662 (let ((beg (overlay-start js2-mode-node-overlay)) | |
10663 (end (overlay-end js2-mode-node-overlay))) | |
10664 ;; Sometimes we're called spuriously. | |
10665 (unless (and p2 | |
10666 (>= p2 beg) | |
10667 (<= p2 end)) | |
10668 (js2-with-unmodifying-text-property-changes | |
10669 (remove-text-properties beg end '(point-left nil))) | |
10670 (delete-overlay js2-mode-node-overlay) | |
10671 (setq js2-mode-node-overlay nil))))) | |
10672 | |
10673 (defun js2-mode-reset () | |
10674 "Debugging helper; resets everything." | |
10675 (interactive) | |
10676 (js2-mode-exit) | |
10677 (js2-mode)) | |
10678 | |
10679 (defsubst js2-mode-show-warn-or-err (e face) | |
10680 "Highlight a warning or error E with FACE. | |
10681 E is a list of ((MSG-KEY MSG-ARG) BEG END)." | |
10682 (let* ((key (first e)) | |
10683 (beg (second e)) | |
10684 (end (+ beg (third e))) | |
10685 ;; Don't inadvertently go out of bounds. | |
10686 (beg (max (point-min) (min beg (point-max)))) | |
10687 (end (max (point-min) (min end (point-max)))) | |
10688 (js2-highlight-level 3) ; so js2-set-face is sure to fire | |
10689 (ovl (make-overlay beg end))) | |
10690 (overlay-put ovl 'face face) | |
10691 (overlay-put ovl 'js2 t) | |
10692 (put-text-property beg end 'help-echo (js2-get-msg key)) | |
10693 (put-text-property beg end 'point-entered #'js2-echo-error))) | |
10694 | |
10695 (defun js2-remove-overlays () | |
10696 "Remove overlays from buffer that have a `js2' property." | |
10697 (let ((beg (point-min)) | |
10698 (end (point-max))) | |
10699 (save-excursion | |
10700 (dolist (o (overlays-in beg end)) | |
10701 (when (overlay-get o 'js2) | |
10702 (delete-overlay o)))))) | |
10703 | |
10704 (defun js2-mode-fontify-regions () | |
10705 "Apply fontifications recorded during parsing." | |
10706 ;; We defer clearing faces as long as possible to eliminate flashing. | |
10707 (js2-clear-face (point-min) (point-max)) | |
10708 ;; have to reverse the recorded fontifications so that errors and | |
10709 ;; warnings overwrite the normal fontifications | |
10710 (dolist (f (nreverse js2-mode-fontifications)) | |
10711 (put-text-property (first f) (second f) 'face (third f))) | |
10712 (setq js2-mode-fontifications nil) | |
10713 (dolist (p js2-mode-deferred-properties) | |
10714 (apply #'put-text-property p)) | |
10715 (setq js2-mode-deferred-properties nil)) | |
10716 | |
10717 (defun js2-mode-show-errors () | |
10718 "Highlight syntax errors." | |
10719 (when js2-mode-show-parse-errors | |
10720 (dolist (e (js2-ast-root-errors js2-mode-ast)) | |
10721 (js2-mode-show-warn-or-err e 'js2-error-face)))) | |
10722 | |
10723 (defun js2-mode-remove-suppressed-warnings () | |
10724 "Take suppressed warnings out of the AST warnings list. | |
10725 This ensures that the counts and `next-error' are correct." | |
10726 (setf (js2-ast-root-warnings js2-mode-ast) | |
10727 (js2-delete-if | |
10728 (lambda (e) | |
10729 (let ((key (caar e))) | |
10730 (or | |
10731 (and (not js2-strict-trailing-comma-warning) | |
10732 (string-match "trailing\\.comma" key)) | |
10733 (and (not js2-strict-cond-assign-warning) | |
10734 (string= key "msg.equal.as.assign")) | |
10735 (and js2-missing-semi-one-line-override | |
10736 (string= key "msg.missing.semi") | |
10737 (let* ((beg (second e)) | |
10738 (node (js2-node-at-point beg)) | |
10739 (fn (js2-mode-find-parent-fn node)) | |
10740 (body (and fn (js2-function-node-body fn))) | |
10741 (lc (and body (js2-node-abs-pos body))) | |
10742 (rc (and lc (+ lc (js2-node-len body))))) | |
10743 (and fn | |
10744 (or (null body) | |
10745 (save-excursion | |
10746 (goto-char beg) | |
10747 (and (js2-same-line lc) | |
10748 (js2-same-line rc)))))))))) | |
10749 (js2-ast-root-warnings js2-mode-ast)))) | |
10750 | |
10751 (defun js2-mode-show-warnings () | |
10752 "Highlight strict-mode warnings." | |
10753 (when js2-mode-show-strict-warnings | |
10754 (dolist (e (js2-ast-root-warnings js2-mode-ast)) | |
10755 (js2-mode-show-warn-or-err e 'js2-warning-face)))) | |
10756 | |
10757 (defun js2-echo-error (old-point new-point) | |
10758 "Called by point-motion hooks." | |
10759 (let ((msg (get-text-property new-point 'help-echo))) | |
10760 (if msg | |
10761 (message msg)))) | |
10762 | |
10763 (defalias #'js2-echo-help #'js2-echo-error) | |
10764 | |
10765 (defun js2-enter-key () | |
10766 "Handle user pressing the Enter key." | |
10767 (interactive) | |
10768 (let ((parse-status (save-excursion | |
10769 (parse-partial-sexp (point-min) (point))))) | |
10770 (cond | |
10771 ;; check if we're inside a string | |
10772 ((nth 3 parse-status) | |
10773 (js2-mode-split-string parse-status)) | |
10774 ;; check if inside a block comment | |
10775 ((nth 4 parse-status) | |
10776 (js2-mode-extend-comment)) | |
10777 (t | |
10778 ;; should probably figure out what the mode-map says we should do | |
10779 (if js2-indent-on-enter-key | |
10780 (let ((js2-bounce-indent-flag nil)) | |
10781 (js2-indent-line))) | |
10782 (insert "\n") | |
10783 (if js2-enter-indents-newline | |
10784 (let ((js2-bounce-indent-flag nil)) | |
10785 (js2-indent-line))))))) | |
10786 | |
10787 (defun js2-mode-split-string (parse-status) | |
10788 "Turn a newline in mid-string into a string concatenation." | |
10789 (let* ((col (current-column)) | |
10790 (quote-char (nth 3 parse-status)) | |
10791 (quote-string (string quote-char)) | |
10792 (string-beg (nth 8 parse-status)) | |
10793 (indent (save-match-data | |
10794 (or | |
10795 (save-excursion | |
10796 (back-to-indentation) | |
10797 (if (looking-at "\\+") | |
10798 (current-column))) | |
10799 (save-excursion | |
10800 (goto-char string-beg) | |
10801 (if (looking-back "\\+\\s-+") | |
10802 (goto-char (match-beginning 0))) | |
10803 (current-column)))))) | |
10804 (insert quote-char "\n") | |
10805 (indent-to indent) | |
10806 (insert "+ " quote-string) | |
10807 (when (eolp) | |
10808 (insert quote-string) | |
10809 (backward-char 1)))) | |
10810 | |
10811 (defun js2-mode-extend-comment () | |
10812 "When inside a comment block, add comment prefix." | |
10813 (let (star single col first-line needs-close) | |
10814 (save-excursion | |
10815 (back-to-indentation) | |
10816 (cond | |
10817 ((looking-at "\\*[^/]") | |
10818 (setq star t | |
10819 col (current-column))) | |
10820 ((looking-at "/\\*") | |
10821 (setq star t | |
10822 first-line t | |
10823 col (1+ (current-column)))) | |
10824 ((looking-at "//") | |
10825 (setq single t | |
10826 col (current-column))))) | |
10827 ;; Heuristic for whether we need to close the comment: | |
10828 ;; if we've got a parse error here, assume it's an unterminated | |
10829 ;; comment. | |
10830 (setq needs-close | |
10831 (or | |
10832 (eq (get-text-property (1- (point)) 'point-entered) | |
10833 'js2-echo-error) | |
10834 ;; The heuristic above doesn't work well when we're | |
10835 ;; creating a comment and there's another one downstream, | |
10836 ;; as our parser thinks this one ends at the end of the | |
10837 ;; next one. (You can have a /* inside a js block comment.) | |
10838 ;; So just close it if the next non-ws char isn't a *. | |
10839 (and first-line | |
10840 (eolp) | |
10841 (save-excursion | |
10842 (skip-syntax-forward " ") | |
10843 (not (eq (char-after) ?*)))))) | |
10844 (insert "\n") | |
10845 (cond | |
10846 (star | |
10847 (indent-to col) | |
10848 (insert "* ") | |
10849 (if (and first-line needs-close) | |
10850 (save-excursion | |
10851 (insert "\n") | |
10852 (indent-to col) | |
10853 (insert "*/")))) | |
10854 (single | |
10855 (when (save-excursion | |
10856 (and (zerop (forward-line 1)) | |
10857 (looking-at "\\s-*//"))) | |
10858 (indent-to col) | |
10859 (insert "// ")))))) | |
10860 | |
10861 (defun js2-fill-string (beg quote) | |
10862 "Line-wrap a single-line string into a multi-line string. | |
10863 BEG is the string beginning, QUOTE is the quote char." | |
10864 (let* ((squote (string quote)) | |
10865 (end (if (re-search-forward (concat "[^\\]" squote) | |
10866 (point-at-eol) t) | |
10867 (1+ (match-beginning 0)) | |
10868 (point-at-eol))) | |
10869 (tag (make-marker)) | |
10870 (fill-column (- fill-column 4))) ; make room | |
10871 (unwind-protect | |
10872 (progn | |
10873 (move-marker tag end) | |
10874 (fill-paragraph nil) | |
10875 (goto-char beg) | |
10876 (while (not (js2-same-line tag)) | |
10877 (goto-char (point-at-eol)) | |
10878 (insert squote) | |
10879 (when (zerop (forward-line 1)) | |
10880 (back-to-indentation) | |
10881 (if (looking-at (concat squote "\\s-*$")) | |
10882 (progn | |
10883 (setq end (point-at-eol)) | |
10884 (forward-line -1) | |
10885 (delete-region (point-at-eol) end)) | |
10886 (insert "+ " squote))))) | |
10887 (move-marker tag nil)))) | |
10888 | |
10889 (defun js2-fill-paragraph (arg) | |
10890 "Fill paragraph after point. Prefix ARG means justify as well. | |
10891 Has special handling for filling in comments and strings." | |
10892 (let* ((parse-status (save-excursion | |
10893 (parse-partial-sexp (point-min) (point)))) | |
10894 (quote-char (or (nth 3 parse-status) | |
10895 (save-match-data | |
10896 (if (looking-at "[\"\']") | |
10897 (char-after)))))) | |
10898 (cond | |
10899 (quote-char | |
10900 (js2-fill-string (or (nth 8 parse-status) | |
10901 (point)) | |
10902 quote-char) | |
10903 t) ; or fill-paragraph does evil things afterwards | |
10904 ((nth 4 parse-status) ; in block comment? | |
10905 (js2-fill-comment parse-status arg)) | |
10906 (t | |
10907 (fill-paragraph arg))))) | |
10908 | |
10909 (defun js2-fill-comment (parse-status arg) | |
10910 "Fill-paragraph in a block comment." | |
10911 (let* ((beg (nth 8 parse-status)) | |
10912 (end (save-excursion | |
10913 (goto-char beg) | |
10914 (re-search-forward "[^\\]\\*/" nil t))) | |
10915 indent | |
10916 end-marker) | |
10917 (when end | |
10918 (setq end-marker (make-marker)) | |
10919 (move-marker end-marker end)) | |
10920 (when (and end js2-mode-squeeze-spaces) | |
10921 (save-excursion | |
10922 (save-restriction | |
10923 (narrow-to-region beg end) | |
10924 (goto-char (point-min)) | |
10925 (while (re-search-forward "[ \t][ \t]+" nil t) | |
10926 (replace-match " "))))) | |
10927 ;; `c-fill-paragraph' doesn't indent the continuation stars properly | |
10928 ;; if the comment isn't left-justified. They align to the first star | |
10929 ;; on the first continuation line after the comment-open, so we make | |
10930 ;; sure the first continuation line has the proper indentation. | |
10931 (save-excursion | |
10932 (goto-char beg) | |
10933 (setq indent (1+ (current-column))) | |
10934 (goto-char (point-at-eol)) | |
10935 (skip-chars-forward " \t\r\n") | |
10936 (indent-line-to indent) | |
10937 | |
10938 ;; Invoke `c-fill-paragraph' from the first continuation line, | |
10939 ;; since it provides better results. Otherwise if you're on the | |
10940 ;; last line, it doesn't prefix with stars the way you'd expect. | |
10941 ;; TODO: write our own fill function that works in Emacs 21 | |
10942 (c-fill-paragraph arg)) | |
10943 | |
10944 ;; last line is typically indented wrong, so fix it | |
10945 (when end-marker | |
10946 (save-excursion | |
10947 (goto-char end-marker) | |
10948 (js2-indent-line))))) | |
10949 | |
10950 (defun js2-beginning-of-line () | |
10951 "Toggles point between bol and first non-whitespace char in line. | |
10952 Also moves past comment delimiters when inside comments." | |
10953 (interactive) | |
10954 (let (node beg) | |
10955 (cond | |
10956 ((bolp) | |
10957 (back-to-indentation)) | |
10958 ((looking-at "//") | |
10959 (skip-chars-forward "/ \t")) | |
10960 ((and (eq (char-after) ?*) | |
10961 (setq node (js2-comment-at-point)) | |
10962 (memq (js2-comment-node-format node) '(jsdoc block)) | |
10963 (save-excursion | |
10964 (skip-chars-backward " \t") | |
10965 (bolp))) | |
10966 (skip-chars-forward "\* \t")) | |
10967 (t | |
10968 (goto-char (point-at-bol)))))) | |
10969 | |
10970 (defun js2-end-of-line () | |
10971 "Toggles point between eol and last non-whitespace char in line." | |
10972 (interactive) | |
10973 (if (eolp) | |
10974 (skip-chars-backward " \t") | |
10975 (goto-char (point-at-eol)))) | |
10976 | |
10977 (defun js2-enter-mirror-mode() | |
10978 "Turns on mirror mode, where quotes, brackets etc are mirrored automatically | |
10979 on insertion." | |
10980 (interactive) | |
10981 (define-key js2-mode-map (read-kbd-macro "{") 'js2-mode-match-curly) | |
10982 (define-key js2-mode-map (read-kbd-macro "}") 'js2-mode-magic-close-paren) | |
10983 (define-key js2-mode-map (read-kbd-macro "\"") 'js2-mode-match-double-quote) | |
10984 (define-key js2-mode-map (read-kbd-macro "'") 'js2-mode-match-single-quote) | |
10985 (define-key js2-mode-map (read-kbd-macro "(") 'js2-mode-match-paren) | |
10986 (define-key js2-mode-map (read-kbd-macro ")") 'js2-mode-magic-close-paren) | |
10987 (define-key js2-mode-map (read-kbd-macro "[") 'js2-mode-match-bracket) | |
10988 (define-key js2-mode-map (read-kbd-macro "]") 'js2-mode-magic-close-paren)) | |
10989 | |
10990 (defun js2-leave-mirror-mode() | |
10991 "Turns off mirror mode." | |
10992 (interactive) | |
10993 (dolist (key '("{" "\"" "'" "(" ")" "[" "]")) | |
10994 (define-key js2-mode-map (read-kbd-macro key) 'self-insert-command))) | |
10995 | |
10996 (defsubst js2-mode-inside-string () | |
10997 "Return non-nil if inside a string. | |
10998 Actually returns the quote character that begins the string." | |
10999 (let ((parse-state (save-excursion | |
11000 (parse-partial-sexp (point-min) (point))))) | |
11001 (nth 3 parse-state))) | |
11002 | |
11003 (defsubst js2-mode-inside-comment-or-string () | |
11004 "Return non-nil if inside a comment or string." | |
11005 (or | |
11006 (let ((comment-start | |
11007 (save-excursion | |
11008 (goto-char (point-at-bol)) | |
11009 (if (re-search-forward "//" (point-at-eol) t) | |
11010 (match-beginning 0))))) | |
11011 (and comment-start | |
11012 (<= comment-start (point)))) | |
11013 (let ((parse-state (save-excursion | |
11014 (parse-partial-sexp (point-min) (point))))) | |
11015 (or (nth 3 parse-state) | |
11016 (nth 4 parse-state))))) | |
11017 | |
11018 (defun js2-mode-match-curly (arg) | |
11019 "Insert matching curly-brace." | |
11020 (interactive "p") | |
11021 (insert "{") | |
11022 (if current-prefix-arg | |
11023 (save-excursion | |
11024 (insert "}")) | |
11025 (unless (or (not (looking-at "\\s-*$")) | |
11026 (js2-mode-inside-comment-or-string)) | |
11027 (undo-boundary) | |
11028 | |
11029 ;; absolutely mystifying bug: when inserting the next "\n", | |
11030 ;; the buffer-undo-list is given two new entries: the inserted range, | |
11031 ;; and the incorrect position of the point. It's recorded incorrectly | |
11032 ;; as being before the opening "{", not after it. But it's recorded | |
11033 ;; as the correct value if you're debugging `js2-mode-match-curly' | |
11034 ;; in edebug. I have no idea why it's doing this, but incrementing | |
11035 ;; the inserted position fixes the problem, so that the undo takes us | |
11036 ;; back to just after the user-inserted "{". | |
11037 (insert "\n") | |
11038 (ignore-errors | |
11039 (incf (cadr buffer-undo-list))) | |
11040 | |
11041 (js2-indent-line) | |
11042 (save-excursion | |
11043 (insert "\n}") | |
11044 (let ((js2-bounce-indent-flag (js2-code-at-bol-p))) | |
11045 (js2-indent-line)))))) | |
11046 | |
11047 (defun js2-mode-match-bracket () | |
11048 "Insert matching bracket." | |
11049 (interactive) | |
11050 (insert "[") | |
11051 (unless (or (not (looking-at "\\s-*$")) | |
11052 (js2-mode-inside-comment-or-string)) | |
11053 (save-excursion | |
11054 (insert "]")) | |
11055 (when js2-auto-indent-flag | |
11056 (let ((js2-bounce-indent-flag (js2-code-at-bol-p))) | |
11057 (js2-indent-line))))) | |
11058 | |
11059 (defun js2-mode-match-paren () | |
11060 "Insert matching paren unless already inserted." | |
11061 (interactive) | |
11062 (insert "(") | |
11063 (unless (or (not (looking-at "\\s-*$")) | |
11064 (js2-mode-inside-comment-or-string)) | |
11065 (save-excursion | |
11066 (insert ")")) | |
11067 (when js2-auto-indent-flag | |
11068 (let ((js2-bounce-indent-flag (js2-code-at-bol-p))) | |
11069 (js2-indent-line))))) | |
11070 | |
11071 (defsubst js2-match-quote (quote-string) | |
11072 (let ((start-quote (js2-mode-inside-string))) | |
11073 (cond | |
11074 ;; inside a comment - don't do quote-matching, since we can't | |
11075 ;; reliably figure out if we're in a string inside the comment | |
11076 ((js2-comment-at-point) | |
11077 (insert quote-string)) | |
11078 ((not start-quote) | |
11079 ;; not in string => insert matched quotes | |
11080 (insert quote-string) | |
11081 ;; exception: if we're just before a word, don't double it. | |
11082 (unless (looking-at "[^ \t\r\n]") | |
11083 (save-excursion | |
11084 (insert quote-string)))) | |
11085 ((looking-at quote-string) | |
11086 (if (looking-back "[^\\]\\\\") | |
11087 (insert quote-string) | |
11088 (forward-char 1))) | |
11089 ((and js2-mode-escape-quotes | |
11090 (save-excursion | |
11091 (save-match-data | |
11092 (re-search-forward quote-string (point-at-eol) t)))) | |
11093 ;; inside terminated string, escape quote (unless already escaped) | |
11094 (insert (if (looking-back "[^\\]\\\\") | |
11095 quote-string | |
11096 (concat "\\" quote-string)))) | |
11097 (t | |
11098 (insert quote-string))))) ; else terminate the string | |
11099 | |
11100 (defun js2-mode-match-single-quote () | |
11101 "Insert matching single-quote." | |
11102 (interactive) | |
11103 (let ((parse-status (parse-partial-sexp (point-min) (point)))) | |
11104 ;; don't match inside comments, since apostrophe is more common | |
11105 (if (nth 4 parse-status) | |
11106 (insert "'") | |
11107 (js2-match-quote "'")))) | |
11108 | |
11109 (defun js2-mode-match-double-quote () | |
11110 "Insert matching double-quote." | |
11111 (interactive) | |
11112 (js2-match-quote "\"")) | |
11113 | |
11114 (defun js2-mode-magic-close-paren () | |
11115 "Skip over close-paren rather than inserting, where appropriate. | |
11116 Uses some heuristics to try to figure out the right thing to do." | |
11117 (interactive) | |
11118 (let* ((parse-status (parse-partial-sexp (point-min) (point))) | |
11119 (open-pos (nth 1 parse-status)) | |
11120 (close last-input-char) | |
11121 (open (cond | |
11122 ((eq close 41) ; close-paren | |
11123 40) ; open-paren | |
11124 ((eq close 93) ; close-bracket | |
11125 91) ; open-bracket | |
11126 ((eq close ?}) | |
11127 ?{) | |
11128 (t nil)))) | |
11129 (if (and (looking-at (string close)) | |
11130 (eq open (char-after open-pos)) | |
11131 (js2-same-line open-pos)) | |
11132 (forward-char 1) | |
11133 (insert (string close))) | |
11134 (blink-matching-open))) | |
11135 | |
11136 (defun js2-mode-wait-for-parse (callback) | |
11137 "Invoke CALLBACK when parsing is finished. | |
11138 If parsing is already finished, calls CALLBACK immediately." | |
11139 (if (not js2-mode-buffer-dirty-p) | |
11140 (funcall callback) | |
11141 (push callback js2-mode-pending-parse-callbacks) | |
11142 (add-hook 'js2-parse-finished-hook #'js2-mode-parse-finished))) | |
11143 | |
11144 (defun js2-mode-parse-finished () | |
11145 "Invoke callbacks in `js2-mode-pending-parse-callbacks'." | |
11146 ;; We can't let errors propagate up, since it prevents the | |
11147 ;; `js2-parse' method from completing normally and returning | |
11148 ;; the ast, which makes things mysteriously not work right. | |
11149 (unwind-protect | |
11150 (dolist (cb js2-mode-pending-parse-callbacks) | |
11151 (condition-case err | |
11152 (funcall cb) | |
11153 (error (message "%s" err)))) | |
11154 (setq js2-mode-pending-parse-callbacks nil))) | |
11155 | |
11156 (defun js2-mode-flag-region (from to flag) | |
11157 "Hide or show text from FROM to TO, according to FLAG. | |
11158 If FLAG is nil then text is shown, while if FLAG is t the text is hidden. | |
11159 Returns the created overlay if FLAG is non-nil." | |
11160 (remove-overlays from to 'invisible 'js2-outline) | |
11161 (when flag | |
11162 (let ((o (make-overlay from to))) | |
11163 (overlay-put o 'invisible 'js2-outline) | |
11164 (overlay-put o 'isearch-open-invisible | |
11165 'js2-isearch-open-invisible) | |
11166 o))) | |
11167 | |
11168 ;; Function to be set as an outline-isearch-open-invisible' property | |
11169 ;; to the overlay that makes the outline invisible (see | |
11170 ;; `js2-mode-flag-region'). | |
11171 (defun js2-isearch-open-invisible (overlay) | |
11172 ;; We rely on the fact that isearch places point on the matched text. | |
11173 (js2-mode-show-element)) | |
11174 | |
11175 (defun js2-mode-invisible-overlay-bounds (&optional pos) | |
11176 "Return cons cell of bounds of folding overlay at POS. | |
11177 Returns nil if not found." | |
11178 (let ((overlays (overlays-at (or pos (point)))) | |
11179 o) | |
11180 (while (and overlays | |
11181 (not o)) | |
11182 (if (overlay-get (car overlays) 'invisible) | |
11183 (setq o (car overlays)) | |
11184 (setq overlays (cdr overlays)))) | |
11185 (if o | |
11186 (cons (overlay-start o) (overlay-end o))))) | |
11187 | |
11188 (defun js2-mode-function-at-point (&optional pos) | |
11189 "Return the innermost function node enclosing current point. | |
11190 Returns nil if point is not in a function." | |
11191 (let ((node (js2-node-at-point pos))) | |
11192 (while (and node (not (js2-function-node-p node))) | |
11193 (setq node (js2-node-parent node))) | |
11194 (if (js2-function-node-p node) | |
11195 node))) | |
11196 | |
11197 (defun js2-mode-toggle-element () | |
11198 "Hide or show the foldable element at the point." | |
11199 (interactive) | |
11200 (let (comment fn pos) | |
11201 (save-excursion | |
11202 (save-match-data | |
11203 (cond | |
11204 ;; /* ... */ comment? | |
11205 ((js2-block-comment-p (setq comment (js2-comment-at-point))) | |
11206 (if (js2-mode-invisible-overlay-bounds | |
11207 (setq pos (+ 3 (js2-node-abs-pos comment)))) | |
11208 (progn | |
11209 (goto-char pos) | |
11210 (js2-mode-show-element)) | |
11211 (js2-mode-hide-element))) | |
11212 | |
11213 ;; //-comment? | |
11214 ((save-excursion | |
11215 (back-to-indentation) | |
11216 (looking-at js2-mode-//-comment-re)) | |
11217 (js2-mode-toggle-//-comment)) | |
11218 | |
11219 ;; function? | |
11220 ((setq fn (js2-mode-function-at-point)) | |
11221 (setq pos (and (js2-function-node-body fn) | |
11222 (js2-node-abs-pos (js2-function-node-body fn)))) | |
11223 (goto-char (1+ pos)) | |
11224 (if (js2-mode-invisible-overlay-bounds) | |
11225 (js2-mode-show-element) | |
11226 (js2-mode-hide-element))) | |
11227 (t | |
11228 (message "Nothing at point to hide or show"))))))) | |
11229 | |
11230 (defun js2-mode-hide-element () | |
11231 "Fold/hide contents of a block, showing ellipses. | |
11232 Show the hidden text with \\[js2-mode-show-element]." | |
11233 (interactive) | |
11234 (if js2-mode-buffer-dirty-p | |
11235 (js2-mode-wait-for-parse #'js2-mode-hide-element)) | |
11236 (let (node body beg end) | |
11237 (cond | |
11238 ((js2-mode-invisible-overlay-bounds) | |
11239 (message "already hidden")) | |
11240 (t | |
11241 (setq node (js2-node-at-point)) | |
11242 (cond | |
11243 ((js2-block-comment-p node) | |
11244 (js2-mode-hide-comment node)) | |
11245 (t | |
11246 (while (and node (not (js2-function-node-p node))) | |
11247 (setq node (js2-node-parent node))) | |
11248 (if (and node | |
11249 (setq body (js2-function-node-body node))) | |
11250 (progn | |
11251 (setq beg (js2-node-abs-pos body) | |
11252 end (+ beg (js2-node-len body))) | |
11253 (js2-mode-flag-region (1+ beg) (1- end) 'hide)) | |
11254 (message "No collapsable element found at point")))))))) | |
11255 | |
11256 (defun js2-mode-show-element () | |
11257 "Show the hidden element at current point." | |
11258 (interactive) | |
11259 (let ((bounds (js2-mode-invisible-overlay-bounds))) | |
11260 (if bounds | |
11261 (js2-mode-flag-region (car bounds) (cdr bounds) nil) | |
11262 (message "Nothing to un-hide")))) | |
11263 | |
11264 (defun js2-mode-show-all () | |
11265 "Show all of the text in the buffer." | |
11266 (interactive) | |
11267 (js2-mode-flag-region (point-min) (point-max) nil)) | |
11268 | |
11269 (defun js2-mode-toggle-hide-functions () | |
11270 (interactive) | |
11271 (if js2-mode-functions-hidden | |
11272 (js2-mode-show-functions) | |
11273 (js2-mode-hide-functions))) | |
11274 | |
11275 (defun js2-mode-hide-functions () | |
11276 "Hides all non-nested function bodies in the buffer. | |
11277 Use \\[js2-mode-show-all] to reveal them, or \\[js2-mode-show-element] | |
11278 to open an individual entry." | |
11279 (interactive) | |
11280 (if js2-mode-buffer-dirty-p | |
11281 (js2-mode-wait-for-parse #'js2-mode-hide-functions)) | |
11282 (if (null js2-mode-ast) | |
11283 (message "Oops - parsing failed") | |
11284 (setq js2-mode-functions-hidden t) | |
11285 (js2-visit-ast js2-mode-ast #'js2-mode-function-hider))) | |
11286 | |
11287 (defun js2-mode-function-hider (n endp) | |
11288 (when (not endp) | |
11289 (let ((tt (js2-node-type n)) | |
11290 body beg end) | |
11291 (cond | |
11292 ((and (= tt js2-FUNCTION) | |
11293 (setq body (js2-function-node-body n))) | |
11294 (setq beg (js2-node-abs-pos body) | |
11295 end (+ beg (js2-node-len body))) | |
11296 (js2-mode-flag-region (1+ beg) (1- end) 'hide) | |
11297 nil) ; don't process children of function | |
11298 (t | |
11299 t))))) ; keep processing other AST nodes | |
11300 | |
11301 (defun js2-mode-show-functions () | |
11302 "Un-hide any folded function bodies in the buffer." | |
11303 (interactive) | |
11304 (setq js2-mode-functions-hidden nil) | |
11305 (save-excursion | |
11306 (goto-char (point-min)) | |
11307 (while (/= (goto-char (next-overlay-change (point))) | |
11308 (point-max)) | |
11309 (dolist (o (overlays-at (point))) | |
11310 (when (and (overlay-get o 'invisible) | |
11311 (not (overlay-get o 'comment))) | |
11312 (js2-mode-flag-region (overlay-start o) (overlay-end o) nil)))))) | |
11313 | |
11314 (defun js2-mode-hide-comment (n) | |
11315 (let* ((head (if (eq (js2-comment-node-format n) 'jsdoc) | |
11316 3 ; /** | |
11317 2)) ; /* | |
11318 (beg (+ (js2-node-abs-pos n) head)) | |
11319 (end (- (+ beg (js2-node-len n)) head 2)) | |
11320 (o (js2-mode-flag-region beg end 'hide))) | |
11321 (overlay-put o 'comment t))) | |
11322 | |
11323 (defun js2-mode-toggle-hide-comments () | |
11324 "Folds all block comments in the buffer. | |
11325 Use \\[js2-mode-show-all] to reveal them, or \\[js2-mode-show-element] | |
11326 to open an individual entry." | |
11327 (interactive) | |
11328 (if js2-mode-comments-hidden | |
11329 (js2-mode-show-comments) | |
11330 (js2-mode-hide-comments))) | |
11331 | |
11332 (defun js2-mode-hide-comments () | |
11333 (interactive) | |
11334 (if js2-mode-buffer-dirty-p | |
11335 (js2-mode-wait-for-parse #'js2-mode-hide-comments)) | |
11336 (if (null js2-mode-ast) | |
11337 (message "Oops - parsing failed") | |
11338 (setq js2-mode-comments-hidden t) | |
11339 (dolist (n (js2-ast-root-comments js2-mode-ast)) | |
11340 (let ((format (js2-comment-node-format n))) | |
11341 (when (js2-block-comment-p n) | |
11342 (js2-mode-hide-comment n)))) | |
11343 (js2-mode-hide-//-comments))) | |
11344 | |
11345 (defsubst js2-mode-extend-//-comment (direction) | |
11346 "Find start or end of a block of similar //-comment lines. | |
11347 DIRECTION is -1 to look back, 1 to look forward. | |
11348 INDENT is the indentation level to match. | |
11349 Returns the end-of-line position of the furthest adjacent | |
11350 //-comment line with the same indentation as the current line. | |
11351 If there is no such matching line, returns current end of line." | |
11352 (let ((pos (point-at-eol)) | |
11353 (indent (current-indentation))) | |
11354 (save-excursion | |
11355 (save-match-data | |
11356 (while (and (zerop (forward-line direction)) | |
11357 (looking-at js2-mode-//-comment-re) | |
11358 (eq indent (length (match-string 1)))) | |
11359 (setq pos (point-at-eol))) | |
11360 pos)))) | |
11361 | |
11362 (defun js2-mode-hide-//-comments () | |
11363 "Fold adjacent 1-line comments, showing only snippet of first one." | |
11364 (let (beg end) | |
11365 (save-excursion | |
11366 (save-match-data | |
11367 (goto-char (point-min)) | |
11368 (while (re-search-forward js2-mode-//-comment-re nil t) | |
11369 (setq beg (point) | |
11370 end (js2-mode-extend-//-comment 1)) | |
11371 (unless (eq beg end) | |
11372 (overlay-put (js2-mode-flag-region beg end 'hide) | |
11373 'comment t)) | |
11374 (goto-char end) | |
11375 (forward-char 1)))))) | |
11376 | |
11377 (defun js2-mode-toggle-//-comment () | |
11378 "Fold or un-fold any multi-line //-comment at point. | |
11379 Caller should have determined that this line starts with a //-comment." | |
11380 (let* ((beg (point-at-eol)) | |
11381 (end beg)) | |
11382 (save-excursion | |
11383 (goto-char end) | |
11384 (if (js2-mode-invisible-overlay-bounds) | |
11385 (js2-mode-show-element) | |
11386 ;; else hide the comment | |
11387 (setq beg (js2-mode-extend-//-comment -1) | |
11388 end (js2-mode-extend-//-comment 1)) | |
11389 (unless (eq beg end) | |
11390 (overlay-put (js2-mode-flag-region beg end 'hide) | |
11391 'comment t)))))) | |
11392 | |
11393 (defun js2-mode-show-comments () | |
11394 "Un-hide any hidden comments, leaving other hidden elements alone." | |
11395 (interactive) | |
11396 (setq js2-mode-comments-hidden nil) | |
11397 (save-excursion | |
11398 (goto-char (point-min)) | |
11399 (while (/= (goto-char (next-overlay-change (point))) | |
11400 (point-max)) | |
11401 (dolist (o (overlays-at (point))) | |
11402 (when (overlay-get o 'comment) | |
11403 (js2-mode-flag-region (overlay-start o) (overlay-end o) nil)))))) | |
11404 | |
11405 (defun js2-mode-display-warnings-and-errors () | |
11406 "Turn on display of warnings and errors." | |
11407 (interactive) | |
11408 (setq js2-mode-show-parse-errors t | |
11409 js2-mode-show-strict-warnings t) | |
11410 (js2-reparse 'force)) | |
11411 | |
11412 (defun js2-mode-hide-warnings-and-errors () | |
11413 "Turn off display of warnings and errors." | |
11414 (interactive) | |
11415 (setq js2-mode-show-parse-errors nil | |
11416 js2-mode-show-strict-warnings nil) | |
11417 (js2-reparse 'force)) | |
11418 | |
11419 (defun js2-mode-toggle-warnings-and-errors () | |
11420 "Toggle the display of warnings and errors. | |
11421 Some users don't like having warnings/errors reported while they type." | |
11422 (interactive) | |
11423 (setq js2-mode-show-parse-errors (not js2-mode-show-parse-errors) | |
11424 js2-mode-show-strict-warnings (not js2-mode-show-strict-warnings)) | |
11425 (if (interactive-p) | |
11426 (message "warnings and errors %s" | |
11427 (if js2-mode-show-parse-errors | |
11428 "enabled" | |
11429 "disabled"))) | |
11430 (js2-reparse 'force)) | |
11431 | |
11432 (defun js2-mode-customize () | |
11433 (interactive) | |
11434 (customize-group 'js2-mode)) | |
11435 | |
11436 (defun js2-mode-forward-sexp (&optional arg) | |
11437 "Move forward across one statement or balanced expression. | |
11438 With ARG, do it that many times. Negative arg -N means | |
11439 move backward across N balanced expressions." | |
11440 (interactive "p") | |
11441 (setq arg (or arg 1)) | |
11442 (if js2-mode-buffer-dirty-p | |
11443 (js2-mode-wait-for-parse #'js2-mode-forward-sexp)) | |
11444 (let (node end (start (point))) | |
11445 (cond | |
11446 ;; backward-sexp | |
11447 ;; could probably make this "better" for some cases: | |
11448 ;; - if in statement block (e.g. function body), go to parent | |
11449 ;; - infix exprs like (foo in bar) - maybe go to beginning | |
11450 ;; of infix expr if in the right-side expression? | |
11451 ((and arg (minusp arg)) | |
11452 (dotimes (i (- arg)) | |
11453 (js2-backward-sws) | |
11454 (forward-char -1) ; enter the node we backed up to | |
11455 (setq node (js2-node-at-point (point) t)) | |
11456 (goto-char (if node | |
11457 (js2-node-abs-pos node) | |
11458 (point-min))))) | |
11459 (t | |
11460 ;; forward-sexp | |
11461 (js2-forward-sws) | |
11462 (dotimes (i arg) | |
11463 (js2-forward-sws) | |
11464 (setq node (js2-node-at-point (point) t) | |
11465 end (if node (+ (js2-node-abs-pos node) | |
11466 (js2-node-len node)))) | |
11467 (goto-char (or end (point-max)))))))) | |
11468 | |
11469 (defun js2-next-error (&optional arg reset) | |
11470 "Move to next parse error. | |
11471 Typically invoked via \\[next-error]. | |
11472 ARG is the number of errors, forward or backward, to move. | |
11473 RESET means start over from the beginning." | |
11474 (interactive "p") | |
11475 (if (or (null js2-mode-ast) | |
11476 (and (null (js2-ast-root-errors js2-mode-ast)) | |
11477 (null (js2-ast-root-warnings js2-mode-ast)))) | |
11478 (message "No errors") | |
11479 (when reset | |
11480 (goto-char (point-min))) | |
11481 (let* ((errs (copy-sequence | |
11482 (append (js2-ast-root-errors js2-mode-ast) | |
11483 (js2-ast-root-warnings js2-mode-ast)))) | |
11484 (continue t) | |
11485 (start (point)) | |
11486 (count (or arg 1)) | |
11487 (backward (minusp count)) | |
11488 (sorter (if backward '> '<)) | |
11489 (stopper (if backward '< '>)) | |
11490 (count (abs count)) | |
11491 all-errs | |
11492 err) | |
11493 ;; sort by start position | |
11494 (setq errs (sort errs (lambda (e1 e2) | |
11495 (funcall sorter (second e1) (second e2)))) | |
11496 all-errs errs) | |
11497 ;; find nth error with pos > start | |
11498 (while (and errs continue) | |
11499 (when (funcall stopper (cadar errs) start) | |
11500 (setq err (car errs)) | |
11501 (if (zerop (decf count)) | |
11502 (setq continue nil))) | |
11503 (setq errs (cdr errs))) | |
11504 (if err | |
11505 (goto-char (second err)) | |
11506 ;; wrap around to first error | |
11507 (goto-char (second (car all-errs))) | |
11508 ;; if we were already on it, echo msg again | |
11509 (if (= (point) start) | |
11510 (js2-echo-error (point) (point))))))) | |
11511 | |
11512 (defun js2-mouse-3 () | |
11513 "Make right-click move the point to the click location. | |
11514 This makes right-click context menu operations a bit more intuitive. | |
11515 The point will not move if the region is active, however, to avoid | |
11516 destroying the region selection." | |
11517 (interactive) | |
11518 (when (and js2-move-point-on-right-click | |
11519 (not mark-active)) | |
11520 (let ((e last-input-event)) | |
11521 (ignore-errors | |
11522 (goto-char (cadadr e)))))) | |
11523 | |
11524 (defun js2-mode-create-imenu-index () | |
11525 "Return an alist for `imenu--index-alist'." | |
11526 ;; This is built up in `js2-parse-record-imenu' during parsing. | |
11527 (when js2-mode-ast | |
11528 ;; if we have an ast but no recorder, they're requesting a rescan | |
11529 (unless js2-imenu-recorder | |
11530 (js2-reparse 'force)) | |
11531 (prog1 | |
11532 (js2-build-imenu-index) | |
11533 (setq js2-imenu-recorder nil | |
11534 js2-imenu-function-map nil)))) | |
11535 | |
11536 (defun js2-mode-find-tag () | |
11537 "Replacement for `find-tag-default'. | |
11538 `find-tag-default' returns a ridiculous answer inside comments." | |
11539 (let (beg end) | |
11540 (js2-with-underscore-as-word-syntax | |
11541 (save-excursion | |
11542 (if (and (not (looking-at "[A-Za-z0-9_$]")) | |
11543 (looking-back "[A-Za-z0-9_$]")) | |
11544 (setq beg (progn (forward-word -1) (point)) | |
11545 end (progn (forward-word 1) (point))) | |
11546 (setq beg (progn (forward-word 1) (point)) | |
11547 end (progn (forward-word -1) (point)))) | |
11548 (replace-regexp-in-string | |
11549 "[\"']" "" | |
11550 (buffer-substring-no-properties beg end)))))) | |
11551 | |
11552 (defun js2-mode-forward-sibling () | |
11553 "Move to the end of the sibling following point in parent. | |
11554 Returns non-nil if successful, or nil if there was no following sibling." | |
11555 (let* ((node (js2-node-at-point)) | |
11556 (parent (js2-mode-find-enclosing-fn node)) | |
11557 sib) | |
11558 (when (setq sib (js2-node-find-child-after (point) parent)) | |
11559 (goto-char (+ (js2-node-abs-pos sib) | |
11560 (js2-node-len sib)))))) | |
11561 | |
11562 (defun js2-mode-backward-sibling () | |
11563 "Move to the beginning of the sibling node preceding point in parent. | |
11564 Parent is defined as the enclosing script or function." | |
11565 (let* ((node (js2-node-at-point)) | |
11566 (parent (js2-mode-find-enclosing-fn node)) | |
11567 sib) | |
11568 (when (setq sib (js2-node-find-child-before (point) parent)) | |
11569 (goto-char (js2-node-abs-pos sib))))) | |
11570 | |
11571 (defun js2-beginning-of-defun () | |
11572 "Go to line on which current function starts, and return non-nil. | |
11573 If we're not in a function, go to beginning of previous script-level element." | |
11574 (interactive) | |
11575 (let ((parent (js2-node-parent-script-or-fn (js2-node-at-point))) | |
11576 pos sib) | |
11577 (cond | |
11578 ((and (js2-function-node-p parent) | |
11579 (not (eq (point) (setq pos (js2-node-abs-pos parent))))) | |
11580 (goto-char pos)) | |
11581 (t | |
11582 (js2-mode-backward-sibling))))) | |
11583 | |
11584 (defun js2-end-of-defun () | |
11585 "Go to the char after the last position of the current function. | |
11586 If we're not in a function, skips over the next script-level element." | |
11587 (interactive) | |
11588 (let ((parent (js2-node-parent-script-or-fn (js2-node-at-point)))) | |
11589 (if (not (js2-function-node-p parent)) | |
11590 ;; punt: skip over next script-level element beyond point | |
11591 (js2-mode-forward-sibling) | |
11592 (goto-char (+ 1 (+ (js2-node-abs-pos parent) | |
11593 (js2-node-len parent))))))) | |
11594 | |
11595 (defun js2-mark-defun (&optional allow-extend) | |
11596 "Put mark at end of this function, point at beginning. | |
11597 The function marked is the one that contains point. | |
11598 | |
11599 Interactively, if this command is repeated, | |
11600 or (in Transient Mark mode) if the mark is active, | |
11601 it marks the next defun after the ones already marked." | |
11602 (interactive "p") | |
11603 (let (extended) | |
11604 (when (and allow-extend | |
11605 (or (and (eq last-command this-command) (mark t)) | |
11606 (and transient-mark-mode mark-active))) | |
11607 (let ((sib (save-excursion | |
11608 (goto-char (mark)) | |
11609 (if (js2-mode-forward-sibling) | |
11610 (point)))) | |
11611 node) | |
11612 (if sib | |
11613 (progn | |
11614 (set-mark sib) | |
11615 (setq extended t)) | |
11616 ;; no more siblings - try extending to enclosing node | |
11617 (goto-char (mark t))))) | |
11618 (when (not extended) | |
11619 (let ((node (js2-node-at-point (point) t)) ; skip comments | |
11620 ast fn stmt parent beg end) | |
11621 (when (js2-ast-root-p node) | |
11622 (setq ast node | |
11623 node (or (js2-node-find-child-after (point) node) | |
11624 (js2-node-find-child-before (point) node)))) | |
11625 ;; only mark whole buffer if we can't find any children | |
11626 (if (null node) | |
11627 (setq node ast)) | |
11628 (if (js2-function-node-p node) | |
11629 (setq parent node) | |
11630 (setq fn (js2-mode-find-enclosing-fn node) | |
11631 stmt (if (or (null fn) | |
11632 (js2-ast-root-p fn)) | |
11633 (js2-mode-find-first-stmt node)) | |
11634 parent (or stmt fn))) | |
11635 (setq beg (js2-node-abs-pos parent) | |
11636 end (+ beg (js2-node-len parent))) | |
11637 (push-mark beg) | |
11638 (goto-char end) | |
11639 (exchange-point-and-mark))))) | |
11640 | |
11641 (defun js2-narrow-to-defun () | |
11642 "Narrow to the function enclosing point." | |
11643 (interactive) | |
11644 (let* ((node (js2-node-at-point (point) t)) ; skip comments | |
11645 (fn (if (js2-script-node-p node) | |
11646 node | |
11647 (js2-mode-find-enclosing-fn node))) | |
11648 (beg (js2-node-abs-pos fn))) | |
11649 (unless (js2-ast-root-p fn) | |
11650 (narrow-to-region beg (+ beg (js2-node-len fn)))))) | |
11651 | |
11652 (defalias 'js2r 'js2-mode-reset) | |
11653 | |
11654 (provide 'js2-mode) | |
11655 | |
11656 ;;; js2-mode.el ends here | |
11657 | |
11658 | |
11659 ;;; js2.el ends here |