Skip to content

Commit

Permalink
Implement run/show for quoted expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Jan 13, 2018
1 parent db47f14 commit 385a87f
Show file tree
Hide file tree
Showing 27 changed files with 401 additions and 14 deletions.
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ class CompilationUnit(val source: SourceFile) {
object CompilationUnit {

/** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)

/** Make a compilation unit the given unpickled tree */
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
assert(!unpickled.isEmpty, unpickled)
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()))
val unit1 = new CompilationUnit(source)
unit1.tpdTree = unpickled
if (forceTrees)
force.traverse(unit1.tpdTree)
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompilationUnit.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.util.NoSource

import scala.quoted.Expr

/* Compilation unit containing the contents of a quoted expression */
class ExprCompilationUnit(val expr: Expr[_]) extends CompilationUnit(NoSource) {
override def toString = s"Expr($expr)"
}
36 changes: 36 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprCompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dotty.tools.dotc
package quoted

import dotty.tools.backend.jvm.GenBCode
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.{Mode, Phases}
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.transform.Pickler
import dotty.tools.io.VirtualDirectory

/** Compiler that takes the contents of a quoted expression `expr` and produces
* a class file with `class ' { def apply: Object = expr }`.
*/
class ExprCompiler(directory: VirtualDirectory) extends Compiler {

/** A GenBCode phase that outputs to a virtual directory */
private class ExprGenBCode extends GenBCode {
override def phaseName = "genBCode"
override def outputDir(implicit ctx: Context) = directory
}

override def phases: List[List[Phase]] = {
val backendPhases = super.phases.dropWhile {
case List(_: Pickler) => false
case _ => true
}.tail

List(new ExprFrontend(putInClass = true)) ::
Phases.replace(classOf[GenBCode], _ => new ExprGenBCode :: Nil, backendPhases)
}

override def newRun(implicit ctx: Context): ExprRun = {
reset()
new ExprRun(this, ctx.addMode(Mode.ReadPositions))
}
}
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprDecompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dotty.tools.dotc.quoted

import java.io.PrintStream

import dotty.tools.dotc.core.Phases.Phase

/** Compiler that takes the contents of a quoted expression `expr` and produces outputs
* the pretty printed code.
*/
class ExprDecompiler(out: PrintStream) extends ExprCompiler(null) {
override def phases: List[List[Phase]] = List(
List(new ExprFrontend(putInClass = false)), // Create class from Expr
List(new QuotePrinter(out)) // Print all loaded classes
)
}
55 changes: 55 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprFrontend.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.core.Scopes._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.quoted.PickledQuotes
import dotty.tools.dotc.typer.FrontEnd
import dotty.tools.dotc.util.Positions._
import dotty.tools.dotc.util.SourceFile
import dotty.tools.io._

import scala.quoted.Expr

/** Frontend that receives scala.quoted.Expr as input */
class ExprFrontend(putInClass: Boolean) extends FrontEnd {
import tpd._

override def isTyper = false

override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = {
units.map {
case exprUnit: ExprCompilationUnit =>
val tree =
if (putInClass) inClass(exprUnit.expr)
else PickledQuotes.quotedToTree(exprUnit.expr)
val source = new SourceFile("", Seq())
CompilationUnit.mkCompilationUnit(source, tree, forceTrees = true)
}
}

/** Places the contents of expr in a compilable tree for a class
* with the following format.
* `package __root__ { class ' { def apply: Any = <expr> } }`
*/
private def inClass(expr: Expr[_])(implicit ctx: Context): Tree = {
val pos = Position(0)
val assocFile = new PlainFile(Path("<quote>"))

val cls = ctx.newCompleteClassSymbol(defn.RootClass, nme.QUOTE.toTypeName, EmptyFlags,
defn.ObjectType :: Nil, newScope, coord = pos, assocFile = assocFile).entered.asClass
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered

val quoted = PickledQuotes.quotedToTree(expr)(ctx.withOwner(meth))

val run = DefDef(meth, quoted)
val classTree = ClassDef(cls, DefDef(cls.primaryConstructor.asTerm), run :: Nil)
PackageDef(ref(defn.RootPackage).asInstanceOf[Ident], classTree :: Nil).withPos(pos)
}
}
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/ExprRun.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dotty.tools.dotc
package quoted

