[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] 13/46: Support ES6 array comprehensions
From: |
Dmitry Gutov |
Subject: |
[elpa] 13/46: Support ES6 array comprehensions |
Date: |
Sat, 15 Nov 2014 20:57:59 +0000 |
dgutov pushed a commit to branch master
in repository elpa.
commit e999314670776a9d18e16a9cca63dc98b86fd950
Author: Dmitry Gutov <address@hidden>
Date: Tue Jul 8 23:13:51 2014 +0300
Support ES6 array comprehensions
---
js2-mode.el | 330 +++++++++++++++++++++++++++++++------------------------
tests/parser.el | 13 ++-
2 files changed, 198 insertions(+), 145 deletions(-)
diff --git a/js2-mode.el b/js2-mode.el
index 54d7dcb..3418d9d 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -3674,75 +3674,87 @@ as opposed to required parens such as those enclosing
an if-conditional."
(js2-print-ast (js2-paren-node-expr n) 0)
(insert ")"))
-(defstruct (js2-array-comp-node
+(defstruct (js2-comp-node
(:include js2-scope)
(:constructor nil)
- (:constructor make-js2-array-comp-node (&key (type js2-ARRAYCOMP)
- (pos js2-ts-cursor)
- len result
- loops filter
- if-pos lp rp)))
+ (:constructor make-js2-comp-node (&key (type js2-ARRAYCOMP)
+ (pos js2-ts-cursor)
+ len result
+ loops filters
+ form)))
"AST node for an Array comprehension such as [[x,y] for (x in foo) for (y in
bar)]."
result ; result expression (just after left-bracket)
- loops ; a Lisp list of `js2-array-comp-loop-node'
- filter ; guard/filter expression
- if-pos ; buffer pos of 'if' keyword, if present, else nil
- lp ; buffer position of if-guard left-paren, or nil if not present
- rp) ; buffer position of if-guard right-paren, or nil if not present
-
-(put 'cl-struct-js2-array-comp-node 'js2-visitor 'js2-visit-array-comp-node)
-(put 'cl-struct-js2-array-comp-node 'js2-printer 'js2-print-array-comp-node)
-
-(defun js2-visit-array-comp-node (n v)
- (js2-visit-ast (js2-array-comp-node-result n) v)
- (dolist (l (js2-array-comp-node-loops n))
+ loops ; a Lisp list of `js2-comp-loop-node'
+ filters ; a Lisp list of guard/filter expressions
+ form ; ARRAY, LEGACY_ARRAY or STAR_GENERATOR
+ ; SpiderMonkey also supports "legacy generator expressions", but we
dont.
+ )
+
+(put 'cl-struct-js2-comp-node 'js2-visitor 'js2-visit-comp-node)
+(put 'cl-struct-js2-comp-node 'js2-printer 'js2-print-comp-node)
+
+(defun js2-visit-comp-node (n v)
+ (js2-visit-ast (js2-comp-node-result n) v)
+ (dolist (l (js2-comp-node-loops n))
(js2-visit-ast l v))
- (js2-visit-ast (js2-array-comp-node-filter n) v))
+ (dolist (f (js2-comp-node-filters n))
+ (js2-visit-ast f v)))
-(defun js2-print-array-comp-node (n i)
+(defun js2-print-comp-node (n i)
(let ((pad (js2-make-pad i))
- (result (js2-array-comp-node-result n))
- (loops (js2-array-comp-node-loops n))
- (filter (js2-array-comp-node-filter n)))
+ (result (js2-comp-node-result n))
+ (loops (js2-comp-node-loops n))
+ (filters (js2-comp-node-filters n))
+ (legacy-p (eq (js2-comp-node-form n) 'LEGACY_ARRAY)))
(insert pad "[")
- (js2-print-ast result 0)
+ (when legacy-p
+ (js2-print-ast result 0))
(dolist (l loops)
- (insert " ")
- (js2-print-ast l 0))
- (when filter
- (insert " if (")
- (js2-print-ast filter 0)
- (insert ")"))
+ (when legacy-p
+ (insert " "))
+ (js2-print-ast l 0)
+ (unless legacy-p
+ (insert " ")))
+ (dolist (f filters)
+ (when legacy-p
+ (insert " "))
+ (insert "if (")
+ (js2-print-ast f 0)
+ (insert ")")
+ (unless legacy-p
+ (insert " ")))
+ (unless legacy-p
+ (js2-print-ast result 0))
(insert "]")))
-(defstruct (js2-array-comp-loop-node
+(defstruct (js2-comp-loop-node
(:include js2-for-in-node)
(:constructor nil)
- (:constructor make-js2-array-comp-loop-node (&key (type js2-FOR)
- (pos
js2-ts-cursor)
- len iterator
- object in-pos
- foreach-p
- each-pos
- forof-p
- lp rp)))
+ (:constructor make-js2-comp-loop-node (&key (type js2-FOR)
+ (pos js2-ts-cursor)
+ len iterator
+ object in-pos
+ foreach-p
+ each-pos
+ forof-p
+ lp rp)))
"AST subtree for each 'for (foo in bar)' loop in an array comprehension.")
-(put 'cl-struct-js2-array-comp-loop-node 'js2-visitor
'js2-visit-array-comp-loop)
-(put 'cl-struct-js2-array-comp-loop-node 'js2-printer
'js2-print-array-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-visitor 'js2-visit-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-printer 'js2-print-comp-loop)
-(defun js2-visit-array-comp-loop (n v)
- (js2-visit-ast (js2-array-comp-loop-node-iterator n) v)
- (js2-visit-ast (js2-array-comp-loop-node-object n) v))
+(defun js2-visit-comp-loop (n v)
+ (js2-visit-ast (js2-comp-loop-node-iterator n) v)
+ (js2-visit-ast (js2-comp-loop-node-object n) v))
-(defun js2-print-array-comp-loop (n _i)
+(defun js2-print-comp-loop (n _i)
(insert "for ")
- (when (js2-array-comp-loop-node-foreach-p n) (insert "each "))
+ (when (js2-comp-loop-node-foreach-p n) (insert "each "))
(insert "(")
- (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
- (insert (if (js2-array-comp-loop-node-forof-p n)
+ (js2-print-ast (js2-comp-loop-node-iterator n) 0)
+ (insert (if (js2-comp-loop-node-forof-p n)
" of " " in "))
- (js2-print-ast (js2-array-comp-loop-node-object n) 0)
+ (js2-print-ast (js2-comp-loop-node-object n) 0)
(insert ")"))
(defstruct (js2-empty-expr-node
@@ -4210,8 +4222,8 @@ Returns nil for zero-length child lists or unsupported
nodes."
;; All because Common Lisp doesn't support multiple inheritance for defstructs.
(defconst js2-paren-expr-nodes
- '(cl-struct-js2-array-comp-loop-node
- cl-struct-js2-array-comp-node
+ '(cl-struct-js2-comp-loop-node
+ cl-struct-js2-comp-node
cl-struct-js2-call-node
cl-struct-js2-catch-node
cl-struct-js2-do-node
@@ -4263,8 +4275,8 @@ Note that the position may be nil in the case of a parse
error."
(js2-catch-node-lp node))
((js2-let-node-p node)
(js2-let-node-lp node))
- ((js2-array-comp-node-p node)
- (js2-array-comp-node-lp node))
+ ((js2-comp-node-p node)
+ 0)
((js2-with-node-p node)
(js2-with-node-lp node))
((js2-xml-dot-query-node-p node)
@@ -4298,8 +4310,8 @@ Note that the position may be nil in the case of a parse
error."
(js2-catch-node-rp node))
((js2-let-node-p node)
(js2-let-node-rp node))
- ((js2-array-comp-node-p node)
- (js2-array-comp-node-rp node))
+ ((js2-comp-node-p node)
+ (1- (js2-node-len node)))
((js2-with-node-p node)
(js2-with-node-rp node))
((js2-xml-dot-query-node-p node)
@@ -9145,7 +9157,7 @@ array-literals, array comprehensions and regular
expressions."
((= tt js2-FUNCTION)
(js2-parse-function-expr))
((= tt js2-LB)
- (js2-parse-array-literal))
+ (js2-parse-array-comp-or-literal))
((= tt js2-LC)
(js2-parse-object-literal))
((= tt js2-LET)
@@ -9246,13 +9258,19 @@ array-literals, array comprehensions and regular
expressions."
(point)))
comma-pos))
-(defun js2-parse-array-literal ()
- (let ((pos (js2-current-token-beg))
- (after-lb-or-comma t)
+(defun js2-parse-array-comp-or-literal ()
+ (let ((pos (js2-current-token-beg)))
+ (if (and (>= js2-language-version 200)
+ (js2-match-token js2-FOR))
+ (js2-parse-array-comp pos)
+ (js2-parse-array-literal pos))))
+
+(defun js2-parse-array-literal (pos)
+ (let ((after-lb-or-comma t)
after-comma tt elems pn
(continue t))
(unless js2-is-in-destructuring
- (js2-push-scope (make-js2-scope))) ; for array comp
+ (js2-push-scope (make-js2-scope))) ; for the legacy array comp
(while continue
(setq tt (js2-get-token))
(cond
@@ -9296,7 +9314,7 @@ array-literals, array comprehensions and regular
expressions."
(not (cdr elems))) ; but no 2nd element
(js2-unget-token)
(setf continue nil
- pn (js2-parse-array-comprehension (car elems) pos)))
+ pn (js2-parse-legacy-array-comp (car elems) pos)))
;; another element
(t
(unless after-lb-or-comma
@@ -9314,97 +9332,121 @@ array-literals, array comprehensions and regular
expressions."
(js2-pop-scope))
pn))
-(defun js2-parse-array-comprehension (expr pos)
- "Parse a JavaScript 1.7 Array Comprehension.
+(defun js2-parse-legacy-array-comp (expr pos)
+ "Parse a legacy array comprehension (JavaScript 1.7).
EXPR is the first expression after the opening left-bracket.
POS is the beginning of the LB token preceding EXPR.
We should have just parsed the 'for' keyword before calling this function."
- (let (loops loop first filter if-pos result)
- (while (= (js2-get-token) js2-FOR)
- (let ((prev (car loops))) ; rearrange scope chain
- (push (setq loop (js2-parse-array-comp-loop)) loops)
- (if prev ; each loop is parent scope to the next one
- (setf (js2-scope-parent-scope loop) prev)
- ; first loop takes expr scope's parent
- (setf (js2-scope-parent-scope (setq first loop))
- (js2-scope-parent-scope js2-current-scope)))))
- (js2-unget-token)
- ;; set expr scope's parent to the last loop
- (setf (js2-scope-parent-scope js2-current-scope) (car loops))
- (if (/= (js2-get-token) js2-IF)
- (js2-unget-token)
- (setq if-pos (- (js2-current-token-beg) pos) ; relative
- filter (js2-parse-condition)))
+ (let ((current-scope js2-current-scope)
+ loops first filter if-pos result)
+ (unwind-protect
+ (progn
+ (while (js2-match-token js2-FOR)
+ (let ((loop (make-js2-comp-loop-node)))
+ (js2-push-scope loop)
+ (push loop loops)
+ (js2-parse-comp-loop loop)))
+ ;; First loop takes expr scope's parent.
+ (setf (js2-scope-parent-scope (setq first (car (last loops))))
+ (js2-scope-parent-scope current-scope))
+ ;; Set expr scope's parent to the last loop.
+ (setf (js2-scope-parent-scope current-scope) (car loops))
+ (if (/= (js2-get-token) js2-IF)
+ (js2-unget-token)
+ (setq filter (js2-parse-condition))))
+ (dotimes (_ (1- (length loops)))
+ (js2-pop-scope)))
(js2-must-match js2-RB "msg.no.bracket.arg" pos)
- (setq result (make-js2-array-comp-node :pos pos
- :len (- js2-ts-cursor pos)
- :result expr
- :loops (nreverse loops)
- :filter (car filter)
- :lp (js2-relpos (second filter) pos)
- :rp (js2-relpos (third filter) pos)
- :if-pos if-pos))
+ (setq result (make-js2-comp-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :result expr
+ :loops (nreverse loops)
+ :filters (and filter (list (car filter)))
+ :form 'LEGACY_ARRAY))
(apply #'js2-node-add-children result expr (car filter)
- (js2-array-comp-node-loops result))
- (setq js2-current-scope first) ; pop to the first loop
+ (js2-comp-node-loops result))
result))
-(defun js2-parse-array-comp-loop ()
- "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
-Last token peeked should be the initial FOR."
- (let ((pos (js2-current-token-beg))
- (pn (make-js2-array-comp-loop-node))
- tt iter obj foreach-p forof-p in-pos each-pos lp rp)
- (assert (= (js2-current-token-type) js2-FOR))
- (js2-push-scope pn)
+(defun js2-parse-array-comp (pos)
+ "Parse an ES6 array comprehension.
+POS is the beginning of the LB token.
+We should have just parsed the 'for' keyword before calling this function."
+ (let (loops filters if-pos expr result)
(unwind-protect
(progn
- (when (js2-match-token js2-NAME)
- (if (string= (js2-current-token-string) "each")
- (progn
- (setq foreach-p t
- each-pos (- (js2-current-token-beg) pos)) ; relative
- (js2-record-face 'font-lock-keyword-face))
- (js2-report-error "msg.no.paren.for")))
- (if (js2-must-match js2-LP "msg.no.paren.for")
- (setq lp (- (js2-current-token-beg) pos)))
- (setq tt (js2-peek-token))
- (cond
- ((or (= tt js2-LB)
- (= tt js2-LC))
- (js2-get-token)
- (setq iter (js2-parse-destruct-primary-expr))
- (js2-define-destruct-symbols iter js2-LET
- 'font-lock-variable-name-face t))
- ((js2-match-token js2-NAME)
- (setq iter (js2-create-name-node)))
- (t
- (js2-report-error "msg.bad.var")))
- ;; Define as a let since we want the scope of the variable to
- ;; be restricted to the array comprehension
- (if (js2-name-node-p iter)
- (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
- (if (or (js2-match-token js2-IN)
- (and (>= js2-language-version 200)
- (js2-match-contextual-kwd "of")
- (setq forof-p t)))
- (setq in-pos (- (js2-current-token-beg) pos))
- (js2-report-error "msg.in.after.for.name"))
- (setq obj (js2-parse-expr))
- (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
- (setq rp (- (js2-current-token-beg) pos)))
- (setf (js2-node-pos pn) pos
- (js2-node-len pn) (- js2-ts-cursor pos)
- (js2-array-comp-loop-node-iterator pn) iter
- (js2-array-comp-loop-node-object pn) obj
- (js2-array-comp-loop-node-in-pos pn) in-pos
- (js2-array-comp-loop-node-each-pos pn) each-pos
- (js2-array-comp-loop-node-foreach-p pn) foreach-p
- (js2-array-comp-loop-node-forof-p pn) forof-p
- (js2-array-comp-loop-node-lp pn) lp
- (js2-array-comp-loop-node-rp pn) rp)
- (js2-node-add-children pn iter obj))
- (js2-pop-scope))
+ (js2-unget-token)
+ (while (js2-match-token js2-FOR)
+ (let ((loop (make-js2-comp-loop-node)))
+ (js2-push-scope loop)
+ (push loop loops)
+ (js2-parse-comp-loop loop)))
+ (while (js2-match-token js2-IF)
+ (push (car (js2-parse-condition)) filters))
+ (setq expr (js2-parse-assign-expr)))
+ (dolist (_ loops)
+ (js2-pop-scope)))
+ (js2-must-match js2-RB "msg.no.bracket.arg" pos)
+ (setq result (make-js2-comp-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :result expr
+ :loops (nreverse loops)
+ :filters (nreverse filters)
+ :form 'ARRAY))
+ (apply #'js2-node-add-children result (js2-comp-node-loops result))
+ (apply #'js2-node-add-children result expr (js2-comp-node-filters result))
+ result))
+
+(defun js2-parse-comp-loop (pn &optional only-of-p)
+ "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
+The current token should be the initial FOR.
+If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed."
+ (let ((pos (js2-comp-loop-node-pos pn))
+ tt iter obj foreach-p forof-p in-pos each-pos lp rp)
+ (when (and (not only-of-p) (js2-match-token js2-NAME))
+ (if (string= (js2-current-token-string) "each")
+ (progn
+ (setq foreach-p t
+ each-pos (- (js2-current-token-beg) pos)) ; relative
+ (js2-record-face 'font-lock-keyword-face))
+ (js2-report-error "msg.no.paren.for")))
+ (if (js2-must-match js2-LP "msg.no.paren.for")
+ (setq lp (- (js2-current-token-beg) pos)))
+ (setq tt (js2-peek-token))
+ (cond
+ ((or (= tt js2-LB)
+ (= tt js2-LC))
+ (js2-get-token)
+ (setq iter (js2-parse-destruct-primary-expr))
+ (js2-define-destruct-symbols iter js2-LET
+ 'font-lock-variable-name-face t))
+ ((js2-match-token js2-NAME)
+ (setq iter (js2-create-name-node)))
+ (t
+ (js2-report-error "msg.bad.var")))
+ ;; Define as a let since we want the scope of the variable to
+ ;; be restricted to the array comprehension
+ (if (js2-name-node-p iter)
+ (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
+ (if (or (and (not only-of-p) (js2-match-token js2-IN))
+ (and (>= js2-language-version 200)
+ (js2-match-contextual-kwd "of")
+ (setq forof-p t)))
+ (setq in-pos (- (js2-current-token-beg) pos))
+ (js2-report-error "msg.in.after.for.name"))
+ (setq obj (js2-parse-expr))
+ (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
+ (setq rp (- (js2-current-token-beg) pos)))
+ (setf (js2-node-pos pn) pos
+ (js2-node-len pn) (- js2-ts-cursor pos)
+ (js2-comp-loop-node-iterator pn) iter
+ (js2-comp-loop-node-object pn) obj
+ (js2-comp-loop-node-in-pos pn) in-pos
+ (js2-comp-loop-node-each-pos pn) each-pos
+ (js2-comp-loop-node-foreach-p pn) foreach-p
+ (js2-comp-loop-node-forof-p pn) forof-p
+ (js2-comp-loop-node-lp pn) lp
+ (js2-comp-loop-node-rp pn) rp)
+ (js2-node-add-children pn iter obj)
pn))
(defun js2-parse-object-literal ()
@@ -11154,7 +11196,7 @@ move backward across N balanced expressions."
(cond
((or (js2-array-node-p node)
(js2-object-node-p node)
- (js2-array-comp-node-p node)
+ (js2-comp-node-p node)
(memq (aref node 0) '(cl-struct-js2-block-node cl-struct-js2-scope)))
(cons abs-pos (+ abs-pos (js2-node-len node) -1)))
((js2-paren-expr-node-p node)
diff --git a/tests/parser.el b/tests/parser.el
index a3fa1e0..9500e1c 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -135,9 +135,12 @@ the test."
;;; 'of' contextual keyword
-(js2-deftest-parse parse-array-comp-loop-with-of
+(js2-deftest-parse parse-legacy-array-comp-loop-with-of
"[a for (a of [])];")
+(js2-deftest-parse parse-array-comp-loop
+ "[for (a of []) a];")
+
(js2-deftest-parse parse-for-of
"for (var a of []) {\n}")
@@ -250,6 +253,14 @@ the test."
(js2-deftest-parse harmony-generator-yield-star "(function*(a) { yield*
a;\n});")
+;;; Comprehensions
+
+(js2-deftest-parse parse-legacy-array-comp-loop-with-filter
+ "[a for (a in b) if (a == 2)];")
+
+(js2-deftest-parse parse-array-comp-loop-with-filters
+ "[for (a in b) if (a == 2) if (b != 10) a];")
+
;;; Scopes
(js2-deftest ast-symbol-table-includes-fn-node "function foo() {}"
- [elpa] 08/46: Fix parsing of function statements in blocks, (continued)
- [elpa] 08/46: Fix parsing of function statements in blocks, Dmitry Gutov, 2014/11/15
- [elpa] 04/46: Try to improve formatting, Dmitry Gutov, 2014/11/15
- [elpa] 09/46: Rename mention of espresso-mode to js-mode, Dmitry Gutov, 2014/11/15
- [elpa] 10/46: js2-parse-array-literal: improve trailing comma warning, Dmitry Gutov, 2014/11/15
- [elpa] 07/46: Cut down on reserved words; improve error reporting, Dmitry Gutov, 2014/11/15
- [elpa] 11/46: Support yield*, Dmitry Gutov, 2014/11/15
- [elpa] 16/46: Support Unicode Identifiers, Dmitry Gutov, 2014/11/15
- [elpa] 17/46: Check if buffer is alive before reparsing., Dmitry Gutov, 2014/11/15
- [elpa] 19/46: Better docstrings, Dmitry Gutov, 2014/11/15
- [elpa] 14/46: Support ES6 generator comprehensions, Dmitry Gutov, 2014/11/15
- [elpa] 13/46: Support ES6 array comprehensions,
Dmitry Gutov <=
- [elpa] 12/46: Add `.' to electric-indent-chars for method continuations, Dmitry Gutov, 2014/11/15
- [elpa] 15/46: Add Contributing section, Dmitry Gutov, 2014/11/15
- [elpa] 24/46: Small tweak, Dmitry Gutov, 2014/11/15
- [elpa] 23/46: Add js2-language-version checks, Dmitry Gutov, 2014/11/15
- [elpa] 25/46: Ignore integer overflow, Dmitry Gutov, 2014/11/15
- [elpa] 26/46: Allow 'in' operator inside 'for' init when unambiguous, Dmitry Gutov, 2014/11/15
- [elpa] 27/46: Fix parsing of let expressions, Dmitry Gutov, 2014/11/15
- [elpa] 18/46: Merge pull request #149 from lewang/dont-parse-killed-buffer, Dmitry Gutov, 2014/11/15
- [elpa] 28/46: js2-parse-let: Simplify, Dmitry Gutov, 2014/11/15
- [elpa] 22/46: Support ES6 number literals, Dmitry Gutov, 2014/11/15