rum-goggles/v1/vendor/github.com/robertkrimen/otto/cmpl_evaluate_statement.go

429 lines
10 KiB
Go
Raw Permalink Normal View History

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