Skip to content

Commit 8b61f0a

Browse files
Scripting Solution: scala can compile & run *.scala files
1 parent f8289ee commit 8b61f0a

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
package dotty.tools.repl
22

3+
import java.io.File
4+
35
/** Main entry point to the REPL */
4-
object Main {
5-
def main(args: Array[String]): Unit =
6-
new ReplDriver(args).runUntilQuit()
7-
}
6+
object Main:
7+
def preprocessArgs(args: Array[String]): (Array[String], Option[File], Array[String]) =
8+
val scriptIndex = args.lastIndexWhere(_.endsWith(".scala"))
9+
if scriptIndex < 0 then (args, None, Array.empty[String])
10+
else (args.take(scriptIndex), Some(File(args(scriptIndex))), args.drop(scriptIndex + 1))
11+
end preprocessArgs
12+
13+
def main(args: Array[String]): Unit = preprocessArgs(args) match
14+
case (compilerArgs, Some(scriptFile), scriptArgs) =>
15+
try ScriptingDriver(compilerArgs, scriptFile, scriptArgs).compileAndRun()
16+
catch
17+
case ScriptingException(msg) =>
18+
println(s"Error: $msg")
19+
sys.exit(1)
20+
case (compilerArgs, None, scriptArgs) if scriptArgs.isEmpty =>
21+
new ReplDriver(args).runUntilQuit()
22+
case _ =>
23+
throw RuntimeException(s"Incorrect arguments to REPL: ${args.mkString(" ")}")
24+
end Main
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package dotty.tools.repl
2+
3+
import java.nio.file.{ Files, Path }
4+
import java.io.File
5+
import java.net.{ URL, URLClassLoader }
6+
import java.lang.reflect.{ Modifier, Method }
7+
8+
import scala.jdk.CollectionConverters._
9+
10+
import dotty.tools.dotc.{ Driver, Compiler }
11+
import dotty.tools.dotc.core.Contexts, Contexts.{ Context, ContextBase, ctx }
12+
import dotty.tools.dotc.config.CompilerCommand
13+
import dotty.tools.io.{ PlainDirectory, Directory }
14+
import dotty.tools.dotc.reporting.Reporter
15+
import dotty.tools.dotc.config.Settings.Setting._
16+
17+
import sys.process._
18+
19+
class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver:
20+
def compileAndRun(): Unit =
21+
val outDir = Files.createTempDirectory("scala3-scripting")
22+
val (toCompile, rootCtx) = setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh)
23+
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
24+
new PlainDirectory(Directory(outDir)))
25+
26+
if doCompile(newCompiler, toCompile).hasErrors then
27+
throw ScriptingException("Errors encountered during compilation")
28+
29+
try detectMainMethod(outDir, ctx.settings.classpath.value).invoke(null, scriptArgs)
30+
catch
31+
case e: java.lang.reflect.InvocationTargetException =>
32+
throw e.getCause
33+
34+
end compileAndRun
35+
36+
private def detectMainMethod(outDir: Path, classpath: String): Method =
37+
val outDirURL = outDir.toUri.toURL
38+
val classpathUrls = classpath.split(":").map(File(_).toURI.toURL)
39+
val cl = URLClassLoader(classpathUrls :+ outDirURL)
40+
41+
def collectMainMethods(target: File, path: String): List[Method] =
42+
val nameWithoutExtension = target.getName.takeWhile(_ != '.')
43+
val targetPath =
44+
if path.nonEmpty then s"${path}.${nameWithoutExtension}"
45+
else nameWithoutExtension
46+
47+
if target.isDirectory then
48+
for
49+
packageMember <- target.listFiles.toList
50+
membersMainMethod <- collectMainMethods(packageMember, targetPath)
51+
yield membersMainMethod
52+
else if target.getName.endsWith(".class") then
53+
val cls = cl.loadClass(targetPath)
54+
val method =
55+
try cls.getMethod("main", classOf[Array[String]])
56+
catch
57+
case _: java.lang.NoSuchMethodException => null
58+
59+
if method != null && Modifier.isStatic(method.getModifiers) then List(method)
60+
else Nil
61+
else Nil
62+
end collectMainMethods
63+
64+
val candidates = for
65+
file <- outDir.toFile.listFiles.toList
66+
method <- collectMainMethods(file, "")
67+
yield method
68+
69+
candidates match
70+
case Nil =>
71+
throw ScriptingException("No main methods detected in your script")
72+
case _ :: _ :: _ =>
73+
throw ScriptingException("More than one main method detected in your script")
74+
case m :: Nil => m
75+
end match
76+
end detectMainMethod
77+
end ScriptingDriver
78+
79+
case class ScriptingException(msg: String) extends RuntimeException(msg)

0 commit comments

Comments
 (0)