Dmitry Panov 5 gadi atpakaļ
vecāks
revīzija
9f519f4c9b
7 mainītis faili ar 220 papildinājumiem un 58 dzēšanām
  1. 10 0
      ast/node.go
  2. 40 0
      compiler_stmt.go
  3. 49 0
      compiler_test.go
  4. 34 9
      parser/statement.go
  5. 10 6
      runtime.go
  6. 46 41
      token/token_const.go
  7. 31 2
      vm.go

+ 10 - 0
ast/node.go

@@ -269,6 +269,13 @@ type (
 		Body   Statement
 	}
 
+	ForOfStatement struct {
+		For    file.Idx
+		Into   Expression
+		Source Expression
+		Body   Statement
+	}
+
 	ForStatement struct {
 		For         file.Idx
 		Initializer Expression
@@ -344,6 +351,7 @@ func (*DoWhileStatement) _statementNode()    {}
 func (*EmptyStatement) _statementNode()      {}
 func (*ExpressionStatement) _statementNode() {}
 func (*ForInStatement) _statementNode()      {}
+func (*ForOfStatement) _statementNode()      {}
 func (*ForStatement) _statementNode()        {}
 func (*IfStatement) _statementNode()         {}
 func (*LabelledStatement) _statementNode()   {}
@@ -431,6 +439,7 @@ func (self *DoWhileStatement) Idx0() file.Idx    { return self.Do }
 func (self *EmptyStatement) Idx0() file.Idx      { return self.Semicolon }
 func (self *ExpressionStatement) Idx0() file.Idx { return self.Expression.Idx0() }
 func (self *ForInStatement) Idx0() file.Idx      { return self.For }
+func (self *ForOfStatement) Idx0() file.Idx      { return self.For }
 func (self *ForStatement) Idx0() file.Idx        { return self.For }
 func (self *IfStatement) Idx0() file.Idx         { return self.If }
 func (self *LabelledStatement) Idx0() file.Idx   { return self.Label.Idx0() }
@@ -492,6 +501,7 @@ func (self *DoWhileStatement) Idx1() file.Idx    { return self.Test.Idx1() }
 func (self *EmptyStatement) Idx1() file.Idx      { return self.Semicolon + 1 }
 func (self *ExpressionStatement) Idx1() file.Idx { return self.Expression.Idx1() }
 func (self *ForInStatement) Idx1() file.Idx      { return self.Body.Idx1() }
+func (self *ForOfStatement) Idx1() file.Idx      { return self.Body.Idx1() }
 func (self *ForStatement) Idx1() file.Idx        { return self.Body.Idx1() }
 func (self *IfStatement) Idx1() file.Idx {
 	if self.Alternate != nil {

+ 40 - 0
compiler_stmt.go

@@ -28,6 +28,8 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
 		c.compileForStatement(v, needResult)
 	case *ast.ForInStatement:
 		c.compileForInStatement(v, needResult)
+	case *ast.ForOfStatement:
+		c.compileForOfStatement(v, needResult)
 	case *ast.WhileStatement:
 		c.compileWhileStatement(v, needResult)
 	case *ast.BranchStatement:
@@ -63,6 +65,8 @@ func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult
 	switch s := v.Statement.(type) {
 	case *ast.ForInStatement:
 		c.compileLabeledForInStatement(s, needResult, label)
+	case *ast.ForOfStatement:
+		c.compileLabeledForOfStatement(s, needResult, label)
 	case *ast.ForStatement:
 		c.compileLabeledForStatement(s, needResult, label)
 	case *ast.WhileStatement:
@@ -333,6 +337,42 @@ func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResul
 	c.emit(enumPop)
 }
 
+func (c *compiler) compileForOfStatement(v *ast.ForOfStatement, needResult bool) {
+	c.compileLabeledForOfStatement(v, needResult, "")
+}
+
+func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label string) {
+	c.block = &block{
+		typ:        blockLoop,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
+	}
+
+	c.compileExpression(v.Source).emitGetter(true)
+	c.emit(iterate)
+	if needResult {
+		c.emit(loadUndef)
+	}
+	start := len(c.p.code)
+	c.markBlockStart()
+	c.block.cont = start
+
+	c.emit(nil)
+	c.compileExpression(v.Into).emitSetter(&c.enumGetExpr)
+	c.emit(pop)
+	if needResult {
+		c.emit(pop) // remove last result
+	}
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
+	c.emit(jump(start - len(c.p.code)))
+	c.p.code[start] = iterNext(len(c.p.code) - start)
+	c.leaveBlock()
+	c.markBlockStart()
+	c.emit(enumPop)
+}
+
 func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) {
 	c.compileLabeledWhileStatement(v, needResult, "")
 }

+ 49 - 0
compiler_test.go

@@ -1968,6 +1968,55 @@ func TestEmptyCodeError(t *testing.T) {
 	}
 }
 