import dotty.tools.dotc.core.Contexts._

import scala.quoted._

class ExprRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) {
def compileExpr(expr: Expr[_]): Unit = {
val units = new ExprCompilationUnit(expr) :: Nil
compileUnits(units)
}
}
61 changes: 61 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/QuoteDriver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package dotty.tools.dotc.quoted

import dotty.tools.dotc.Driver
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.StdNames._
import dotty.tools.io.VirtualDirectory

import dotty.tools.repl.AbstractFileClassLoader

import scala.quoted.Expr

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.nio.charset.StandardCharsets

class QuoteDriver extends Driver {

def run[T](expr: Expr[T]): T = {
val ctx: Context = initCtx.fresh
// TODO enable optimisation?
// ctx.settings.optimise.update(true)(ctx)

val outDir = new VirtualDirectory("(memory)", None)

new ExprCompiler(outDir).newRun(ctx).compileExpr(expr)

val classLoader = new AbstractFileClassLoader(outDir, this.getClass.getClassLoader)

val clazz = classLoader.loadClass(nme.QUOTE.toString)
val method = clazz.getMethod("apply")
val instance = clazz.newInstance()

method.invoke(instance).asInstanceOf[T]
}

def show(expr: Expr[_]): String = {
val ctx: Context = initCtx.fresh
ctx.settings.color.update("never")(ctx) // TODO support colored show
val baos = new ByteArrayOutputStream
var ps: PrintStream = null
try {
ps = new PrintStream(baos, true, "utf-8")

new ExprDecompiler(ps).newRun(ctx).compileExpr(expr)

new String(baos.toByteArray, StandardCharsets.UTF_8)
}
finally if (ps != null) ps.close()
}

override def initCtx: Context = {
val ictx = super.initCtx.fresh
val compilerClasspath = System.getProperty("dotty.tools.dotc.classpath")
assert(compilerClasspath ne null, "System property `dotty.tools.dotc.classpath` is not set.")
val classpath = System.getProperty("java.class.path")
val scalaLib = classpath.split(":").filter(_.contains("scala-library")).mkString(":")
ictx.settings.classpath.update(compilerClasspath + ":" + scalaLib)(ictx)
ictx
}

}
17 changes: 17 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dotty.tools.dotc.quoted

import java.io.PrintStream

import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Phases.Phase

/** Pretty prints the compilation unit to an output stream */
class QuotePrinter(out: PrintStream) extends Phase {

override def phaseName: String = "quotePrinter"

override def run(implicit ctx: Context): Unit = {
val unit = ctx.compilationUnit
out.print(unit.tpdTree.show)
}
}
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/quoted/Runners.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dotty.tools.dotc.quoted

import scala.quoted.Expr
import scala.runtime.quoted._

