This commit is contained in:
kunfei 2023-04-20 23:42:51 +08:00
parent cd7f139dd3
commit 41eacf72fc
13 changed files with 202 additions and 75 deletions

View File

@ -1,7 +0,0 @@
package io.legado.app.constant
import com.script.rhino.RhinoScriptEngine
val SCRIPT_ENGINE: RhinoScriptEngine by lazy {
RhinoScriptEngine()
}

View File

@ -1,16 +1,18 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import cn.hutool.crypto.symmetric.AES import cn.hutool.crypto.symmetric.AES
import com.script.SimpleBindings
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.rule.RowUi import io.legado.app.data.entities.rule.RowUi
import io.legado.app.help.CacheManager import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.model.SharedJsScope import io.legado.app.model.SharedJsScope
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.* import io.legado.app.utils.*
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.mozilla.javascript.Scriptable import org.mozilla.javascript.Scriptable
@ -227,19 +229,22 @@ interface BaseSource : JsExtensions {
* 执行JS * 执行JS
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun evalJS(jsStr: String, bindingsConfig: SimpleBindings.() -> Unit = {}): Any? { fun evalJS(jsStr: String, bindingsConfig: Bindings.() -> Unit = {}): Any? {
val bindings = SimpleBindings() val bindings = Bindings()
bindings.apply(bindingsConfig) bindings.apply(bindingsConfig)
bindings["java"] = this bindings["java"] = this
bindings["source"] = this bindings["source"] = this
bindings["baseUrl"] = getKey() bindings["baseUrl"] = getKey()
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager bindings["cache"] = CacheManager
val scope = SCRIPT_ENGINE.getRuntimeScope(SCRIPT_ENGINE.getScriptContext(bindings)) return Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
getShareScope()?.let { getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
return SCRIPT_ENGINE.eval(jsStr, scope) evaluate(scope, jsStr)
}
} }
fun getShareScope(): Scriptable? { fun getShareScope(): Scriptable? {

View File

@ -3,7 +3,6 @@
package io.legado.app.help.book package io.legado.app.help.book
import android.net.Uri import android.net.Uri
import com.script.SimpleBindings
import io.legado.app.constant.* import io.legado.app.constant.*
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
@ -11,6 +10,9 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.putBindings
import io.legado.app.utils.* import io.legado.app.utils.*
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
@ -234,11 +236,15 @@ fun Book.getExportFileName(suffix: String): String {
if (jsStr.isNullOrBlank()) { if (jsStr.isNullOrBlank()) {
return "$name 作者:${getRealAuthor()}.$suffix" return "$name 作者:${getRealAuthor()}.$suffix"
} }
val bindings = SimpleBindings() val bindings = Bindings()
bindings["name"] = name bindings["name"] = name
bindings["author"] = getRealAuthor() bindings["author"] = getRealAuthor()
return kotlin.runCatching { return kotlin.runCatching {
SCRIPT_ENGINE.eval(jsStr, bindings).toString() + "." + suffix Rhino.use {
val scope = initStandardObjects()
scope.putBindings(bindings)
evaluateString(scope, jsStr, "name&author", 1, null)
}.toString() + "." + suffix
}.onFailure { }.onFailure {
AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it) AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it)
}.getOrDefault("${name} 作者:${getRealAuthor()}.$suffix") }.getOrDefault("${name} 作者:${getRealAuthor()}.$suffix")

View File

@ -1,8 +1,6 @@
package io.legado.app.model package io.legado.app.model
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.newCallStrResponse import io.legado.app.help.http.newCallStrResponse
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
@ -33,9 +31,8 @@ object SharedJsScope {
val key = MD5Utils.md5Encode(jsLib) val key = MD5Utils.md5Encode(jsLib)
var scope = scopeMap[key]?.get() var scope = scopeMap[key]?.get()
if (scope == null) { if (scope == null) {
val context = SCRIPT_ENGINE.getScriptContext(SimpleBindings())
scope = SCRIPT_ENGINE.getRuntimeScope(context)
Rhino.use { Rhino.use {
scope = initStandardObjects()
if (jsLib.isJsonObject()) { if (jsLib.isJsonObject()) {
val jsMap: Map<String, String> = GSON.fromJson( val jsMap: Map<String, String> = GSON.fromJson(
jsLib, jsLib,

View File

@ -2,14 +2,16 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import com.script.SimpleBindings
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.* import io.legado.app.data.entities.*
import io.legado.app.help.CacheManager import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
@ -681,7 +683,7 @@ class AnalyzeRule(
* 执行JS * 执行JS
*/ */
fun evalJS(jsStr: String, result: Any? = null): Any? { fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings() val bindings = Bindings()
bindings["java"] = this bindings["java"] = this
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager bindings["cache"] = CacheManager
@ -693,12 +695,14 @@ class AnalyzeRule(
bindings["title"] = chapter?.title bindings["title"] = chapter?.title
bindings["src"] = content bindings["src"] = content
bindings["nextChapterUrl"] = nextChapterUrl bindings["nextChapterUrl"] = nextChapterUrl
val context = SCRIPT_ENGINE.getScriptContext(bindings) return Rhino.use {
val scope = SCRIPT_ENGINE.getRuntimeScope(context) val scope = initStandardObjects()
scope.putBindings(bindings)
source?.getShareScope()?.let { source?.getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
return SCRIPT_ENGINE.eval(jsStr, scope) evaluate(scope, jsStr)
}
} }
override fun getSource(): BaseSource? { override fun getSource(): BaseSource? {

View File

@ -5,12 +5,10 @@ import android.util.Base64
import androidx.annotation.Keep import androidx.annotation.Keep
import cn.hutool.core.util.HexUtil import cn.hutool.core.util.HexUtil
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.script.SimpleBindings
import io.legado.app.constant.AppConst.UA_NAME import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.constant.AppPattern.dataUriRegex import io.legado.app.constant.AppPattern.dataUriRegex
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.BaseSource import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
@ -20,6 +18,10 @@ import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.glide.GlideHeaders import io.legado.app.help.glide.GlideHeaders
import io.legado.app.help.http.* import io.legado.app.help.http.*
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBindings
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -253,7 +255,7 @@ class AnalyzeUrl(
* 执行JS * 执行JS
*/ */
fun evalJS(jsStr: String, result: Any? = null): Any? { fun evalJS(jsStr: String, result: Any? = null): Any? {
val bindings = SimpleBindings() val bindings = Bindings()
bindings["java"] = this bindings["java"] = this
bindings["baseUrl"] = baseUrl bindings["baseUrl"] = baseUrl
bindings["cookie"] = CookieStore bindings["cookie"] = CookieStore
@ -265,12 +267,14 @@ class AnalyzeUrl(
bindings["book"] = ruleData as? Book bindings["book"] = ruleData as? Book
bindings["source"] = source bindings["source"] = source
bindings["result"] = result bindings["result"] = result
val context = SCRIPT_ENGINE.getScriptContext(bindings) return Rhino.use {
val scope = SCRIPT_ENGINE.getRuntimeScope(context) val scope = initStandardObjects()
scope.putBindings(bindings)
source?.getShareScope()?.let { source?.getShareScope()?.let {
scope.prototype = it scope.prototype = it
} }
return SCRIPT_ENGINE.eval(jsStr, scope) evaluate(scope, jsStr)
}
} }
fun put(key: String, value: String): String { fun put(key: String, value: String): String {

View File

@ -3,7 +3,6 @@ package io.legado.app.model.localBook
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.script.SimpleBindings
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.* import io.legado.app.constant.*
import io.legado.app.data.appDb import io.legado.app.data.appDb
@ -20,6 +19,9 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException import io.legado.app.lib.webdav.WebDavException
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBinding
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.Entities import org.jsoup.nodes.Entities
@ -265,13 +267,15 @@ object LocalBook {
var author = "" var author = ""
if (!AppConfig.bookImportFileName.isNullOrBlank()) { if (!AppConfig.bookImportFileName.isNullOrBlank()) {
try { try {
//在脚本中定义如何分解文件名成书名、作者名
val jsonStr = SCRIPT_ENGINE.eval(
//在用户脚本后添加捕获author、name的代码只要脚本中author、name有值就会被捕获 //在用户脚本后添加捕获author、name的代码只要脚本中author、name有值就会被捕获
AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})", val js =
//将文件名注入到脚本的src变量中 AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})"
SimpleBindings().also { it["src"] = tempFileName } //在脚本中定义如何分解文件名成书名、作者名
).toString() val jsonStr = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("src", tempFileName)
evaluate(scope, js)
}.toString()
val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr) val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)
.getOrThrow() .getOrThrow()
name = bookMess["name"] ?: "" name = bookMess["name"] ?: ""

View File

@ -0,0 +1,54 @@
/*
* Decompiled with CFR 0.152.
*/
package io.legado.app.rhino
class Bindings @JvmOverloads constructor(
private val map: MutableMap<String, Any?> = HashMap()
) : MutableMap<String, Any?> {
override fun put(key: String, value: Any?): Any? {
return map.put(key, value)
}
override fun putAll(from: Map<out String, Any?>) {
map.putAll(from)
}
override fun clear() {
map.clear()
}
override fun containsKey(key: String): Boolean {
return map.containsKey(key)
}
override fun containsValue(value: Any?): Boolean {
return map.containsValue(value)
}
override val entries: MutableSet<MutableMap.MutableEntry<String, Any?>>
get() = map.entries
override operator fun get(key: String): Any? {
return map[key]
}
override fun isEmpty(): Boolean {
return map.isEmpty()
}
override val keys: MutableSet<String>
get() = map.keys
override fun remove(key: String): Any? {
return map.remove(key)
}
override val size: Int
get() = map.size
override val values: MutableCollection<Any?>
get() = map.values
}

View File

@ -8,11 +8,10 @@ import org.mozilla.javascript.Wrapper
object Rhino { object Rhino {
inline fun use(block: Context.() -> Any?): Any? { inline fun <T> use(block: Context.() -> T): T {
return try { return try {
val cx = Context.enter() val cx = Context.enter()
val result = block.invoke(cx) block.invoke(cx)
unwrapReturnValue(result)
} finally { } finally {
Context.exit() Context.exit()
} }

View File

@ -7,12 +7,56 @@ import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.ScriptableObject import org.mozilla.javascript.ScriptableObject
import java.io.Reader import java.io.Reader
fun Context.evaluateString(scope: Scriptable, source: String, sourceName: String) { fun Context.evaluate(
evaluateString(scope, source, sourceName, 1, null) scope: Scriptable,
source: String,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
return Rhino.unwrapReturnValue(
evaluateString(scope, source, sourceName, lineno, securityDomain)
)
} }
fun Context.evaluateReader(scope: Scriptable, reader: Reader, sourceName: String) { fun Context.evaluate(
evaluateReader(scope, reader, sourceName, 1, null) bindings: Bindings,
source: String,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
val scope = initStandardObjects()
scope.putBindings(bindings)
return Rhino.unwrapReturnValue(
evaluateString(scope, source, sourceName, lineno, securityDomain)
)
}
fun Context.evaluate(
scope: Scriptable,
reader: Reader,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
return Rhino.unwrapReturnValue(
evaluateReader(scope, reader, sourceName, lineno, securityDomain)
)
}
fun Context.evaluate(
bindings: Bindings,
reader: Reader,
sourceName: String = "<Unknown source>",
lineno: Int = 1,
securityDomain: Any? = null
): Any? {
val scope = initStandardObjects()
scope.putBindings(bindings)
return Rhino.unwrapReturnValue(
evaluateReader(scope, reader, sourceName, lineno, securityDomain)
)
} }
fun Scriptable.putBinding(key: String, value: Any?) { fun Scriptable.putBinding(key: String, value: Any?) {
@ -20,7 +64,7 @@ fun Scriptable.putBinding(key: String, value: Any?) {
ScriptableObject.putProperty(this, key, wrappedOut) ScriptableObject.putProperty(this, key, wrappedOut)
} }
fun Scriptable.putBindings(bindings: Map<String, Any?>) { fun Scriptable.putBindings(bindings: Bindings) {
bindings.forEach { (t, u) -> bindings.forEach { (t, u) ->
putBinding(t, u) putBinding(t, u)
} }

View File

@ -1,20 +1,23 @@
package io.legado.app.utils package io.legado.app.utils
import com.script.SimpleBindings
import io.legado.app.constant.AppPattern.EXP_PATTERN import io.legado.app.constant.AppPattern.EXP_PATTERN
import io.legado.app.constant.SCRIPT_ENGINE import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
object JsUtils { object JsUtils {
fun evalJs(js: String, bindingsFun: ((SimpleBindings) -> Unit)? = null): String { fun evalJs(js: String, bindingsFun: ((Bindings) -> Unit)? = null): String {
val bindings = SimpleBindings() val bindings = Bindings()
bindingsFun?.invoke(bindings) bindingsFun?.invoke(bindings)
if (js.contains("{{") && js.contains("}}")) { if (js.contains("{{") && js.contains("}}")) {
val sb = StringBuffer() val sb = StringBuffer()
val expMatcher = EXP_PATTERN.matcher(js) val expMatcher = EXP_PATTERN.matcher(js)
while (expMatcher.find()) { while (expMatcher.find()) {
val result = expMatcher.group(1)?.let { val result = expMatcher.group(1)?.let {
SCRIPT_ENGINE.eval(it, bindings) Rhino.use {
evaluate(bindings, it)
}
} ?: "" } ?: ""
if (result is String) { if (result is String) {
expMatcher.appendReplacement(sb, result) expMatcher.appendReplacement(sb, result)
@ -27,7 +30,9 @@ object JsUtils {
expMatcher.appendTail(sb) expMatcher.appendTail(sb)
return sb.toString() return sb.toString()
} }
return SCRIPT_ENGINE.eval(js, bindings).toString() return Rhino.use {
evaluate(bindings, js)
}.toString()
} }

View File

@ -1,11 +1,12 @@
package io.legado.app.utils package io.legado.app.utils
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.exception.RegexTimeoutException import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.CrashHandler import io.legado.app.help.CrashHandler
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.rhino.Bindings
import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx import splitties.init.appCtx
@ -30,10 +31,11 @@ fun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): Stri
val stringBuffer = StringBuffer() val stringBuffer = StringBuffer()
while (matcher.find()) { while (matcher.find()) {
if (isJs) { if (isJs) {
val bindings = SimpleBindings() val bindings = Bindings()
bindings["result"] = matcher.group() bindings["result"] = matcher.group()
val jsResult = val jsResult = Rhino.use {
SCRIPT_ENGINE.eval(replacement1, bindings).toString() evaluate(bindings, replacement1)
}.toString()
matcher.appendReplacement(stringBuffer, jsResult) matcher.appendReplacement(stringBuffer, jsResult)
} else { } else {
matcher.appendReplacement(stringBuffer, replacement1) matcher.appendReplacement(stringBuffer, replacement1)

View File

@ -1,14 +1,13 @@
package io.legado.app package io.legado.app
import com.script.SimpleBindings import com.script.SimpleBindings
import io.legado.app.constant.SCRIPT_ENGINE
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.rhino.Rhino import io.legado.app.rhino.Rhino
import io.legado.app.rhino.evaluate
import io.legado.app.rhino.putBinding import io.legado.app.rhino.putBinding
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.mozilla.javascript.Scriptable
class JsTest { class JsTest {
@ -56,7 +55,7 @@ class JsTest {
val scope = initStandardObjects() val scope = initStandardObjects()
evaluateString(scope, printJs, "print", 1, null) evaluateString(scope, printJs, "print", 1, null)
scope scope
} as Scriptable }
@Language("js") @Language("js")
val jsFor = """ val jsFor = """
@ -85,7 +84,9 @@ class JsTest {
@Test @Test
fun testReturnNull() { fun testReturnNull() {
val result = SCRIPT_ENGINE.eval("null") val result = Rhino.use {
evaluate(initStandardObjects(), "null")
}
Assert.assertEquals(null, result) Assert.assertEquals(null, result)
} }
@ -101,9 +102,11 @@ class JsTest {
.replace(/\;/g,"") .replace(/\;/g,"")
.replace(/\:/g,"") .replace(/\:/g,"")
""".trimIndent() """.trimIndent()
val bindings = SimpleBindings() val result = Rhino.use {
bindings["result"] = ",.!?…;:" val scope = initStandardObjects()
val result = SCRIPT_ENGINE.eval(js, bindings).toString() scope.putBinding("result", ",.!?…;:")
evaluate(scope, js)
}
Assert.assertEquals(result, ",。!?……;:") Assert.assertEquals(result, ",。!?……;:")
} }
@ -115,22 +118,29 @@ class JsTest {
bindings["chapter"] = chapter bindings["chapter"] = chapter
@Language("js") @Language("js")
val js = "chapter.title" val js = "chapter.title"
val result = SCRIPT_ENGINE.eval(js, bindings) val result = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("chapter", chapter)
evaluate(scope, js)
}
Assert.assertEquals(result, "xxxyyy") Assert.assertEquals(result, "xxxyyy")
} }
@Test @Test
fun javaListForEach() { fun javaListForEach() {
val list = arrayListOf(1, 2, 3) val list = arrayListOf(1, 2, 3)
val bindings = SimpleBindings()
bindings["list"] = list
@Language("js") @Language("js")
val js = """ val js = """
var result = 0 var result = 0
list.forEach(item => {result = result + item}) list.forEach(item => {result = result + item})
result result
""".trimIndent() """.trimIndent()
val result = SCRIPT_ENGINE.eval(js, bindings) val result = Rhino.use {
val scope = initStandardObjects()
scope.putBinding("list", list)
evaluate(scope, js)
}
Assert.assertEquals(result, 6.0) Assert.assertEquals(result, 6.0)
} }