Skip to content

Commit 0054041

Browse files
committed
Add Scala AdaptedScriptEngine with support for print redirection and variable binding
1 parent 2197446 commit 0054041

File tree

5 files changed

+300
-124
lines changed

5 files changed

+300
-124
lines changed

pom.xml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</parent>
1111

1212
<artifactId>scripting-scala</artifactId>
13-
<version>0.2.3-SNAPSHOT</version>
13+
<version>0.3.0-SNAPSHOT</version>
1414

1515
<name>SciJava Scripting: Scala</name>
1616
<description>JSR-223-compliant Scala scripting language plugin.</description>
@@ -106,6 +106,23 @@ Wisconsin-Madison.</license.copyrightOwners>
106106
<groupId>net.alchim31.maven</groupId>
107107
<artifactId>scala-maven-plugin</artifactId>
108108
<version>4.8.0</version>
109+
<executions>
110+
<execution>
111+
<id>scala-compile-first</id>
112+
<phase>process-resources</phase>
113+
<goals>
114+
<goal>add-source</goal>
115+
<goal>compile</goal>
116+
</goals>
117+
</execution>
118+
<execution>
119+
<id>scala-test-compile</id>
120+
<phase>process-test-resources</phase>
121+
<goals>
122+
<goal>testCompile</goal>
123+
</goals>
124+
</execution>
125+
</executions>
109126
</plugin>
110127
</plugins>
111128
</build>

src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptEngine.java

Lines changed: 0 additions & 71 deletions
This file was deleted.

src/main/java/org/scijava/plugins/scripting/scala/ScalaScriptLanguage.java

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,21 @@
3030

3131
package org.scijava.plugins.scripting.scala;
3232

33-
import java.net.URLClassLoader;
34-
import java.util.Arrays;
35-
import java.util.stream.Collectors;
36-
3733
import javax.script.ScriptEngine;
38-
import javax.script.ScriptEngineManager;
3934

4035
import org.scijava.log.LogService;
4136
import org.scijava.plugin.Parameter;
4237
import org.scijava.plugin.Plugin;
4338
import org.scijava.script.AdaptedScriptLanguage;
4439
import org.scijava.script.ScriptLanguage;
4540

