diff --git a/src/main/java/de/neemann/digital/core/element/Keys.java b/src/main/java/de/neemann/digital/core/element/Keys.java index fbc564266..085a3cbb1 100644 --- a/src/main/java/de/neemann/digital/core/element/Keys.java +++ b/src/main/java/de/neemann/digital/core/element/Keys.java @@ -531,10 +531,5 @@ public final class Keys { */ public static final Key.LongString EXTERNAL_CODE = new Key.LongString("Code").setRows(20).setColumns(40); - /** - * The parameters to be used by the external process - */ - public static final Key.KeyFile EXTERNAL_EXECUTABLE - = new Key.KeyFile("externalExecutable", new File("")); } diff --git a/src/main/java/de/neemann/digital/core/extern/External.java b/src/main/java/de/neemann/digital/core/extern/External.java index af31d1cb5..60a9c97fd 100644 --- a/src/main/java/de/neemann/digital/core/extern/External.java +++ b/src/main/java/de/neemann/digital/core/extern/External.java @@ -11,7 +11,6 @@ import de.neemann.digital.lang.Lang; import de.neemann.gui.ErrorMessage; import javax.swing.*; -import java.io.File; import java.io.IOException; /** @@ -38,15 +37,13 @@ public class External extends Node implements Element { .addAttribute(Keys.EXTERNAL_INPUTS) .addAttribute(Keys.EXTERNAL_OUTPUTS) .addAttribute(Keys.EXTERNAL_CODE) - .addAttribute(Keys.PROCESS_TYPE) - .addAttribute(Keys.EXTERNAL_EXECUTABLE); + .addAttribute(Keys.PROCESS_TYPE); private final ProcessFactory.Type type; private final PortDefinition ins; private final PortDefinition outs; private final ObservableValues outputs; private final String code; - private final File executable; private final String label; private ObservableValues inputs; private ProcessHandler processHandler; @@ -64,7 +61,6 @@ public class External extends Node implements Element { label = attr.getCleanLabel(); type = attr.get(Keys.PROCESS_TYPE); code = attr.get(Keys.EXTERNAL_CODE); - executable = attr.get(Keys.EXTERNAL_EXECUTABLE); } @Override @@ -101,7 +97,7 @@ public class External extends Node implements Element { @Override public void init(Model model) throws NodeException { try { - processHandler = ProcessFactory.create(type, label, code, ins, outs, executable); + processHandler = ProcessFactory.create(type, label, code, ins, outs); } catch (IOException e) { throw new NodeException(Lang.get("err_errorCreatingProcess"), this, -1, null, e); } diff --git a/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java b/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java index 3dc1b8b62..113bf97d4 100644 --- a/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java +++ b/src/main/java/de/neemann/digital/core/extern/ProcessFactory.java @@ -9,7 +9,6 @@ import de.neemann.digital.core.extern.handler.GHDLProcess; import de.neemann.digital.core.extern.handler.Generic; import de.neemann.digital.lang.Lang; -import java.io.File; import java.io.IOException; /** @@ -43,16 +42,15 @@ public final class ProcessFactory { * @param code the code to use * @param inputs the inputs to use * @param outputs the outputs to use - * @param executable the parameters to use * @return the created process handler * @throws IOException IOException */ - public static ProcessHandler create(Type type, String label, String code, PortDefinition inputs, PortDefinition outputs, File executable) throws IOException { + public static ProcessHandler create(Type type, String label, String code, PortDefinition inputs, PortDefinition outputs) throws IOException { switch (type) { case Generic: - return new Generic(executable); + return new Generic(code); case GHDL: - return new GHDLProcess(executable, label, code, inputs, outputs); + return new GHDLProcess(label, code, inputs, outputs); default: throw new IOException(Lang.get("err_processType_N_notFound", type.name())); } diff --git a/src/main/java/de/neemann/digital/core/extern/handler/GHDLProcess.java b/src/main/java/de/neemann/digital/core/extern/handler/GHDLProcess.java index 3d41159be..39d21bf9c 100644 --- a/src/main/java/de/neemann/digital/core/extern/handler/GHDLProcess.java +++ b/src/main/java/de/neemann/digital/core/extern/handler/GHDLProcess.java @@ -11,20 +11,28 @@ import java.io.File; import java.io.IOException; /** - * Can use GHDL as VHDL simulator + * Can use GHDL as VHDL simulator. */ public class GHDLProcess extends VHDLProcess { + /** * Creates a new instance * - * @param executable the executable * @param label the label * @param code the code * @param inputs th inputs to use * @param outputs the outputs to use * @throws IOException IOException */ - public GHDLProcess(File executable, String label, String code, PortDefinition inputs, PortDefinition outputs) throws IOException { + public GHDLProcess(String label, String code, PortDefinition inputs, PortDefinition outputs) throws IOException { super(label, code, inputs, outputs); + + String ghdl = "ghdl"; + + File file = getVHDLFile(); + ProcessStarter.start(file.getParentFile(), ghdl, "-a", "--ieee=synopsys", file.getName()); + ProcessStarter.start(file.getParentFile(), ghdl, "-e", "--ieee=synopsys", "stdIOInterface"); + ProcessBuilder pb = new ProcessBuilder(ghdl, "-r", "--ieee=synopsys", "stdIOInterface").redirectErrorStream(true).directory(file.getParentFile()); + setProcess(pb.start()); } } diff --git a/src/main/java/de/neemann/digital/core/extern/handler/Generic.java b/src/main/java/de/neemann/digital/core/extern/handler/Generic.java index 6ca91f191..a2b5c6fb8 100644 --- a/src/main/java/de/neemann/digital/core/extern/handler/Generic.java +++ b/src/main/java/de/neemann/digital/core/extern/handler/Generic.java @@ -5,7 +5,6 @@ */ package de.neemann.digital.core.extern.handler; -import java.io.File; import java.io.IOException; /** @@ -18,8 +17,9 @@ public class Generic extends StdIOProcess { * @param file the name of the application to start * @throws IOException IOException */ - public Generic(File file) throws IOException { - ProcessBuilder pb = new ProcessBuilder(file.getPath()); + public Generic(String file) throws IOException { + String[] args = file.split(" "); + ProcessBuilder pb = new ProcessBuilder(args).redirectErrorStream(true); setProcess(pb.start()); } } diff --git a/src/main/java/de/neemann/digital/core/extern/handler/ProcessStarter.java b/src/main/java/de/neemann/digital/core/extern/handler/ProcessStarter.java new file mode 100644 index 000000000..8bff7718a --- /dev/null +++ b/src/main/java/de/neemann/digital/core/extern/handler/ProcessStarter.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.core.extern.handler; + +import de.neemann.digital.lang.Lang; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * Helper to start and wait for a process. + */ +public final class ProcessStarter { + + private ProcessStarter() { + } + + /** + * Helper to start a process. + * If result value is not null an exception is thrown. + * + * @param dir the folder in which the process is started + * @param args the argument + * @return the console output + * @throws IOException IOException + */ + public static String start(File dir, String... args) throws IOException { + ProcessBuilder pb = new ProcessBuilder(args).redirectErrorStream(true).directory(dir); + Process p; + try { + p = pb.start(); + } catch (IOException e) { + throw new IOException(Lang.get("err_couldNotStartProcess_N", Arrays.toString(args))); + } + ReaderThread rt = new ReaderThread(p.getInputStream()); + rt.start(); + try { + int exitValue = p.waitFor(); + rt.join(); + + String output = rt.toString(); + + if (exitValue != 0) + throw new IOException(Lang.get("err_exitValueNotNull_N_O", exitValue, output)); + + return output; + } catch (InterruptedException e) { + throw new IOException(e); + } + } + + private static final class ReaderThread extends Thread { + private final ByteArrayOutputStream baos; + private final InputStream in; + + private ReaderThread(InputStream in) { + this.in = in; + baos = new ByteArrayOutputStream(); + } + + @Override + public void run() { + try { + try { + byte[] buffer = new byte[4096]; + int len; + while ((len = in.read(buffer)) > 0) + baos.write(buffer, 0, len); + } finally { + in.close(); + } + } catch (IOException e) { + // do nothing, simply end the thread + } + } + + @Override + public String toString() { + return baos.toString(); + } + } +} diff --git a/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java b/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java index 9b2495333..3ae2cf0cc 100644 --- a/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java +++ b/src/main/java/de/neemann/digital/core/extern/handler/StdIOProcess.java @@ -11,15 +11,18 @@ import de.neemann.digital.core.extern.ProcessHandler; import de.neemann.digital.lang.Lang; import java.io.*; +import java.util.LinkedList; /** - * The generic process description + * Communicates with an external process by sending values and receiving results via the stdio. */ public class StdIOProcess implements ProcessHandler { private static final String PREFIX = "Digital:"; + private static final int MAX_CONSOLE_LINES = 30; private Process process; private BufferedWriter writer; private Thread thread; + private LinkedList consoleOut; private final Object lock = new Object(); private String dataFound; @@ -56,11 +59,14 @@ public class StdIOProcess implements ProcessHandler { */ private void setReaderWriter(BufferedReader reader, BufferedWriter writer) { this.writer = writer; - + consoleOut = new LinkedList<>(); thread = new Thread(() -> { try { String line; while ((line = reader.readLine()) != null) { + consoleOut.add(line); + while (consoleOut.size() > MAX_CONSOLE_LINES) + consoleOut.removeFirst(); if (line.startsWith(PREFIX)) { synchronized (lock) { while (dataFound != null) @@ -161,8 +167,12 @@ public class StdIOProcess implements ProcessHandler { } v.set(value, highZ); } - } else - throw new IOException(Lang.get("err_processTerminatedUnexpected")); + } else { + StringBuilder sb = new StringBuilder(); + for (String s : consoleOut) + sb.append(s).append("\n"); + throw new IOException(Lang.get("err_processTerminatedUnexpected_O", sb.toString())); + } } @Override diff --git a/src/main/java/de/neemann/digital/core/extern/handler/VHDLProcess.java b/src/main/java/de/neemann/digital/core/extern/handler/VHDLProcess.java index 85b9984dc..222f805eb 100644 --- a/src/main/java/de/neemann/digital/core/extern/handler/VHDLProcess.java +++ b/src/main/java/de/neemann/digital/core/extern/handler/VHDLProcess.java @@ -9,12 +9,15 @@ import de.neemann.digital.core.extern.Port; import de.neemann.digital.core.extern.PortDefinition; import java.io.*; +import java.nio.file.Files; /** - * Creates a VHDL process + * Creates a VHDL file which is able to cummunicate via stdio. + * The given code is used as VHDL code. */ public class VHDLProcess extends StdIOProcess { private final File file; + private final File dir; private static class InstanceHolder { private static final String TEMPLATE = loadTemplate(); @@ -49,14 +52,16 @@ public class VHDLProcess extends StdIOProcess { public VHDLProcess(String label, String code, PortDefinition inputs, PortDefinition outputs) throws IOException { String t = InstanceHolder.TEMPLATE; t = t.replace("%name%", label); - t = t.replace("%incount%", Integer.toString(inputs.getBits())); - t = t.replace("%outcount%", Integer.toString(outputs.getBits())); + t = t.replace("%incount%", Integer.toString(inputs.getBits() - 1)); + t = t.replace("%outcount%", Integer.toString(outputs.getBits() - 1)); t = t.replace("%ports%", createPorts(inputs, outputs)); t = t.replace("%signals%", createSignals(inputs, outputs)); t = t.replace("%map%", createMap(inputs, outputs)); t = t.replace("%inOutMapping%", createInOutMapping(inputs, outputs)); - file = File.createTempFile(label, ".vhdl"); + dir = Files.createTempDirectory("digital_vhdl_").toFile(); + + file = new File(dir, label + ".vhdl"); try (Writer w = new FileWriter(file)) { w.write(code); w.write("\n\n\n"); @@ -67,7 +72,7 @@ public class VHDLProcess extends StdIOProcess { /** * @return the created vhdl file */ - public File getFile() { + public File getVHDLFile() { return file; } @@ -145,6 +150,20 @@ public class VHDLProcess extends StdIOProcess { @Override public void close() throws IOException { super.close(); - //if (file != null) file.delete(); + if (dir != null) + removeFolder(dir); + } + + private static void removeFolder(File dir) { + File[] list = dir.listFiles(); + if (list != null) { + for (File f : list) { + if (f.isDirectory()) + removeFolder(f); + else + f.delete(); + } + } + dir.delete(); } } diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index c1f1d11f8..5c85758f1 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -846,9 +846,13 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Der externe Prozess konnte nicht gestartet werden! Zeitüberschreitung beim Lesen von Daten! Es wurden nicht genug Daten empfangen! - Der empfangene Text enthielt ein ungültiges Zeichen: {0}! - Der Prozess wurde unerwartet beendet! + Der Prozess sendete ein ungültiges Zeichen: {0}! + Der Prozess wurde unerwartet beendet! + {0} Der Prozess konnte nicht beendet werden! + Prozess konnte nicht gestartet werden: {0} + Rückgabewert war nicht 0 sondern {0}: + {1} Adress-Bits Anzahl der Adress-Bits, die verwendet werden. diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 3ddda8e55..a2cacd0b5 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -842,8 +842,13 @@ The names of the variables may not be unique. Timeout reading data from external process! Not enough data received! The received text contains an invalid character: {0}! - The process has terminated unexpected! + The process has terminated unexpected! + {0} Could not terminate the process! + Could not start process: {0} + Result value was not 0 but {0}: + {1} + Address Bits Number of address bits used. diff --git a/src/main/resources/templates/VHDLStdIOTemplate.templ b/src/main/resources/templates/VHDLStdIOTemplate.templ index 975f9a893..9ab9ec263 100644 --- a/src/main/resources/templates/VHDLStdIOTemplate.templ +++ b/src/main/resources/templates/VHDLStdIOTemplate.templ @@ -27,10 +27,10 @@ architecture top_sim_a of stdIOInterface is variable result : string (1 to slv'length); variable r : integer; begin - r := 1; + r := slv'length; for i in slv'range loop result(r) := chr(slv(i)); - r := r + 1; + r := r - 1; end loop; return result; end str;