Skip to content

Update Scala to version 3 #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 24, 2023
77 changes: 61 additions & 16 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.scijava</groupId>
<artifactId>pom-scijava</artifactId>
<version>27.0.1</version>
<version>34.0.0</version>
<relativePath />
</parent>

<artifactId>scripting-scala</artifactId>
<version>0.2.3-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>

<name>SciJava Scripting: Scala</name>
<description>JSR-223-compliant Scala scripting language plugin.</description>
Expand Down Expand Up @@ -53,6 +53,10 @@
<name>Keith Schulze</name>
<properties><id>keithschulze</id></properties>
</contributor>
<contributor>
<name>Jarek Sacha</name>
<properties><id>jpsacha</id></properties>
</contributor>
</contributors>

<mailingLists>
Expand Down Expand Up @@ -82,6 +86,7 @@

<properties>
<package-name>org.scijava.plugins.scripting.scala</package-name>
<main-class>org.scijava.plugins.scripting.scala.Main</main-class>

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

<scala.version>2.12.1</scala.version>
<scala.version>3.2.2</scala.version>
</properties>

<dependencies>
<!-- SciJava dependencies -->
<dependency>
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
</dependency>

<!-- Scala dependencies -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.8.0</version>
<configuration>
<args>
<arg>-unchecked</arg>
<arg>-deprecation</arg>
<arg>-explain</arg>
<arg>-explain-types</arg>
<arg>-release</arg>
<arg>8</arg>
</args>
</configuration>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<!-- SciJava dependencies -->
<dependency>
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
</dependency>

<!-- Scala dependencies -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala3-compiler_3</artifactId>
<version>${scala.version}</version>
<exclusions>
</exclusions>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
/*
/*-
* #%L
* JSR-223-compliant Scala scripting language plugin.
* %%
* Copyright (C) 2013 - 2016 Board of Regents of the University of
* Wisconsin-Madison.
* Copyright (C) 2014 - 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
Expand All @@ -27,45 +26,13 @@
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

package org.scijava.plugins.scripting.scala;

import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.scijava.script.AdaptedScriptEngine;

/**
* Scala interpreter
*
* @author Keith Schulze
* @see ScriptEngine
*/
public class ScalaScriptEngine extends AdaptedScriptEngine {

public ScalaScriptEngine(ScriptEngine engine) {
super(engine);
}

@Override
public Object get(String key) {
// First try to get value from bindings
Object value = super.get(key);
import org.scijava.script.ScriptREPL;

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

return value;
}
public static void main(String... args) throws Exception {
ScriptREPL.main(args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@

package org.scijava.plugins.scripting.scala;

import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.stream.Collectors;

import javax.script.ScriptEngine;

import org.scijava.log.LogService;
Expand All @@ -42,17 +38,13 @@
import org.scijava.script.AdaptedScriptLanguage;
import org.scijava.script.ScriptLanguage;

import scala.tools.nsc.ConsoleWriter;
import scala.tools.nsc.NewLinePrintWriter;
import scala.tools.nsc.Settings;
import scala.tools.nsc.interpreter.Scripted;

/**
* An adapter of the Scala interpreter to the SciJava scripting interface.
*
* @author Curtis Rueden
* @author Keith Schulze
* @author Johannes Schindelin
* @author Jarek Sacha
* @see ScriptEngine
*/
@Plugin(type = ScriptLanguage.class, name = "Scala")
Expand All @@ -67,25 +59,7 @@ public ScalaScriptLanguage() {

@Override
public ScriptEngine getScriptEngine() {
final Settings settings = new Settings();
settings.classpath().value_$eq(getClasspath());

Scripted eng = Scripted.apply(new Scripted.Factory(), settings,
new NewLinePrintWriter(new ConsoleWriter(), true));

return new ScalaScriptEngine(eng);
}

/** Retrieves the current classpath as a string. */
private String getClasspath() {
final ClassLoader cl = ClassLoader.getSystemClassLoader();
if (!(cl instanceof URLClassLoader)) {
log.warn("Cannot retrieve classpath from class loader of type '" +
cl.getClass().getName() + "'");
return System.getProperty("java.class.path");
}
return Arrays.stream(((URLClassLoader) cl).getURLs()).map(//
url -> url.getPath() //
).collect(Collectors.joining(System.getProperty("path.separator")));
final ScriptEngine eng = new dotty.tools.repl.ScriptEngine();
return new ScalaAdaptedScriptEngine(eng);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package org.scijava.plugins.scripting.scala

import java.io.{OutputStream, Reader, StringWriter, Writer}
import javax.script.*
import scala.collection.mutable
import scala.jdk.CollectionConverters.*
import scala.util.Try

/**
* Adapted Scala ScriptEngine
*
* @author Jarek Sacha
* @author Keith Schulze
* @see ScriptEngine
*/
class ScalaAdaptedScriptEngine(engine: ScriptEngine) extends AbstractScriptEngine:

import ScalaAdaptedScriptEngine.*

private val buffer = new Array[Char](8192)

@throws[ScriptException]
override def eval(reader: Reader, context: ScriptContext): AnyRef = eval(stringFromReader(reader), context)

@throws[ScriptException]
override def eval(script: String, context: ScriptContext): AnyRef =
emulateBinding(context)
val r = evalInner(script, context)
// Scala returns `Unit` when no value is returned. Script Engine (or the
// Java side) expects `null` when no value was returned.
// Anything else return as is.
r match
case _: Unit => null
case x => x

private def emulateBinding(context: ScriptContext): Unit =

// Scala 3.2.2 ignores bindings, emulate binding using setup script
// Create a line with variable declaration for each binding item
val lines =
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}")
}
}

val script = lines
.flatten
.filter(_.nonEmpty)
.mkString("\n")

if script.nonEmpty then
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)

// Redirect output to writes provided by context
Console.withOut(WriterOutputStream(context.getWriter)) {
Console.withErr(WriterOutputStream(context.getErrorWriter)) {
engine.eval(script, context)
}
}

private def stringFromReader(in: Reader) =
val out = new StringWriter()
var n = in.read(buffer)
while n > -1 do
out.write(buffer, 0, n)
n = in.read(buffer)
in.close()
out.toString

override def createBindings(): Bindings = engine.createBindings

override def getFactory: ScriptEngineFactory = engine.getFactory

override def get(key: String): AnyRef =
// First try to get value from bindings
var value = super.get(key)

// NB: Extracting values from Scala Script Engine are a little tricky.
// Values (variables) initialised or computed in the script are
// not added to the bindings of the CompiledScript AFAICT. Therefore
// the only way to extract them is to evaluate the variable and
// capture the return. If it evaluates to null or throws a
// a ScriptException, we simply return null.
if value == null then
try
value = evalInner(key, getContext)
catch
case _: ScriptException =>
// HACK: Explicitly ignore ScriptException, which arises if
// key is not found. This feels bad because it fails silently
// for the user, but it mimics behaviour in other script langs.

value
end get

end ScalaAdaptedScriptEngine

object ScalaAdaptedScriptEngine:
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 def isValidVariableName(name: String): Boolean = variableNamePattern.matches(name)
end ScalaAdaptedScriptEngine
Loading