diff --git a/lib/ast.js b/lib/ast.js index 65a236e7..bc631166 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -262,6 +262,22 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", { } }, AST_IterationStatement); +var AST_ForOf = DEFNODE("ForOf", "init name object", { + $documentation: "A `for ... of` statement", + $propdoc: { + init: "[AST_Node] the `for/of` initialization code", + name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", + object: "[AST_Node] the object that we're looping through" + }, + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.init._walk(visitor); + this.object._walk(visitor); + this.body._walk(visitor); + }); + } +}, AST_IterationStatement); + var AST_With = DEFNODE("With", "expression", { $documentation: "A `with` statement", $propdoc: { diff --git a/lib/output.js b/lib/output.js index c424a632..8271a6f7 100644 --- a/lib/output.js +++ b/lib/output.js @@ -721,6 +721,19 @@ function OutputStream(options) { output.space(); self._do_print_body(output); }); + DEFPRINT(AST_ForOf, function(self, output){ + output.print("for"); + output.space(); + output.with_parens(function(){ + self.init.print(output); + output.space(); + output.print("of"); + output.space(); + self.object.print(output); + }); + output.space(); + self._do_print_body(output); + }); DEFPRINT(AST_With, function(self, output){ output.print("with"); output.space(); @@ -923,7 +936,7 @@ function OutputStream(options) { def.print(output); }); var p = output.parent(); - var in_for = p instanceof AST_For || p instanceof AST_ForIn; + var in_for = p instanceof AST_For || p instanceof AST_ForIn || p instanceof AST_ForOf; var avoid_semicolon = in_for && p.init === this; if (!avoid_semicolon) output.semicolon(); @@ -961,7 +974,7 @@ function OutputStream(options) { output.print("="); output.space(); var p = output.parent(1); - var noin = p instanceof AST_For || p instanceof AST_ForIn; + var noin = p instanceof AST_For || p instanceof AST_ForIn || p instanceof AST_ForOf; parenthesize_for_noin(self.value, output, noin); } }); diff --git a/lib/parse.js b/lib/parse.js index edeea0fa..0323d2cc 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,7 +44,7 @@ "use strict"; -var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return yield switch throw try typeof var let void while with'; +var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in of instanceof new return yield switch throw try typeof var let void while with'; var KEYWORDS_ATOM = 'false null true'; var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield' + " " + KEYWORDS_ATOM + " " + KEYWORDS; @@ -63,6 +63,7 @@ var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; var OPERATORS = makePredicate([ "in", + "of", "instanceof", "typeof", "new", @@ -607,7 +608,7 @@ var PRECEDENCE = (function(a, ret){ ["^"], ["&"], ["==", "===", "!=", "!=="], - ["<", ">", "<=", ">=", "in", "instanceof"], + ["<", ">", "<=", ">=", "in", "of", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], ["*", "/", "%"] @@ -928,6 +929,12 @@ function parse($TEXT, options) { next(); return for_in(init); } + if (is("operator", "of")) { + if (init instanceof AST_Var && init.definitions.length > 1) + croak("Only one variable declaration allowed in for..of loop"); + next(); + return for_of(init); + } } return regular_for(init); }; @@ -958,6 +965,18 @@ function parse($TEXT, options) { }); }; + function for_of(init){ + var lhs = init instanceof AST_Var ? init.definitions[0].name : null; + var obj = expression(true); + expect(")"); + return new AST_ForOf({ + init : init, + name : lhs, + object : obj, + body : in_loop(statement) + }); + } + var function_ = function(ctor) { var in_statement = ctor === AST_Defun; var isgenerator = is("operator", "*"); @@ -1396,7 +1415,7 @@ function parse($TEXT, options) { var expr_op = function(left, min_prec, no_in) { var op = is("operator") ? S.token.value : null; - if (op == "in" && no_in) op = null; + if ((op == "in" || op == "of")&& no_in) op = null; var prec = op != null ? PRECEDENCE[op] : null; if (prec != null && prec > min_prec) { next();