From 44921b307a27f8b62b8f58f984f9f118b771189e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 7 May 2024 16:55:38 +0200 Subject: [PATCH 1/9] Add feature flags jsNode and jsWeb for website/node --- effekt/js/src/main/scala/effekt/Backend.scala | 2 +- effekt/jvm/src/main/scala/effekt/Backend.scala | 2 +- .../src/main/scala/effekt/generator/js/JavaScript.scala | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/effekt/js/src/main/scala/effekt/Backend.scala b/effekt/js/src/main/scala/effekt/Backend.scala index d30509d47..901293462 100644 --- a/effekt/js/src/main/scala/effekt/Backend.scala +++ b/effekt/js/src/main/scala/effekt/Backend.scala @@ -1,6 +1,6 @@ package effekt class Backend { - val compiler = generator.js.JavaScript() + val compiler = generator.js.JavaScript(List("jsWeb")) val runner = () } diff --git a/effekt/jvm/src/main/scala/effekt/Backend.scala b/effekt/jvm/src/main/scala/effekt/Backend.scala index 4bf4b0d3d..b94f82a65 100644 --- a/effekt/jvm/src/main/scala/effekt/Backend.scala +++ b/effekt/jvm/src/main/scala/effekt/Backend.scala @@ -27,7 +27,7 @@ case class Backend[E](name: String, compiler: Compiler[E], runner: Runner[E]) object Backend { def backend(name: String): Backend[_] = name match { - case "js" => Backend("js", js.JavaScript(), JSRunner) + case "js" => Backend("js", js.JavaScript(List("jsNode")), JSRunner) case "chez-monadic" => Backend("chez-monadic", chez.ChezSchemeMonadic(), ChezMonadicRunner) case "chez-callcc" => Backend("chez-callcc", chez.ChezSchemeCallCC(), ChezCallCCRunner) case "chez-lift" => Backend("chez-lift", chez.ChezSchemeLift(), ChezLiftRunner) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index 086fb64e4..4604255d9 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -9,13 +9,13 @@ import kiama.output.PrettyPrinterTypes.Document import kiama.util.Source -class JavaScript extends Compiler[String] { +class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[String] { // Implementation of the Compiler Interface: // ----------------------------------------- def extension = ".js" - override def supportedFeatureFlags: List[String] = TransformerMonadicWhole.jsFeatureFlags + override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerMonadicWhole.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { case Stage.Core => Core(source).map { res => core.PrettyPrinter.format(res.core) } From 322aea4cdd1891a7db314717d8483cfe5a1603ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 28 May 2024 10:02:40 +0200 Subject: [PATCH 2/9] Start splitting js into js and js-web --- effekt/js/src/main/scala/effekt/Backend.scala | 2 +- .../jvm/src/main/scala/effekt/Backend.scala | 3 ++- effekt/jvm/src/main/scala/effekt/Runner.scala | 22 ++++++++++++++++++- .../effekt/generator/js/JavaScript.scala | 2 ++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/effekt/js/src/main/scala/effekt/Backend.scala b/effekt/js/src/main/scala/effekt/Backend.scala index 901293462..aee886ce5 100644 --- a/effekt/js/src/main/scala/effekt/Backend.scala +++ b/effekt/js/src/main/scala/effekt/Backend.scala @@ -1,6 +1,6 @@ package effekt class Backend { - val compiler = generator.js.JavaScript(List("jsWeb")) + val compiler = generator.js.JavaScriptWeb() val runner = () } diff --git a/effekt/jvm/src/main/scala/effekt/Backend.scala b/effekt/jvm/src/main/scala/effekt/Backend.scala index b94f82a65..f30e78408 100644 --- a/effekt/jvm/src/main/scala/effekt/Backend.scala +++ b/effekt/jvm/src/main/scala/effekt/Backend.scala @@ -27,7 +27,8 @@ case class Backend[E](name: String, compiler: Compiler[E], runner: Runner[E]) object Backend { def backend(name: String): Backend[_] = name match { - case "js" => Backend("js", js.JavaScript(List("jsNode")), JSRunner) + case "js" => Backend("js", js.JavaScriptNode(), JSNodeRunner) + case "js-web" => Backend("js-web", js.JavaScriptWeb(), JSWebRunner) case "chez-monadic" => Backend("chez-monadic", chez.ChezSchemeMonadic(), ChezMonadicRunner) case "chez-callcc" => Backend("chez-callcc", chez.ChezSchemeCallCC(), ChezCallCCRunner) case "chez-lift" => Backend("chez-lift", chez.ChezSchemeLift(), ChezLiftRunner) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 4202fe790..a42e0cdc2 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -119,7 +119,7 @@ trait Runner[Executable] { } } -object JSRunner extends Runner[String] { +object JSNodeRunner extends Runner[String] { import scala.sys.process.Process val extension = "js" @@ -157,6 +157,26 @@ object JSRunner extends Runner[String] { createScript(exePath, "node", jsMainFilePath) } } +object JSWebRunner extends Runner[String] { + import scala.sys.process.Process + + val extension = "js" + + def standardLibraryPath(root: File): File = root / "libraries" / "js" + + override def prelude: List[String] = List("effekt", "immutable/option", "immutable/list") + + def checkSetup(): Either[String, Unit] = + if canRunExecutable("node", "--version") then Right(()) + else Left("Cannot find nodejs. This is required to use the JavaScript backend.") + + /** + * Creates an executable `.js` file besides the given `.js` file ([[path]]) + * and then returns the absolute path of the created executable. + */ + def build(path: String)(using C: Context): String = + ??? // TODO +} trait ChezRunner extends Runner[String] { val extension = "ss" diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index 4604255d9..864fc3964 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -62,3 +62,5 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St private def pretty(stmts: List[js.Stmt]): Document = js.PrettyPrinter.format(stmts) } +class JavaScriptWeb extends JavaScript(List("jsWeb")) {} +class JavaScriptNode extends JavaScript(List("jsNode")) {} From d4e7ddebe19764755e726340beac1f19c9a9389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 28 May 2024 10:35:20 +0200 Subject: [PATCH 3/9] Implement hotfix from #405 --- effekt/shared/src/main/scala/effekt/generator/js/Tree.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/Tree.scala b/effekt/shared/src/main/scala/effekt/generator/js/Tree.scala index ce1c49e00..13264f105 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/Tree.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/Tree.scala @@ -43,7 +43,7 @@ case class Module(name: JSName, imports: List[Import], exports: List[Export], st js.Destruct(names, js.Call(Variable(JSName("require")), List(JsString(s"./${ file }")))) } - val exportStatement = js.Assign(RawExpr("module.exports"), + val exportStatement = js.Assign(RawExpr(s"(typeof module != \"undefined\" && module !== null ? module : {}).exports = ${name.name}"), js.Object(exports.map { e => e.name -> e.expr }) ) From 5c9a3a3b55ddd45e9f5f2bf93a7b088632398f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 28 May 2024 11:53:55 +0200 Subject: [PATCH 4/9] js-web: For now, generate simple html file for testing --- .../src/main/scala/effekt/EffektConfig.scala | 2 +- effekt/jvm/src/main/scala/effekt/Runner.scala | 25 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 7dd3bceb3..f6dfd9be9 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -50,7 +50,7 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- ) val backend: ScallopOption[Backend[_]] = choice( - choices = List("js", "chez-callcc", "chez-monadic", "chez-lift", "llvm", "ml"), + choices = List("js", "js-web", "chez-callcc", "chez-monadic", "chez-lift", "llvm", "ml"), name = "backend", descr = "The backend that should be used", default = Some("js"), diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index a42e0cdc2..182854883 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -167,15 +167,32 @@ object JSWebRunner extends Runner[String] { override def prelude: List[String] = List("effekt", "immutable/option", "immutable/list") def checkSetup(): Either[String, Unit] = - if canRunExecutable("node", "--version") then Right(()) - else Left("Cannot find nodejs. This is required to use the JavaScript backend.") + Left("Running js-web code directly is not supported (yet). Use `--compile` to generate a js file / `--build` to generate a html file.") // TODO /** - * Creates an executable `.js` file besides the given `.js` file ([[path]]) + * Creates an openable `.html` file besides the given `.js` file ([[path]]) * and then returns the absolute path of the created executable. */ def build(path: String)(using C: Context): String = - ??? // TODO + import java.nio.file.Path + val out = C.config.outputPath().getAbsolutePath + val jsFilePath = (out / path).unixPath + val jsFileName = jsFilePath.split("/").last + val htmlFilePath = jsFilePath.stripSuffix(s".$extension") + ".html" + val mainName = "$" + jsFileName.stripSuffix(".js") + ".main" + val htmlContent = + s""" + | + | + | + | + | + | + |""".stripMargin + IO.createFile(htmlFilePath, htmlContent, false) + htmlFilePath } trait ChezRunner extends Runner[String] { From dea2f8e648cd8b8146abf3fc7f995f7b870381a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 28 May 2024 12:43:12 +0200 Subject: [PATCH 5/9] js-web: Also open web page in browser (linux/mac only) --- effekt/jvm/src/main/scala/effekt/Runner.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 182854883..aef56e4da 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -171,7 +171,7 @@ object JSWebRunner extends Runner[String] { /** * Creates an openable `.html` file besides the given `.js` file ([[path]]) - * and then returns the absolute path of the created executable. + * and then returns the absolute path of a shell script opening it. */ def build(path: String)(using C: Context): String = import java.nio.file.Path @@ -179,6 +179,7 @@ object JSWebRunner extends Runner[String] { val jsFilePath = (out / path).unixPath val jsFileName = jsFilePath.split("/").last val htmlFilePath = jsFilePath.stripSuffix(s".$extension") + ".html" + val shFilePath = jsFilePath.stripSuffix(s".$extension") val mainName = "$" + jsFileName.stripSuffix(".js") + ".main" val htmlContent = s""" @@ -192,7 +193,10 @@ object JSWebRunner extends Runner[String] { | |""".stripMargin IO.createFile(htmlFilePath, htmlContent, false) - htmlFilePath + IO.createFile(shFilePath, + s"#!/bin/sh\nxdg-open ${htmlFilePath} || open ${htmlFilePath} || echo \"Cannot open browser\"", + true) + shFilePath } trait ChezRunner extends Runner[String] { From b316efb65a7c2b83562b67ce655f885435e1f843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 25 Jun 2024 16:37:36 +0200 Subject: [PATCH 6/9] Fix for current version --- effekt/jvm/src/main/scala/effekt/Runner.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index aef56e4da..06bf262d9 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -162,9 +162,9 @@ object JSWebRunner extends Runner[String] { val extension = "js" - def standardLibraryPath(root: File): File = root / "libraries" / "js" + def standardLibraryPath(root: File): File = root / "libraries" / "common" - override def prelude: List[String] = List("effekt", "immutable/option", "immutable/list") + override def prelude: List[String] = List("effekt", "option", "list", "result", "exception", "array", "string", "ref") def checkSetup(): Either[String, Unit] = Left("Running js-web code directly is not supported (yet). Use `--compile` to generate a js file / `--build` to generate a html file.") // TODO From af71adcdb4983c8b17c33de1663906faee54042f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Tue, 25 Jun 2024 16:38:23 +0200 Subject: [PATCH 7/9] jsWeb: do not generate a script to open browser, but just ask the user to --- effekt/jvm/src/main/scala/effekt/Runner.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 06bf262d9..6c6c072ac 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -179,7 +179,6 @@ object JSWebRunner extends Runner[String] { val jsFilePath = (out / path).unixPath val jsFileName = jsFilePath.split("/").last val htmlFilePath = jsFilePath.stripSuffix(s".$extension") + ".html" - val shFilePath = jsFilePath.stripSuffix(s".$extension") val mainName = "$" + jsFileName.stripSuffix(".js") + ".main" val htmlContent = s""" @@ -193,10 +192,7 @@ object JSWebRunner extends Runner[String] { | |""".stripMargin IO.createFile(htmlFilePath, htmlContent, false) - IO.createFile(shFilePath, - s"#!/bin/sh\nxdg-open ${htmlFilePath} || open ${htmlFilePath} || echo \"Cannot open browser\"", - true) - shFilePath + C.abort(s"Open file://${htmlFilePath} in your browser or include ${jsFilePath}.") } trait ChezRunner extends Runner[String] { From 89ba30860bb9c846a4e279250fff6ad992d51948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 28 Jun 2024 09:30:31 +0200 Subject: [PATCH 8/9] js-web: Error message + TODO Do not suggest that we will add support to run directly. I don't think we ever want that. --- effekt/jvm/src/main/scala/effekt/Runner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 6c6c072ac..fd257251d 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -167,7 +167,7 @@ object JSWebRunner extends Runner[String] { override def prelude: List[String] = List("effekt", "option", "list", "result", "exception", "array", "string", "ref") def checkSetup(): Either[String, Unit] = - Left("Running js-web code directly is not supported (yet). Use `--compile` to generate a js file / `--build` to generate a html file.") // TODO + Left("Running js-web code directly is not supported. Use `--compile` to generate a js file / `--build` to generate a html file.") /** * Creates an openable `.html` file besides the given `.js` file ([[path]]) From 1b240fffe0dc8adbe0816a80314fdb11a53e49bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcial=20Gai=C3=9Fert?= Date: Fri, 28 Jun 2024 09:31:29 +0200 Subject: [PATCH 9/9] js-web: Update doc comment --- effekt/jvm/src/main/scala/effekt/Runner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index fd257251d..20050f484 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -171,7 +171,7 @@ object JSWebRunner extends Runner[String] { /** * Creates an openable `.html` file besides the given `.js` file ([[path]]) - * and then returns the absolute path of a shell script opening it. + * and then errors out, printing it's path. */ def build(path: String)(using C: Context): String = import java.nio.file.Path