46-
//import scala.tools.nsc.ConsoleWriter;
47-
//import scala.tools.nsc.NewLinePrintWriter;
48-
//import scala.tools.nsc.Settings;
49-
//import scala.tools.nsc.interpreter.shell.Scripted;
50-
5141
/**
5242
* An adapter of the Scala interpreter to the SciJava scripting interface.
5343
*
5444
* @author Curtis Rueden
5545
* @author Keith Schulze
5646
* @author Johannes Schindelin
47+
* @author Jarek Sacha
5748
* @see ScriptEngine
5849
*/
5950
@Plugin(type = ScriptLanguage.class, name = "Scala")
@@ -68,27 +59,7 @@ public ScalaScriptLanguage() {
6859

6960
@Override
7061
public ScriptEngine getScriptEngine() {
71-
// final Settings settings = new Settings();
72-
// settings.classpath().value_$eq(getClasspath());
73-
//
74-
// Scripted eng = Scripted.apply(new Scripted.Factory(), settings,
75-
// new NewLinePrintWriter(new ConsoleWriter(), true));
76-
// x = new dotty.tools.repl.ScriptEngine();
77-
//
78-
final ScriptEngine eng = new ScriptEngineManager().getEngineByName("scala");
79-
return new ScalaScriptEngine(eng);
80-
}
81-
82-
/** Retrieves the current classpath as a string. */
83-
private String getClasspath() {
84-
final ClassLoader cl = ClassLoader.getSystemClassLoader();
85-
if (!(cl instanceof URLClassLoader)) {
86-
log.warn("Cannot retrieve classpath from class loader of type '" +
87-
cl.getClass().getName() + "'");
88-
return System.getProperty("java.class.path");
89-
}
90-
return Arrays.stream(((URLClassLoader) cl).getURLs()).map(//
91-
url -> url.getPath() //
92-
).collect(Collectors.joining(System.getProperty("path.separator")));
62+
final ScriptEngine eng = new dotty.tools.repl.ScriptEngine();
63+
return new ScalaAdaptedScriptEngine(eng);
9364
}
9465
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package org.scijava.plugins.scripting.scala
2+
3+
import java.io.{OutputStream, Reader, StringWriter, Writer}
4+
import javax.script.*
5+
import scala.collection.mutable
6+
import scala.jdk.CollectionConverters.*
7+
import scala.util.Try
8+
9+
/**
10+
* Adapted Scala ScriptEngine
11+
*
12+
* @author Jarek Sacha
13+
* @author Keith Schulze
14+
* @see ScriptEngine
15+
*/
16+
class ScalaAdaptedScriptEngine(engine: ScriptEngine) extends AbstractScriptEngine:
17+
18+
import ScalaAdaptedScriptEngine.*
19+
20+
private val buffer = new Array[Char](8192)
21+
22+
@throws[ScriptException]
23+
override def eval(reader: Reader, context: ScriptContext): AnyRef = eval(stringFromReader(reader), context)
24+
25+
@throws[ScriptException]
26+
override def eval(script: String, context: ScriptContext): AnyRef =
27+
emulateBinding(context)
28+
evalInner(script, context)
29+
30+
private def emulateBinding(context: ScriptContext): Unit =
31+
32+
// Scala 3.2.2 ignores bindings, emulate binding using setup script
33+
// Create a line with variable declaration for each binding item
34+
val lines =
35+
for
36+
scope <- context.getScopes.asScala
37+
bindings <- Option(context.getBindings(scope)).map(_.asScala) // bindings in context can be null
38+
yield {
39+
for (name, value) <- bindings yield {
40+
value match
41+
case v: Double => s"val $name : Double = ${v}d"
42+
case v: Float => s"val $name : Float = ${v}f"
43+
case v: Long => s"val $name : Long = ${v}L"
44+
case v: Int => s"val $name : Int = $v"
45+
case v: Char => s"val $name : Char = '$v'"
46+
case v: Short => s"val $name : Short = $v"
47+
case v: Byte => s"val $name : Byte = $v"
48+
case v: Boolean => s"val $name : Int = $v"
49+
case o: AnyRef if isValidVariableName(name) =>
50+
_transfer = o
51+
val typeName = Option(o).map(_.getClass.getCanonicalName).getOrElse("AnyRef")
52+
s"""
53+
|val $name : $typeName = {
54+
| val t = org.scijava.plugins.scripting.scala.ScalaAdaptedScriptEngine._transfer
55+
| t.asInstanceOf[$typeName]
56+
|}""".stripMargin
57+
case _: AnyRef => "" // ignore if name is not a variable
58+
case v: Unit =>
59+
throw ScriptException(s"Unsupported type for bind variable $name: ${v.getClass}")
60+
}
61+
}
62+
63+
val script = lines
64+
.flatten
65+
.filter(_.nonEmpty)
66+
.mkString("\n")
67+
68+
if script.nonEmpty then
69+
evalInner(script, context)
70+
71+
end emulateBinding
72+
73+
private def evalInner(script: String, context: ScriptContext) =
74+
class WriterOutputStream(w: Writer) extends OutputStream:
75+
override def write(b: Int): Unit = w.write(b)
76+
77+
// Redirect output to writes provided by context
78+
Console.withOut(WriterOutputStream(context.getWriter)) {
79+
Console.withErr(WriterOutputStream(context.getErrorWriter)) {
80+
engine.eval(script, context)
81+
}
82+
}
83+
84+
private def stringFromReader(in: Reader) =
85+
val out = new StringWriter()
86+
var n = in.read(buffer)
87+
while n > -1 do
88+
out.write(buffer, 0, n)
89+
n = in.read(buffer)
90+
in.close()
91+
out.toString
92+
93+
override def createBindings(): Bindings = engine.createBindings
94+
95+
override def getFactory: ScriptEngineFactory = engine.getFactory
96+
97+
override def get(key: String): AnyRef =
98+
// First try to get value from bindings
99+
var value = super.get(key)
100+
101+
// NB: Extracting values from Scala Script Engine are a little tricky.// NB: Extracting values from Scala Script Engine are a little tricky.
102+
// Values (variables) initialised or computed in the script are// Values (variables) initialised or computed in the script are
103+
// not added to the bindings of the CompiledScript AFAICT. Therefore// not added to the bindings of the CompiledScript AFAICT. Therefore
104+
// the only way to extract them is to evaluate the variable and// the only way to extract them is to evaluate the variable and
105+
// capture the return. If it evaluates to null or throws a// capture the return. If it evaluates to null or throws a
106+
// a ScriptException, we simply return null.// a ScriptException, we simply return null.
107+
if value == null then
108+
try
109+
value = evalInner(key, getContext)
110+
catch
111+
case _: ScriptException =>
112+
// HACK: Explicitly ignore ScriptException, which arises if
113+
// key is not found. This feels bad because it fails silently
114+
// for the user, but it mimics behaviour in other script langs.
115+
116+
value
117+
end get
118+
119+
end ScalaAdaptedScriptEngine
120+
121+
object ScalaAdaptedScriptEngine:
122+
private lazy val variableNamePattern = """^[a-zA-Z_$][a-zA-Z_$0-9]*$""".r
123+
124+
/** Do not use externally despite it is declared public. IT is public so it is accessible from scripts */
125+
// noinspection ScalaWeakerAccess
126+
var _transfer: Object = _
127+
128+
private def isValidVariableName(name: String): Boolean = variableNamePattern.matches(name)
129+
end ScalaAdaptedScriptEngine

0 commit comments

Comments
 (0)