first working apio support

This commit is contained in:
hneemann 2019-05-14 12:29:31 +02:00
parent 6717ce9090
commit 0101548366
11 changed files with 670 additions and 4 deletions

View File

@ -40,8 +40,13 @@ auftritt.}}</string>
</visualElement>
<visualElement>
<elementName>Not</elementName>
<elementAttributes/>
<pos x="180" y="180"/>
<elementAttributes>
<entry>
<string>wideShape</string>
<boolean>true</boolean>
</entry>
</elementAttributes>
<pos x="160" y="180"/>
</visualElement>
<visualElement>
<elementName>In</elementName>
@ -261,7 +266,7 @@ C 1 1 0 1
</wire>
<wire>
<p1 x="140" y="180"/>
<p2 x="180" y="180"/>
<p2 x="160" y="180"/>
</wire>
<wire>
<p1 x="140" y="40"/>

View File

@ -778,5 +778,10 @@ public final class Keys {
.setMin(0)
.setSecondary();
/**
* Stores the IDE settings file
*/
public static final Key<File> SETTINGS_IDE_CONFIG =
new Key.KeyFile("ideSettings", new File("")).setSecondary().setRequiresRestart();
}

View File

@ -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() {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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<Command> commands;
private ArrayList<FileToCreate> 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<Command> 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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -1017,6 +1017,8 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="err_midiInstrument_N_NotAvailable">Das MIDI-Instrument {0} ist nicht verfügbar.</string>
<string name="err_midiInstrumentsNotAvailable">Die MIDI-Instrumente sind nicht verfügbar.</string>
<string name="err_whileExecutingTests_N0">Während der Ausführung der Tests "{0}" ist ein Fehler aufgetreten!</string>
<string name="err_hdlNotKnown_N">HDL nicht bekannt: {0}</string>
<string name="msg_errorStartCommand_N">Fehler beim Starten des Kommandos {0}</string>
<string name="key_AddrBits">Adress-Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Anzahl der Adress-Bits, die verwendet werden.</string>
@ -1330,6 +1332,10 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="key_enabled">Aktiviert</string>
<string name="key_enabled_tt">Aktiviert oder deaktiviert diese Komponente.</string>
<string name="key_ideSettings">IDE-Einstellungen</string>
<string name="key_ideSettings_tt">Kann für eine IDE-Integration verwendet werden.
Erlaubt den Start externer Tools, um z.B. einen FPGA zu programmieren o.ä.</string>
<string name="mod_insertWire">Leitung eingefügt.</string>
<string name="mod_insertCopied">Aus Zwischenablage eingefügt.</string>
<string name="mod_setKey_N0_in_element_N1">Wert ''{0}'' in Element ''{1}'' verändert.</string>
@ -1683,6 +1689,8 @@ Stellen Sie sicher, dass der Flash-Vorgang abgeschlossen ist, bevor Sie diesen D
<string name="msg_errorCreatingSvgTemplate">Fehler beim Erzeugen der SVG-Datei.</string>
<string name="msg_couldNotCreateStats">Statistik konnte nicht erzeugt werden.</string>
<string name="msg_errorReadingIDEConfig_N">Fehler beim Einladen der IDE-Konfiguration {0}</string>
<string name="ok">Ok</string>
<string name="rot_0"></string>
<string name="rot_180">180°</string>

View File

@ -1009,6 +1009,8 @@
<string name="err_midiInstrument_N_NotAvailable">The MIDI instrument {0} is not available.</string>
<string name="err_midiInstrumentsNotAvailable">The MIDI instruments are not available.</string>
<string name="err_whileExecutingTests_N0">During the execution of the tests "{0}" an error has occurred!</string>
<string name="err_hdlNotKnown_N">HDL not known: {0}</string>
<string name="msg_errorStartCommand_N">Error starting the command {0}</string>
<string name="key_AddrBits">Address Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Number of address bits used.</string>
@ -1318,6 +1320,10 @@
<string name="key_enabled">Enabled</string>
<string name="key_enabled_tt">Enables or disables this component.</string>
<string name="key_ideSettings">IDE settings</string>
<string name="key_ideSettings_tt">Used to configurate an IDE integration.
Allows the start of external tools, e.g. to program an FPGA or similar.</string>
<string name="mod_insertWire">Inserted wire.</string>
<string name="mod_insertCopied">Insert from clipboard.</string>
<string name="mod_setKey_N0_in_element_N1">Value ''{0}'' in component ''{1}'' modified.</string>
@ -1665,6 +1671,7 @@ Make sure the flash process is complete before closing this dialog!</string>
<string name="msg_errorImportingSvg">Error while importing the SVG file.</string>
<string name="msg_errorCreatingSvgTemplate">Error creating the SVG template.</string>
<string name="msg_couldNotCreateStats">Statistics could not be created.</string>
<string name="msg_errorReadingIDEConfig_N">Error while reading the IDE configuration {0}</string>
<string name="ok">OK</string>
<string name="rot_0"></string>

View File

@ -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 = "<ide name=\"APIO\">\n" +
" <commands>\n" +
" <command name=\"build\" requires=\"verilog\" filter=\"false\">\n" +
" <arg>make</arg>\n" +
" </command>\n" +
" <command name=\"prog\" requires=\"verilog\" filter=\"true\">\n" +
" <arg>make</arg>\n" +
" <arg>&lt;?=dir?&gt;/&lt;?=shortname?&gt;.v</arg>\n" +
" </command>\n" +
" </commands>\n" +
" </ide>\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<Command> 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 = "<ide name=\"APIO\">\n" +
" <commands>\n" +
" <command name=\"build\" requires=\"verilog\" filter=\"false\">\n" +
" <arg>make</arg>\n" +
" </command>\n" +
" </commands>\n" +
" <files>\n" +
" <file name=\"file1\" overwrite=\"true\" filter=\"false\">\n" +
" <content>deal with &lt;?=path?&gt;</content>\n" +
" </file>\n" +
" <file name=\"file2\" overwrite=\"true\" filter=\"true\">\n" +
" <content>deal with &lt;?=path?&gt;</content>\n" +
" </file>\n" +
" <file name=\"&lt;?=shortname?&gt;.z\" overwrite=\"true\" filter=\"false\">\n" +
" <content>test</content>\n" +
" </file>\n" +
" </files>\n" +
" </ide>\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<Command> commands = c.getCommands();
assertEquals(1, commands.size());
c.executeCommand(commands.get(0));
assertEquals(4, fileWriter.files.size());
assertEquals("deal with <?=path?>", 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<String, ByteArrayOutputStream> files = new HashMap<>();
private ArrayList<StartedCommand> 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;
}
}
}