package otto import ( "fmt" "runtime" "github.com/robertkrimen/otto/token" ) func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value { // Allow interpreter interruption // If the Interrupt channel is nil, then // we avoid runtime.Gosched() overhead (if any) // FIXME: Test this if self.otto.Interrupt != nil { runtime.Gosched() select { case value := <-self.otto.Interrupt: value() default: } } switch node := node.(type) { case *_nodeBlockStatement: labels := self.labels self.labels = nil value := self.cmpl_evaluate_nodeStatementList(node.list) switch value.kind { case valueResult: switch value.evaluateBreak(labels) { case resultBreak: return emptyValue } } return value case *_nodeBranchStatement: target := node.label switch node.branch { // FIXME Maybe node.kind? node.operator? case token.BREAK: return toValue(newBreakResult(target)) case token.CONTINUE: return toValue(newContinueResult(target)) } case *_nodeDebuggerStatement: if self.debugger != nil { self.debugger(self.otto) } return emptyValue // Nothing happens. case *_nodeDoWhileStatement: return self.cmpl_evaluate_nodeDoWhileStatement(node) case *_nodeEmptyStatement: return emptyValue case *_nodeExpressionStatement: return self.cmpl_evaluate_nodeExpression(node.expression) case *_nodeForInStatement: return self.cmpl_evaluate_nodeForInStatement(node) case *_nodeForStatement: return self.cmpl_evaluate_nodeForStatement(node) case *_nodeIfStatement: return self.cmpl_evaluate_nodeIfStatement(node) case *_nodeLabelledStatement: self.labels = append(self.labels, node.label) defer func() { if len(self.labels) > 0 { self.labels = self.labels[:len(self.labels)-1] // Pop the label } else { self.labels = nil } }() return self.cmpl_evaluate_nodeStatement(node.statement) case *_nodeReturnStatement: if node.argument != nil { return toValue(newReturnResult(self.cmpl_evaluate_nodeExpression(node.argument).resolve())) } return toValue(newReturnResult(Value{})) case *_nodeSwitchStatement: return self.cmpl_evaluate_nodeSwitchStatement(node) case *_nodeThrowStatement: value := self.cmpl_evaluate_nodeExpression(node.argument).resolve() panic(newException(value)) case *_nodeTryStatement: return self.cmpl_evaluate_nodeTryStatement(node) case *_nodeVariableStatement: // Variables are already defined, this is initialization only for _, variable := range node.list { self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression)) } return emptyValue case *_nodeWhileStatement: return self.cmpl_evaluate_nodeWhileStatement(node) case *_nodeWithStatement: return self.cmpl_evaluate_nodeWithStatement(node) } panic(fmt.Errorf("Here be dragons: evaluate_nodeStatement(%T)", node)) } func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Value { var result Value for _, node := range list { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { case valueResult: return value case valueEmpty: default: // We have getValue here to (for example) trigger a // ReferenceError (of the not defined variety) // Not sure if this is the best way to error out early // for such errors or if there is a better way // TODO Do we still need this? result = value.resolve() } } return result } func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileStatement) Value { labels := append(self.labels, "") self.labels = nil test := node.test result := emptyValue resultBreak: for { for _, node := range node.body { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { case valueResult: switch value.evaluateBreakContinue(labels) { case resultReturn: return value case resultBreak: break resultBreak case resultContinue: goto resultContinue } case valueEmpty: default: result = value } } resultContinue: if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() { // Stahp: do ... while (false) break } } return result } func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement) Value { labels := append(self.labels, "") self.labels = nil source := self.cmpl_evaluate_nodeExpression(node.source) sourceValue := source.resolve() switch sourceValue.kind { case valueUndefined, valueNull: return emptyValue } sourceObject := self.toObject(sourceValue) into := node.into body := node.body result := emptyValue object := sourceObject for object != nil { enumerateValue := emptyValue object.enumerate(false, func(name string) bool { into := self.cmpl_evaluate_nodeExpression(into) // In the case of: for (var abc in def) ... if into.reference() == nil { identifier := into.string() // TODO Should be true or false (strictness) depending on context into = toValue(getIdentifierReference(self, self.scope.lexical, identifier, false, -1)) } self.putValue(into.reference(), toValue_string(name)) for _, node := range body { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { case valueResult: switch value.evaluateBreakContinue(labels) { case resultReturn: enumerateValue = value return false case resultBreak: object = nil return false case resultContinue: return true } case valueEmpty: default: enumerateValue = value } } return true }) if object == nil { break } object = object.prototype if !enumerateValue.isEmpty() { result = enumerateValue } } return result } func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Value { labels := append(self.labels, "") self.labels = nil initializer := node.initializer test := node.test update := node.update body := node.body if initializer != nil { initialResult := self.cmpl_evaluate_nodeExpression(initializer) initialResult.resolve() // Side-effect trigger } result := emptyValue resultBreak: for { if test != nil { testResult := self.cmpl_evaluate_nodeExpression(test) testResultValue := testResult.resolve() if testResultValue.bool() == false { break } } // this is to prevent for cycles with no body from running forever if len(body) == 0 && self.otto.Interrupt != nil { runtime.Gosched() select { case value := <-self.otto.Interrupt: value() default: } } for _, node := range body { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { case valueResult: switch value.evaluateBreakContinue(labels) { case resultReturn: return value case resultBreak: break resultBreak case resultContinue: goto resultContinue } case valueEmpty: default: result = value } } resultContinue: if update != nil { updateResult := self.cmpl_evaluate_nodeExpression(update) updateResult.resolve() // Side-effect trigger } } return result } func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value { test := self.cmpl_evaluate_nodeExpression(node.test) testValue := test.resolve() if testValue.bool() { return self.cmpl_evaluate_nodeStatement(node.consequent) } else if node.alternate != nil { return self.cmpl_evaluate_nodeStatement(node.alternate) } return emptyValue } func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value { labels := append(self.labels, "") self.labels = nil discriminantResult := self.cmpl_evaluate_nodeExpression(node.discriminant) target := node.default_ for index, clause := range node.body { test := clause.test if test != nil { if self.calculateComparison(token.STRICT_EQUAL, discriminantResult, self.cmpl_evaluate_nodeExpression(test)) { target = index break } } } result := emptyValue if target != -1 { for _, clause := range node.body[target:] { for _, statement := range clause.consequent { value := self.cmpl_evaluate_nodeStatement(statement) switch value.kind { case valueResult: switch value.evaluateBreak(labels) { case resultReturn: return value case resultBreak: return emptyValue } case valueEmpty: default: result = value } } } } return result } func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Value { tryCatchValue, exception := self.tryCatchEvaluate(func() Value { return self.cmpl_evaluate_nodeStatement(node.body) }) if exception && node.catch != nil { outer := self.scope.lexical self.scope.lexical = self.newDeclarationStash(outer) defer func() { self.scope.lexical = outer }() // TODO If necessary, convert TypeError => TypeError // That, is, such errors can be thrown despite not being JavaScript "native" // strict = false self.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false) // FIXME node.CatchParameter // FIXME node.Catch tryCatchValue, exception = self.tryCatchEvaluate(func() Value { return self.cmpl_evaluate_nodeStatement(node.catch.body) }) } if node.finally != nil { finallyValue := self.cmpl_evaluate_nodeStatement(node.finally) if finallyValue.kind == valueResult { return finallyValue } } if exception { panic(newException(tryCatchValue)) } return tryCatchValue } func (self *_runtime) cmpl_evaluate_nodeWhileStatement(node *_nodeWhileStatement) Value { test := node.test body := node.body labels := append(self.labels, "") self.labels = nil result := emptyValue resultBreakContinue: for { if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() { // Stahp: while (false) ... break } for _, node := range body { value := self.cmpl_evaluate_nodeStatement(node) switch value.kind { case valueResult: switch value.evaluateBreakContinue(labels) { case resultReturn: return value case resultBreak: break resultBreakContinue case resultContinue: continue resultBreakContinue } case valueEmpty: default: result = value } } } return result } func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value { object := self.cmpl_evaluate_nodeExpression(node.object) outer := self.scope.lexical lexical := self.newObjectStash(self.toObject(object.resolve()), outer) self.scope.lexical = lexical defer func() { self.scope.lexical = outer }() return self.cmpl_evaluate_nodeStatement(node.body) }