diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java index dc60e61fa..00582ff58 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompilationProcess.java @@ -12,13 +12,16 @@ import de.peeeq.wurstscript.intermediatelang.interpreter.ILStackFrame; import de.peeeq.wurstscript.jassAst.JassProg; import de.peeeq.wurstscript.jassprinter.JassPrinter; +import de.peeeq.wurstscript.luaAst.LuaCompilationUnit; import de.peeeq.wurstscript.translation.imtranslation.ImTranslator; +import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator; import de.peeeq.wurstscript.utils.Utils; import org.eclipse.jdt.annotation.Nullable; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.util.Locale; import java.util.Optional; import java.util.function.Supplier; @@ -82,35 +85,53 @@ public CompilationProcess(WurstGui gui, RunArgs runArgs) { if (runArgs.isRunTests()) { timeTaker.measure("Run tests", - () -> runTests(compiler.getImTranslator(), compiler, runArgs.getTestTimeout(), runArgs.getTestFilter())); + () -> runTests(gui, compiler, runArgs)); } timeTaker.measure("Run compiletime functions", () ->compiler.runCompiletime(WurstProjectConfigData.empty(), isProd, false)); - JassProg jassProg = timeTaker.measure("Transform program to Jass", - compiler::transformProgToJass); + CharSequence mapScript; + File outputMapscript; + if (runArgs.isLua()) { + LuaCompilationUnit luaCode = timeTaker.measure("Transform program to Lua", + compiler::transformProgToLua); + if (luaCode == null || gui.getErrorCount() > 0) { + return null; + } - if (jassProg == null || gui.getErrorCount() > 0) { - return null; - } + gui.sendProgress("Printing Lua"); + StringBuilder luaOutput = new StringBuilder(); + timeTaker.measure("Print Lua", () -> luaCode.print(luaOutput, 0)); + mapScript = luaOutput; + LuaTranslator.assertNoLeakedHashtableNativeCalls(mapScript.toString()); + LuaTranslator.assertNoLeakedGetHandleIdCalls(mapScript.toString()); + CharSequence compiledLua = mapScript; + outputMapscript = timeTaker.measure("Write Lua", + () -> writeMapscript(compiledLua)); + } else { + JassProg jassProg = timeTaker.measure("Transform program to Jass", + compiler::transformProgToJass); - boolean withSpace; - withSpace = !runArgs.isOptimize(); + if (jassProg == null || gui.getErrorCount() > 0) { + return null; + } - gui.sendProgress("Printing Jass"); + boolean withSpace = !runArgs.isOptimize(); + gui.sendProgress("Printing Jass"); - JassPrinter printer = new JassPrinter(withSpace, jassProg); - CharSequence mapScript = timeTaker.measure("Print Jass", - (Supplier) printer::printProg); + JassPrinter printer = new JassPrinter(withSpace, jassProg); + mapScript = timeTaker.measure("Print Jass", + (Supplier) printer::printProg); - // output to file - File outputMapscript = timeTaker.measure("Print Jass", - () -> writeMapscript(mapScript)); + CharSequence compiledJass = mapScript; + outputMapscript = timeTaker.measure("Write Jass", + () -> writeMapscript(compiledJass)); - if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { - boolean pjassError = timeTaker.measure("Run PJass", + if (!runArgs.isDisablePjass() && !runArgs.isLegacyJassTypeChecks()) { + boolean pjassError = timeTaker.measure("Run PJass", () -> runPjass(outputMapscript)); - if (pjassError) return null; + if (pjassError) return null; + } } timeTaker.printReport(); return mapScript; @@ -139,13 +160,22 @@ private boolean runPjass(File outputMapscript) { private File writeMapscript(CharSequence mapScript) { gui.sendProgress("Writing output file"); File outputMapscript; + File staleJassOutput = null; if (runArgs.getOutFile() != null) { - outputMapscript = new File(runArgs.getOutFile()); + String outputPath = runArgs.getOutFile(); + if (runArgs.isLua() && outputPath.toLowerCase(Locale.ROOT).endsWith(".j")) { + staleJassOutput = new File(outputPath); + outputPath = outputPath.substring(0, outputPath.length() - 2) + ".lua"; + } + outputMapscript = new File(outputPath); } else { - outputMapscript = new File("./temp/output.j"); + outputMapscript = new File("./temp/output." + (runArgs.isLua() ? "lua" : "j")); } outputMapscript.getParentFile().mkdirs(); try { + if (staleJassOutput != null) { + java.nio.file.Files.deleteIfExists(staleJassOutput.toPath()); + } FileUtils.write(mapScript, outputMapscript); return outputMapscript; } catch (IOException e) { @@ -153,14 +183,15 @@ private File writeMapscript(CharSequence mapScript) { } } - private void runTests(ImTranslator translator, WurstCompilerJassImpl compiler, int testTimeout, Optional testFilter) { + public static void runTests(WurstGui gui, WurstCompilerJassImpl compiler, RunArgs runArgs) { + ImTranslator translator = compiler.getImTranslator(); PrintStream out = System.out; // tests gui.sendProgress("Running tests"); if (!runArgs.isCompactOutput()) { System.out.println("Running tests"); } - RunTests runTests = new RunTests(Optional.empty(), 0, 0, Optional.empty(), testTimeout, testFilter, runArgs.isCompactOutput()) { + RunTests runTests = new RunTests(Optional.empty(), 0, 0, Optional.empty(), runArgs.getTestTimeout(), runArgs.getTestFilter(), runArgs.isCompactOutput()) { @Override protected void print(String message) { out.print(message); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java index 74181b452..57752900d 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java @@ -1,7 +1,5 @@ package de.peeeq.wurstio; -import com.google.common.base.Charsets; -import com.google.common.io.Files; import org.wurstscript.projectconfig.WurstProjectConfigData; import org.wurstscript.projectconfig.WurstProjectConfigReader; import de.peeeq.wurstio.compilationserver.WurstServer; @@ -11,8 +9,6 @@ import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstio.languageserver.requests.CliBuildMap; import de.peeeq.wurstio.map.importer.ImportFile; -import de.peeeq.wurstio.mpq.MpqEditor; -import de.peeeq.wurstio.mpq.MpqEditorFactory; import de.peeeq.wurstio.objectreader.ObjectExportService; import de.peeeq.wurstscript.CompileTimeInfo; import de.peeeq.wurstscript.ErrorReporting; @@ -147,11 +143,19 @@ public static void main(String[] args) { compileArgs = new RunArgs(mergedArgs); } - if (runArgs.isBuild() && runArgs.getInputmap() != null && workspaceroot != null) { + if (workspaceroot != null) { Path root = Paths.get(workspaceroot); - Path inputMap = root.resolve(runArgs.getInputmap()); + Path inputMap = runArgs.isBuild() && runArgs.getInputmap() != null + ? root.resolve(runArgs.getInputmap()) + : runArgs.getMapFile() == null ? null : Paths.get(runArgs.getMapFile()); WurstProjectConfigData projectConfig = WurstProjectConfigReader.load(root.resolve(FILE_NAME)); - if (java.nio.file.Files.exists(inputMap) && projectConfig != null) { + if (inputMap != null) { + if (!java.nio.file.Files.exists(inputMap)) { + throw new RuntimeException("Input map does not exist: " + inputMap); + } + if (projectConfig == null) { + throw new RuntimeException(FILE_NAME + " file doesn't exist or is invalid."); + } CliBuildMap cliBuildMap = new CliBuildMap( WFile.create(root.toFile()), Optional.of(inputMap.toFile()), @@ -170,30 +174,14 @@ public static void main(String[] args) { } } - String mapFilePath = runArgs.getMapFile(); - CompilationProcess compilationProcess = new CompilationProcess(gui, compileArgs); @Nullable CharSequence compiledScript; - if (mapFilePath != null && workspaceroot != null) { - try (MpqEditor mpqEditor = MpqEditorFactory.getEditor(Optional.of(new File(mapFilePath)))) { - File projectFolder = Paths.get(workspaceroot).toFile(); - compiledScript = compilationProcess.doCompilation(mpqEditor, projectFolder, true); - if (compiledScript != null) { - gui.sendProgress("Writing to map"); - mpqEditor.deleteFile("war3map.j"); - byte[] war3map = compiledScript.toString().getBytes(Charsets.UTF_8); - mpqEditor.insertFile("war3map.j", war3map); - } - ImportFile.importFilesFromImports(projectFolder, mpqEditor); - } - } else { - compiledScript = compilationProcess.doCompilation(null, true); - } + compiledScript = compilationProcess.doCompilation(null, true); if (compiledScript != null) { - File scriptFile = new File("compiled.j.txt"); - Files.write(compiledScript.toString().getBytes(Charsets.UTF_8), scriptFile); + File scriptFile = new File(compileArgs.isLua() ? "compiled.lua.txt" : "compiled.j.txt"); + java.nio.file.Files.writeString(scriptFile.toPath(), compiledScript); } gui.sendProgress("Finished!"); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java index 1153ea15d..ca30ce142 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java @@ -3,6 +3,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Files; import org.wurstscript.projectconfig.WurstProjectConfigData; +import de.peeeq.wurstio.CompilationProcess; import de.peeeq.wurstio.Pjass; import de.peeeq.wurstio.TimeTaker; import de.peeeq.wurstio.UtilsIO; @@ -172,6 +173,13 @@ protected File compileMap(File projectFolder, WurstGui gui, Optional mapCo throw new RequestFailedException(MessageType.Error, "Could not compile project (error in translation): " + gui.getErrorList().get(0)); } + if (runArgs.isRunTests()) { + CompilationProcess.runTests(gui, compiler, runArgs); + if (gui.getErrorCount() > 0) { + throw new RequestFailedException(MessageType.Error, "Could not compile project: tests failed."); + } + } + timeTaker.measure("Runinng Compiletime Functions", () -> compiler.runCompiletime(projectConfigData, isProd, runArgs.isCompiletimeCache())); if (runArgs.isLua()) { @@ -768,6 +776,7 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui, CompilationResult result = compileScript(modelManager, gui, Optional.of(targetMapFile), projectConfig, buildDir, isProductionBuild()); injectMapData(gui, Optional.of(targetMapFile), result); + writeRequestedScript(result); targetMapFile = ensureWritableBuildOutput(targetMapFile, true); java.nio.file.Files.copy(getCachedMapFile().toPath(), targetMapFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); @@ -787,6 +796,30 @@ protected File executeBuildMapPipeline(ModelManager modelManager, WurstGui gui, return targetMapFile; } + private void writeRequestedScript(CompilationResult result) throws IOException { + if (runArgs.getOutFile() == null || result.script == null) { + return; + } + + String outputPath = runArgs.getOutFile(); + File outputFile = new File(outputPath); + if (runArgs.isLua() && outputPath.toLowerCase(Locale.ROOT).endsWith(".j")) { + java.nio.file.Files.deleteIfExists(outputFile.toPath()); + outputPath = outputPath.substring(0, outputPath.length() - 2) + ".lua"; + outputFile = new File(outputPath); + } + + File parent = outputFile.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + java.nio.file.Files.copy( + result.script.toPath(), + outputFile.toPath(), + java.nio.file.StandardCopyOption.REPLACE_EXISTING + ); + } + protected boolean isProductionBuild() { return !runArgs.isDevBuild(); } diff --git a/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java new file mode 100644 index 000000000..43f4baf6c --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/de/peeeq/wurstio/CompilationProcessLuaTests.java @@ -0,0 +1,35 @@ +package de.peeeq.wurstio; + +import de.peeeq.wurstscript.RunArgs; +import de.peeeq.wurstscript.gui.WurstGuiCliImpl; +import org.testng.annotations.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class CompilationProcessLuaTests { + + @Test + public void luaModeDoesNotEmitJassScript() throws Exception { + Path project = Files.createTempDirectory("wurst-cli-lua"); + Path source = project.resolve("Main.wurst"); + Path requestedJassOutput = project.resolve("output.j"); + Path output = project.resolve("output.lua"); + Files.writeString(source, "package Main\nfunction foo()\nendpackage\n"); + Files.writeString(requestedJassOutput, "stale jass output"); + + RunArgs runArgs = new RunArgs("-lua", "-out", requestedJassOutput.toString(), source.toString()); + CompilationProcess process = new CompilationProcess(new WurstGuiCliImpl(true), runArgs); + + CharSequence result = process.doCompilation(null, project.toFile(), false); + + assertTrue(result != null, "Lua compilation should succeed"); + assertTrue(Files.exists(output), "Lua output should be written"); + assertFalse(Files.exists(requestedJassOutput), "Lua compilation must not emit a .j file"); + assertFalse(result.toString().contains("takes nothing returns nothing"), + "CLI Lua compilation must not use the Jass backend"); + } +}