123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- package otto
- import (
- "fmt"
- goruntime "runtime"
- "github.com/robertkrimen/otto/token"
- )
- func (rt *runtime) cmplEvaluateNodeStatement(node nodeStatement) Value {
- // Allow interpreter interruption
- // If the Interrupt channel is nil, then
- // we avoid runtime.Gosched() overhead (if any)
- // FIXME: Test this
- if rt.otto.Interrupt != nil {
- goruntime.Gosched()
- select {
- case value := <-rt.otto.Interrupt:
- value()
- default:
- }
- }
- switch node := node.(type) {
- case *nodeBlockStatement:
- labels := rt.labels
- rt.labels = nil
- value := rt.cmplEvaluateNodeStatementList(node.list)
- if value.kind == valueResult {
- if value.evaluateBreak(labels) == 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))
- default:
- panic(fmt.Errorf("unknown node branch token %T", node))
- }
- case *nodeDebuggerStatement:
- if rt.debugger != nil {
- rt.debugger(rt.otto)
- }
- return emptyValue // Nothing happens.
- case *nodeDoWhileStatement:
- return rt.cmplEvaluateNodeDoWhileStatement(node)
- case *nodeEmptyStatement:
- return emptyValue
- case *nodeExpressionStatement:
- return rt.cmplEvaluateNodeExpression(node.expression)
- case *nodeForInStatement:
- return rt.cmplEvaluateNodeForInStatement(node)
- case *nodeForStatement:
- return rt.cmplEvaluateNodeForStatement(node)
- case *nodeIfStatement:
- return rt.cmplEvaluateNodeIfStatement(node)
- case *nodeLabelledStatement:
- rt.labels = append(rt.labels, node.label)
- defer func() {
- if len(rt.labels) > 0 {
- rt.labels = rt.labels[:len(rt.labels)-1] // Pop the label
- } else {
- rt.labels = nil
- }
- }()
- return rt.cmplEvaluateNodeStatement(node.statement)
- case *nodeReturnStatement:
- if node.argument != nil {
- return toValue(newReturnResult(rt.cmplEvaluateNodeExpression(node.argument).resolve()))
- }
- return toValue(newReturnResult(Value{}))
- case *nodeSwitchStatement:
- return rt.cmplEvaluateNodeSwitchStatement(node)
- case *nodeThrowStatement:
- value := rt.cmplEvaluateNodeExpression(node.argument).resolve()
- panic(newException(value))
- case *nodeTryStatement:
- return rt.cmplEvaluateNodeTryStatement(node)
- case *nodeVariableStatement:
- // Variables are already defined, this is initialization only
- for _, variable := range node.list {
- rt.cmplEvaluateNodeVariableExpression(variable.(*nodeVariableExpression))
- }
- return emptyValue
- case *nodeWhileStatement:
- return rt.cmplEvaluateModeWhileStatement(node)
- case *nodeWithStatement:
- return rt.cmplEvaluateNodeWithStatement(node)
- default:
- panic(fmt.Errorf("unknown node statement type %T", node))
- }
- }
- func (rt *runtime) cmplEvaluateNodeStatementList(list []nodeStatement) Value {
- var result Value
- for _, node := range list {
- value := rt.cmplEvaluateNodeStatement(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 (rt *runtime) cmplEvaluateNodeDoWhileStatement(node *nodeDoWhileStatement) Value {
- labels := append(rt.labels, "") //nolint:gocritic
- rt.labels = nil
- test := node.test
- result := emptyValue
- resultBreak:
- for {
- for _, node := range node.body {
- value := rt.cmplEvaluateNodeStatement(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 !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
- // Stahp: do ... while (false)
- break
- }
- }
- return result
- }
- func (rt *runtime) cmplEvaluateNodeForInStatement(node *nodeForInStatement) Value {
- labels := append(rt.labels, "") //nolint:gocritic
- rt.labels = nil
- source := rt.cmplEvaluateNodeExpression(node.source)
- sourceValue := source.resolve()
- switch sourceValue.kind {
- case valueUndefined, valueNull:
- return emptyValue
- }
- sourceObject := rt.toObject(sourceValue)
- into := node.into
- body := node.body
- result := emptyValue
- obj := sourceObject
- for obj != nil {
- enumerateValue := emptyValue
- obj.enumerate(false, func(name string) bool {
- into := rt.cmplEvaluateNodeExpression(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(rt, rt.scope.lexical, identifier, false, -1))
- }
- rt.putValue(into.reference(), stringValue(name))
- for _, node := range body {
- value := rt.cmplEvaluateNodeStatement(node)
- switch value.kind {
- case valueResult:
- switch value.evaluateBreakContinue(labels) {
- case resultReturn:
- enumerateValue = value
- return false
- case resultBreak:
- obj = nil
- return false
- case resultContinue:
- return true
- }
- case valueEmpty:
- default:
- enumerateValue = value
- }
- }
- return true
- })
- if obj == nil {
- break
- }
- obj = obj.prototype
- if !enumerateValue.isEmpty() {
- result = enumerateValue
- }
- }
- return result
- }
- func (rt *runtime) cmplEvaluateNodeForStatement(node *nodeForStatement) Value {
- labels := append(rt.labels, "") //nolint:gocritic
- rt.labels = nil
- initializer := node.initializer
- test := node.test
- update := node.update
- body := node.body
- if initializer != nil {
- initialResult := rt.cmplEvaluateNodeExpression(initializer)
- initialResult.resolve() // Side-effect trigger
- }
- result := emptyValue
- resultBreak:
- for {
- if test != nil {
- testResult := rt.cmplEvaluateNodeExpression(test)
- testResultValue := testResult.resolve()
- if !testResultValue.bool() {
- break
- }
- }
- // this is to prevent for cycles with no body from running forever
- if len(body) == 0 && rt.otto.Interrupt != nil {
- goruntime.Gosched()
- select {
- case value := <-rt.otto.Interrupt:
- value()
- default:
- }
- }
- for _, node := range body {
- value := rt.cmplEvaluateNodeStatement(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 := rt.cmplEvaluateNodeExpression(update)
- updateResult.resolve() // Side-effect trigger
- }
- }
- return result
- }
- func (rt *runtime) cmplEvaluateNodeIfStatement(node *nodeIfStatement) Value {
- test := rt.cmplEvaluateNodeExpression(node.test)
- testValue := test.resolve()
- if testValue.bool() {
- return rt.cmplEvaluateNodeStatement(node.consequent)
- } else if node.alternate != nil {
- return rt.cmplEvaluateNodeStatement(node.alternate)
- }
- return emptyValue
- }
- func (rt *runtime) cmplEvaluateNodeSwitchStatement(node *nodeSwitchStatement) Value {
- labels := append(rt.labels, "") //nolint:gocritic
- rt.labels = nil
- discriminantResult := rt.cmplEvaluateNodeExpression(node.discriminant)
- target := node.defaultIdx
- for index, clause := range node.body {
- test := clause.test
- if test != nil {
- if rt.calculateComparison(token.STRICT_EQUAL, discriminantResult, rt.cmplEvaluateNodeExpression(test)) {
- target = index
- break
- }
- }
- }
- result := emptyValue
- if target != -1 {
- for _, clause := range node.body[target:] {
- for _, statement := range clause.consequent {
- value := rt.cmplEvaluateNodeStatement(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 (rt *runtime) cmplEvaluateNodeTryStatement(node *nodeTryStatement) Value {
- tryCatchValue, exep := rt.tryCatchEvaluate(func() Value {
- return rt.cmplEvaluateNodeStatement(node.body)
- })
- if exep && node.catch != nil {
- outer := rt.scope.lexical
- rt.scope.lexical = rt.newDeclarationStash(outer)
- defer func() {
- rt.scope.lexical = outer
- }()
- // TODO If necessary, convert TypeError<runtime> => TypeError
- // That, is, such errors can be thrown despite not being JavaScript "native"
- // strict = false
- rt.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false)
- // FIXME node.CatchParameter
- // FIXME node.Catch
- tryCatchValue, exep = rt.tryCatchEvaluate(func() Value {
- return rt.cmplEvaluateNodeStatement(node.catch.body)
- })
- }
- if node.finally != nil {
- finallyValue := rt.cmplEvaluateNodeStatement(node.finally)
- if finallyValue.kind == valueResult {
- return finallyValue
- }
- }
- if exep {
- panic(newException(tryCatchValue))
- }
- return tryCatchValue
- }
- func (rt *runtime) cmplEvaluateModeWhileStatement(node *nodeWhileStatement) Value {
- test := node.test
- body := node.body
- labels := append(rt.labels, "") //nolint:gocritic
- rt.labels = nil
- result := emptyValue
- resultBreakContinue:
- for {
- if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
- // Stahp: while (false) ...
- break
- }
- for _, node := range body {
- value := rt.cmplEvaluateNodeStatement(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 (rt *runtime) cmplEvaluateNodeWithStatement(node *nodeWithStatement) Value {
- obj := rt.cmplEvaluateNodeExpression(node.object)
- outer := rt.scope.lexical
- lexical := rt.newObjectStash(rt.toObject(obj.resolve()), outer)
- rt.scope.lexical = lexical
- defer func() {
- rt.scope.lexical = outer
- }()
- return rt.cmplEvaluateNodeStatement(node.body)
- }
|