Parsing scala 3 code from a String into Scala 3 AST at runtime - scala

My goal is to get Scala 3 code as a String and to parse it into Abstract Syntax Tree for Scala 3 at runtime. In the process if the code has compilation errors, I should get that as part of some exception.
The larger goal is to end up with Expr[T] if the scala code is valid and execute it by splicing in the right bits(I have this part covered).
This was doable in Scala 2.* using scala-reflect here.
val source =
"""
|object HelloWorld {
| def main(args: Array[String]): Unit = {
| println("Hello, world!")
| }
|}
|
|HelloWorld.main(Array())
|""".stripMargin
val tree = toolbox.parse(source)
val binary = toolbox.compile(tree)
binary()
But as far as I can surmise, in Scala 3, scala-reflect will not be ported.
How could I achieve the same in Scala 3?
Some relevant links here and here

Ohh, you can look at ammonite: parser: https://github.com/com-lihaoyi/Ammonite/blob/master/amm/compiler/src/main/scala-3/ammonite/compiler/Parsers.scala
(They create a virtual file and run a compiler on it).
If you don't want evaluation but just AST, then maybe scalameta [https://scalameta.org/] will be enough. As I know, scala3 syntax is supported in the latest version, but scalameta itself (i.e. processing of parsed tree) is on scala2.

Related

Evalutate complex type with quasiquote scala, unlifting

I need to compile function and then evaluate it with different parameters of type List[Map[String, AnyRef]].
I have the following code that does not compile with such the type but compiles with simple type like List[Int].
I found that there are just certain implementations of Liftable in scala.reflect.api.StandardLiftables.StandardLiftableInstances
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
val functionWrapper =
"""
object FunctionWrapper {
def makeBody(messages: List[Map[String, AnyRef]]) = Map.empty
}""".stripMargin
val functionSymbol =
tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])
val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))
tb.eval(q"$functionSymbol.function($list)")
Getting compilation error for this, how can I make it work?
Error:(22, 38) Can't unquote List[Map[String,AnyRef]], consider using
... or providing an implicit instance of
Liftable[List[Map[String,AnyRef]]]
tb.eval(q"$functionSymbol.function($list)")
^
The problem comes not from complicated type but from the attempt to use AnyRef. When you unquote some literal, it means you want the infrastructure to be able to create a valid syntax tree to create an object that would exactly match the object you pass. Unfortunately this is obviously not possible for all objects. For example, assume that you've passed a reference to Thread.currentThread() as a part of the Map. How it could possible work? Compiler is just not able to recreate such a complicated object (not to mention making it the current thread). So you have two obvious alternatives:
Make you argument also a Tree i.e. something like this
def testTree() = {
val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
val functionWrapper =
"""
| object FunctionWrapper {
|
| def makeBody(messages: List[Map[String, AnyRef]]) = Map.empty
|
| }
""".stripMargin
val functionSymbol =
tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])
//val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))
val list = q"""List(Map("1" -> "2"))"""
val res = tb.eval(q"$functionSymbol.makeBody($list)")
println(s"testTree = $res")
}
The obvious drawback of this approach is that you loose type safety at compile time and might need to provide a lot of context for the tree to work
Another approach is to not try to pass anything containing AnyRef to the compiler-infrastructure. It means you create some function-like Wrapper:
package so {
trait Wrapper {
def call(args: List[Map[String, AnyRef]]): Map[String, AnyRef]
}
}
and then make your generated code return a Wrapper instead of directly executing the logic and call the Wrapper from the usual Scala code rather than inside compiled code. Something like this:
def testWrapper() = {
val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
val functionWrapper =
"""
|object FunctionWrapper {
| import scala.collection._
| import so.Wrapper /* <- here probably different package :) */
|
| def createWrapper(): Wrapper = new Wrapper {
| override def call(args: List[Map[String, AnyRef]]): Map[String, AnyRef] = Map.empty
| }
|}
| """.stripMargin
val functionSymbol = tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])
val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))
val tree: tb.u.Tree = q"$functionSymbol.createWrapper()"
val wrapper = tb.eval(tree).asInstanceOf[Wrapper]
val res = wrapper.call(list)
println(s"testWrapper = $res")
}
P.S. I'm not sure what are you doing but beware of performance issues. Scala is a hard language to compile and thus it might easily take more time to compile your custom code than to run it. If performance becomes an issue you might need to use some other methods such as full-blown macro-code-generation or at least caching of the compiled code.

How to add symbols into a parsed AST?

