Skip to content

Commit

Permalink
Add back object wrapping for… objects
Browse files Browse the repository at this point in the history
If the user code solely consists of a singleton, like
```scala
object Foo {
  val n = 2
}
```
then it is wrapped in an object rather than a class.

This allows users to wrap some code in objects rather than classes if needed.

In particular, this is useful for macro code (than can't be defined in classes that we instantiate ourselves), or for definitions that are later processed by macros (definitions from classes are harder to handle than those in objects)
  • Loading branch information
alexarchambault committed Jan 2, 2018
1 parent 0ec7733 commit 96e4a0b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 46 deletions.
7 changes: 7 additions & 0 deletions amm/interp/src/main/scala/ammonite/interp/Parsers.scala
Expand Up @@ -60,6 +60,8 @@ object Parsers {
val Splitter0 = P( StatementBlock(Fail) )
val Splitter = P( ("{" ~ Splitter0 ~ "}" | Splitter0) ~ End )

val ObjParser = P( ObjDef )

/**
* Attempts to break a code blob into multiple statements. Returns `None` if
* it thinks the code blob is "incomplete" and requires more input
Expand All @@ -79,6 +81,11 @@ object Parsers {
}
}

def isObjDef(code: String): Boolean = {
ObjParser.parse(code)
.fold((_, _, _) => false, (_, _) => true)
}

val Separator = P( WL ~ "@" ~~ CharIn(" " + System.lineSeparator).rep(1) )
val CompilationUnit = P( WL.! ~ StatementBlock(Separator) ~ WL )
val ScriptSplitter = P( CompilationUnit.repX(1, Separator) ~ End)
Expand Down
130 changes: 85 additions & 45 deletions amm/interp/src/main/scala/ammonite/interp/Preprocessor.scala
Expand Up @@ -297,60 +297,98 @@ object Preprocessor{

//we need to normalize topWrapper and bottomWrapper in order to ensure
//the snippets always use the platform-specific newLine
val topWrapper = codeWrapper.top(pkgName, imports, indexedWrapperName)

val bottomWrapper = codeWrapper.bottom(printCode, indexedWrapperName, extraCode)
val (topWrapper, bottomWrapper) =
codeWrapper(code, pkgName, imports, printCode, indexedWrapperName, extraCode)
val importsLen = topWrapper.length

(topWrapper + code + bottomWrapper, importsLen)
}


trait CodeWrapper{
def top(pkgName: Seq[Name], imports: Imports, indexedWrapperName: Name): String
def bottom(printCode: String, indexedWrapperName: Name, extraCode: String): String
def apply(
code: String,
pkgName: Seq[Name],
imports: Imports,
printCode: String,
indexedWrapperName: Name,
extraCode: String
): (String, String)
}
object CodeWrapper extends CodeWrapper{
private val q = "\""
def top(pkgName: Seq[Name], imports: Imports, indexedWrapperName: Name) = {

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(Seq(Name("instance")))) {
val name = data.prefix.drop(prefix.length).dropRight(1).last
(data.copy(prefix = Seq(name)), Seq(name -> data.prefix))
} else
(data, Nil)
}
.unzip
def apply(
code: String,
pkgName: Seq[Name],
imports: Imports,
printCode: String,
indexedWrapperName: Name,
extraCode: String
) = {

val isObjDef = Parsers.isObjDef(code)

if (isObjDef) {
val top = normalizeNewlines(s"""
package ${pkgName.head.encoded}
package ${Util.encodeScalaSourcePath(pkgName.tail)}

(Imports(l), reqVals0.flatten)
}
$imports

val requiredVals = reqVals
.distinct
.groupBy(_._1)
.mapValues(_.map(_._2))
.toVector
.sortBy(_._1.raw)
.collect {
case (key, Seq(path)) =>
val encoded = Util.encodeScalaSourcePath(path)
s"final val ${key.backticked}: $encoded.type = " +
s"if (__amm_usedThings($q$q$q${key.raw}$q$q$q)) $encoded else null$newLine"
case (key, values) =>
throw new Exception(
"Should not happen - several required values with the same name" +
s"(name: $key, values: $values)"
)
object ${indexedWrapperName.backticked}{
val instance: Helper.type = Helper
def $$main() = instance.$$main()

object Helper extends java.io.Serializable {
"""
)

val bottom = normalizeNewlines(s"""\ndef $$main() = { $printCode }
override def toString = "${indexedWrapperName.encoded}"
$extraCode
}}
""")

(top, bottom)
} 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(Seq(Name("instance")))) {
val name = data.prefix.drop(prefix.length).dropRight(1).last
(data.copy(prefix = Seq(name)), Seq(name -> data.prefix))
} else
(data, Nil)
}
.unzip

(Imports(l), reqVals0.flatten)
}
.mkString

normalizeNewlines(s"""
val requiredVals = reqVals
.distinct
.groupBy(_._1)
.mapValues(_.map(_._2))
.toVector
.sortBy(_._1.raw)
.collect {
case (key, Seq(path)) =>
val encoded = Util.encodeScalaSourcePath(path)
s"final val ${key.backticked}: $encoded.type = " +
s"if (__amm_usedThings($q$q$q${key.raw}$q$q$q)) $encoded else null$newLine"
case (key, values) =>
throw new Exception(
"Should not happen - several required values with the same name " +
"(name: $key, values: $values)"
)
}
.mkString

val top = normalizeNewlines(s"""
package ${pkgName.head.encoded}
package ${Util.encodeScalaSourcePath(pkgName.tail)}

Expand All @@ -370,17 +408,19 @@ $requiredVals
$reworkedImports

final class Helper extends java.io.Serializable{\n"""
)
}
)

def bottom(printCode: String, indexedWrapperName: Name, extraCode: String) =
normalizeNewlines(s"""\ndef $$main() = { $printCode }
val bottom = normalizeNewlines(s"""\ndef $$main() = { $printCode }
override def toString = "${indexedWrapperName.encoded}"
$extraCode
}}
""")

final class UsedThings(obj: AnyRef) {
(top, bottom)
}
}

final class UsedThings(obj: AnyRef) {

lazy val rawCode: String = {
var is: java.io.InputStream = null
Expand Down
Expand Up @@ -21,7 +21,7 @@ object SerializationTests extends TestSuite{

check.session(
s"""
@ object costlySideEffect extends Serializable {
@ object costlySideEffect {
@ import java.nio.file.{Files, Paths}
@ private val path = Paths.get("a")
@ def apply() = Files.write(path, Array[Byte]())
Expand Down

0 comments on commit 96e4a0b

Please sign in to comment.