ammonite.compiler.CodeClassWrapper.scala Maven / Gradle / Ivy
The newest version!
package ammonite.compiler
import ammonite.compiler.iface.CodeWrapper
import ammonite.util._
import ammonite.util.Util.{CodeSource, newLine, normalizeNewlines}
import scala.language.postfixOps
object CodeClassWrapper extends CodeWrapper {
/*
* The goal of this code wrapper is that the user code:
* - should be in a class rather than a singleton,
* - should see the previous commands results via instances of these classes,
* not referencing singletons along the way.
*
* Only dealing with class instances at runtime, rather than singletons, behaves
* well wrt Java serialization. Singletons don't write their fields during serialization,
* and re-compute them when deserialized. On the other hand, class instances serialize
* and de-serialize their fields, as expected.
*
* It still allows users to wrap code in singletons rather than a class if they want to:
* user code that solely consists of a singleton, is itself wrapped in a singleton,
* rather than a class. This is useful for macro code, or definitions that are
* themselves processed by macros (definitions in objects are easier to process from
* macros).
*/
private val userCodeNestingLevel = 2
private val q = "\""
private val tq = "\"\"\""
override val wrapperPath: Seq[Name] = Seq(Name("instance"))
def apply(
code: String,
source: CodeSource,
imports: Imports,
printCode: String,
indexedWrapperName: Name,
extraCode: String
) = {
import source.pkgName
val isObjDef = Parsers.isObjDef(code)
if (isObjDef) {
val top = normalizeNewlines(s"""
package ${pkgName.head.encoded}
package ${Util.encodeScalaSourcePath(pkgName.tail)}
$imports
object ${indexedWrapperName.backticked}{
val instance: Helper.type = Helper
def $$main() = instance.$$main()
object Helper extends _root_.java.io.Serializable {
""")
val bottom = normalizeNewlines(s"""\ndef $$main() = { $printCode }
override def toString = "${indexedWrapperName.encoded}";
$extraCode
}}
""")
(top, bottom, userCodeNestingLevel)
} else {
val (reworkedImports, reqVals) = {
val (l, reqVals0) = imports
.value
.map { data =>
val prefix = Seq(Name("_root_"), Name("ammonite"), Name("$sess"))
if (data.prefix.startsWith(prefix) && data.prefix.endsWith(wrapperPath)) {
val name = data.prefix.drop(prefix.length).dropRight(wrapperPath.length).last
(data.copy(prefix = Seq(name)), Seq(name -> data.prefix))
} else
(data, Nil)
}
.unzip
(Imports(l), reqVals0.flatten)
}
val requiredVals = reqVals
.distinct
.groupBy(_._1)
.mapValues(_.map(_._2))
.toVector
.sortBy(_._1.raw)
.collect {
case (key, Seq(path)) =>
/*
* Via __amm_usedThings, that itself relies on the *-tree.txt resources generated
* via the AmmonitePlugin, we can know whether the current command uses things from
* each of the previous ones, and null-ify the references to those that are unused.
* That way, the unused commands don't prevent serializing this command.
*/
val encoded = Util.encodeScalaSourcePath(path)
s"val ${key.backticked}: $encoded.type = " +
s"if (__amm_usedThings($tq${key.raw}$tq)) $encoded " +
s"else null.asInstanceOf[$encoded.type]$newLine"
case (key, values) =>
throw new Exception(
"Should not happen - several required values with the same name " +
s"(name: $key, values: $values)"
)
}
.mkString
val usedThingsSet: String = {
if (reqVals.isEmpty) ""
else
"@_root_.scala.transient private val __amm_usedThings = " +
"_root_.ammonite.repl.ReplBridge.value.usedEarlierDefinitions.iterator.toSet"
}
val top = normalizeNewlines(s"""
package ${pkgName.head.encoded}
package ${Util.encodeScalaSourcePath(pkgName.tail)}
object ${indexedWrapperName.backticked}{
val wrapper = new ${indexedWrapperName.backticked}
val instance = new wrapper.Helper
def $$main() = instance.$$main()
}
final class ${indexedWrapperName.backticked} extends _root_.java.io.Serializable {
$usedThingsSet
override def toString = $q${indexedWrapperName.encoded}$q
$requiredVals
$reworkedImports
final class Helper extends _root_.java.io.Serializable{\n""")
val bottom = normalizeNewlines(s"""\ndef $$main() = { $printCode }
override def toString = "${indexedWrapperName.encoded}";
$extraCode
}}
""")
(top, bottom, userCodeNestingLevel)
}
}
}