Skip to content

Commit ab312ef

Browse files
authored
Merge pull request #7 from jpsacha/Scala_3
Update Scala to version 3
2 parents 558746f + 8e8231d commit ab312ef

File tree

5 files changed

+431
-110
lines changed

5 files changed

+431
-110
lines changed

pom.xml

+61-16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
33
<modelVersion>4.0.0</modelVersion>
44

55
<parent>
66
<groupId>org.scijava</groupId>
77
<artifactId>pom-scijava</artifactId>
8-
<version>27.0.1</version>
8+
<version>34.0.0</version>
99
<relativePath />
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>
@@ -53,6 +53,10 @@
5353
<name>Keith Schulze</name>
5454
<properties><id>keithschulze</id></properties>
5555
</contributor>
56+
<contributor>
57+
<name>Jarek Sacha</name>
58+
<properties><id>jpsacha</id></properties>
59+
</contributor>
5660
</contributors>
5761

5862
<mailingLists>
@@ -82,6 +86,7 @@
8286

8387
<properties>
8488
<package-name>org.scijava.plugins.scripting.scala</package-name>
89+
<main-class>org.scijava.plugins.scripting.scala.Main</main-class>
8590

8691
<license.licenseName>bsd_2</license.licenseName>
8792
<license.copyrightOwners>Board of Regents of the University of
@@ -91,22 +96,62 @@ Wisconsin-Madison.</license.copyrightOwners>
9196
<!-- NB: Deploy releases to the SciJava Maven repository. -->
9297
<releaseProfiles>sign,deploy-to-scijava</releaseProfiles>
9398

94-
<scala.version>2.12.1</scala.version>
99+
<scala.version>3.2.2</scala.version>
95100
</properties>
96101

97-
<dependencies>
98-
<!-- SciJava dependencies -->
99-
<dependency>
100-
<groupId>org.scijava</groupId>
101-
<artifactId>scijava-common</artifactId>
102-
</dependency>
103102

104-
<!-- Scala dependencies -->
105-
<dependency>
106-
<groupId>org.scala-lang</groupId>
107-
<artifactId>scala-compiler</artifactId>
108-
<version>${scala.version}</version>
109-
</dependency>
103+
<build>
104+
<plugins>
105+
<plugin>
106+
<groupId>net.alchim31.maven</groupId>
107+
<artifactId>scala-maven-plugin</artifactId>
108+
<version>4.8.0</version>
109+
<configuration>
110+
<args>
111+
<arg>-unchecked</arg>
112+
<arg>-deprecation</arg>
113+
<arg>-explain</arg>
114+
<arg>-explain-types</arg>
115+
<arg>-release</arg>
116+
<arg>8</arg>
117+
</args>
118+
</configuration>
119+
<executions>
120+
<execution>
121+
<id>scala-compile-first</id>
122+
<phase>process-resources</phase>
123+
<goals>
124+
<goal>add-source</goal>
125+
<goal>compile</goal>
126+
</goals>
127+
</execution>
128+
<execution>
129+
<id>scala-test-compile</id>
130+
<phase>process-test-resources</phase>
131+
<goals>
132+
<goal>testCompile</goal>
133+
</goals>
134+
</execution>
135+
</executions>
136+
</plugin>
137+
</plugins>
138+
</build>
139+
140+
<dependencies>
141+
<!-- SciJava dependencies -->
142+
<dependency>
143+
<groupId>org.scijava</groupId>
144+
<artifactId>scijava-common</artifactId>
145+
</dependency>
146+
147+
<!-- Scala dependencies -->
148+
<dependency>
149+
<groupId>org.scala-lang</groupId>
150+
<artifactId>scala3-compiler_3</artifactId>
151+
<version>${scala.version}</version>
152+
<exclusions>
153+
</exclusions>
154+
</dependency>
110155

111156
<!-- Test dependencies -->
112157
<dependency>
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
/*
1+
/*-
22
* #%L
33
* JSR-223-compliant Scala scripting language plugin.
44
* %%
5-
* Copyright (C) 2013 - 2016 Board of Regents of the University of
6-
* Wisconsin-Madison.
5+
* Copyright (C) 2014 - 2023 SciJava developers.
76
* %%
87
* Redistribution and use in source and binary forms, with or without
98
* modification, are permitted provided that the following conditions are met:
10-
*
9+
*
1110
* 1. Redistributions of source code must retain the above copyright notice,
1211
* this list of conditions and the following disclaimer.
1312
* 2. Redistributions in binary form must reproduce the above copyright notice,
1413
* this list of conditions and the following disclaimer in the documentation
1514
* and/or other materials provided with the distribution.
16-
*
15+
*
1716
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1817
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1918
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
@@ -27,45 +26,13 @@
2726
* POSSIBILITY OF SUCH DAMAGE.
2827
* #L%
2928
*/
30-
3129
package org.scijava.plugins.scripting.scala;
3230

33-
import javax.script.ScriptEngine;
34-
import javax.script.ScriptException;
35-
36-
import org.scijava.script.AdaptedScriptEngine;
37-
38-
/**
39-
* Scala interpreter
40-
*
41-
* @author Keith Schulze
42-
* @see ScriptEngine
43-
*/
44-
public class ScalaScriptEngine extends AdaptedScriptEngine {
45-
46-
public ScalaScriptEngine(ScriptEngine engine) {
47-
super(engine);
48-
}
49-
50-
@Override
51-
public Object get(String key) {
52-
// First try to get value from bindings
53-
Object value = super.get(key);
31+
import org.scijava.script.ScriptREPL;
5432

55-
// NB: Extracting values from Scala Script Engine are a little tricky.
56-
// Values (variables) initialised or computed in the script are
57-
// not added to the bindings of the CompiledScript AFAICT. Therefore
58-
// the only way to extract them is to evaluate the variable and
59-
// capture the return. If it evaluates to null or throws a
60-
// a ScriptException, we simply return null.
61-
if (value == null) try {
62-
value = super.eval(key);
63-
} catch (ScriptException ignored) {
64-
// HACK: Explicitly ignore ScriptException, which arises if
65-
// key is not found. This feels bad because it fails silently
66-
// for the user, but it mimics behaviour in other script langs.
67-
}
33+
public class Main {
6834

69-
return value;
70-
}
35+
public static void main(String... args) throws Exception {
36+
ScriptREPL.main(args);
37+
}
7138
}

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

+3-29
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@
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;
3834

3935
import org.scijava.log.LogService;
@@ -42,17 +38,13 @@
4238
import org.scijava.script.AdaptedScriptLanguage;
4339
import org.scijava.script.ScriptLanguage;
4440

45-
import scala.tools.nsc.ConsoleWriter;
46-
import scala.tools.nsc.NewLinePrintWriter;
47-
import scala.tools.nsc.Settings;
48-
import scala.tools.nsc.interpreter.Scripted;
49-
5041
/**
5142
* An adapter of the Scala interpreter to the SciJava scripting interface.
5243
*
5344
* @author Curtis Rueden
5445
* @author Keith Schulze
5546
* @author Johannes Schindelin
47+
* @author Jarek Sacha
5648
* @see ScriptEngine
5749
*/
5850
@Plugin(type = ScriptLanguage.class, name = "Scala")
@@ -67,25 +59,7 @@ public ScalaScriptLanguage() {
6759

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

0 commit comments

Comments
 (0)