From 3e909aa749bbfded100f7797de205837e29ce950 Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Thu, 21 Nov 2024 17:11:31 +0100 Subject: [PATCH 1/5] WIP --- .../scripting/NonForkingScriptRunner.scala | 4 +-- .../replpp/scripting/ScriptingDriver.scala | 9 ++++-- .../replpp/scripting/WrapForMainArgs.scala | 29 ++++++++++++------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala index 1d4e64e..b986cab 100644 --- a/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala +++ b/core/src/main/scala/replpp/scripting/NonForkingScriptRunner.scala @@ -36,13 +36,11 @@ object NonForkingScriptRunner { commandArgs ++ parameterArgs } - if (config.runBefore.nonEmpty) - println(s"[WARNING] ScriptingDriver does not support `runBefore` code, the given ${config.runBefore.size} statements will be ignored") - val verboseEnabled = replpp.verboseEnabled(config) new ScriptingDriver( compilerArgs = replpp.compilerArgs(config) :+ "-nowarn", predefFiles = allPredefFiles(config), + runBeforeSourceLines = config.runBefore, scriptFile = scriptFile, scriptArgs = scriptArgs.toArray, verbose = verboseEnabled diff --git a/core/src/main/scala/replpp/scripting/ScriptingDriver.scala b/core/src/main/scala/replpp/scripting/ScriptingDriver.scala index 299fdd2..d3cb6b3 100644 --- a/core/src/main/scala/replpp/scripting/ScriptingDriver.scala +++ b/core/src/main/scala/replpp/scripting/ScriptingDriver.scala @@ -20,8 +20,13 @@ import scala.util.{Failure, Try} * Main difference: we don't (need to) recursively look for main method entry points in the entire classpath, * because we have a fixed class and method name that ScriptRunner uses when it embeds the script and predef code. * */ -class ScriptingDriver(compilerArgs: Array[String], predefFiles: Seq[Path], scriptFile: Path, scriptArgs: Array[String], verbose: Boolean) { - private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile)) +class ScriptingDriver(compilerArgs: Array[String], + predefFiles: Seq[Path], + runBeforeSourceLines: Seq[String], + scriptFile: Path, + scriptArgs: Array[String], + verbose: Boolean) { + private val wrappingResult = WrapForMainArgs(Files.readString(scriptFile), runBeforeSourceLines) private val wrappedScript = Files.createTempFile("wrapped-script", ".sc") private val tempFiles = Seq.newBuilder[Path] private var executed = false diff --git a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala index b9c77f3..285319a 100644 --- a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala +++ b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala @@ -7,18 +7,32 @@ object WrapForMainArgs { /** linesBeforeWrappedCode: allows us to adjust line numbers in error reporting... */ case class WrappingResult(fullScript: String, linesBeforeWrappedCode: Int) - def apply(scriptCode: String): WrappingResult = { - var linesBeforeWrappedCode = 0 + def apply(scriptCode: String, runBeforeAsSourceLines: Seq[String]): WrappingResult = { + var linesBeforeWrappedCode = runBeforeAsSourceLines.size // to adjust line number reporting + val runBeforeCode = runBeforeAsSourceLines.mkString("\n") val mainImpl = if (scriptCode.contains("@main")) { - scriptCode + // + // TODO integrate `runBeforeCode` into scriptCode` - understand how mainargs does it.. +// ??? +// scriptCode + s"""$runBeforeCode + |$scriptCode""".stripMargin } else { - linesBeforeWrappedCode += 1 + linesBeforeWrappedCode += 1 // because we added the following line _before_ the wrapped script code s"""@main def _execMain(): Unit = { + |$runBeforeCode |$scriptCode |}""".stripMargin } + val codeBefore = + s"""import replpp.shaded.mainargs + |import mainargs.main // intentionally shadow any potentially given @main + | + |// ScriptingDriver expects an object with a predefined name and a main entrypoint method + |object ${ScriptingDriver.MainClassName} {""".stripMargin + linesBeforeWrappedCode += codeBefore.lines().count().toInt val fullScript = s"""$codeBefore @@ -33,11 +47,4 @@ object WrapForMainArgs { WrappingResult(fullScript, linesBeforeWrappedCode) } - private val codeBefore = - s"""import replpp.shaded.mainargs - |import mainargs.main // intentionally shadow any potentially given @main - | - |// ScriptingDriver expects an object with a predefined name and a main entrypoint method - |object ${ScriptingDriver.MainClassName} {""".stripMargin - } From 1b29e31a2ddaceda860c5dfa6953d6c6a1485e4f Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Mon, 25 Nov 2024 13:04:33 +0100 Subject: [PATCH 2/5] fixup implementation, add tests --- .../replpp/scripting/WrapForMainArgs.scala | 35 ++++++------ .../replpp/scripting/ScriptRunnerTests.scala | 55 ++++++++++++++++++- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala index 285319a..3c8f749 100644 --- a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala +++ b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala @@ -8,34 +8,31 @@ object WrapForMainArgs { case class WrappingResult(fullScript: String, linesBeforeWrappedCode: Int) def apply(scriptCode: String, runBeforeAsSourceLines: Seq[String]): WrappingResult = { - var linesBeforeWrappedCode = runBeforeAsSourceLines.size // to adjust line number reporting + var linesBeforeWrappedCode = 0 // to adjust line number reporting + val runBeforeCode = runBeforeAsSourceLines.mkString("\n") + val wrapperCodeStart = + s"""import replpp.shaded.mainargs + |import mainargs.main // intentionally shadow any potentially given @main + | + |// ScriptingDriver expects an object with a predefined name and a main entrypoint method + |object ${ScriptingDriver.MainClassName} { + |$runBeforeCode + |""".stripMargin + val mainImpl = - if (scriptCode.contains("@main")) { - // - // TODO integrate `runBeforeCode` into scriptCode` - understand how mainargs does it.. -// ??? -// scriptCode - s"""$runBeforeCode - |$scriptCode""".stripMargin - } else { + if (scriptCode.contains("@main")) + scriptCode + else { linesBeforeWrappedCode += 1 // because we added the following line _before_ the wrapped script code s"""@main def _execMain(): Unit = { - |$runBeforeCode |$scriptCode |}""".stripMargin } - val codeBefore = - s"""import replpp.shaded.mainargs - |import mainargs.main // intentionally shadow any potentially given @main - | - |// ScriptingDriver expects an object with a predefined name and a main entrypoint method - |object ${ScriptingDriver.MainClassName} {""".stripMargin - - linesBeforeWrappedCode += codeBefore.lines().count().toInt + linesBeforeWrappedCode += wrapperCodeStart.lines().count().toInt val fullScript = - s"""$codeBefore + s"""$wrapperCodeStart |$mainImpl | | def ${ScriptingDriver.MainMethodName}(args: Array[String]): Unit = { diff --git a/core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala b/core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala index 0e56d7d..8bb7dd7 100644 --- a/core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala +++ b/core/src/test/scala/replpp/scripting/ScriptRunnerTests.scala @@ -66,6 +66,34 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers { }.get shouldBe "iwashere-predefFile" } + "runBeforeCode" in { + execTest { testOutputPath => + TestSetup( + s"""import java.nio.file.* + |val string = MaxValue + ";" + fromRunBeforeCode + |Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin, + adaptConfig = _.copy(runBefore = List( + "import Byte.MaxValue", + "val fromRunBeforeCode = \"iwashere-runBeforeCode\"" + )) + ) + }.get shouldBe "127;iwashere-runBeforeCode" + } + + "predefFiles and runBeforeCode" in { + execTest { testOutputPath => + val predefFile = os.temp("""val bar = "iwashere-predefFile"""").toNIO + TestSetup( + s"""import java.nio.file.* + |val string = MinValue + ";" + bar + |Files.writeString(Path.of("$testOutputPath"), string)""".stripMargin, + adaptConfig = _.copy( + predefFiles = List(predefFile), + runBefore = List("import Byte.MinValue")) + ) + }.get shouldBe "-128;iwashere-predefFile" + } + "additional dependencies" in { execTest { testOutputPath => TestSetup( @@ -204,7 +232,7 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers { // TODO: this isn't the case yet: note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 1 } - "error is in imported file" in { + "error in imported file" in { ensureErrors { () => val additionalScript = os.temp() os.write.over(additionalScript, @@ -220,12 +248,12 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers { // note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2 } - "error is in predef file" in { + "error in predef file" in { ensureErrors { () => val predefFile = os.temp() os.write.over(predefFile, s"""val foo = 42 - |val thisWillNotCompile: Int = "because we need an Int" + |val thisWillNotCompile: Int = "because this a String and not an Int" |""".stripMargin) TestSetup( "val bar = 34".stripMargin, @@ -235,6 +263,27 @@ class ScriptRunnerTests extends AnyWordSpec with Matchers { // note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2 } + "error in runBeforeCode" in { + ensureErrors { () => + TestSetup( + "val bar = 34".stripMargin, + adaptConfig = _.copy(runBefore = Seq("val thisWillNotCompile: Int = \"because this a String and not an Int\"")) + ) + } + } + + "error in script with given runBeforeCode" in { + ensureErrors { () => + TestSetup( + s"""val foo = 42 + |val thisWillNotCompile: Int = "because this a String and not an Int" + |""".stripMargin, + adaptConfig = _.copy(runBefore = Seq("val bar = 34")) + ) + // note: if we intercepted the stdout/stderr, we could/should observe that the error is reported in line 2 + } + } + } } From 3098677158931bc4333955d815c25a54cdc25fa5 Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Mon, 25 Nov 2024 14:17:16 +0100 Subject: [PATCH 3/5] fixup line numbers --- .../scala/replpp/scripting/WrapForMainArgs.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala index 3c8f749..dcd0c52 100644 --- a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala +++ b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala @@ -8,18 +8,21 @@ object WrapForMainArgs { case class WrappingResult(fullScript: String, linesBeforeWrappedCode: Int) def apply(scriptCode: String, runBeforeAsSourceLines: Seq[String]): WrappingResult = { - var linesBeforeWrappedCode = 0 // to adjust line number reporting - val runBeforeCode = runBeforeAsSourceLines.mkString("\n") + val wrapperCodeStart = s"""import replpp.shaded.mainargs |import mainargs.main // intentionally shadow any potentially given @main | |// ScriptingDriver expects an object with a predefined name and a main entrypoint method |object ${ScriptingDriver.MainClassName} { + |// runBeforeCode START |$runBeforeCode + |// runBeforeCode END |""".stripMargin + var linesBeforeWrappedCode = 0 // to adjust line number reporting + val mainImpl = if (scriptCode.contains("@main")) scriptCode @@ -31,6 +34,7 @@ object WrapForMainArgs { } linesBeforeWrappedCode += wrapperCodeStart.lines().count().toInt + linesBeforeWrappedCode += 1 // for the line break after $wrapperCodeStart val fullScript = s"""$wrapperCodeStart |$mainImpl @@ -41,6 +45,11 @@ object WrapForMainArgs { |} |""".stripMargin + println(s"XXX0 wrapperCodeStart lines: " + wrapperCodeStart.lines().count().toInt) + println(s"XXX1 linesBeforeWrappedCode=$linesBeforeWrappedCode") + println(s"XXX2 fullScript start:") + println(fullScript) + println(s"XXX2 fullScript end") WrappingResult(fullScript, linesBeforeWrappedCode) } From dbb584fe093cd76cd0493b2249f702fa10ac388d Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Mon, 25 Nov 2024 14:37:47 +0100 Subject: [PATCH 4/5] drop debug println --- core/src/main/scala/replpp/scripting/WrapForMainArgs.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala index dcd0c52..957bf43 100644 --- a/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala +++ b/core/src/main/scala/replpp/scripting/WrapForMainArgs.scala @@ -45,11 +45,6 @@ object WrapForMainArgs { |} |""".stripMargin - println(s"XXX0 wrapperCodeStart lines: " + wrapperCodeStart.lines().count().toInt) - println(s"XXX1 linesBeforeWrappedCode=$linesBeforeWrappedCode") - println(s"XXX2 fullScript start:") - println(fullScript) - println(s"XXX2 fullScript end") WrappingResult(fullScript, linesBeforeWrappedCode) } From bdd8a131210dfbafbd6981baaf1efb8342f701ae Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Mon, 25 Nov 2024 14:54:07 +0100 Subject: [PATCH 5/5] readme update --- README.md | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 920e638..01de38e 100644 --- a/README.md +++ b/README.md @@ -20,28 +20,31 @@ srp Prerequisite: jdk11+ ## TOC + - [Benefits over / comparison with](#benefits-over--comparison-with) * [Regular Scala REPL](#regular-scala-repl) * [Ammonite](#ammonite) * [scala-cli](#scala-cli) - [Prerequisite for all of the below: run `sbt stage` or download the latest release](#prerequisite-for-all-of-the-below-run-sbt-stage-or-download-the-latest-release) -- [REPL](#repl) +- [Usage](#usage) * [run with defaults](#run-with-defaults) * [execute code at the start with `--runBefore`](#execute-code-at-the-start-with---runbefore) - * [`--predef`: code that is compiled but not executed](#--predef-code-that-is-compiled-but-not-executed) + * [`--predef`: add source files to the classpath](#--predef-add-source-files-to-the-classpath) * [Operators: Redirect to file, pipe to external command](#operators-redirect-to-file-pipe-to-external-command) * [Add dependencies with maven coordinates](#add-dependencies-with-maven-coordinates) * [Importing additional script files interactively](#importing-additional-script-files-interactively) * [Adding classpath entries](#adding-classpath-entries) +- [REPL](#repl) * [Rendering of output](#rendering-of-output) * [Exiting the REPL](#exiting-the-repl) * [customize prompt, greeting and exit code](#customize-prompt-greeting-and-exit-code) * [Looking up the current terminal width](#looking-up-the-current-terminal-width) - [Scripting](#scripting) * [Simple "Hello world" script](#simple-hello-world-script) - * [Predef file(s) used in script](#predef-files-used-in-script) - * [Importing files / scripts](#importing-files--scripts) - * [Dependencies](#dependencies) + * [Importing other files / scripts with `using file` directive](#importing-other-files--scripts-with-using-file-directive) + * [Dependencies with `using dep` directive](#dependencies-with-using-dep-directive) * [@main entrypoints](#main-entrypoints) * [multiple @main entrypoints](#multiple-main-entrypoints) * [named parameters](#named-parameters) @@ -64,6 +67,7 @@ Prerequisite: jdk11+ - [Fineprint](#fineprint) + ## Benefits over / comparison with ### Regular Scala REPL @@ -93,7 +97,8 @@ scala-cli wraps and invokes the regular Scala REPL (by default; or optionally Am ## Prerequisite for all of the below: run `sbt stage` or download the latest release -## REPL +## Usage +The below features are all demonstrated using the REPL but also work when running scripts. ### run with defaults ```bash @@ -265,6 +270,8 @@ println(new Foo().foo)' > myScript.sc ./srp --script myScript.sc ``` +## REPL + ### Rendering of output Unlike the stock Scala REPL, srp does _not_ truncate the output by default. You can optionally specify the maxHeight parameter though: @@ -327,18 +334,7 @@ echo 'println("Hello!")' > test-simple.sc cat out.txt # prints 'i was here' ``` -### Predef file(s) used in script -```bash -echo 'val foo = "Hello, predef file"' > test-predef-file.sc -echo 'println(foo)' > test-predef.sc -``` - -```bash -./srp --script test-predef.sc --predef test-predef-file.sc -``` -To import multiple scripts, you can specify this parameter multiple times. - -### Importing files / scripts +### Importing other files / scripts with `using file` directive ```bash echo 'val foo = 42' > foo.sc @@ -348,7 +344,7 @@ println(foo)' > test.sc ./srp --script test.sc ``` -### Dependencies +### Dependencies with `using dep` directive Dependencies can be added via `//> using dep` syntax (like in scala-cli). ```bash