(This is loosely related to Scala script in 2.11 and Generating a class from string and instantiating it in Scala 2.10).
In the code below I have a code parsed runtime using Toolbox into a corresponding AST. How can add symbol definitions (prefix in the code below) to the AST so that those symbols can be used in the expression tree?
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
object Main extends App {
val cm = runtimeMirror(getClass.getClassLoader)
val tb = cm.mkToolBox()
val expr = tb.parse("println(i)")
val build = internal.reificationSupport
val prefix = build.setInfo(build.newFreeTerm("i", 2), typeOf[Int])
// TODO: add prefix before expr by some AST manipulation
tb.eval(expr)
}

Scala script in 2.11

I have found an example code for a Scala runtime scripting in answer to Generating a class from string and instantiating it in Scala 2.10, however the code seems to be obsolete for 2.11 - I cannot find any function corresponding to build.setTypeSignature. Even if it worked, the code seems hard to read and follow to me.
How can Scala scripts be compiled and executed in Scala 2.11?
Let us assume I want following:
define several variables (names and values)
compile script
(optional improvement) change variable values
execute script
For simplicity consider following example:
I want to define following variables (programmatically, from the code, not from the script text):
val a = 1
val s = "String"
I want a following script to be compiled and on execution a String value "a is 1, s is String" returned from it:
s"a is $a, s is $s"
How should my functions look like?
def setupVariables() = ???
def compile() = ???
def changeVariables() = ???
def execute() : String = ???
Scala 2.11 adds a JSR-223 scripting engine. It should give you the functionality you are looking for. Just as a reminder, as with all of these sorts of dynamic things, including the example listed in the description above, you will lose type safety. You can see below that the return type is always Object.
Scala REPL Example:
scala> import javax.script.ScriptEngineManager
import javax.script.ScriptEngineManager
scala> val e = new ScriptEngineManager().getEngineByName("scala")
e: javax.script.ScriptEngine = scala.tools.nsc.interpreter.IMain#566776ad
scala> e.put("a", 1)
a: Object = 1
scala> e.put("s", "String")
s: Object = String
scala> e.eval("""s"a is $a, s is $s"""")
res6: Object = a is 1, s is String`
An addition example as an application running under scala 2.11.6:
import javax.script.ScriptEngineManager
object EvalTest{
def main(args: Array[String]){
val e = new ScriptEngineManager().getEngineByName("scala")
e.put("a", 1)
e.put("s", "String")
println(e.eval("""s"a is $a, s is $s""""))
}
}
For this application to work make sure to include the library dependency.
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value

how to import a class in scala

I am learning scala recently, the package in scala confused me.
I have a file named StockPriceFinder.scala:
// StockPriceFinder.scala
object StockPriceFinder {
def getTickersAndUnits() = {
val stockAndUnitsXML = scala.xml.XML.load("stocks.xml")
(Map[String, Int]() /: (stocksAndUnitsXML \ "symbol")) {
(map, symbolNode) =>
val ticker = (symbolNode \ "#ticker").toString
val units = (symbolNode \ "units").text.toInt
map ++ Map(ticker -> units)
}
}
}
then I want to use StockPriceFinder in test.scala which is in the same folder:
val symbolAndUnits = StockPriceFinder.getTickersAndUnits
but when I run it with scala test.scala, I got error:error: not found: value StockPriceFinder. In Java, if this two source files are in the same folder, I do not need to import and I can use it directly, so how can I import StockPriceFinder correctly in scala?
I have tried to use import StockPriceFinder in test.scala, but it still does not work.
You don't need to import StockPriceFinder if the files are in the same package (not folder).
But you do need to compile StockPriceFinder.scala first and pass the correct classpath to scala.
scalac StockPriceFinder.scala
scala -cp . test.scala
should work (might be a bit off). However, you shouldn't do it manually, since it becomes unmanageable very quickly; use SBT or other build tools (Maven, Gradle).

Scala Presentation Compiler

Hi I've been trying to get the presentation compiler to work but I'm getting the following error. Any help regarding this would be appreciated. I've already seen other questions and few projects where it has been implemented but everyone uses the Global.Run which is not being recognized in the REPL. This the code and the error below it. I've installed scala 2.10.3 in windows 8.1
import scala.tools.nsc.{Global,Settings}
import scala.tools.nsc.reporters._
object Test {
def main (args: Array[String]) {
val settings = new Settings;
val global = new Global(settings,new ConsoleReporter(settings));
val compiler = global.Run;
}
}
The error is
Sample.scala:8: error: value Run is not a member of scala.tools.nsc.Global
Try this:
import scala.tools.nsc.{Global,Settings}
import scala.tools.nsc.reporters._
object Test {
def main (args: Array[String]) {
val settings = new Settings
val global = new Global(settings,new ConsoleReporter(settings))
val compiler = new global.Run
}
}
Notice new Run instead of Run. There is no companion object for class Run. Maybe it was there before in earlier scala versions. Checked on Scala v2.10.3. Works in REPL.