From 0101548366f82e316053ec72f90eb4704bf2af32 Mon Sep 17 00:00:00 2001 From: hneemann Date: Tue, 14 May 2019 12:29:31 +0200 Subject: [PATCH] first working apio support --- src/main/dig/sequential/JK-MS.dig | 11 +- .../de/neemann/digital/core/element/Keys.java | 5 + .../java/de/neemann/digital/gui/Main.java | 25 +- .../java/de/neemann/digital/gui/Settings.java | 1 + .../java/de/neemann/digital/ide/Command.java | 66 ++++ .../de/neemann/digital/ide/Configuration.java | 338 ++++++++++++++++++ .../de/neemann/digital/ide/FileToCreate.java | 59 +++ .../de/neemann/digital/ide/package-info.java | 10 + src/main/resources/lang/lang_de.xml | 8 + src/main/resources/lang/lang_en.xml | 7 + .../digital/ide/ConfigurationTest.java | 144 ++++++++ 11 files changed, 670 insertions(+), 4 deletions(-) create mode 100644 src/main/java/de/neemann/digital/ide/Command.java create mode 100644 src/main/java/de/neemann/digital/ide/Configuration.java create mode 100644 src/main/java/de/neemann/digital/ide/FileToCreate.java create mode 100644 src/main/java/de/neemann/digital/ide/package-info.java create mode 100644 src/test/java/de/neemann/digital/ide/ConfigurationTest.java diff --git a/src/main/dig/sequential/JK-MS.dig b/src/main/dig/sequential/JK-MS.dig index 497f60e97..dd1a6786a 100644 --- a/src/main/dig/sequential/JK-MS.dig +++ b/src/main/dig/sequential/JK-MS.dig @@ -40,8 +40,13 @@ auftritt.}} Not - - + + + wideShape + true + + + In @@ -261,7 +266,7 @@ C 1 1 0 1 - + 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 0622beaf1..4191fe83b 100644 --- a/src/main/java/de/neemann/digital/core/element/Keys.java +++ b/src/main/java/de/neemann/digital/core/element/Keys.java @@ -778,5 +778,10 @@ public final class Keys { .setMin(0) .setSecondary(); + /** + * Stores the IDE settings file + */ + public static final Key SETTINGS_IDE_CONFIG = + new Key.KeyFile("ideSettings", new File("")).setSecondary().setRequiresRestart(); } diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 5810f6c17..ae1d2eaab 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -50,6 +50,7 @@ import de.neemann.digital.gui.state.StateManager; import de.neemann.digital.hdl.printer.CodePrinter; import de.neemann.digital.hdl.verilog2.VerilogGenerator; import de.neemann.digital.hdl.vhdl2.VHDLGenerator; +import de.neemann.digital.ide.Configuration; import de.neemann.digital.lang.Lang; import de.neemann.digital.testing.TestCaseElement; import de.neemann.digital.testing.TestingDataException; @@ -277,7 +278,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS enableClockShortcut(); - new WindowSizeStorage(builder.mainFrame?"main":"sub").restore(this); + new WindowSizeStorage(builder.mainFrame ? "main" : "sub").restore(this); if (builder.parent != null) { Point p = builder.parent.getLocation(); @@ -288,6 +289,28 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS } else setLocationRelativeTo(null); + checkIDEIntegration(builder, menuBar); + } + + private void checkIDEIntegration(MainBuilder builder, JMenuBar menuBar) { + if (builder.mainFrame) { + File f = Settings.getInstance().get(Keys.SETTINGS_IDE_CONFIG); + if (f.getPath().length() > 0) { + try { + menuBar.add( + Configuration.load(f) + .setCircuitProvider(() -> getCircuitComponent().getCircuit()) + .setFilenameProvider(() -> { + saveChanges(); + return filename; + }) + .setLibraryProvider(() -> library) + .createMenu()); + } catch (IOException e) { + SwingUtilities.invokeLater(new ErrorMessage(Lang.get("msg_errorReadingIDEConfig_N", f.getPath())).addCause(e).setComponent(this)); + } + } + } } private void enableClockShortcut() { diff --git a/src/main/java/de/neemann/digital/gui/Settings.java b/src/main/java/de/neemann/digital/gui/Settings.java index 0680b0ef3..14326584c 100644 --- a/src/main/java/de/neemann/digital/gui/Settings.java +++ b/src/main/java/de/neemann/digital/gui/Settings.java @@ -58,6 +58,7 @@ public final class Settings implements AttributeListener { intList.add(Keys.SETTINGS_ATMISP); intList.add(Keys.SETTINGS_GHDL_PATH); intList.add(Keys.SETTINGS_IVERILOG_PATH); + intList.add(Keys.SETTINGS_IDE_CONFIG); intList.add(Keys.SETTINGS_FONT_SCALING); intList.add(Keys.SETTINGS_MAC_MOUSE); diff --git a/src/main/java/de/neemann/digital/ide/Command.java b/src/main/java/de/neemann/digital/ide/Command.java new file mode 100644 index 000000000..5006d91d5 --- /dev/null +++ b/src/main/java/de/neemann/digital/ide/Command.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 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.ide; + +/** + * Represents a command to execute + */ +public class Command { + private final String name; + private final boolean filter; + private final String requires; + private final String[] args; + + /** + * Creates a new command + * + * @param name the name of the command + * @param requires the hdl which is required, either "verilog" of "vhdl" + * @param filter the true, the commands args are filtered + * @param args the arguments + */ + public Command(String name, String requires, boolean filter, String... args) { + this.name = name; + this.requires = requires; + this.args = args; + this.filter = filter; + } + + /** + * @return the commands name + */ + public String getName() { + return name; + } + + /** + * @return the commands args + */ + public String[] getArgs() { + return args; + } + + /** + * @return true if a hdl is required + */ + public boolean needsHDL() { + return getHDL().length() > 0; + } + + /** + * @return the hdl which is required, either "verilog" of "vhdl" + */ + public String getHDL() { + return requires.trim().toLowerCase(); + } + + /** + * @return true if the arguments needs to be filtered + */ + public boolean isFilter() { + return filter; + } +} diff --git a/src/main/java/de/neemann/digital/ide/Configuration.java b/src/main/java/de/neemann/digital/ide/Configuration.java new file mode 100644 index 000000000..dd3a96348 --- /dev/null +++ b/src/main/java/de/neemann/digital/ide/Configuration.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2019 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.ide; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import de.neemann.digital.core.extern.ProcessStarter; +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.library.ElementLibrary; +import de.neemann.digital.gui.SaveAsHelper; +import de.neemann.digital.hdl.hgs.Context; +import de.neemann.digital.hdl.hgs.HGSEvalException; +import de.neemann.digital.hdl.hgs.Parser; +import de.neemann.digital.hdl.hgs.ParserException; +import de.neemann.digital.hdl.printer.CodePrinter; +import de.neemann.digital.hdl.verilog2.VerilogGenerator; +import de.neemann.digital.hdl.vhdl2.VHDLGenerator; +import de.neemann.digital.lang.Lang; +import de.neemann.gui.ErrorMessage; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.io.*; +import java.util.ArrayList; + +/** + * Used to create the IDE integration + */ +public final class Configuration { + + /** + * Loads a configuration + * + * @param file the file to load + * @return the configuration + * @throws IOException IOException + */ + public static Configuration load(File file) throws IOException { + return load(new FileInputStream(file)); + } + + /** + * Loads a configuration + * + * @param in the file to load + * @return the configuration + * @throws IOException IOException + */ + public static Configuration load(InputStream in) throws IOException { + try { + XStream xStream = getxStream(); + return (Configuration) xStream.fromXML(in); + } catch (RuntimeException e) { + throw new IOException("error reading XML", e); + } + } + + private static XStream getxStream() { + final XStream xStream = new XStream(new StaxDriver()); + xStream.alias("ide", Configuration.class); + xStream.aliasAttribute(Configuration.class, "name", "name"); + xStream.alias("command", Command.class); + xStream.aliasAttribute(Command.class, "name", "name"); + xStream.aliasAttribute(Command.class, "requires", "requires"); + xStream.aliasAttribute(Command.class, "filter", "filter"); + xStream.addImplicitCollection(Command.class, "args", "arg", String.class); + xStream.alias("file", FileToCreate.class); + xStream.aliasAttribute(FileToCreate.class, "name", "name"); + xStream.aliasAttribute(FileToCreate.class, "overwrite", "overwrite"); + xStream.aliasAttribute(FileToCreate.class, "filter", "filter"); + return xStream; + } + + private String name; + private ArrayList commands; + private ArrayList files; + private transient FilenameProvider filenameProvider; + private transient CircuitProvider circuitProvider; + private transient LibraryProvider libraryProvider; + private transient FileWriter fileWriter; + + private Configuration() { + files = new ArrayList<>(); + commands = new ArrayList<>(); + } + + /** + * Sets the file name provider + * + * @param filenameProvider the file name provider + * @return this for chained calls + */ + public Configuration setFilenameProvider(FilenameProvider filenameProvider) { + this.filenameProvider = filenameProvider; + return this; + } + + /** + * Sets the circuit provider + * + * @param circuitProvider the circuit provider + * @return this for chained calls + */ + public Configuration setCircuitProvider(CircuitProvider circuitProvider) { + this.circuitProvider = circuitProvider; + return this; + } + + /** + * Sets the library provider + * + * @param libraryProvider the library provider + * @return this for chained calls + */ + public Configuration setLibraryProvider(LibraryProvider libraryProvider) { + this.libraryProvider = libraryProvider; + return this; + } + + Configuration setFileWriter(FileWriter fileWriter) { + this.fileWriter = fileWriter; + return this; + } + + /** + * Creates a menu used to start the commands. + * + * @return the menu + */ + public JMenu createMenu() { + JMenu menu = new JMenu(name); + for (Command c : commands) + menu.add(new JMenuItem(new ExecuteAction(c))); + return menu; + } + + private void checkFilesToCreate(File fileToExecute) throws HGSEvalException, IOException, ParserException { + Context context = createContext(fileToExecute); + + if (files != null) + for (FileToCreate f : files) { + context.clearOutput(); + Parser p = new Parser(f.getName()); + p.parse().execute(context); + File filename = new File(fileToExecute.getParent(), context.toString()); + + if (f.isOverwrite() || !filename.exists()) { + String content = f.getContent(); + if (f.isFilter()) { + context.clearOutput(); + p = new Parser(content); + p.parse().execute(context); + content = context.toString(); + } + + try (OutputStream out = getFileWriter().getOutputStream(filename)) { + out.write(content.getBytes()); + } + } + } + } + + private Context createContext(File fileToExecute) throws HGSEvalException { + return new Context() + .declareVar("path", fileToExecute.getPath()) + .declareVar("dir", fileToExecute.getParentFile()) + .declareVar("name", fileToExecute.getName()) + .declareVar("shortname", createShortname(fileToExecute.getName())); + } + + private FileWriter getFileWriter() { + if (fileWriter == null) + fileWriter = new DefaultFileWriter(); + return fileWriter; + } + + private String createShortname(String name) { + int p = name.lastIndexOf('.'); + if (p >= 0) + return name.substring(0, p); + return name; + } + + private void writeHDL(String hdl, File digFile) throws IOException { + switch (hdl) { + case "verilog": + File verilogFile = SaveAsHelper.checkSuffix(digFile, "v"); + final CodePrinter verilogPrinter = new CodePrinter(getFileWriter().getOutputStream(verilogFile)); + try (VerilogGenerator vlog = new VerilogGenerator(libraryProvider.getCurrentLibrary(), verilogPrinter)) { + vlog.export(circuitProvider.getCurrentCircuit()); + } + break; + case "vhdl": + File vhdlFile = SaveAsHelper.checkSuffix(digFile, "vhdl"); + final CodePrinter vhdlPrinter = new CodePrinter(getFileWriter().getOutputStream(vhdlFile)); + try (VHDLGenerator vlog = new VHDLGenerator(libraryProvider.getCurrentLibrary(), vhdlPrinter)) { + vlog.export(circuitProvider.getCurrentCircuit()); + } + break; + default: + throw new IOException(Lang.get("err_hdlNotKnown_N", hdl)); + } + } + + /** + * Executes the given command + * + * @param command the command + */ + public void executeCommand(Command command) { + File digFile = filenameProvider.getCurrentFilename(); + if (digFile != null) { + try { + + if (command.needsHDL()) + writeHDL(command.getHDL(), digFile); + + checkFilesToCreate(digFile); + + String[] args = command.getArgs(); + if (command.isFilter()) { + final int argCount = command.getArgs().length; + Context context = createContext(digFile); + for (int i = 0; i < argCount; i++) { + context.clearOutput(); + new Parser(args[i]).parse().execute(context); + args[i] = context.toString(); + } + } + + getFileWriter().startProcess(digFile.getParentFile(), args); + } catch (Exception e) { + getFileWriter().showError(command, e); + } + } + } + + private final class ExecuteAction extends AbstractAction { + private final Command command; + + private ExecuteAction(Command command) { + super(command.getName()); + this.command = command; + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + executeCommand(command); + } + } + + ArrayList getCommands() { + return commands; + } + + /** + * Interface used to provide a file. + */ + public interface FilenameProvider { + /** + * @return the file which is to create + */ + File getCurrentFilename(); + } + + /** + * Interface used to provide the circuit + */ + public interface CircuitProvider { + /** + * @return the circuit which is to use + */ + Circuit getCurrentCircuit(); + } + + /** + * Interface used to provide the library. + */ + public interface LibraryProvider { + /** + * @return the library which currently used + */ + ElementLibrary getCurrentLibrary(); + } + + /** + * Interface used to write a file + */ + public interface FileWriter { + + /** + * Creates an output stream + * + * @param filename the filename + * @return the output stream + * @throws IOException IOException + */ + OutputStream getOutputStream(File filename) throws IOException; + + /** + * Starts a process + * + * @param dir the folder to start the process in + * @param args the arguments + * @throws IOException IOException + */ + void startProcess(File dir, String[] args) throws IOException; + + /** + * Shows an error message + * + * @param command the command that failed + * @param e the error + */ + void showError(Command command, Exception e); + } + + private static final class DefaultFileWriter implements FileWriter { + + @Override + public OutputStream getOutputStream(File filename) throws FileNotFoundException { + return new FileOutputStream(filename); + } + + @Override + public void startProcess(File dir, String[] args) throws IOException { + ProcessStarter.start(dir, args); + } + + @Override + public void showError(Command command, Exception e) { + new ErrorMessage(Lang.get("msg_errorStartCommand_N", command.getName())).addCause(e).show(); + } + } +} diff --git a/src/main/java/de/neemann/digital/ide/FileToCreate.java b/src/main/java/de/neemann/digital/ide/FileToCreate.java new file mode 100644 index 000000000..4e9e203a1 --- /dev/null +++ b/src/main/java/de/neemann/digital/ide/FileToCreate.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 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.ide; + +/** + * Represents a file to create + */ +public class FileToCreate { + private final String name; + private final String content; + private final boolean overwrite; + private final boolean filter; + + /** + * The file to create + * + * @param name the name of the file + * @param content the files content + * @param overwrite overwrite every time a command is executed + * @param filter the files content needs to be filtered + */ + public FileToCreate(String name, String content, boolean overwrite, boolean filter) { + this.name = name; + this.content = content; + this.overwrite = overwrite; + this.filter = filter; + } + + /** + * @return The file name of the file. Is always filtered + */ + public String getName() { + return name; + } + + /** + * @return true if the file needs to be overwritten, every time a command is executed. + */ + public boolean isOverwrite() { + return overwrite; + } + + /** + * @return true if the files contend needs to be filtered. + */ + public boolean isFilter() { + return filter; + } + + /** + * @return the files content + */ + public String getContent() { + return content; + } +} diff --git a/src/main/java/de/neemann/digital/ide/package-info.java b/src/main/java/de/neemann/digital/ide/package-info.java new file mode 100644 index 000000000..10e292d14 --- /dev/null +++ b/src/main/java/de/neemann/digital/ide/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2019 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ + +/** + * Classes used to provide the IDE integration + */ +package de.neemann.digital.ide; diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 0a5cc9615..088c4f111 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1017,6 +1017,8 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Das MIDI-Instrument {0} ist nicht verfügbar. Die MIDI-Instrumente sind nicht verfügbar. Während der Ausführung der Tests "{0}" ist ein Fehler aufgetreten! + HDL nicht bekannt: {0} + Fehler beim Starten des Kommandos {0} Adress-Bits Anzahl der Adress-Bits, die verwendet werden. @@ -1330,6 +1332,10 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Aktiviert Aktiviert oder deaktiviert diese Komponente. + IDE-Einstellungen + Kann für eine IDE-Integration verwendet werden. + Erlaubt den Start externer Tools, um z.B. einen FPGA zu programmieren o.ä. + Leitung eingefügt. Aus Zwischenablage eingefügt. Wert ''{0}'' in Element ''{1}'' verändert. @@ -1683,6 +1689,8 @@ Stellen Sie sicher, dass der Flash-Vorgang abgeschlossen ist, bevor Sie diesen D Fehler beim Erzeugen der SVG-Datei. Statistik konnte nicht erzeugt werden. + Fehler beim Einladen der IDE-Konfiguration {0} + Ok 180° diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 82999f3bc..217aad2ee 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -1009,6 +1009,8 @@ The MIDI instrument {0} is not available. The MIDI instruments are not available. During the execution of the tests "{0}" an error has occurred! + HDL not known: {0} + Error starting the command {0} Address Bits Number of address bits used. @@ -1318,6 +1320,10 @@ Enabled Enables or disables this component. + IDE settings + Used to configurate an IDE integration. + Allows the start of external tools, e.g. to program an FPGA or similar. + Inserted wire. Insert from clipboard. Value ''{0}'' in component ''{1}'' modified. @@ -1665,6 +1671,7 @@ Make sure the flash process is complete before closing this dialog! Error while importing the SVG file. Error creating the SVG template. Statistics could not be created. + Error while reading the IDE configuration {0} OK diff --git a/src/test/java/de/neemann/digital/ide/ConfigurationTest.java b/src/test/java/de/neemann/digital/ide/ConfigurationTest.java new file mode 100644 index 000000000..92961abb8 --- /dev/null +++ b/src/test/java/de/neemann/digital/ide/ConfigurationTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 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.ide; + +import de.neemann.digital.core.NodeException; +import de.neemann.digital.draw.elements.PinException; +import de.neemann.digital.draw.library.ElementNotFoundException; +import de.neemann.digital.integration.Resources; +import de.neemann.digital.integration.ToBreakRunner; +import junit.framework.TestCase; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class ConfigurationTest extends TestCase { + + public void testStart() throws IOException, ElementNotFoundException, PinException, NodeException { + String xml = "\n" + + " \n" + + " \n" + + " make\n" + + " \n" + + " \n" + + " make\n" + + " <?=dir?>/<?=shortname?>.v\n" + + " \n" + + " \n" + + " \n"; + + + ToBreakRunner br = new ToBreakRunner(new File(Resources.getRoot(), "dig/hdl/negSimple.dig")); + + final TestFileWriter fileWriter = new TestFileWriter(); + Configuration c = Configuration.load(new ByteArrayInputStream(xml.getBytes())) + .setFilenameProvider(() -> new File("z/test.dig")) + .setCircuitProvider(br::getCircuit) + .setLibraryProvider(br::getLibrary) + .setFileWriter(fileWriter); + ArrayList commands = c.getCommands(); + assertEquals(2, commands.size()); + + c.executeCommand(commands.get(0)); + + assertEquals(1, fileWriter.files.size()); + assertTrue(fileWriter.files.containsKey("z/test.v")); + + assertEquals(1, fileWriter.commands.size()); + assertEquals("z", fileWriter.commands.get(0).dir.getPath()); + assertEquals("[make]", Arrays.toString(fileWriter.commands.get(0).args)); + + fileWriter.clear(); + c.executeCommand(commands.get(1)); + + assertEquals(1, fileWriter.files.size()); + assertTrue(fileWriter.files.containsKey("z/test.v")); + + assertEquals(1, fileWriter.commands.size()); + assertEquals("z", fileWriter.commands.get(0).dir.getPath()); + assertEquals("[make, z/test.v]", Arrays.toString(fileWriter.commands.get(0).args)); + } + + public void testFileWriter() throws IOException, ElementNotFoundException, PinException, NodeException { + String xml = "\n" + + " \n" + + " \n" + + " make\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " deal with <?=path?>\n" + + " \n" + + " \n" + + " deal with <?=path?>\n" + + " \n" + + " \n" + + " test\n" + + " \n" + + " \n" + + " \n"; + + + ToBreakRunner br = new ToBreakRunner(new File(Resources.getRoot(), "dig/hdl/negSimple.dig")); + + final TestFileWriter fileWriter = new TestFileWriter(); + Configuration c = Configuration.load(new ByteArrayInputStream(xml.getBytes())) + .setFilenameProvider(() -> new File("z/test.dig")) + .setCircuitProvider(br::getCircuit) + .setLibraryProvider(br::getLibrary) + .setFileWriter(fileWriter); + ArrayList commands = c.getCommands(); + assertEquals(1, commands.size()); + + c.executeCommand(commands.get(0)); + + assertEquals(4, fileWriter.files.size()); + assertEquals("deal with ", fileWriter.files.get("z/file1").toString()); + assertEquals("deal with z/test.dig", fileWriter.files.get("z/file2").toString()); + assertEquals("test", fileWriter.files.get("z/test.z").toString()); + } + + + private class TestFileWriter implements Configuration.FileWriter { + private HashMap files = new HashMap<>(); + private ArrayList commands = new ArrayList<>(); + + @Override + public OutputStream getOutputStream(File filename) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + files.put(filename.getPath(), baos); + return baos; + } + + @Override + public void startProcess(File dir, String[] args) { + commands.add(new StartedCommand(dir, args)); + } + + @Override + public void showError(Command command, Exception e) { + throw new RuntimeException(command.getName(), e); + } + + private void clear() { + files.clear(); + commands.clear(); + } + } + + private class StartedCommand { + private final File dir; + private final String[] args; + + private StartedCommand(File dir, String[] args) { + this.dir = dir; + this.args = args; + } + } +} \ No newline at end of file