diff --git a/.gitignore b/.gitignore
index 08c660b..a4fd068 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,6 @@
# Maven #
target/
+
+# IntelliJ IDEA #
+/.idea/
diff --git a/.scalafmt.conf b/.scalafmt.conf
new file mode 100644
index 0000000..36dc7b5
--- /dev/null
+++ b/.scalafmt.conf
@@ -0,0 +1,14 @@
+# [[https://github.com/scalameta/scalafmt]]
+
+version = 3.7.3
+
+runner.dialect = scala3
+
+preset = IntelliJ
+align.preset = more
+maxColumn = 120
+docstrings.style = Asterisk
+docstrings.blankFirstLine = yes
+docstrings.wrap = no
+importSelectors = singleLine
+newlines.source = keep
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index 08db1a8..8082288 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2013 - 2023, SciJava developers.
+Copyright (c) 2013 - 2024, SciJava developers.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
diff --git a/pom.xml b/pom.xml
index a258a05..6d7955b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
org.scijava
pom-scijava
- 34.1.0
+ 35.1.0
scripting-scala
- 0.3.0-SNAPSHOT
+ 0.3.3-SNAPSHOT
SciJava Scripting: Scala
JSR-223-compliant Scala scripting language plugin.
@@ -95,16 +95,40 @@
sign,deploy-to-scijava
- 3.2.2
+ 3.3.0
+ 4.8.1
+
+
+
+ org.scijava
+ scijava-common
+
+
+
+
+ org.scala-lang
+ scala3-compiler_3
+ ${scala.version}
+
+
+
-
-
-
- net.alchim31.maven
- scala-maven-plugin
- 4.8.0
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ ${scala-maven-plugin.version}
-unchecked
@@ -132,31 +156,7 @@
-
-
-
-
-
-
-
- org.scijava
- scijava-common
-
-
-
-
- org.scala-lang
- scala3-compiler_3
- ${scala.version}
-
-
-
-
-
-
- junit
- junit
- test
-
-
+
+
+
diff --git a/src/main/java/org/scijava/plugins/scripting/scala/Main.java b/src/main/java/org/scijava/plugins/scripting/scala/Main.java
index 2d729cc..e272aa8 100644
--- a/src/main/java/org/scijava/plugins/scripting/scala/Main.java
+++ b/src/main/java/org/scijava/plugins/scripting/scala/Main.java
@@ -2,7 +2,7 @@
* #%L
* JSR-223-compliant Scala scripting language plugin.
* %%
- * Copyright (C) 2013 - 2023 SciJava developers.
+ * Copyright (C) 2013 - 2024 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
diff --git a/src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptLanguage.java b/src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptLanguage.java
index 035cbed..1d863e8 100644
--- a/src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptLanguage.java
+++ b/src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptLanguage.java
@@ -2,7 +2,7 @@
* #%L
* JSR-223-compliant Scala scripting language plugin.
* %%
- * Copyright (C) 2013 - 2023 SciJava developers.
+ * Copyright (C) 2013 - 2024 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -29,17 +29,17 @@
package org.scijava.plugins.scripting.scala;
-import javax.script.ScriptEngine;
-
import org.scijava.log.LogService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.script.AdaptedScriptLanguage;
import org.scijava.script.ScriptLanguage;
+import javax.script.ScriptEngine;
+
/**
* An adapter of the Scala interpreter to the SciJava scripting interface.
- *
+ *
* @author Curtis Rueden
* @author Keith Schulze
* @author Johannes Schindelin
@@ -49,16 +49,10 @@
@Plugin(type = ScriptLanguage.class, name = "Scala")
public class ScalaScriptLanguage extends AdaptedScriptLanguage {
- @Parameter
- private LogService log;
-
- public ScalaScriptLanguage() {
- super("scala");
- }
+ @Parameter
+ private LogService log;
- @Override
- public ScriptEngine getScriptEngine() {
- final ScriptEngine eng = new dotty.tools.repl.ScriptEngine();
- return new ScalaAdaptedScriptEngine(eng);
- }
+ public ScalaScriptLanguage() {
+ super(new ScalaAdaptedScriptEngineFactory());
+ }
}
diff --git a/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
new file mode 100644
index 0000000..45807c2
--- /dev/null
+++ b/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
@@ -0,0 +1 @@
+org.scijava.plugins.scripting.scala.ScalaScriptLanguage
diff --git a/src/main/scala/dotty/tools/repl/SciJavaScriptEngine.scala b/src/main/scala/dotty/tools/repl/SciJavaScriptEngine.scala
new file mode 100644
index 0000000..429639e
--- /dev/null
+++ b/src/main/scala/dotty/tools/repl/SciJavaScriptEngine.scala
@@ -0,0 +1,104 @@
+package dotty.tools.repl
+
+// This file is based on dotty.tools.repl.ScriptEngine in Scala Compiler
+// It is using some package private classes from `dotty.tools.repl`
+// Only necessary changes are made, no corrects existing compiler warnings, etc.
+
+import dotty.tools.dotc
+import dotty.tools.dotc.core.StdNames.str
+
+import java.io.{Reader, StringWriter}
+import javax.script.{AbstractScriptEngine, Bindings, ScriptContext, ScriptEngineFactory, ScriptException, SimpleBindings, ScriptEngine as JScriptEngine}
+import scala.language.unsafeNulls
+
+/**
+ * A JSR 223 (Scripting API) compatible wrapper around the REPL for improved
+ * interoperability with software that supports it.
+ *
+ * It works by instantiating a new script engine through the script engine manager.
+ * The script engine provides a eval method to evaluate scripts in string form.
+ * Example use:
+ *
+ * val m = new javax.script.ScriptEngineManager()
+ * val e = m.getEngineByName("scala")
+ * println(e.eval("42"))
+ */
+class SciJavaScriptEngine(classPath: String) extends AbstractScriptEngine {
+ new javax.script.ScriptEngineManager()
+ private val driver = new ReplDriver(
+ Array(
+ "-classpath",
+ classPath,
+ "-usejavacp",
+ "-color:never",
+ "-Xrepl-disable-display"
+ ),
+ Console.out,
+ None
+ )
+ private val rendering = new Rendering(Some(getClass.getClassLoader))
+ private var state: State = driver.initialState
+
+ def getFactory: ScriptEngineFactory = new ScriptEngine.Factory
+
+ def createBindings: Bindings = new SimpleBindings
+
+ /* Evaluate with the given context. */
+ @throws[ScriptException]
+ def eval(script: String, context: ScriptContext): Object = {
+ val vid = state.valIndex
+ state = driver.run(script)(using state)
+ val oid = state.objectIndex
+ Class.forName(s"${Rendering.REPL_WRAPPER_NAME_PREFIX}$oid", true, rendering.classLoader()(using state.context))
+ .getDeclaredMethods.find(_.getName == s"${str.REPL_RES_PREFIX}$vid")
+ .map(_.invoke(null))
+ .getOrElse(null)
+ }
+
+ @throws[ScriptException]
+ def eval(reader: Reader, context: ScriptContext): Object = eval(stringFromReader(reader), context)
+
+ private val buffer = new Array[Char](8192)
+
+ def stringFromReader(in: Reader) = {
+ val out = new StringWriter
+ var n = in.read(buffer)
+ while (n > -1) {
+ out.write(buffer, 0, n)
+ n = in.read(buffer)
+ }
+ in.close
+ out.toString
+ }
+}
+
+object SciJavaScriptEngine {
+ import java.util.Arrays
+ import scala.util.Properties
+ class Factory(classPath: String) extends ScriptEngineFactory {
+ def getEngineName = "Scala REPL for SciJava"
+ def getEngineVersion = "3.0"
+ def getExtensions = Arrays.asList("scala", "sc")
+ def getLanguageName = "Scala"
+ def getLanguageVersion = Properties.versionString
+ def getMimeTypes = Arrays.asList("application/x-scala")
+ def getNames = Arrays.asList("scala")
+
+ def getMethodCallSyntax(obj: String, m: String, args: String*) = s"$obj.$m(${args.mkString(", ")})"
+
+ def getOutputStatement(toDisplay: String) = s"""print("$toDisplay")"""
+
+ def getParameter(key: String): Object = key match {
+ case JScriptEngine.ENGINE => getEngineName
+ case JScriptEngine.ENGINE_VERSION => getEngineVersion
+ case JScriptEngine.LANGUAGE => getLanguageName
+ case JScriptEngine.LANGUAGE_VERSION => getLanguageVersion
+ case JScriptEngine.NAME => getNames.get(0)
+ case _ => null
+ }
+
+ def getProgram(statements: String*) = statements.mkString("; ")
+
+ def getScriptEngine: JScriptEngine = new SciJavaScriptEngine(classPath)
+ }
+}
diff --git a/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngine.scala b/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngine.scala
index 8f39b4e..4854b4a 100644
--- a/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngine.scala
+++ b/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngine.scala
@@ -1,3 +1,32 @@
+/*
+ * #%L
+ * JSR-223-compliant Scala scripting language plugin.
+ * %%
+ * Copyright (C) 2013 - 2023 SciJava developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
package org.scijava.plugins.scripting.scala
import java.io.{OutputStream, Reader, StringWriter, Writer}
@@ -5,6 +34,8 @@ import javax.script.*
import scala.collection.mutable
import scala.jdk.CollectionConverters.*
import scala.util.Try
+import scala.util.control.NonFatal
+import scala.util.matching.Regex
/**
* Adapted Scala ScriptEngine
@@ -37,55 +68,88 @@ class ScalaAdaptedScriptEngine(engine: ScriptEngine) extends AbstractScriptEngin
// Scala 3.2.2 ignores bindings, emulate binding using setup script
// Create a line with variable declaration for each binding item
- val lines =
+ val transfers: mutable.Seq[Seq[String]] =
for
scope <- context.getScopes.asScala
bindings <- Option(context.getBindings(scope)).map(_.asScala) // bindings in context can be null
yield {
- for (name, value) <- bindings yield {
- value match
- case v: Double => s"val $name : Double = ${v}d"
- case v: Float => s"val $name : Float = ${v}f"
- case v: Long => s"val $name : Long = ${v}L"
- case v: Int => s"val $name : Int = $v"
- case v: Char => s"val $name : Char = '$v'"
- case v: Short => s"val $name : Short = $v"
- case v: Byte => s"val $name : Byte = $v"
- case v: Boolean => s"val $name : Int = $v"
- case o: AnyRef if isValidVariableName(name) =>
- _transfer = o
- val typeName = Option(o).map(_.getClass.getCanonicalName).getOrElse("AnyRef")
- s"""
- |val $name : $typeName = {
- | val t = org.scijava.plugins.scripting.scala.ScalaAdaptedScriptEngine._transfer
- | t.asInstanceOf[$typeName]
- |}""".stripMargin
- case _: AnyRef => "" // ignore if name is not a variable
- case v: Unit =>
- throw ScriptException(s"Unsupported type for bind variable $name: ${v.getClass}")
+ for (name, value) <- bindings.toSeq yield {
+ if isValidVariableName(name) then
+ val validName = addBackticksIfNeeded(name)
+ value match
+ case v: Double => s"val $validName : Double = ${v}d"
+ case v: Float => s"val $validName : Float = ${v}f"
+ case v: Long => s"val $validName : Long = ${v}L"
+ case v: Int => s"val $validName : Int = $v"
+ case v: Char => s"val $validName : Char = '$v'"
+ case v: Short => s"val $validName : Short = $v"
+ case v: Byte => s"val $validName : Byte = $v"
+ case v: Boolean => s"val $validName : Int = $v"
+ case v: AnyRef =>
+ val transferIndex = BindingSupport.nextTransferIndex
+ BindingSupport.__transfer(transferIndex) = v
+ val typeName = Option(v)
+ .map { oo =>
+ val tt: Array[_] = oo.getClass.getTypeParameters
+ tt.foreach(t => log(s"${oo.getClass.getCanonicalName} TYPE PARAM: ${t.getClass.getName}"))
+ val p = tt.map(_ => "_").mkString("[", ",", "]")
+ val n = oo.getClass.getCanonicalName
+ if tt.nonEmpty then n + p else n
+ }
+ .getOrElse("AnyRef")
+ val validTypeName = addBackticksIfNeeded(typeName)
+ s"""
+ |val $validName : $validTypeName = {
+ | val t = org.scijava.plugins.scripting.scala.ScalaAdaptedScriptEngine.BindingSupport
+ | ._transfer($transferIndex)
+ | t.asInstanceOf[$validTypeName]
+ |}""".stripMargin
+ case v: Unit =>
+ throw ScriptException(s"Unsupported type for bind variable $name: ${v.getClass}")
+ else
+ "" // ignore if name is not a variable
}
}
- val script = lines
+ val script = transfers
.flatten
.filter(_.nonEmpty)
.mkString("\n")
- if script.nonEmpty then
- evalInner(script, context)
+ evalInner(script, context)
end emulateBinding
- private def evalInner(script: String, context: ScriptContext) =
- class WriterOutputStream(w: Writer) extends OutputStream:
- override def write(b: Int): Unit = w.write(b)
+ private def evalInner(script: String, context: ScriptContext): AnyRef =
+ log(
+ s"""
+ |LOG[evalInner] script
+ |BEGIN
+ |---------------------------
+ |$script
+ |---------------------------
+ |END
+ |""".stripMargin
+ )
+ if script.trim.isEmpty then
+ log("LOG[evalInner] script is empty, skipping evaluation")
+ null
+ else
+ class WriterOutputStream(w: Writer) extends OutputStream:
+ override def write(b: Int): Unit = w.write(b)
- // Redirect output to writes provided by context
- Console.withOut(WriterOutputStream(context.getWriter)) {
- Console.withErr(WriterOutputStream(context.getErrorWriter)) {
- engine.eval(script, context)
- }
- }
+ try
+ // Redirect output to writes provided by context
+ Console.withOut(WriterOutputStream(context.getWriter)) {
+ Console.withErr(WriterOutputStream(context.getErrorWriter)) {
+ engine.eval(script, context)
+ }
+ }
+ catch
+ case NonFatal(t) =>
+ log(s"LOG[evalInner] in eval: $t")
+ t.printStackTrace()
+ throw t
private def stringFromReader(in: Reader) =
val out = new StringWriter()
@@ -122,14 +186,94 @@ class ScalaAdaptedScriptEngine(engine: ScriptEngine) extends AbstractScriptEngin
value
end get
+ private def log(msg: String): Unit = {
+ if ScalaAdaptedScriptEngine.DEBUG then
+ Console.out.println(msg)
+ }
+
end ScalaAdaptedScriptEngine
object ScalaAdaptedScriptEngine:
+ private val DEBUG: Boolean = false
private lazy val variableNamePattern = """^[a-zA-Z_$][a-zA-Z_$0-9]*$""".r
-
- /** Do not use externally despite it is declared public. IT is public so it is accessible from scripts */
- // noinspection ScalaWeakerAccess
- var _transfer: Object = _
+ private val scala3Keywords = Seq(
+ "abstract",
+ "case",
+ "catch",
+ "class",
+ "def",
+ "do",
+ "else",
+ "enum",
+ "export",
+ "extends",
+ "false",
+ "final",
+ "finally",
+ "for",
+ "given",
+ "if",
+ "implicit",
+ "import",
+ "lazy",
+ "match",
+ "new",
+ "null",
+ "object",
+ "override",
+ "package",
+ "private",
+ "protected",
+ "return",
+ "sealed",
+ "super",
+ "then",
+ "throw",
+ "trait",
+ "true",
+ "try",
+ "type",
+ "val",
+ "var",
+ "while",
+ "with",
+ "yield"
+ )
private def isValidVariableName(name: String): Boolean = variableNamePattern.matches(name)
+
+ private[scala] def addBackticksIfNeeded(referenceName: String): String =
+ referenceName
+ .split("\\.")
+ .map(n => if scala3Keywords.contains(n) then s"`$n`" else n)
+ .mkString(".")
+
+ /**
+ * Temporary support for implementing binding in the script engine.
+ * It has limited capacity and does not free memory.
+ * Access to storage is public, so it is visible from scripts.
+ */
+ //noinspection ScalaWeakerAccess
+ object BindingSupport:
+ private val MaxTransfers: Int = 1024 * 1024
+
+ /**
+ * Do not use externally despite it is declared public.
+ * It is public so it is accessible from scripts that are used to emulate variable binding
+ */
+ // noinspection ScalaWeakerAccess,ScalaUnusedSymbol
+ def _transfer: Seq[AnyRef] = __transfer.toSeq
+
+ private[scala] val __transfer: mutable.ListBuffer[AnyRef] = mutable.ListBuffer.empty[AnyRef]
+
+ private var lastTransferIndex = -1
+
+ private[scala] def nextTransferIndex: Int =
+ if lastTransferIndex + 1 >= MaxTransfers then
+ throw new IllegalStateException("ScalaAdaptedScriptEngine: maximum transfer limit reached")
+
+ lastTransferIndex += 1
+ __transfer.append(null)
+ lastTransferIndex
+
end ScalaAdaptedScriptEngine
diff --git a/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngineFactory.scala b/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngineFactory.scala
new file mode 100644
index 0000000..967baf9
--- /dev/null
+++ b/src/main/scala/org/scijava/plugins/scripting/scala/ScalaAdaptedScriptEngineFactory.scala
@@ -0,0 +1,79 @@
+/*
+ * #%L
+ * JSR-223-compliant Scala scripting language plugin.
+ * %%
+ * Copyright (C) 2013 - 2023 SciJava developers.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.scijava.plugins.scripting.scala
+
+import org.scijava.plugins.scripting.scala.ScalaAdaptedScriptEngine
+
+import java.net.URLClassLoader
+import java.nio.file.Paths
+import java.util
+import javax.script.{ScriptEngine, ScriptEngineFactory}
+
+/**
+ * A factory for ScalaAdaptedScriptEngine.
+ *
+ * @author Jarek Sacha
+ * @see ScriptEngineFactory
+ */
+class ScalaAdaptedScriptEngineFactory extends ScriptEngineFactory:
+
+ private val factory = new dotty.tools.repl.SciJavaScriptEngine.Factory(classPath)
+
+ /**
+ * Returns an instance of the `ScalaAdaptedScriptEngine`.
+ * A new instance is returned.
+ *
+ * @return A new `ScalaAdaptedScriptEngine` instance.
+ */
+ override def getScriptEngine = new ScalaAdaptedScriptEngine(factory.getScriptEngine)
+
+ override def getEngineName: String = factory.getEngineName
+ override def getEngineVersion: String = factory.getEngineVersion
+ override def getExtensions: util.List[String] = factory.getExtensions
+ override def getLanguageName: String = factory.getLanguageName
+ override def getLanguageVersion: String = factory.getLanguageVersion
+ override def getMimeTypes: util.List[String] = factory.getMimeTypes
+ override def getNames: util.List[String] = factory.getNames
+ override def getOutputStatement(toDisplay: String): String = factory.getOutputStatement(toDisplay)
+ override def getParameter(key: String): AnyRef = factory.getParameter(key)
+ override def getMethodCallSyntax(obj: String, m: String, args: String*): String =
+ factory.getMethodCallSyntax(obj, m, args*)
+ override def getProgram(statements: String*): String = factory.getProgram(statements*)
+
+ /**
+ * Retrieves the current classpath as a string.
+ */
+ def classPath: String = ClassLoader.getSystemClassLoader match
+ case cl: URLClassLoader =>
+ cl.getURLs
+ .map(url => Paths.get(url.toURI).toString)
+ .mkString(System.getProperty("path.separator"))
+ case _ =>
+ System.getProperty("java.class.path")
diff --git a/src/test/java/org/scijava/plugins/scripting/scala/ScalaTest.java b/src/test/java/org/scijava/plugins/scripting/scala/ScalaTest.java
index fee834e..504bfa2 100644
--- a/src/test/java/org/scijava/plugins/scripting/scala/ScalaTest.java
+++ b/src/test/java/org/scijava/plugins/scripting/scala/ScalaTest.java
@@ -2,7 +2,7 @@
* #%L
* JSR-223-compliant Scala scripting language plugin.
* %%
- * Copyright (C) 2013 - 2023 SciJava developers.
+ * Copyright (C) 2013 - 2024 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -35,12 +35,15 @@
import org.scijava.script.ScriptModule;
import org.scijava.script.ScriptService;
+import javax.script.ScriptContext;
import javax.script.ScriptEngine;
+import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* Scala unit tests.
@@ -182,6 +185,28 @@ public void testPutString() throws Exception {
}
}
+ @Test
+ public void testPut3Strings() throws Exception {
+ // Check that multiple AnyRef variable are bound correctly
+ try (final Context context = new Context(ScriptService.class)) {
+ final ScriptEngine engine = getEngine(context);
+ final String expected1 = "Ala ma kota";
+ final String expected2 = "Kot ma Ale";
+ final String expected3 = "Reksio nie ma butow";
+ engine.put("v1", expected1);
+ engine.put("v2", expected2);
+ engine.put("v3", expected3);
+ final String script = "\n" +
+ "val o1:String = v1\n" +
+ "val o2:String = v2\n" +
+ "val o3:String = v3\n";
+ engine.eval(script);
+ assertEquals(expected1, engine.get("o1"));
+ assertEquals(expected2, engine.get("o2"));
+ assertEquals(expected3, engine.get("o3"));
+ }
+ }
+
@Test
public void testPutInt() throws Exception {
@@ -197,6 +222,23 @@ public void testPutInt() throws Exception {
}
}
+ @Test
+ public void testPutSeqInt() throws Exception {
+ try (final Context context = new Context(ScriptService.class)) {
+ final ScriptEngine engine = getEngine(context);
+
+ final List expected = new ArrayList<>();
+ expected.add(7);
+ expected.add(13);
+ expected.add(-1);
+ engine.put("v", expected);
+ final String script = "val v1 = v";
+ engine.eval(script);
+ final Object actual = engine.get("v1");
+ assertEquals(expected, actual);
+ }
+ }
+
@Test
public void testLocals() throws Exception {
@@ -210,7 +252,7 @@ public void testLocals() throws Exception {
assertEquals("17", engine.eval("hello").toString());
assertEquals("17", engine.get("hello").toString());
-// With Scala 3.2.2 cannot reset bindings correctly, will skip the ret of the test
+// With Scala 3.2.2 cannot reset bindings correctly, will skip the rest of the test
// final Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
// bindings.clear();
// assertNull(engine.get("hello"));
@@ -266,4 +308,64 @@ public void testImportsRetained() throws Exception {
assertEquals(result, result2);
}
}
+
+ /**
+ * Test for issue #5: "eval should sometimes make an entry in the ENGINE_SCOPE bindings"
+ */
+ @Test
+ public void issue5() {
+ try (final Context context = new Context(ScriptService.class)) {
+
+ final ScriptService scriptService = context.getService(ScriptService.class);
+ final ScriptEngine engine = scriptService.getLanguageByName("scala").getScriptEngine();
+
+ assertFalse(engine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey("ten"));
+ engine.put("ten", 10);
+ assertEquals(10, engine.get("ten"));
+ assertTrue(engine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey("ten"));
+
+ engine.put("twenty", 20);
+ assertEquals(20, engine.get("twenty"));
+ }
+ }
+
+ /**
+ * Test for issue #9: "ScriptREPL is not usable - problem with variables named as Scala keywords"
+ */
+ @Test
+ public void issue9() {
+ try (final Context context = new Context(ScriptService.class)) {
+
+ final ScriptService scriptService = context.getService(ScriptService.class);
+ final ScriptEngine engine = scriptService.getLanguageByName("scala").getScriptEngine();
+
+ final String name = "object";
+ final int expectedValue = 7;
+
+ assertFalse(engine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey(name));
+ engine.put(name, expectedValue);
+ assertTrue(engine.getBindings(ScriptContext.ENGINE_SCOPE).containsKey(name));
+ assertEquals(expectedValue, engine.get(name));
+
+ final String script1 = "val abc = `" + name + "`";
+ try {
+ engine.eval(script1);
+ } catch (ScriptException e) {
+ fail("Failed to execute valid script: \"" + script1 + "\"");
+ }
+
+ final Object r = engine.get("abc");
+ assertEquals(expectedValue, r);
+
+ }
+ }
+
+ @Test
+ public void addBackticksIfNeededTest() {
+ final String expected = "org.scijava.`object`.DefaultObjectService";
+ final String name = "org.scijava.object.DefaultObjectService";
+ final String correctedName = ScalaAdaptedScriptEngine.addBackticksIfNeeded(name);
+
+ assertEquals(expected, correctedName);
+ }
}