From 2183ce2ba5078be7f9242c92deff8e944aa5a2e1 Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Wed, 18 Oct 2023 18:06:57 +0200 Subject: [PATCH 1/6] split process of evaluating and building --- .../src/main/scala/effekt/EffektConfig.scala | 6 +- effekt/jvm/src/main/scala/effekt/Runner.scala | 64 ++++++++++++------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 8bc308a57..cfa9fb00f 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -12,8 +12,8 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args) { val compile: ScallopOption[Boolean] = toggle( "compile", - descrYes = "Compile the Effekt program", - descrNo = "Run the effekt program in the interpreter", + descrYes = "Only compile the Effekt program to a backend specific executable", + descrNo = "Compile and run the effekt program in the interpreter", default = Some(false) ) @@ -118,6 +118,6 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args) { validateFilesIsDirectory(includePath) - // force some other configs manually to intialize them when compiling with native-image + // force some other configs manually to initialize them when compiling with native-image server; output; filenames } diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index eeea120c5..c57cd4d7c 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -2,8 +2,11 @@ package effekt import effekt.context.Context import effekt.util.messages.FatalPhaseError -import effekt.util.paths.{ File, file } +import effekt.util.paths.{File, file} import effekt.util.getOrElseAborting +import kiama.util.IO + +import java.lang.reflect.Executable /** * Interface used by [[Driver]] and [[EffektTests]] to run a compiled program. @@ -45,6 +48,11 @@ trait Runner[Executable] { */ def checkSetup(): Either[String, Unit] + /** + * Builds a given executable and returns the resulting path to the executable. + */ + def build(executable: Executable)(using Context): String + /** * Runs the executable (e.g. the main file). */ @@ -53,7 +61,7 @@ trait Runner[Executable] { def canRunExecutable(command: String*): Boolean = try { Process(command).run(ProcessIO(out => (), in => (), err => ())).exitValue() == 0 - } catch { _ => false } + } catch { case _ => false } /** * Helper function to run an executable @@ -93,10 +101,13 @@ object JSRunner extends Runner[String] { if canRunExecutable("node", "--version") then Right(()) else Left("Cannot find nodejs. This is required to use the JavaScript backend.") - def eval(path: String)(using C: Context): Unit = + def build(path: String)(using C: Context): String = val out = C.config.outputPath() val jsFile = (out / path).unixPath - val jsScript = s"require('${jsFile}').main().run()" + s"require('${jsFile}').main().run()" + + def eval(path: String)(using C: Context): Unit = + val jsScript = build(path) exec("node", "--eval", jsScript) } @@ -110,10 +121,12 @@ trait ChezRunner extends Runner[String] { if canRunExecutable("scheme", "--help") then Right(()) else Left("Cannot find scheme. This is required to use the ChezScheme backend.") - def eval(path: String)(using C: Context): Unit = + def build(path: String)(using C: Context): String = val out = C.config.outputPath() - val chezFile = (out / path).unixPath - exec("scheme", "--script", chezFile) + (out / path).unixPath + + def eval(path: String)(using C: Context): Unit = + exec("scheme", "--script", build(path)) } object ChezMonadicRunner extends ChezRunner { @@ -144,13 +157,7 @@ object LLVMRunner extends Runner[String] { optCmd.getOrElseAborting { return Left("Cannot find opt. This is required to use the LLVM backend.") } Right(()) - /** - * Compile the LLVM source file (`<...>.ll`) to an executable - * - * Requires LLVM and GCC to be installed on the machine. - * Assumes [[path]] has the format "SOMEPATH.ll". - */ - def eval(path: String)(using C: Context): Unit = + override def build(path: String)(using C: Context): String = val out = C.config.outputPath() val basePath = (out / path.stripSuffix(".ll")).unixPath val llPath = basePath + ".ll" @@ -168,8 +175,16 @@ object LLVMRunner extends Runner[String] { val gccMainFile = (C.config.libPath / "main.c").unixPath val executableFile = basePath exec(gcc, gccMainFile, "-o", executableFile, objPath) + executableFile - exec(executableFile) + /** + * Compile the LLVM source file (`<...>.ll`) to an executable + * + * Requires LLVM and GCC to be installed on the machine. + * Assumes [[path]] has the format "SOMEPATH.ll". + */ + def eval(path: String)(using C: Context): Unit = + exec(build(path)) } @@ -186,18 +201,21 @@ object MLRunner extends Runner[String] { if canRunExecutable("mlton") then Right(()) else Left("Cannot find mlton. This is required to use the ML backend.") - /** - * Compile the LLVM source file (`<...>.ll`) to an executable - * - * Requires LLVM and GCC to be installed on the machine. - * Assumes [[path]] has the format "SOMEPATH.ll". - */ - def eval(path: String)(using C: Context): Unit = + override def build(path: String)(using C: Context): String = val out = C.config.outputPath() val buildFile = (out / "main.mlb").canonicalPath val executable = (out / "mlton-main").canonicalPath exec("mlton", "-default-type", "int64", // to avoid integer overflows "-output", executable, buildFile) - exec(executable) + executable + + /** + * Compile the MLton source file (`<...>.sml`) to an executable. + * + * Requires the MLton compiler to be installed on the machine. + * Assumes [[path]] has the format "SOMEPATH.sml". + */ + def eval(path: String)(using C: Context): Unit = + exec(build(path)) } From dc507aa74d765a17e5fe58b230e8fd8d3e3ac88d Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Wed, 18 Oct 2023 18:39:48 +0200 Subject: [PATCH 2/6] add build flag --- effekt/jvm/src/main/scala/effekt/Driver.scala | 1 + effekt/jvm/src/main/scala/effekt/EffektConfig.scala | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Driver.scala b/effekt/jvm/src/main/scala/effekt/Driver.scala index beaf8b153..0b85d2bf8 100644 --- a/effekt/jvm/src/main/scala/effekt/Driver.scala +++ b/effekt/jvm/src/main/scala/effekt/Driver.scala @@ -84,6 +84,7 @@ trait Driver extends kiama.util.Compiler[EffektConfig, EffektError] { outer => // we are in one of three exclusive modes: LSPServer, Compile, Run if (config.server()) { compiler.runFrontend(src) } else if (config.interpret()) { compile() foreach runner.eval } + else if (config.build()) { compile() foreach runner.build } else if (config.compile()) { compile() } } } catch { diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index cfa9fb00f..75361cd74 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -12,8 +12,13 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args) { val compile: ScallopOption[Boolean] = toggle( "compile", - descrYes = "Only compile the Effekt program to a backend specific executable", - descrNo = "Compile and run the effekt program in the interpreter", + descrYes = "Compile the Effekt program to the backend specific representation", + default = Some(false) + ) + + val build: ScallopOption[Boolean] = toggle( + "build", + descrYes = "Compile the Effekt program and build a backend specific executable", default = Some(false) ) From b4f6a46a4f4c251a54e58037a6825c737d3848bc Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Thu, 19 Oct 2023 00:47:14 +0200 Subject: [PATCH 3/6] output js script to execute compiled program --- effekt/jvm/src/main/scala/effekt/EffektConfig.scala | 4 ++-- effekt/jvm/src/main/scala/effekt/Runner.scala | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 75361cd74..bb9fe4ca6 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -15,7 +15,7 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args) { descrYes = "Compile the Effekt program to the backend specific representation", default = Some(false) ) - + val build: ScallopOption[Boolean] = toggle( "build", descrYes = "Compile the Effekt program and build a backend specific executable", @@ -119,7 +119,7 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args) { def requiresCompilation(): Boolean = !server() - def interpret(): Boolean = !server() && !compile() + def interpret(): Boolean = !server() && !compile() && !build() validateFilesIsDirectory(includePath) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index c57cd4d7c..bcddeb69a 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -4,9 +4,8 @@ import effekt.context.Context import effekt.util.messages.FatalPhaseError import effekt.util.paths.{File, file} import effekt.util.getOrElseAborting -import kiama.util.IO -import java.lang.reflect.Executable +import kiama.util.IO /** * Interface used by [[Driver]] and [[EffektTests]] to run a compiled program. @@ -103,8 +102,13 @@ object JSRunner extends Runner[String] { def build(path: String)(using C: Context): String = val out = C.config.outputPath() - val jsFile = (out / path).unixPath - s"require('${jsFile}').main().run()" + val jsFilePath = (out / path).unixPath + // create "executable" using shebang besides the .js file + val jsScriptFilePath = (out / path.stripSuffix(s".$extension")).unixPath + val jsScript = s"require('${jsFilePath}').main().run()" + val shebang = "#!/usr/bin/env node" + IO.createFile(jsScriptFilePath, s"$shebang\n$jsScript") + jsScript def eval(path: String)(using C: Context): Unit = val jsScript = build(path) From 203798207c6619763ec00c18fee3ccc9e72f1f79 Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Sat, 21 Oct 2023 13:18:11 +0200 Subject: [PATCH 4/6] create bash script executable for scheme backend --- effekt/jvm/src/main/scala/effekt/Runner.scala | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index bcddeb69a..4b1cf9e50 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -4,7 +4,6 @@ import effekt.context.Context import effekt.util.messages.FatalPhaseError import effekt.util.paths.{File, file} import effekt.util.getOrElseAborting - import kiama.util.IO /** @@ -85,6 +84,25 @@ trait Runner[Executable] { } go(progs0) } + + /** + * Create a executable file with at the given path with the given content. The file will have + * the permissions UNIX permissions 744. + */ + def createExecutableFile(path: String, content: String)(using C: Context): Unit = { + import java.nio.file.{Files, Path} + import java.nio.file.attribute.PosixFilePermission.* + import scala.jdk.CollectionConverters.SetHasAsJava + + val path1 = Path.of(path) + val perms = Set( + OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, + GROUP_READ, + OTHERS_READ + ) + IO.createFile(path, content) + Files.setPosixFilePermissions(path1, SetHasAsJava(perms).asJava) + } } object JSRunner extends Runner[String] { @@ -101,18 +119,17 @@ object JSRunner extends Runner[String] { else Left("Cannot find nodejs. This is required to use the JavaScript backend.") def build(path: String)(using C: Context): String = - val out = C.config.outputPath() - val jsFilePath = (out / path).unixPath + val out = C.config.outputPath().getAbsolutePath.stripSuffix(".") + val jsFilePath = (out / path.stripPrefix(".")).unixPath // create "executable" using shebang besides the .js file - val jsScriptFilePath = (out / path.stripSuffix(s".$extension")).unixPath + val jsScriptFilePath = jsFilePath.stripSuffix(s".$extension") val jsScript = s"require('${jsFilePath}').main().run()" val shebang = "#!/usr/bin/env node" - IO.createFile(jsScriptFilePath, s"$shebang\n$jsScript") - jsScript + createExecutableFile(jsScriptFilePath, s"$shebang\n$jsScript") + jsScriptFilePath def eval(path: String)(using C: Context): Unit = - val jsScript = build(path) - exec("node", "--eval", jsScript) + exec(build(path)) } trait ChezRunner extends Runner[String] { @@ -126,11 +143,15 @@ trait ChezRunner extends Runner[String] { else Left("Cannot find scheme. This is required to use the ChezScheme backend.") def build(path: String)(using C: Context): String = - val out = C.config.outputPath() - (out / path).unixPath + val out = C.config.outputPath().getAbsolutePath + val schemeFilePath = (out / path).unixPath + val bashScriptPath = schemeFilePath.stripSuffix(s".$extension") + val bashScript = s"#!/bin/bash\nscheme --script $schemeFilePath" + createExecutableFile(bashScriptPath, bashScript) + bashScriptPath def eval(path: String)(using C: Context): Unit = - exec("scheme", "--script", build(path)) + exec(build(path)) } object ChezMonadicRunner extends ChezRunner { From c2f406fbc9c258ff8a7da9d8b0e57af306ef9c41 Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Mon, 23 Oct 2023 14:29:01 +0200 Subject: [PATCH 5/6] give default implementation for eval --- effekt/jvm/src/main/scala/effekt/Runner.scala | 76 +++++++------------ 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/effekt/jvm/src/main/scala/effekt/Runner.scala b/effekt/jvm/src/main/scala/effekt/Runner.scala index 4b1cf9e50..1ee7b1457 100644 --- a/effekt/jvm/src/main/scala/effekt/Runner.scala +++ b/effekt/jvm/src/main/scala/effekt/Runner.scala @@ -52,9 +52,10 @@ trait Runner[Executable] { def build(executable: Executable)(using Context): String /** - * Runs the executable (e.g. the main file). + * Runs the executable (e.g. the main file) by calling the build function. */ - def eval(executable: Executable)(using Context): Unit + def eval(executable: Executable)(using Context): Unit = + exec(build(executable)) def canRunExecutable(command: String*): Boolean = try { @@ -84,25 +85,6 @@ trait Runner[Executable] { } go(progs0) } - - /** - * Create a executable file with at the given path with the given content. The file will have - * the permissions UNIX permissions 744. - */ - def createExecutableFile(path: String, content: String)(using C: Context): Unit = { - import java.nio.file.{Files, Path} - import java.nio.file.attribute.PosixFilePermission.* - import scala.jdk.CollectionConverters.SetHasAsJava - - val path1 = Path.of(path) - val perms = Set( - OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, - GROUP_READ, - OTHERS_READ - ) - IO.createFile(path, content) - Files.setPosixFilePermissions(path1, SetHasAsJava(perms).asJava) - } } object JSRunner extends Runner[String] { @@ -118,18 +100,19 @@ object JSRunner extends Runner[String] { 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 = - val out = C.config.outputPath().getAbsolutePath.stripSuffix(".") - val jsFilePath = (out / path.stripPrefix(".")).unixPath + val out = C.config.outputPath().getAbsolutePath + val jsFilePath = (out / path).unixPath // create "executable" using shebang besides the .js file val jsScriptFilePath = jsFilePath.stripSuffix(s".$extension") val jsScript = s"require('${jsFilePath}').main().run()" val shebang = "#!/usr/bin/env node" - createExecutableFile(jsScriptFilePath, s"$shebang\n$jsScript") + IO.createFile(jsScriptFilePath, s"$shebang\n$jsScript", true) jsScriptFilePath - - def eval(path: String)(using C: Context): Unit = - exec(build(path)) } trait ChezRunner extends Runner[String] { @@ -142,16 +125,17 @@ trait ChezRunner extends Runner[String] { if canRunExecutable("scheme", "--help") then Right(()) else Left("Cannot find scheme. This is required to use the ChezScheme backend.") + /** + * Creates an executable bash script besides the given `.ss` file ([[path]]) + * and returns the resulting absolute path. + */ def build(path: String)(using C: Context): String = val out = C.config.outputPath().getAbsolutePath val schemeFilePath = (out / path).unixPath val bashScriptPath = schemeFilePath.stripSuffix(s".$extension") val bashScript = s"#!/bin/bash\nscheme --script $schemeFilePath" - createExecutableFile(bashScriptPath, bashScript) + IO.createFile(bashScriptPath, bashScript, true) bashScriptPath - - def eval(path: String)(using C: Context): Unit = - exec(build(path)) } object ChezMonadicRunner extends ChezRunner { @@ -182,6 +166,12 @@ object LLVMRunner extends Runner[String] { optCmd.getOrElseAborting { return Left("Cannot find opt. This is required to use the LLVM backend.") } Right(()) + /** + * Compile the LLVM source file (`<...>.ll`) to an executable + * + * Requires LLVM and GCC to be installed on the machine. + * Assumes [[path]] has the format "SOMEPATH.ll". + */ override def build(path: String)(using C: Context): String = val out = C.config.outputPath() val basePath = (out / path.stripSuffix(".ll")).unixPath @@ -201,15 +191,6 @@ object LLVMRunner extends Runner[String] { val executableFile = basePath exec(gcc, gccMainFile, "-o", executableFile, objPath) executableFile - - /** - * Compile the LLVM source file (`<...>.ll`) to an executable - * - * Requires LLVM and GCC to be installed on the machine. - * Assumes [[path]] has the format "SOMEPATH.ll". - */ - def eval(path: String)(using C: Context): Unit = - exec(build(path)) } @@ -226,6 +207,12 @@ object MLRunner extends Runner[String] { if canRunExecutable("mlton") then Right(()) else Left("Cannot find mlton. This is required to use the ML backend.") + /** + * Compile the MLton source file (`<...>.sml`) to an executable. + * + * Requires the MLton compiler to be installed on the machine. + * Assumes [[path]] has the format "SOMEPATH.sml". + */ override def build(path: String)(using C: Context): String = val out = C.config.outputPath() val buildFile = (out / "main.mlb").canonicalPath @@ -234,13 +221,4 @@ object MLRunner extends Runner[String] { "-default-type", "int64", // to avoid integer overflows "-output", executable, buildFile) executable - - /** - * Compile the MLton source file (`<...>.sml`) to an executable. - * - * Requires the MLton compiler to be installed on the machine. - * Assumes [[path]] has the format "SOMEPATH.sml". - */ - def eval(path: String)(using C: Context): Unit = - exec(build(path)) } From acda3fae7fe6e8a1f9c11437210fe5a28021494d Mon Sep 17 00:00:00 2001 From: dvdvgt Date: Mon, 23 Oct 2023 14:33:53 +0200 Subject: [PATCH 6/6] update kiama --- kiama | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiama b/kiama index 650f69aa7..89515c8d1 160000 --- a/kiama +++ b/kiama @@ -1 +1 @@ -Subproject commit 650f69aa70be0dc235f7608d3470f7b1660d3ebe +Subproject commit 89515c8d17c2772d1caba6f1ef7a9ff5d1d93022