2024-02-24 21:00:04 +00:00
|
|
|
package otto
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-04-04 14:46:14 +00:00
|
|
|
goruntime "runtime"
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
"github.com/robertkrimen/otto/token"
|
|
|
|
)
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
func (rt *runtime) cmplEvaluateNodeStatement(node nodeStatement) Value {
|
2024-02-24 21:00:04 +00:00
|
|
|
// 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()
|
2024-02-24 21:00:04 +00:00
|
|
|
select {
|
2024-04-04 14:46:14 +00:00
|
|
|
case value := <-rt.otto.Interrupt:
|
2024-02-24 21:00:04 +00:00
|
|
|
value()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch node := node.(type) {
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeBlockStatement:
|
|
|
|
labels := rt.labels
|
|
|
|
rt.labels = nil
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatementList(node.list)
|
|
|
|
if value.kind == valueResult {
|
|
|
|
if value.evaluateBreak(labels) == resultBreak {
|
2024-02-24 21:00:04 +00:00
|
|
|
return emptyValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeBranchStatement:
|
2024-02-24 21:00:04 +00:00
|
|
|
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-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeDebuggerStatement:
|
|
|
|
if rt.debugger != nil {
|
|
|
|
rt.debugger(rt.otto)
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
return emptyValue // Nothing happens.
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeDoWhileStatement:
|
|
|
|
return rt.cmplEvaluateNodeDoWhileStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeEmptyStatement:
|
2024-02-24 21:00:04 +00:00
|
|
|
return emptyValue
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeExpressionStatement:
|
|
|
|
return rt.cmplEvaluateNodeExpression(node.expression)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeForInStatement:
|
|
|
|
return rt.cmplEvaluateNodeForInStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeForStatement:
|
|
|
|
return rt.cmplEvaluateNodeForStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeIfStatement:
|
|
|
|
return rt.cmplEvaluateNodeIfStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeLabelledStatement:
|
|
|
|
rt.labels = append(rt.labels, node.label)
|
2024-02-24 21:00:04 +00:00
|
|
|
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
|
2024-02-24 21:00:04 +00:00
|
|
|
} else {
|
2024-04-04 14:46:14 +00:00
|
|
|
rt.labels = nil
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
}()
|
2024-04-04 14:46:14 +00:00
|
|
|
return rt.cmplEvaluateNodeStatement(node.statement)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeReturnStatement:
|
2024-02-24 21:00:04 +00:00
|
|
|
if node.argument != nil {
|
2024-04-04 14:46:14 +00:00
|
|
|
return toValue(newReturnResult(rt.cmplEvaluateNodeExpression(node.argument).resolve()))
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
return toValue(newReturnResult(Value{}))
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeSwitchStatement:
|
|
|
|
return rt.cmplEvaluateNodeSwitchStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeThrowStatement:
|
|
|
|
value := rt.cmplEvaluateNodeExpression(node.argument).resolve()
|
2024-02-24 21:00:04 +00:00
|
|
|
panic(newException(value))
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeTryStatement:
|
|
|
|
return rt.cmplEvaluateNodeTryStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeVariableStatement:
|
2024-02-24 21:00:04 +00:00
|
|
|
// 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))
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
return emptyValue
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
case *nodeWhileStatement:
|
|
|
|
return rt.cmplEvaluateModeWhileStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
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-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
func (rt *runtime) cmplEvaluateNodeStatementList(list []nodeStatement) Value {
|
2024-02-24 21:00:04 +00:00
|
|
|
var result Value
|
|
|
|
for _, node := range list {
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
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
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
test := node.test
|
|
|
|
|
|
|
|
result := emptyValue
|
|
|
|
resultBreak:
|
|
|
|
for {
|
|
|
|
for _, node := range node.body {
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
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() {
|
2024-02-24 21:00:04 +00:00
|
|
|
// 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-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
source := rt.cmplEvaluateNodeExpression(node.source)
|
2024-02-24 21:00:04 +00:00
|
|
|
sourceValue := source.resolve()
|
|
|
|
|
|
|
|
switch sourceValue.kind {
|
|
|
|
case valueUndefined, valueNull:
|
|
|
|
return emptyValue
|
|
|
|
}
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
sourceObject := rt.toObject(sourceValue)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
into := node.into
|
|
|
|
body := node.body
|
|
|
|
|
|
|
|
result := emptyValue
|
2024-04-04 14:46:14 +00:00
|
|
|
obj := sourceObject
|
|
|
|
for obj != nil {
|
2024-02-24 21:00:04 +00:00
|
|
|
enumerateValue := emptyValue
|
2024-04-04 14:46:14 +00:00
|
|
|
obj.enumerate(false, func(name string) bool {
|
|
|
|
into := rt.cmplEvaluateNodeExpression(into)
|
2024-02-24 21:00:04 +00:00
|
|
|
// 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-02-24 21:00:04 +00:00
|
|
|
}
|
2024-04-04 14:46:14 +00:00
|
|
|
rt.putValue(into.reference(), stringValue(name))
|
2024-02-24 21:00:04 +00:00
|
|
|
for _, node := range body {
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
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
|
2024-02-24 21:00:04 +00:00
|
|
|
return false
|
|
|
|
case resultContinue:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
case valueEmpty:
|
|
|
|
default:
|
|
|
|
enumerateValue = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2024-04-04 14:46:14 +00:00
|
|
|
if obj == nil {
|
2024-02-24 21:00:04 +00:00
|
|
|
break
|
|
|
|
}
|
2024-04-04 14:46:14 +00:00
|
|
|
obj = obj.prototype
|
2024-02-24 21:00:04 +00:00
|
|
|
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
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
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)
|
2024-02-24 21:00:04 +00:00
|
|
|
initialResult.resolve() // Side-effect trigger
|
|
|
|
}
|
|
|
|
|
|
|
|
result := emptyValue
|
|
|
|
resultBreak:
|
|
|
|
for {
|
|
|
|
if test != nil {
|
2024-04-04 14:46:14 +00:00
|
|
|
testResult := rt.cmplEvaluateNodeExpression(test)
|
2024-02-24 21:00:04 +00:00
|
|
|
testResultValue := testResult.resolve()
|
2024-04-04 14:46:14 +00:00
|
|
|
if !testResultValue.bool() {
|
2024-02-24 21:00:04 +00:00
|
|
|
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()
|
2024-02-24 21:00:04 +00:00
|
|
|
select {
|
2024-04-04 14:46:14 +00:00
|
|
|
case value := <-rt.otto.Interrupt:
|
2024-02-24 21:00:04 +00:00
|
|
|
value()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, node := range body {
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
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)
|
2024-02-24 21:00:04 +00:00
|
|
|
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)
|
2024-02-24 21:00:04 +00:00
|
|
|
testValue := test.resolve()
|
|
|
|
if testValue.bool() {
|
2024-04-04 14:46:14 +00:00
|
|
|
return rt.cmplEvaluateNodeStatement(node.consequent)
|
2024-02-24 21:00:04 +00:00
|
|
|
} else if node.alternate != nil {
|
2024-04-04 14:46:14 +00:00
|
|
|
return rt.cmplEvaluateNodeStatement(node.alternate)
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-02-24 21:00:04 +00:00
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
discriminantResult := rt.cmplEvaluateNodeExpression(node.discriminant)
|
|
|
|
target := node.defaultIdx
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
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)) {
|
2024-02-24 21:00:04 +00:00
|
|
|
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)
|
2024-02-24 21:00:04 +00:00
|
|
|
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-02-24 21:00:04 +00:00
|
|
|
})
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
if exep && node.catch != nil {
|
|
|
|
outer := rt.scope.lexical
|
|
|
|
rt.scope.lexical = rt.newDeclarationStash(outer)
|
2024-02-24 21:00:04 +00:00
|
|
|
defer func() {
|
2024-04-04 14:46:14 +00:00
|
|
|
rt.scope.lexical = outer
|
2024-02-24 21:00:04 +00:00
|
|
|
}()
|
|
|
|
// 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)
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
// 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)
|
2024-02-24 21:00:04 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if node.finally != nil {
|
2024-04-04 14:46:14 +00:00
|
|
|
finallyValue := rt.cmplEvaluateNodeStatement(node.finally)
|
2024-02-24 21:00:04 +00:00
|
|
|
if finallyValue.kind == valueResult {
|
|
|
|
return finallyValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
if exep {
|
2024-02-24 21:00:04 +00:00
|
|
|
panic(newException(tryCatchValue))
|
|
|
|
}
|
|
|
|
|
|
|
|
return tryCatchValue
|
|
|
|
}
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
func (rt *runtime) cmplEvaluateModeWhileStatement(node *nodeWhileStatement) Value {
|
2024-02-24 21:00:04 +00:00
|
|
|
test := node.test
|
|
|
|
body := node.body
|
2024-04-04 14:46:14 +00:00
|
|
|
labels := append(rt.labels, "") //nolint: gocritic
|
|
|
|
rt.labels = nil
|
2024-02-24 21:00:04 +00:00
|
|
|
|
|
|
|
result := emptyValue
|
|
|
|
resultBreakContinue:
|
|
|
|
for {
|
2024-04-04 14:46:14 +00:00
|
|
|
if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
|
2024-02-24 21:00:04 +00:00
|
|
|
// Stahp: while (false) ...
|
|
|
|
break
|
|
|
|
}
|
|
|
|
for _, node := range body {
|
2024-04-04 14:46:14 +00:00
|
|
|
value := rt.cmplEvaluateNodeStatement(node)
|
2024-02-24 21:00:04 +00:00
|
|
|
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
|
2024-02-24 21:00:04 +00:00
|
|
|
defer func() {
|
2024-04-04 14:46:14 +00:00
|
|
|
rt.scope.lexical = outer
|
2024-02-24 21:00:04 +00:00
|
|
|
}()
|
|
|
|
|
2024-04-04 14:46:14 +00:00
|
|
|
return rt.cmplEvaluateNodeStatement(node.body)
|
2024-02-24 21:00:04 +00:00
|
|
|
}
|