+func TestForOfArray(t *testing.T) {
+	const SCRIPT = `
+	var array = [0, 'a', true, false, null, /* hole */, undefined, NaN];
+	var i = 0;
+	
+	for (var value of array) {
+	  assert.sameValue(value, array[i], 'element at index ' + i);
+	  i++;
+	}
+	
+	assert.sameValue(i, 8, 'Visits all elements');
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
+func TestForOfReturn(t *testing.T) {
+	const SCRIPT = `
+	var callCount = 0;
+	var iterationCount = 0;
+	var iterable = {};
+	var x = {
+	  set attr(_) {
+		throw new Test262Error();
+	  }
+	};
+	
+	iterable[Symbol.iterator] = function() {
+	  return {
+		next: function() {
+		  return { done: false, value: 0 };
+		},
+		return: function() {
+		  callCount += 1;
+		}
+	  }
+	};
+	
+	assert.throws(Test262Error, function() {
+	  for (x.attr of iterable) {
+		iterationCount += 1;
+	  }
+	});
+	
+	assert.sameValue(iterationCount, 0, 'The loop body is not evaluated');
+	assert.sameValue(callCount, 1, 'Iterator is closed');
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
 // FIXME
 /*
 func TestDummyCompile(t *testing.T) {

+ 34 - 9
parser/statement.go

@@ -378,6 +378,21 @@ func (self *_parser) parseForIn(idx file.Idx, into ast.Expression) *ast.ForInSta
 	}
 }
 
+func (self *_parser) parseForOf(idx file.Idx, into ast.Expression) *ast.ForOfStatement {
+
+	// Already have consumed "<into> of"
+
+	source := self.parseExpression()
+	self.expect(token.RIGHT_PARENTHESIS)
+
+	return &ast.ForOfStatement{
+		For:    idx,
+		Into:   into,
+		Source: source,
+		Body:   self.parseIterationStatement(),
+	}
+}
+
 func (self *_parser) parseFor(idx file.Idx, initializer ast.Expression) *ast.ForStatement {
 
 	// Already have consumed "<initializer> ;"
@@ -410,6 +425,7 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 	var left []ast.Expression
 
 	forIn := false
+	forOf := false
 	if self.token != token.SEMICOLON {
 
 		allowIn := self.scope.allowIn
@@ -418,33 +434,42 @@ func (self *_parser) parseForOrForInStatement() ast.Statement {
 			var_ := self.idx
 			self.next()
 			list := self.parseVariableDeclarationList(var_)
-			if len(list) == 1 && self.token == token.IN {
-				self.next() // in
-				forIn = true
-				left = []ast.Expression{list[0]} // There is only one declaration
-			} else {
-				left = list
+			if len(list) == 1 {
+				if self.token == token.IN {
+					self.next() // in
+					forIn = true
+				} else if self.token == token.OF {
+					self.next() // of
+					forOf = true
+				}
 			}
+			left = list
 		} else {
 			left = append(left, self.parseExpression())
 			if self.token == token.IN {
 				self.next()
 				forIn = true
+			} else if self.token == token.OF {
+				self.next()
+				forOf = true
 			}
 		}
 		self.scope.allowIn = allowIn
 	}
 
-	if forIn {
+	if forIn || forOf {
 		switch left[0].(type) {
 		case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.VariableExpression:
 			// These are all acceptable
 		default:
-			self.error(idx, "Invalid left-hand side in for-in")
+			self.error(idx, "Invalid left-hand side in for-in or for-of")
 			self.nextStatement()
 			return &ast.BadStatement{From: idx, To: self.idx}
 		}
-		return self.parseForIn(idx, left[0])
+		if forIn {
+			return self.parseForIn(idx, left[0])
+		}
+		return self.parseForOf(idx, left[0])
 	}
 
 	self.expect(token.SEMICOLON)

+ 10 - 6
runtime.go

@@ -1712,6 +1712,15 @@ func (r *Runtime) getIterator(obj Value, method func(FunctionCall) Value) *Objec
 	}))
 }
 
+func returnIter(iter *Object) {
+	retMethod := toMethod(iter.self.getStr("return", nil))
+	if retMethod != nil {
+		_ = tryFunc(func() {
+			retMethod(FunctionCall{This: iter})
+		})
+	}
+}
+
 func (r *Runtime) iterate(iter *Object, step func(Value)) {
 	for {
 		res := r.toObject(toMethod(iter.self.getStr("next", nil))(FunctionCall{This: iter}))
@@ -1722,12 +1731,7 @@ func (r *Runtime) iterate(iter *Object, step func(Value)) {
 			step(nilSafe(res.self.getStr("value", nil)))
 		})
 		if err != nil {
-			retMethod := toMethod(iter.self.getStr("return", nil))
-			if retMethod != nil {
-				_ = tryFunc(func() {
-					retMethod(FunctionCall{This: iter})
-				})
-			}
+			returnIter(iter)
 			panic(err)
 		}
 	}

+ 46 - 41
token/token_const.go

@@ -77,6 +77,7 @@ const (
 	firstKeyword
 	IF
 	IN
+	OF
 	DO
 
 	VAR
@@ -173,6 +174,7 @@ var token2string = [...]string{
 	QUESTION_MARK:               "?",
 	IF:                          "if",
 	IN:                          "in",
+	OF:                          "of",
 	DO:                          "do",
 	VAR:                         "var",
 	FOR:                         "for",
@@ -200,148 +202,151 @@ var token2string = [...]string{
 }
 
 var keywordTable = map[string]_keyword{
-	"if": _keyword{
+	"if": {
 		token: IF,
 	},
-	"in": _keyword{
+	"in": {
 		token: IN,
 	},
-	"do": _keyword{
+	"of": {
+		token: OF,
+	},
+	"do": {
 		token: DO,
 	},
-	"var": _keyword{
+	"var": {
 		token: VAR,
 	},
-	"for": _keyword{
+	"for": {
 		token: FOR,
 	},
-	"new": _keyword{
+	"new": {
 		token: NEW,
 	},
-	"try": _keyword{
+	"try": {
 		token: TRY,
 	},
-	"this": _keyword{
+	"this": {
 		token: THIS,
 	},
-	"else": _keyword{
+	"else": {
 		token: ELSE,
 	},
-	"case": _keyword{
+	"case": {
 		token: CASE,
 	},
-	"void": _keyword{
+	"void": {
 		token: VOID,
 	},
-	"with": _keyword{
+	"with": {
 		token: WITH,
 	},
-	"while": _keyword{
+	"while": {
 		token: WHILE,
 	},
-	"break": _keyword{
+	"break": {
 		token: BREAK,
 	},
-	"catch": _keyword{
+	"catch": {
 		token: CATCH,
 	},
-	"throw": _keyword{
+	"throw": {
 		token: THROW,
 	},
-	"return": _keyword{
+	"return": {
 		token: RETURN,
 	},
-	"typeof": _keyword{
+	"typeof": {
 		token: TYPEOF,
 	},
-	"delete": _keyword{
+	"delete": {
 		token: DELETE,
 	},
-	"switch": _keyword{
+	"switch": {
 		token: SWITCH,
 	},
-	"default": _keyword{
+	"default": {
 		token: DEFAULT,
 	},
-	"finally": _keyword{
+	"finally": {
 		token: FINALLY,
 	},
-	"function": _keyword{
+	"function": {
 		token: FUNCTION,
 	},
-	"continue": _keyword{
+	"continue": {
 		token: CONTINUE,
 	},
-	"debugger": _keyword{
+	"debugger": {
 		token: DEBUGGER,
 	},
-	"instanceof": _keyword{
+	"instanceof": {
 		token: INSTANCEOF,
 	},
-	"const": _keyword{
+	"const": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"class": _keyword{
+	"class": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"enum": _keyword{
+	"enum": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"export": _keyword{
+	"export": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"extends": _keyword{
+	"extends": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"import": _keyword{
+	"import": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"super": _keyword{
+	"super": {
 		token:         KEYWORD,
 		futureKeyword: true,
 	},
-	"implements": _keyword{
+	"implements": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"interface": _keyword{
+	"interface": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"let": _keyword{
+	"let": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"package": _keyword{
+	"package": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"private": _keyword{
+	"private": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"protected": _keyword{
+	"protected": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"public": _keyword{
+	"public": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,
 	},
-	"static": _keyword{
+	"static": {
 		token:         KEYWORD,
 		futureKeyword: true,
 		strict:        true,

+ 31 - 2
vm.go

@@ -34,8 +34,9 @@ type context struct {
 }
 
 type iterStackItem struct {
-	val Value
-	f   iterNextFunc
+	val  Value
+	f    iterNextFunc
+	iter *Object
 }
 
 type ref interface {
@@ -362,6 +363,9 @@ func (vm *vm) try(f func()) (ex *Exception) {
 				// Restore other stacks
 				iterTail := vm.iterStack[iterLen:]
 				for i := range iterTail {
+					if iter := iterTail[i].iter; iter != nil {
+						returnIter(iter)
+					}
 					iterTail[i] = iterStackItem{}
 				}
 				vm.iterStack = vm.iterStack[:iterLen]
@@ -2446,3 +2450,28 @@ func (_enumPop) exec(vm *vm) {
 	vm.iterStack = vm.iterStack[:l]
 	vm.pc++
 }
+
+type _iterate struct{}
+
+var iterate _iterate
+
+func (_iterate) exec(vm *vm) {
+	iter := vm.r.getIterator(vm.stack[vm.sp-1], nil)
+	vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter})
+	vm.sp--
+	vm.pc++
+}
+
+type iterNext int32
+
+func (jmp iterNext) exec(vm *vm) {
+	l := len(vm.iterStack) - 1
+	iter := vm.iterStack[l].iter
+	res := vm.r.toObject(toMethod(iter.self.getStr("next", nil))(FunctionCall{This: iter}))
+	if nilSafe(res.self.getStr("done", nil)).ToBoolean() {
+		vm.pc += int(jmp)
+	} else {
+		vm.iterStack[l].val = nilSafe(res.self.getStr("value", nil))
+		vm.pc++
+	}
+}