/** Default runners for quoted expressions */
object Runners {
implicit def runner[T]: Runner[T] = (expr: Expr[T]) => new QuoteDriver().run(expr)
implicit def show[T]: Show[T] = (expr: Expr[T]) => new QuoteDriver().show(expr)
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object Splicer {
/** Splice the Tree for a Quoted expression which is constructed via a reflective call to the given method */
private def reflectiveSplice(tree: Tree)(implicit ctx: Context): Tree = {
val interpreter = new Interpreter
interpreter.interpretTree[quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
interpreter.interpretTree[scala.quoted.Expr[_]](tree).map(PickledQuotes.quotedToTree(_)).getOrElse(tree)
}

}
8 changes: 8 additions & 0 deletions compiler/test/dotty/Jars.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ object Jars {
val dottyInterfaces: String = sys.env.get("DOTTY_INTERFACE")
.getOrElse(Properties.dottyInterfaces)

/** Scala asm Jar */
lazy val scalaAsm: String =
findJarFromRuntime("scala-asm-6.0.0-scala-1")

/** Dotty extras classpath from env or properties */
val dottyExtras: List[String] = sys.env.get("DOTTY_EXTRAS")
.map(_.split(":").toList).getOrElse(Properties.dottyExtras)
Expand All @@ -25,6 +29,10 @@ object Jars {
val dottyTestDeps: List[String] =
dottyLib :: dottyCompiler :: dottyInterfaces :: dottyExtras

/** Dotty runtime with compiler dependencies, used for quoted.Expr.run */
val dottyRunWithCompiler: List[String] =
dottyLib :: dottyCompiler :: dottyInterfaces :: scalaAsm :: Nil

def scalaLibrary: String = sys.env.get("DOTTY_SCALA_LIBRARY")
.getOrElse(findJarFromRuntime("scala-library-2."))

Expand Down
4 changes: 3 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ class CompilationTests extends ParallelTesting {
@Test def runAll: Unit = {
implicit val testGroup: TestGroup = TestGroup("runAll")
compileFilesInDir("../tests/run", defaultOptions) +
compileFilesInDir("../tests/run-no-optimise", defaultOptions)
compileFilesInDir("../tests/run-no-optimise", defaultOptions) +
compileFile("../tests/run-special/quote-run.scala", defaultRunWithCompilerOptions) +
compileFile("../tests/run-special/quote-run-2.scala", defaultRunWithCompilerOptions)
}.checkRuns()

// Generic java signatures tests ---------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/vulpix/ChildJVMMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ChildJVMMain {
static final String MessageEnd = "##THIS IS THE END FOR ME, GOODBYE##";

private static void runMain(String dir) throws Exception {
System.setProperty("dotty.tools.dotc.classpath", dir);
ArrayList<URL> cp = new ArrayList<>();
for (String path : dir.split(":"))
cp.add(new File(path).toURI().toURL());
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ object TestConfiguration {
val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions)
val defaultOptimised = defaultUnoptimised and "-optimise"
val defaultOptions = defaultUnoptimised
val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":"))
val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes"
val allowDoubleBindings = defaultOptions without "-Yno-double-bindings"
val picklingOptions = defaultUnoptimised and (
Expand Down
12 changes: 11 additions & 1 deletion dist/bin/dotr
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ fi
source "$PROG_HOME/bin/common"

declare -a residual_args
declare -a system_properties
run_repl=false
with_compiler=false
CLASS_PATH=""

while [[ $# -gt 0 ]]; do
Expand All @@ -44,6 +46,10 @@ while [[ $# -gt 0 ]]; do
shift
shift
;;
-with-compiler)
with_compiler=true
shift
;;
-d)
DEBUG="$DEBUG_STR"
shift
Expand All @@ -69,5 +75,9 @@ else
else
cp_arg+="$PSEP$CLASS_PATH"
fi
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${residual_args[@]}"
if [ $with_compiler == true ]; then
cp_arg+="$PSEP$DOTTY_COMP$PSEP$DOTTY_INTF$PSEP$SCALA_ASM"
system_properties+=("-Ddotty.tools.dotc.classpath=$DOTTY_COMP$PSEP$DOTTY_LIB$PSEP$DOTTY_INTF$PSEP$SCALA_ASM")
fi
eval exec "\"$JAVACMD\"" "$DEBUG" "-classpath \"$cp_arg\"" "${system_properties[@]}" "${residual_args[@]}"
fi
7 changes: 5 additions & 2 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package scala.quoted

import scala.runtime.quoted.{Runner, Show}

abstract class Expr[T] extends Quoted {
def unary_~ : T = throw new Error("~ should have been compiled away")
def run: T = ???
final def unary_~ : T = throw new Error("~ should have been compiled away")
final def run(implicit runner: Runner[T]): T = runner.run(this)
final def show(implicit runner: Show[T]): String = runner.run(this)
}

object Expr {
Expand Down
9 changes: 9 additions & 0 deletions library/src/scala/runtime/quoted/Runner.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scala.runtime.quoted

import scala.annotation.implicitNotFound
import scala.quoted.Expr

@implicitNotFound("Could not find implicit Runner. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`")
trait Runner[T] {
def run(expr: Expr[T]): T
}
9 changes: 9 additions & 0 deletions library/src/scala/runtime/quoted/Show.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scala.runtime.quoted

import scala.annotation.implicitNotFound
import scala.quoted.Expr

@implicitNotFound("Could not find implicit Show. Default runner can must be imported with `import dotty.tools.dotc.quoted.Runners._`")
trait Show[T] {
def run(expr: Expr[T]): String
}
Loading

0 comments on commit 385a87f

Please sign in to comment.