Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ abstract class Preprocessor {

object Preprocessor {

/**
*
*
* @param code
* @param prefixCharLength
* @param userCodeNestingLevel if 0, assume code isn't generated / wrapped by Ammonite
*/
case class Output(
code: String,
prefixCharLength: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ class AmmonitePlugin(
unit,
output,
usedEarlierDefinitions,
userCodeNestingLevel,
topWrapperLen
userCodeNestingLevel
)
}
}
Expand All @@ -80,8 +79,7 @@ object AmmonitePlugin {
unit: g.CompilationUnit,
output: Seq[ImportData] => Unit,
usedEarlierDefinitions: Seq[String] => Unit,
userCodeNestingLevel: => Int,
topWrapperLen: => Int
userCodeNestingLevel: => Int
) = {

count += 1
Expand All @@ -108,7 +106,7 @@ object AmmonitePlugin {
}

userCodeNestingLevel match {
case 1 =>
case 0 | 1 =>
/*
* We don't try to determine what previous commands are actually used here.
* userCodeNestingLevel == 1 likely corresponds to the default object-based
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ class CompilerLifecycleManager(
// Enforce the invariant that every piece of code Ammonite ever compiles,
// gets run within the `ammonite` package. It's further namespaced into
// things like `ammonite.$file` or `ammonite.$sess`, but it has to be
// within `ammonite`
assert(processed.code.trim.startsWith("package ammonite"))
// within `ammonite`.
// We make an exception for raw sources, which have nesting level 0.
assert(
processed.userCodeNestingLevel == 0 ||
processed.code.trim.startsWith("package ammonite")
)

init()
val compiled = compiler.compile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ object Parsers extends IParser {
ignoreIncomplete: Boolean,
fileName: String
): Option[Either[String, Seq[String]]] =
if (ignoreIncomplete) {
if (code.startsWith("package "))
if (code.endsWith("\n\n")) Some(Right(Seq(code.stripSuffix("\n"))))
else None
else if (ignoreIncomplete) {
// We use `instrument` to detect when the parser has reached the end of the
// input, any time during the parse. If it has done so, and failed, we
// consider the input incomplete.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ class CompilerLifecycleManager(
// Enforce the invariant that every piece of code Ammonite ever compiles,
// gets run within the `ammonite` package. It's further namespaced into
// things like `ammonite.$file` or `ammonite.$sess`, but it has to be
// within `ammonite`
assert(processed.code.trim.startsWith("package ammonite"))
// within `ammonite`.
// We make an exception for raw sources, which have nesting level 0.
assert(
processed.userCodeNestingLevel == 0 ||
processed.code.trim.startsWith("package ammonite")
)

init()
val compiled = compiler.compile(
Expand Down
8 changes: 6 additions & 2 deletions amm/compiler/src/main/scala-3/ammonite/compiler/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ class Parsers extends IParser {
ignoreIncomplete: Boolean = true,
fileName: String = "(console)"
): Option[Either[String, Seq[String]]] =
doSplit(code, ignoreIncomplete, fileName)
.map(_.map(_.map(_._2)))
if (code.startsWith("package "))
if (code.endsWith("\n\n")) Some(Right(Seq(code.stripSuffix("\n"))))
else None
else
doSplit(code, ignoreIncomplete, fileName)
.map(_.map(_.map(_._2)))

private def doSplit(
code: String,
Expand Down
21 changes: 21 additions & 0 deletions amm/interp/src/main/scala/ammonite/interp/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,27 @@ class Interpreter(
} yield (res, Tag("", "", classPathWhitelist.hashCode().toString))
}

def processRawSource(
code: String,
fileName: String
): Res[Seq[(String, Array[Byte])]] = synchronized {
for {
_ <- Catching { case e: ThreadDeath => Evaluator.interrupted(e) }
output <- Res(
compilerManager.compileClass(
Preprocessor.Output(code, 0, 0),
printer,
fileName
),
"Compilation Failed"
)
_ = {
for ((name, bytes) <- output.classFiles.sortBy(_._1) if name.endsWith(".class"))
headFrame.classloader.addClassFile(name.stripSuffix(".class").replace('/', '.'), bytes)
}
} yield output.classFiles
}

def processSingleBlock(
processed: Preprocessor.Output,
codeSource0: CodeSource,
Expand Down
10 changes: 9 additions & 1 deletion amm/repl/src/main/scala/ammonite/repl/Repl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,15 @@ class Repl(
history = history :+ code
}
)
out <- interp.processLine(code, stmts, currentLine, false, () => currentLine += 1)
out <- {
if (code.startsWith("package "))
interp.processRawSource(code, s"source-$currentLine.scala").map { _ =>
currentLine += 1
Evaluated(Nil, Imports())
}
else
interp.processLine(code, stmts, currentLine, false, () => currentLine += 1)
}
} yield {
printer.outStream.println()
out
Expand Down
55 changes: 33 additions & 22 deletions amm/repl/src/test/scala/ammonite/TestRepl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class TestRepl(compilerBuilder: ICompilerBuilder = CompilerBuilder()) { self =>
val (cmdLines, resultLines) =
Predef.augmentString(step).lines.toArray.map(_.drop(margin)).partition(_.startsWith("@"))

val commandText = cmdLines.map(_.stripPrefix("@ ")).toVector
val commandText = cmdLines.map(line => if (line == "@") "" else line.stripPrefix("@ ")).toVector

println(cmdLines.mkString(Util.newLine))
// Make sure all non-empty, non-complete command-line-fragments
Expand Down Expand Up @@ -365,30 +365,41 @@ class TestRepl(compilerBuilder: ICompilerBuilder = CompilerBuilder()) { self =>
warningBuffer.clear()
errorBuffer.clear()
infoBuffer.clear()
val splitted = ammonite.compiler.Parsers.split(input) match {
case None => sys.error(s"No result when splitting input '$input'")
case Some(Left(error)) => sys.error(s"Error when splitting input '$input': $error")
case Some(Right(stmts)) => stmts
}
val processed = interp.processLine(
input,
splitted,
index,
false,
() => currentLine += 1
)
processed match {
case Res.Failure(s) => printer0.error(s)
case Res.Exception(throwable, msg) =>
printer0.error(
Repl.showException(throwable, fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty)

val res =
if (input.startsWith("package "))
interp.processRawSource(input, s"source-$currentLine.scala").map { _ =>
currentLine += 1
Evaluated(Nil, Imports())
}
else {
val splitted = ammonite.compiler.Parsers.split(input) match {
case None => sys.error(s"No result when splitting input '$input'")
case Some(Left(error)) => sys.error(s"Error when splitting input '$input': $error")
case Some(Right(stmts)) => stmts
}
val processed = interp.processLine(
input,
splitted,
index,
false,
() => currentLine += 1
)
processed match {
case Res.Failure(s) => printer0.error(s)
case Res.Exception(throwable, msg) =>
printer0.error(
Repl.showException(throwable, fansi.Attrs.Empty, fansi.Attrs.Empty, fansi.Attrs.Empty)
)

case _ =>
}
Repl.handleOutput(interp, processed)
processed
}

case _ =>
}
Repl.handleOutput(interp, processed)
(
processed,
res,
outString,
resString,
warningBuffer.mkString,
Expand Down
45 changes: 45 additions & 0 deletions amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -856,5 +856,50 @@ object AdvancedTests extends TestSuite {
)
}
}

test("package input") {
check.session(
"""
@ package thing
@
@ object Thing {
@ def message = "Hello"
@ }

@ val message = thing.Thing.message
message: String = "Hello"
"""
)

if (scala2)
check.session(
"""
@ package foo
@
@ object Foo {
@ def message = "Hello"
@ zz
@ }
error: source-2.scala:5: not found: value zz
zz
^
"""
)
else
check.session(
"""
@ package foo
@
@ object Foo {
@ def message = "Hello"
@ zz
@ }
error: -- [E006] Not Found Error: source-2.scala:5:2 ----------------------------------
5 | zz
^^
Not found: zz
"""
)
}
}
}
19 changes: 19 additions & 0 deletions amm/repl/src/test/scala/ammonite/unit/ParserTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ object ParserTests extends TestSuite {
test - assertComplete("""
val r = (1 until 1000).view.filter(n => n % 3 == 0 || n % 5 == 0).sum
""")
test - assertComplete(
// raw source code ending with 2 empty lines: assumed to be complete
"""package thing
|
|object Thing {
| def message = "Hello"
|}
|
|""".stripMargin
)
}
test("notEndOfCommand") {

Expand All @@ -117,6 +127,15 @@ object ParserTests extends TestSuite {
test - assertIncomplete("""
val r = (1 until 1000).view.filter(n => n % 3 == 0 || n % 5 == 0
""")
test - assertIncomplete(
// raw source code *not* ending with 2 empty lines: assumed not to be complete
"""package thing
|
|object Thing {
| def message = "Hello"
|}
|""".stripMargin
)

}
test("commandIsBroken") {
Expand Down