From ad859f8098a73176a845c0e5804a666c05cfa451 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Fri, 7 Aug 2026 16:04:19 +0200 Subject: [PATCH 1/9] [#408] Fix UI lifecycle and responsiveness bugs --- .../gui/dialogs/SchemaEditorDialog.java | 1 + .../gui/dialogs/SchemaEditorDialogTest.java | 2 +- .../plugins/cpu/intel8080/gui/FlagsModel.java | 15 +-- .../cpu/intel8080/gui/StatusPanel.java | 46 +++++---- .../plugins/cpu/ssem/gui/CpuPanel.java | 81 +++++++++------- .../plugins/cpu/zilogZ80/gui/FlagsModel.java | 22 +++-- .../plugins/cpu/zilogZ80/gui/StatusPanel.java | 93 ++++++++++++------- .../device/adm3a/gui/DisplayCanvas.java | 35 +++++-- .../device/ay38910/gui/WaveformPanel.java | 30 +++++- .../device/vt100/gui/DisplayCanvas.java | 35 +++++-- .../zxspectrum/ula/gui/DisplayCanvas.java | 4 +- .../zxspectrum/ula/gui/DisplayWindow.java | 14 ++- 12 files changed, 253 insertions(+), 125 deletions(-) diff --git a/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java b/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java index 3e418e229..6076bef49 100644 --- a/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java +++ b/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java @@ -337,6 +337,7 @@ private void btnSaveActionPerformed(ActionEvent evt) { } catch (CannotUpdateSettingException e) { LOGGER.error("Could not save computer schema", e); dialogs.showError("Could not save computer schema. Please consult log file for details.", "Save schema"); + return; } dispose(); } diff --git a/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java b/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java index fbc04c79a..d0f3a42c0 100644 --- a/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java +++ b/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java @@ -225,7 +225,7 @@ public void saveButtonShowsErrorWhenSchemaSaveFails() throws Exception { "Could not save computer schema. Please consult log file for details.", "Save schema" ); - assertFalse(onEdt(dialog::isDisplayable)); + assertTrue(onEdt(dialog::isDisplayable)); } private SchemaEditorDialog createDialog(Schema schema, Dialogs dialogs) { diff --git a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/FlagsModel.java b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/FlagsModel.java index f81febf48..81a1f51b4 100644 --- a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/FlagsModel.java +++ b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/FlagsModel.java @@ -44,12 +44,15 @@ public Object getValueAt(int rowIndex, int columnIndex) { @Override public void fireTableDataChanged() { - short F = engine.flags; - flagsI[0] = ((F & EmulatorEngine.FLAG_S) != 0) ? 1 : 0; - flagsI[1] = ((F & EmulatorEngine.FLAG_Z) != 0) ? 1 : 0; - flagsI[2] = ((F & EmulatorEngine.FLAG_AC) != 0) ? 1 : 0; - flagsI[3] = ((F & EmulatorEngine.FLAG_P) != 0) ? 1 : 0; - flagsI[4] = ((F & EmulatorEngine.FLAG_C) != 0) ? 1 : 0; + update(engine.flags); + } + + void update(int flagsValue) { + flagsI[0] = ((flagsValue & EmulatorEngine.FLAG_S) != 0) ? 1 : 0; + flagsI[1] = ((flagsValue & EmulatorEngine.FLAG_Z) != 0) ? 1 : 0; + flagsI[2] = ((flagsValue & EmulatorEngine.FLAG_AC) != 0) ? 1 : 0; + flagsI[3] = ((flagsValue & EmulatorEngine.FLAG_P) != 0) ? 1 : 0; + flagsI[4] = ((flagsValue & EmulatorEngine.FLAG_C) != 0) ? 1 : 0; super.fireTableDataChanged(); } } diff --git a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/StatusPanel.java b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/StatusPanel.java index 668cdce6a..997b58aaf 100644 --- a/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/StatusPanel.java +++ b/plugins/cpu/8080-cpu/src/main/java/net/emustudio/plugins/cpu/intel8080/gui/StatusPanel.java @@ -11,7 +11,6 @@ import net.emustudio.plugins.cpu.intel8080.api.Context8080; import javax.swing.*; -import javax.swing.table.AbstractTableModel; import java.awt.*; import static net.emustudio.emulib.runtime.helpers.RadixUtils.formatByteHexString; @@ -22,7 +21,7 @@ public class StatusPanel extends JPanel { private final CpuImpl cpu; private final EmulatorEngine engine; private final Context8080 context; - private final AbstractTableModel flagModel; + private final FlagsModel flagModel; private final JLabel lblFrequency = new JLabel("0,0 kHz"); private final JLabel lblRun = new JLabel("Stopped"); @@ -85,23 +84,34 @@ public void internalStateChanged() { } public void updateGUI() { + final int regA = engine.regs[EmulatorEngine.REG_A]; + final int regB = engine.regs[EmulatorEngine.REG_B]; + final int regC = engine.regs[EmulatorEngine.REG_C]; + final int regD = engine.regs[EmulatorEngine.REG_D]; + final int regE = engine.regs[EmulatorEngine.REG_E]; + final int regH = engine.regs[EmulatorEngine.REG_H]; + final int regL = engine.regs[EmulatorEngine.REG_L]; + final int sp = engine.SP; + final int pc = engine.PC; + final int flags = engine.flags; + final RunState state = runState; SwingUtilities.invokeLater(() -> { - txtRegA.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_A])); - txtRegB.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_B])); - txtRegC.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_C])); - txtRegBC.setText(formatWordHexString((short) engine.regs[EmulatorEngine.REG_B], (short) engine.regs[EmulatorEngine.REG_C])); - txtRegD.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_D])); - txtRegE.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_E])); - txtRegDE.setText(formatWordHexString((short) engine.regs[EmulatorEngine.REG_D], (short) engine.regs[EmulatorEngine.REG_E])); - txtRegH.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_H])); - txtRegL.setText(formatByteHexString(engine.regs[EmulatorEngine.REG_L])); - txtRegHL.setText(formatWordHexString((short) engine.regs[EmulatorEngine.REG_H], (short) engine.regs[EmulatorEngine.REG_L])); - txtRegSP.setText(formatWordHexString(engine.SP)); - txtRegPC.setText(formatWordHexString(engine.PC)); - txtFlags.setText(formatByteHexString(engine.flags)); - flagModel.fireTableDataChanged(); - lblRun.setText(runState.toString()); - spnFrequency.setEnabled(runState != RunState.STATE_RUNNING); + txtRegA.setText(formatByteHexString(regA)); + txtRegB.setText(formatByteHexString(regB)); + txtRegC.setText(formatByteHexString(regC)); + txtRegBC.setText(formatWordHexString((short) regB, (short) regC)); + txtRegD.setText(formatByteHexString(regD)); + txtRegE.setText(formatByteHexString(regE)); + txtRegDE.setText(formatWordHexString((short) regD, (short) regE)); + txtRegH.setText(formatByteHexString(regH)); + txtRegL.setText(formatByteHexString(regL)); + txtRegHL.setText(formatWordHexString((short) regH, (short) regL)); + txtRegSP.setText(formatWordHexString(sp)); + txtRegPC.setText(formatWordHexString(pc)); + txtFlags.setText(formatByteHexString(flags)); + flagModel.update(flags); + lblRun.setText(state.toString()); + spnFrequency.setEnabled(state != RunState.STATE_RUNNING); }); } diff --git a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java index 0bed76e99..828863ae3 100644 --- a/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java +++ b/plugins/cpu/ssem-cpu/src/main/java/net/emustudio/plugins/cpu/ssem/gui/CpuPanel.java @@ -103,43 +103,52 @@ public void runStateChanged(CPU.RunState rs) { @Override public void internalStateChanged() { - SwingUtilities.invokeLater(() -> { - int acc = engine.Acc.get(); - int ci = engine.CI.get(); - - txtA.setText(String.format("%08x", acc)); - txtDecA.setText(String.format("%d", acc)); - txtCI.setText(String.format("%08x", ci / 4)); - txtDecCI.setText(String.format("%d", ci / 4)); - txtBinA.setText(formatBinary(acc)); - txtBinCI.setText(formatBinary(ci)); - - try { - Byte[] mCI = memory.read(ci, 4); - byte line = (byte) NumberUtils.reverseBits(mCI[0] & 0b11111000, 8); - Byte[] mLine = memory.read(line * 4, 4); - - txtMCI.setText(String.format("%08x", NumberUtils.readInt(mCI, NumberUtils.Strategy.REVERSE_BITS))); - txtLine.setText(String.format("%02x", line)); - txtMLine.setText(String.format("%08x", NumberUtils.readInt(mLine, NumberUtils.Strategy.REVERSE_BITS))); - - txtDecMCI.setText(String.format("%d", NumberUtils.readInt(mCI, NumberUtils.Strategy.REVERSE_BITS))); - txtDecLine.setText(String.format("%d", line)); - txtDecMLine.setText(String.format("%d", NumberUtils.readInt(mLine, NumberUtils.Strategy.REVERSE_BITS))); - - txtBinMCI.setText(formatBinary(NumberUtils.readInt(mCI, NumberUtils.Strategy.BIG_ENDIAN))); - txtBinLine.setText(formatBinary(line, 8)); - txtBinMLine.setText(formatBinary(NumberUtils.readInt(mLine, NumberUtils.Strategy.BIG_ENDIAN))); - } catch (IndexOutOfBoundsException e) { - txtLine.setText("?"); - txtDecLine.setText("?"); - txtMCI.setText("?"); - txtDecMCI.setText("?"); - txtMLine.setText("?"); - txtDecMLine.setText("?"); - txtBinMCI.setText("?"); - txtBinMLine.setText("?"); + final int acc = engine.Acc.get(); + final int ci = engine.CI.get(); + final String accHex = String.format("%08x", acc); + final String accDec = String.format("%d", acc); + final String ciHex = String.format("%08x", ci / 4); + final String ciDec = String.format("%d", ci / 4); + final String accBinary = formatBinary(acc); + final String ciBinary = formatBinary(ci); + final String[] memoryValues = new String[9]; + + try { + Byte[] mCI = memory.read(ci, 4); + byte line = (byte) NumberUtils.reverseBits(mCI[0] & 0b11111000, 8); + Byte[] mLine = memory.read(line * 4, 4); + + memoryValues[0] = String.format("%08x", NumberUtils.readInt(mCI, NumberUtils.Strategy.REVERSE_BITS)); + memoryValues[1] = String.format("%02x", line); + memoryValues[2] = String.format("%08x", NumberUtils.readInt(mLine, NumberUtils.Strategy.REVERSE_BITS)); + memoryValues[3] = String.format("%d", NumberUtils.readInt(mCI, NumberUtils.Strategy.REVERSE_BITS)); + memoryValues[4] = String.format("%d", line); + memoryValues[5] = String.format("%d", NumberUtils.readInt(mLine, NumberUtils.Strategy.REVERSE_BITS)); + memoryValues[6] = formatBinary(NumberUtils.readInt(mCI, NumberUtils.Strategy.BIG_ENDIAN)); + memoryValues[7] = formatBinary(line, 8); + memoryValues[8] = formatBinary(NumberUtils.readInt(mLine, NumberUtils.Strategy.BIG_ENDIAN)); + } catch (IndexOutOfBoundsException e) { + for (int i = 0; i < memoryValues.length; i++) { + memoryValues[i] = "?"; } + } + + SwingUtilities.invokeLater(() -> { + txtA.setText(accHex); + txtDecA.setText(accDec); + txtCI.setText(ciHex); + txtDecCI.setText(ciDec); + txtBinA.setText(accBinary); + txtBinCI.setText(ciBinary); + txtMCI.setText(memoryValues[0]); + txtLine.setText(memoryValues[1]); + txtMLine.setText(memoryValues[2]); + txtDecMCI.setText(memoryValues[3]); + txtDecLine.setText(memoryValues[4]); + txtDecMLine.setText(memoryValues[5]); + txtBinMCI.setText(memoryValues[6]); + txtBinLine.setText(memoryValues[7]); + txtBinMLine.setText(memoryValues[8]); }); } diff --git a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/FlagsModel.java b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/FlagsModel.java index d486b7d5b..aa11ab9e2 100644 --- a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/FlagsModel.java +++ b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/FlagsModel.java @@ -39,18 +39,22 @@ public Object getValueAt(int rowIndex, int columnIndex) { @Override public void fireTableDataChanged() { - int F; + int flagsValue; if (registersSet == 0) { - F = cpu.flags; + flagsValue = cpu.flags; } else { - F = cpu.flags2; + flagsValue = cpu.flags2; } - flagsI[0] = ((F & EmulatorEngine.FLAG_S) != 0) ? 1 : 0; - flagsI[1] = ((F & EmulatorEngine.FLAG_Z) != 0) ? 1 : 0; - flagsI[2] = ((F & EmulatorEngine.FLAG_H) != 0) ? 1 : 0; - flagsI[3] = ((F & EmulatorEngine.FLAG_PV) != 0) ? 1 : 0; - flagsI[4] = ((F & EmulatorEngine.FLAG_N) != 0) ? 1 : 0; - flagsI[5] = ((F & EmulatorEngine.FLAG_C) != 0) ? 1 : 0; + update(flagsValue); + } + + void update(int flagsValue) { + flagsI[0] = ((flagsValue & EmulatorEngine.FLAG_S) != 0) ? 1 : 0; + flagsI[1] = ((flagsValue & EmulatorEngine.FLAG_Z) != 0) ? 1 : 0; + flagsI[2] = ((flagsValue & EmulatorEngine.FLAG_H) != 0) ? 1 : 0; + flagsI[3] = ((flagsValue & EmulatorEngine.FLAG_PV) != 0) ? 1 : 0; + flagsI[4] = ((flagsValue & EmulatorEngine.FLAG_N) != 0) ? 1 : 0; + flagsI[5] = ((flagsValue & EmulatorEngine.FLAG_C) != 0) ? 1 : 0; super.fireTableDataChanged(); } diff --git a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/StatusPanel.java b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/StatusPanel.java index 609096302..1ba6a22ac 100644 --- a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/StatusPanel.java +++ b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/gui/StatusPanel.java @@ -119,42 +119,65 @@ private String byteHex(int what) { } public void updateGUI() { + EmulatorEngine engine = cpu.getEngine(); + final int regA1 = engine.regs[EmulatorEngine.REG_A]; + final int flags1 = engine.flags; + final int regB1 = engine.regs[EmulatorEngine.REG_B]; + final int regC1 = engine.regs[EmulatorEngine.REG_C]; + final int regD1 = engine.regs[EmulatorEngine.REG_D]; + final int regE1 = engine.regs[EmulatorEngine.REG_E]; + final int regH1 = engine.regs[EmulatorEngine.REG_H]; + final int regL1 = engine.regs[EmulatorEngine.REG_L]; + final int regA2 = engine.regs2[EmulatorEngine.REG_A]; + final int flags2 = engine.flags2; + final int regB2 = engine.regs2[EmulatorEngine.REG_B]; + final int regC2 = engine.regs2[EmulatorEngine.REG_C]; + final int regD2 = engine.regs2[EmulatorEngine.REG_D]; + final int regE2 = engine.regs2[EmulatorEngine.REG_E]; + final int regH2 = engine.regs2[EmulatorEngine.REG_H]; + final int regL2 = engine.regs2[EmulatorEngine.REG_L]; + final int sp = engine.SP; + final int pc = engine.PC; + final int ix = engine.IX; + final int iy = engine.IY; + final int i = engine.I; + final int r = engine.R; + final CPU.RunState state = runState; SwingUtilities.invokeLater(() -> { - EmulatorEngine engine = cpu.getEngine(); - txtA1.setText(byteHex(engine.regs[EmulatorEngine.REG_A])); - txtF1.setText(byteHex(engine.flags)); - txtB1.setText(byteHex(engine.regs[EmulatorEngine.REG_B])); - txtC1.setText(byteHex(engine.regs[EmulatorEngine.REG_C])); - txtBC1.setText(wordHex(engine.regs[EmulatorEngine.REG_B], engine.regs[EmulatorEngine.REG_C])); - txtD1.setText(byteHex(engine.regs[EmulatorEngine.REG_D])); - txtE1.setText(byteHex(engine.regs[EmulatorEngine.REG_E])); - txtDE1.setText(wordHex(engine.regs[EmulatorEngine.REG_D], engine.regs[EmulatorEngine.REG_E])); - txtH1.setText(byteHex(engine.regs[EmulatorEngine.REG_H])); - txtL1.setText(byteHex(engine.regs[EmulatorEngine.REG_L])); - txtHL1.setText(wordHex(engine.regs[EmulatorEngine.REG_H], engine.regs[EmulatorEngine.REG_L])); - flagModel1.fireTableDataChanged(); - txtA2.setText(byteHex(engine.regs2[EmulatorEngine.REG_A])); - txtF2.setText(byteHex(engine.flags2)); - txtB2.setText(byteHex(engine.regs2[EmulatorEngine.REG_B])); - txtC2.setText(byteHex(engine.regs2[EmulatorEngine.REG_C])); - txtBC2.setText(wordHex(engine.regs2[EmulatorEngine.REG_B], engine.regs2[EmulatorEngine.REG_C])); - txtD2.setText(byteHex(engine.regs2[EmulatorEngine.REG_D])); - txtE2.setText(byteHex(engine.regs2[EmulatorEngine.REG_E])); - txtDE2.setText(wordHex(engine.regs2[EmulatorEngine.REG_D], engine.regs2[EmulatorEngine.REG_E])); - txtH2.setText(byteHex(engine.regs2[EmulatorEngine.REG_H])); - txtL2.setText(byteHex(engine.regs2[EmulatorEngine.REG_L])); - txtHL2.setText(wordHex(engine.regs2[EmulatorEngine.REG_H], engine.regs2[EmulatorEngine.REG_L])); - flagModel2.fireTableDataChanged(); - - txtSP.setText(formatWordHexString(engine.SP)); - txtPC.setText(formatWordHexString(engine.PC)); - txtIX.setText(formatWordHexString(engine.IX)); - txtIY.setText(formatWordHexString(engine.IY)); - txtI.setText(formatByteHexString(engine.I)); - txtR.setText(formatByteHexString(engine.R)); - - lblRunState.setText(runState.toString()); - spnFrequency.setEnabled(runState != CPU.RunState.STATE_RUNNING); + txtA1.setText(byteHex(regA1)); + txtF1.setText(byteHex(flags1)); + txtB1.setText(byteHex(regB1)); + txtC1.setText(byteHex(regC1)); + txtBC1.setText(wordHex(regB1, regC1)); + txtD1.setText(byteHex(regD1)); + txtE1.setText(byteHex(regE1)); + txtDE1.setText(wordHex(regD1, regE1)); + txtH1.setText(byteHex(regH1)); + txtL1.setText(byteHex(regL1)); + txtHL1.setText(wordHex(regH1, regL1)); + flagModel1.update(flags1); + txtA2.setText(byteHex(regA2)); + txtF2.setText(byteHex(flags2)); + txtB2.setText(byteHex(regB2)); + txtC2.setText(byteHex(regC2)); + txtBC2.setText(wordHex(regB2, regC2)); + txtD2.setText(byteHex(regD2)); + txtE2.setText(byteHex(regE2)); + txtDE2.setText(wordHex(regD2, regE2)); + txtH2.setText(byteHex(regH2)); + txtL2.setText(byteHex(regL2)); + txtHL2.setText(wordHex(regH2, regL2)); + flagModel2.update(flags2); + + txtSP.setText(formatWordHexString(sp)); + txtPC.setText(formatWordHexString(pc)); + txtIX.setText(formatWordHexString(ix)); + txtIY.setText(formatWordHexString(iy)); + txtI.setText(formatByteHexString(i)); + txtR.setText(formatByteHexString(r)); + + lblRunState.setText(state.toString()); + spnFrequency.setEnabled(state != CPU.RunState.STATE_RUNNING); }); } diff --git a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java index c8582bcfa..7769615d2 100644 --- a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java +++ b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java @@ -4,6 +4,8 @@ import net.emustudio.emulib.runtime.ui.GUI; import net.emustudio.plugins.device.adm3a.api.Display; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.*; @@ -14,6 +16,7 @@ import static java.awt.RenderingHints.*; public class DisplayCanvas extends Canvas implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(DisplayCanvas.class); private static final Color FOREGROUND = new Color(255, 255, 255); private static final Color BACKGROUND = Color.BLACK; private final Timer repaintTimer; @@ -21,6 +24,7 @@ public class DisplayCanvas extends Canvas implements AutoCloseable { private final AtomicBoolean painting = new AtomicBoolean(false); private volatile DisplayFont displayFont; private volatile Dimension size = new Dimension(0, 0); + private volatile Dimension minimumSize = new Dimension(0, 0); public DisplayCanvas(DisplayFont displayFont, Display display) { this.display = Objects.requireNonNull(display); @@ -48,19 +52,27 @@ public Dimension getPreferredSize() { @Override public Dimension getMinimumSize() { - return this.size; + return new Dimension(minimumSize); } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); - this.size = getSize(); + updateSize(); } @Override public void setBounds(Rectangle r) { super.setBounds(r); - this.size = getSize(); + updateSize(); + } + + private void updateSize() { + Dimension newSize = getSize(); + if (minimumSize.width == 0 && minimumSize.height == 0) { + minimumSize = new Dimension(newSize); + } + size = newSize; } public synchronized void setDisplayFont(DisplayFont font) { @@ -82,17 +94,18 @@ public class PaintCycle implements Runnable { @Override public void run() { strategy = getBufferStrategy(); - if (painting.get()) { + if (painting.get() && strategy != null) { paint(); } } protected void paint() { Dimension dimension = size; + Graphics2D graphics = null; try { do { do { - Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics(); + graphics = (Graphics2D) strategy.getDrawGraphics(); graphics.setColor(BACKGROUND); graphics.fillRect(0, 0, dimension.width, dimension.height); @@ -115,10 +128,20 @@ protected void paint() { } paintCursor(graphics, lineHeight); graphics.dispose(); + graphics = null; } while (strategy.contentsRestored()); strategy.show(); } while (strategy.contentsLost()); - } catch (Exception ignored) { + } catch (IllegalStateException e) { + if (painting.get()) { + LOGGER.warn("Could not paint ADM-3A display", e); + } + } catch (RuntimeException e) { + LOGGER.warn("Could not paint ADM-3A display", e); + } finally { + if (graphics != null) { + graphics.dispose(); + } } } diff --git a/plugins/device/audio-ay3_8910-chip/src/main/java/net/emustudio/plugins/device/ay38910/gui/WaveformPanel.java b/plugins/device/audio-ay3_8910-chip/src/main/java/net/emustudio/plugins/device/ay38910/gui/WaveformPanel.java index d2ffc9295..ead56b83f 100644 --- a/plugins/device/audio-ay3_8910-chip/src/main/java/net/emustudio/plugins/device/ay38910/gui/WaveformPanel.java +++ b/plugins/device/audio-ay3_8910-chip/src/main/java/net/emustudio/plugins/device/ay38910/gui/WaveformPanel.java @@ -12,6 +12,8 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.image.BufferedImage; import java.util.Objects; import java.util.concurrent.Executors; @@ -26,6 +28,8 @@ final class WaveformPanel extends JPanel { private final Ay38910Chip chip; private volatile BufferedImage offscreenImage; + private volatile Dimension renderSize = new Dimension(); + private volatile boolean displayable; private ScheduledExecutorService refreshExecutor; WaveformPanel(Ay38910Chip chip) { @@ -33,6 +37,25 @@ final class WaveformPanel extends JPanel { setOpaque(true); setBackground(BACKGROUND); setPreferredSize(new Dimension(720, 320)); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent event) { + renderSize = event.getComponent().getSize(); + } + }); + } + + @Override + public void addNotify() { + super.addNotify(); + displayable = true; + renderSize = getSize(); + } + + @Override + public void removeNotify() { + displayable = false; + super.removeNotify(); } synchronized void startRefreshing(int refreshMs) { @@ -103,12 +126,13 @@ private int sampleToY(short sample, int midY, int drawableHeight) { private void refreshSamples() { short[] samples = chip.copyRecentWaveform(); - if (!isDisplayable()) { + if (!displayable) { return; } - int width = getWidth(); - int height = getHeight(); + Dimension size = renderSize; + int width = size.width; + int height = size.height; if (width <= 0 || height <= 0) { return; } diff --git a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java index 346a83d1e..58324ebbc 100644 --- a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java +++ b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java @@ -4,6 +4,8 @@ import net.emustudio.plugins.device.vt100.VideoAttribute; import net.emustudio.plugins.device.vt100.api.Display; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.*; @@ -15,12 +17,14 @@ import static java.awt.RenderingHints.*; public class DisplayCanvas extends Canvas implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(DisplayCanvas.class); private static final Color FOREGROUND = new Color(255, 255, 255); private static final Color BACKGROUND = Color.BLACK; private final Timer repaintTimer; private final Display display; // not owning this private final AtomicBoolean painting = new AtomicBoolean(false); private volatile Dimension size = new Dimension(0, 0); + private volatile Dimension minimumSize = new Dimension(0, 0); public DisplayCanvas(Display display) { this.display = Objects.requireNonNull(display); @@ -50,19 +54,27 @@ public Dimension getPreferredSize() { @Override public Dimension getMinimumSize() { - return this.size; + return new Dimension(minimumSize); } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); - this.size = getSize(); + updateSize(); } @Override public void setBounds(Rectangle r) { super.setBounds(r); - this.size = getSize(); + updateSize(); + } + + private void updateSize() { + Dimension newSize = getSize(); + if (minimumSize.width == 0 && minimumSize.height == 0) { + minimumSize = new Dimension(newSize); + } + size = newSize; } @Override @@ -77,17 +89,18 @@ public class PaintCycle implements Runnable { @Override public void run() { strategy = getBufferStrategy(); - if (painting.get()) { + if (painting.get() && strategy != null) { paint(); } } protected void paint() { Dimension dimension = size; + Graphics2D graphics = null; try { do { do { - Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics(); + graphics = (Graphics2D) strategy.getDrawGraphics(); graphics.setColor(BACKGROUND); graphics.fillRect(0, 0, dimension.width, dimension.height); @@ -176,10 +189,20 @@ protected void paint() { graphics.setFont(normalFont); paintCursor(graphics, lineHeight, charWidth); graphics.dispose(); + graphics = null; } while (strategy.contentsRestored()); strategy.show(); } while (strategy.contentsLost()); - } catch (Exception ignored) { + } catch (IllegalStateException e) { + if (painting.get()) { + LOGGER.warn("Could not paint VT100 display", e); + } + } catch (RuntimeException e) { + LOGGER.warn("Could not paint VT100 display", e); + } finally { + if (graphics != null) { + graphics.dispose(); + } } } diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java index 67c69dfe4..07af1cda8 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java @@ -48,6 +48,7 @@ public class DisplayCanvas extends Canvas implements AutoCloseable { private static final Color KEYBOARD_OVERLAY_COLOR = new Color(0, 0, 0, 127); private volatile Dimension size; + private final Dimension minimumSize; private final ULA ula; private final TimingProfile timing; @@ -76,6 +77,7 @@ public DisplayCanvas(ULA ula, TimingProfile timing, KeyboardCanvas keyboardCanva (int) (ZOOM * screenImageWidth + 2 * MARGIN), (int) (ZOOM * screenImageHeight + 2 * MARGIN) ); + this.minimumSize = new Dimension(size); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { @@ -224,7 +226,7 @@ public Dimension getPreferredSize() { @Override public Dimension getMinimumSize() { - return this.size; + return new Dimension(minimumSize); } @Override diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java index 873732786..3d487b450 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayWindow.java @@ -44,6 +44,7 @@ public class DisplayWindow extends DialogBase { private final Dialogs dialogs; private final GUI gui; private final KeyboardCanvas keyboardCanvas; + private final KeyboardDispatcher keyboardDispatcher; private JButton btnRecord; private RecordingSession recordingSession; @@ -65,11 +66,10 @@ public void windowOpened(WindowEvent winEvt) { } public void windowClosed(WindowEvent winEvt) { - stopRecording(false); - canvas.close(); + closeResources(); } }); - KeyboardDispatcher keyboardDispatcher = new KeyboardDispatcher(this); + keyboardDispatcher = new KeyboardDispatcher(this); KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); manager.addKeyEventDispatcher(keyboardDispatcher); @@ -84,9 +84,15 @@ protected boolean shouldCloseOnEscape() { } public void destroy() { + closeResources(); + dispose(); + } + + private void closeResources() { stopRecording(false); canvas.close(); - dispose(); + KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyboardDispatcher); + keyboardDispatcher.close(); } @Override From 844e0eec8bba7d3de39b1f4d226eeff8f005501a Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Fri, 7 Aug 2026 19:49:31 +0200 Subject: [PATCH 2/9] [#408] Optimize UI I/O and terminal rendering --- .../gui/dialogs/SchemaEditorDialog.java | 37 +++++++--- .../gui/schema/SchemaPreviewPanel.java | 50 +++++++++---- .../gui/dialogs/SchemaEditorDialogTest.java | 11 +++ .../gui/schema/SchemaPreviewPanelTest.java | 13 +++- .../device/adm3a/gui/DisplayCanvas.java | 64 +++++++++++++---- .../device/vt100/gui/DisplayCanvas.java | 72 +++++++++++++++---- .../bytemem/gui/actions/DumpMemoryAction.java | 50 ++++++++----- .../bytemem/gui/actions/LoadImageAction.java | 28 ++++++-- .../gui/actions/DumpMemoryActionTest.java | 12 ++++ .../ram/gui/actions/DumpMemoryAction.java | 42 +++++++---- .../ram/gui/actions/LoadImageAction.java | 28 ++++++-- .../rasp/gui/actions/DumpMemoryAction.java | 42 +++++++---- .../rasp/gui/actions/LoadImageAction.java | 28 ++++++-- .../gui/actions/DumpMemoryActionTest.java | 17 ++++- .../rasp/gui/actions/LoadImageActionTest.java | 19 ++++- .../ssem/gui/actions/DumpMemoryAction.java | 56 ++++++++++----- .../ssem/gui/actions/LoadImageAction.java | 50 +++++++++---- .../gui/actions/DumpMemoryActionTest.java | 17 ++++- .../ssem/gui/actions/LoadImageActionTest.java | 14 +++- 19 files changed, 497 insertions(+), 153 deletions(-) diff --git a/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java b/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java index 6076bef49..cfc36560a 100644 --- a/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java +++ b/application/src/main/java/net/emustudio/application/gui/dialogs/SchemaEditorDialog.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import static net.emustudio.application.gui.framework.EmuStudioGui.*; import static net.emustudio.application.settings.ConfigFiles.listPluginFiles; @@ -38,6 +39,7 @@ public class SchemaEditorDialog extends DialogBase implements KeyListener { private final GUI gui; private final DrawingPanel panel; + private final AtomicInteger pluginLoadId = new AtomicInteger(); private boolean buttonSelected = false; private JToggleButton btnBidirection; private JToggleButton btnCPU; @@ -354,18 +356,37 @@ private void btnBidirectionActionPerformed(ActionEvent evt) { } private void resetComboWithPluginFiles(PLUGIN_TYPE pluginType) { - try { - List pluginFiles = listPluginFiles(pluginType); - cmbPlugin.setModel(new PluginComboModel(pluginFiles)); - selectFirstPlugin(); - } catch (IOException e) { - LOGGER.error("Could not load CPU plugin files", e); - cmbPlugin.setModel(EMPTY_MODEL); - } + int loadId = pluginLoadId.incrementAndGet(); + cmbPlugin.setModel(EMPTY_MODEL); + cmbPlugin.setEnabled(false); + + new SwingWorker, Void>() { + @Override + protected List doInBackground() throws IOException { + return listPluginFiles(pluginType); + } + + @Override + protected void done() { + if (loadId != pluginLoadId.get()) { + return; + } + cmbPlugin.setEnabled(true); + try { + cmbPlugin.setModel(new PluginComboModel(get())); + selectFirstPlugin(); + } catch (Exception e) { + LOGGER.error("Could not load plugin files", e); + cmbPlugin.setModel(EMPTY_MODEL); + } + } + }.execute(); } private boolean checkUnsetDrawingTool() { if (buttonSelected) { + pluginLoadId.incrementAndGet(); + cmbPlugin.setEnabled(true); cmbPlugin.setModel(EMPTY_MODEL); groupDraw.clearSelection(); panel.setTool(Tool.TOOL_NOTHING, null); diff --git a/application/src/main/java/net/emustudio/application/gui/schema/SchemaPreviewPanel.java b/application/src/main/java/net/emustudio/application/gui/schema/SchemaPreviewPanel.java index 01b0a8b65..4a72374d6 100644 --- a/application/src/main/java/net/emustudio/application/gui/schema/SchemaPreviewPanel.java +++ b/application/src/main/java/net/emustudio/application/gui/schema/SchemaPreviewPanel.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; public class SchemaPreviewPanel extends JPanel { private final static Logger LOGGER = LoggerFactory.getLogger(SchemaPreviewPanel.class); @@ -43,6 +44,7 @@ public class SchemaPreviewPanel extends JPanel { private int topFactor = 0; private boolean panelResized = false; + private final AtomicBoolean savingImage = new AtomicBoolean(); public SchemaPreviewPanel(Schema schema, Dialogs dialogs) { this.dialogs = Objects.requireNonNull(dialogs); @@ -103,28 +105,46 @@ public void saveSchemaImage() { "Save schema image", "Save", currentDirectory, true, new FileExtensionsFilter("PNG image", "png") ).ifPresent(path -> { + if (!savingImage.compareAndSet(false, true)) { + return; + } lastImageFile = path.toFile(); - // Save the image BufferedImage bi = new BufferedImage(schemaWidth, schemaHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = bi.createGraphics(); - graphics.setBackground(Color.WHITE); - graphics.fillRect(0, 0, schemaWidth, schemaHeight); - RenderingHints hints = new RenderingHints(Map.of( - RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON, - RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY, - RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON - )); - - graphics.setRenderingHints(hints); - paintComponent(graphics); try { - ImageIO.write(bi, "png", lastImageFile); - } catch (IOException e) { - LOGGER.error("Could not save schema image.", e); - dialogs.showError("Could not save schema image. Please see log file for details.", "Save schema image"); + graphics.setBackground(Color.WHITE); + graphics.fillRect(0, 0, schemaWidth, schemaHeight); + RenderingHints hints = new RenderingHints(Map.of( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON, + RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY, + RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON + )); + graphics.setRenderingHints(hints); + paintComponent(graphics); + } finally { + graphics.dispose(); } + + new SwingWorker() { + @Override + protected Void doInBackground() throws IOException { + ImageIO.write(bi, "png", lastImageFile); + return null; + } + + @Override + protected void done() { + savingImage.set(false); + try { + get(); + } catch (Exception e) { + LOGGER.error("Could not save schema image.", e); + dialogs.showError("Could not save schema image. Please see log file for details.", "Save schema image"); + } + } + }.execute(); }); } else { dialogs.showError("Could not save schema image: schema is not set.", "Save schema image"); diff --git a/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java b/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java index d0f3a42c0..8db42f1ae 100644 --- a/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java +++ b/application/src/test/java/net/emustudio/application/gui/dialogs/SchemaEditorDialogTest.java @@ -269,6 +269,7 @@ private void assertPluginButtonBehavior(String tooltip, PLUGIN_TYPE pluginType, triggerButton(button); + waitForPluginLoad(pluginCombo); assertEquals(1, onEdt(pluginCombo::getItemCount).intValue()); assertEquals(displayName(pluginFile), onEdt(() -> String.valueOf(pluginCombo.getSelectedItem()))); assertEquals(expectedTool, getDrawingModel(dialog).drawTool); @@ -298,6 +299,16 @@ private boolean readButtonSelected(SchemaEditorDialog dialog) throws Exception { return getField(dialog, "buttonSelected", Boolean.class); } + private void waitForPluginLoad(JComboBox pluginCombo) throws Exception { + for (int i = 0; i < 200; i++) { + if (onEdt(pluginCombo::getItemCount) == 1) { + return; + } + Thread.sleep(10); + } + throw new AssertionError("Plugin list did not load"); + } + private void setDrawingTool(SchemaEditorDialog dialog, DrawingPanel.Tool tool, String fileName) throws Exception { runOnEdt(() -> { try { diff --git a/application/src/test/java/net/emustudio/application/gui/schema/SchemaPreviewPanelTest.java b/application/src/test/java/net/emustudio/application/gui/schema/SchemaPreviewPanelTest.java index 8ecfc5c90..5577f984b 100644 --- a/application/src/test/java/net/emustudio/application/gui/schema/SchemaPreviewPanelTest.java +++ b/application/src/test/java/net/emustudio/application/gui/schema/SchemaPreviewPanelTest.java @@ -51,8 +51,7 @@ public void paintComputesPreferredSizeAndSaveWritesImage() throws Exception { panel.saveSchemaImage(); - assertTrue(Files.exists(output)); - assertTrue(Files.size(output) > 0); + assertTrue(waitForImage(output)); } } @@ -74,4 +73,14 @@ private CompilerElement findElement(Schema schema) { } throw new AssertionError("Missing compiler element"); } + + private boolean waitForImage(Path image) throws Exception { + for (int i = 0; i < 200; i++) { + if (Files.size(image) > 0) { + return true; + } + Thread.sleep(10); + } + return false; + } } diff --git a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java index 7769615d2..42b7b5b8e 100644 --- a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java +++ b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/gui/DisplayCanvas.java @@ -10,6 +10,7 @@ import javax.swing.*; import java.awt.*; import java.awt.image.BufferStrategy; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -90,17 +91,53 @@ public void close() { public class PaintCycle implements Runnable { private BufferStrategy strategy; + private char[] videoMemory = new char[0]; + private Point cursorPoint = new Point(); + private Dimension dimension = new Dimension(); + private int columns; + private int rows; + private Font font; + private DisplayFont renderFont; @Override public void run() { strategy = getBufferStrategy(); - if (painting.get() && strategy != null) { + if (painting.get() && strategy != null && refreshFrame()) { paint(); } } + private boolean refreshFrame() { + char[] newVideoMemory = display.getVideoMemory(); + Point newCursorPoint = Objects.requireNonNullElse(display.getCursorPoint(), new Point()); + Dimension newDimension = size; + int newColumns = display.getColumns(); + int newRows = display.getRows(); + Font newFont = getFont(); + DisplayFont newRenderFont = displayFont; + + if (Arrays.equals(videoMemory, newVideoMemory) + && cursorPoint.equals(newCursorPoint) + && dimension.equals(newDimension) + && columns == newColumns + && rows == newRows + && Objects.equals(font, newFont) + && renderFont == newRenderFont) { + return false; + } + + videoMemory = newVideoMemory == null ? new char[0] : Arrays.copyOf(newVideoMemory, newVideoMemory.length); + cursorPoint = new Point(newCursorPoint); + dimension = new Dimension(newDimension); + columns = newColumns; + rows = newRows; + font = newFont; + renderFont = newRenderFont; + return true; + } + protected void paint() { - Dimension dimension = size; + Dimension dimension = this.dimension; Graphics2D graphics = null; try { do { @@ -108,25 +145,26 @@ protected void paint() { graphics = (Graphics2D) strategy.getDrawGraphics(); graphics.setColor(BACKGROUND); graphics.fillRect(0, 0, dimension.width, dimension.height); + graphics.setFont(font); - int lineHeight = graphics.getFontMetrics().getHeight() + displayFont.yLineHeightMultiplierOffset; + int lineHeight = graphics.getFontMetrics().getHeight() + renderFont.yLineHeightMultiplierOffset; graphics.setColor(FOREGROUND); graphics.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY); - graphics.setRenderingHint(KEY_FRACTIONALMETRICS, displayFont.fractionalMetrics); + graphics.setRenderingHint(KEY_FRACTIONALMETRICS, renderFont.fractionalMetrics); graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC); graphics.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY); - graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, displayFont.textAntiAliasing); - graphics.setRenderingHint(KEY_ANTIALIASING, displayFont.antiAliasing); + graphics.setRenderingHint(KEY_TEXT_ANTIALIASING, renderFont.textAntiAliasing); + graphics.setRenderingHint(KEY_ANTIALIASING, renderFont.antiAliasing); graphics.setRenderingHint(KEY_STROKE_CONTROL, VALUE_STROKE_NORMALIZE); - for (int y = 0; y < display.getRows(); y++) { + for (int y = 0; y < rows; y++) { graphics.drawChars( - display.getVideoMemory(), - y * display.getColumns(), - display.getColumns(), + videoMemory, + y * columns, + columns, 1, (y + 1) * lineHeight); } - paintCursor(graphics, lineHeight); + paintCursor(graphics, lineHeight, cursorPoint, renderFont); graphics.dispose(); graphics = null; } while (strategy.contentsRestored()); @@ -145,9 +183,7 @@ protected void paint() { } } - private void paintCursor(Graphics graphics, int lineHeight) { - Point cursorPoint = display.getCursorPoint(); - + private void paintCursor(Graphics graphics, int lineHeight, Point cursorPoint, DisplayFont displayFont) { graphics.setXORMode(BACKGROUND); graphics.setColor(FOREGROUND); diff --git a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java index 58324ebbc..90adfdffc 100644 --- a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java +++ b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java @@ -11,6 +11,7 @@ import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.BufferStrategy; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -85,17 +86,61 @@ public void close() { public class PaintCycle implements Runnable { private BufferStrategy strategy; + private char[] videoMemory = new char[0]; + private int[] attributeMemory = new int[0]; + private Point cursorPoint = new Point(); + private Dimension dimension = new Dimension(); + private int columns; + private int rows; + private Font normalFont; + private Font boldFont; + private Font italicFont; + private Font boldItalicFont; @Override public void run() { strategy = getBufferStrategy(); - if (painting.get() && strategy != null) { + if (painting.get() && strategy != null && refreshFrame()) { paint(); } } + private boolean refreshFrame() { + char[] newVideoMemory = display.getVideoMemory(); + int[] newAttributeMemory = display.getAttributeMemory(); + Point newCursorPoint = Objects.requireNonNullElse(display.getCursorPoint(), new Point()); + Dimension newDimension = size; + int newColumns = display.getColumns(); + int newRows = display.getRows(); + Font newNormalFont = getFont(); + + if (Arrays.equals(videoMemory, newVideoMemory) + && Arrays.equals(attributeMemory, newAttributeMemory) + && cursorPoint.equals(newCursorPoint) + && dimension.equals(newDimension) + && columns == newColumns + && rows == newRows + && Objects.equals(normalFont, newNormalFont)) { + return false; + } + + videoMemory = newVideoMemory == null ? new char[0] : Arrays.copyOf(newVideoMemory, newVideoMemory.length); + attributeMemory = newAttributeMemory == null ? null : Arrays.copyOf(newAttributeMemory, newAttributeMemory.length); + cursorPoint = new Point(newCursorPoint); + dimension = new Dimension(newDimension); + columns = newColumns; + rows = newRows; + if (!Objects.equals(normalFont, newNormalFont)) { + normalFont = newNormalFont; + boldFont = normalFont.deriveFont(Font.BOLD); + italicFont = normalFont.deriveFont(Font.ITALIC); + boldItalicFont = normalFont.deriveFont(Font.BOLD | Font.ITALIC); + } + return true; + } + protected void paint() { - Dimension dimension = size; + Dimension dimension = this.dimension; Graphics2D graphics = null; try { do { @@ -103,6 +148,7 @@ protected void paint() { graphics = (Graphics2D) strategy.getDrawGraphics(); graphics.setColor(BACKGROUND); graphics.fillRect(0, 0, dimension.width, dimension.height); + graphics.setFont(normalFont); int lineHeight = graphics.getFontMetrics().getHeight(); graphics.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY); @@ -113,20 +159,20 @@ protected void paint() { graphics.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); graphics.setRenderingHint(KEY_STROKE_CONTROL, VALUE_STROKE_NORMALIZE); - char[] videoMem = display.getVideoMemory(); - int[] attrMem = display.getAttributeMemory(); - int columns = display.getColumns(); - int rows = display.getRows(); + char[] videoMem = videoMemory; + int[] attrMem = attributeMemory; + int columns = this.columns; + int rows = this.rows; Rectangle2D fontRect = getFont().getStringBounds("M", graphics.getFontMetrics().getFontRenderContext()); double charWidth = fontRect.getWidth(); int fontAscent = graphics.getFontMetrics().getAscent(); boolean hasAttributes = attrMem != null && attrMem.length == videoMem.length; - Font normalFont = graphics.getFont(); - Font boldFont = normalFont.deriveFont(Font.BOLD); - Font italicFont = normalFont.deriveFont(Font.ITALIC); - Font boldItalicFont = normalFont.deriveFont(Font.BOLD | Font.ITALIC); + Font normalFont = this.normalFont; + Font boldFont = this.boldFont; + Font italicFont = this.italicFont; + Font boldItalicFont = this.boldItalicFont; for (int y = 0; y < rows; y++) { for (int x = 0; x < columns; x++) { @@ -187,7 +233,7 @@ protected void paint() { // Restore default font for cursor painting graphics.setFont(normalFont); - paintCursor(graphics, lineHeight, charWidth); + paintCursor(graphics, lineHeight, charWidth, cursorPoint); graphics.dispose(); graphics = null; } while (strategy.contentsRestored()); @@ -206,9 +252,7 @@ protected void paint() { } } - private void paintCursor(Graphics graphics, int lineHeight, double charWidth) { - Point cursorPoint = display.getCursorPoint(); - + private void paintCursor(Graphics graphics, int lineHeight, double charWidth, Point cursorPoint) { graphics.setXORMode(BACKGROUND); graphics.setColor(FOREGROUND); diff --git a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryAction.java b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryAction.java index 8d15fa1ea..c5b8f7b26 100644 --- a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryAction.java +++ b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryAction.java @@ -46,24 +46,42 @@ public void actionPerformed(ActionEvent e) { new FileExtensionsFilter("Binary dump", "bin")); dumpPath.ifPresent(path -> { - try { - if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { - try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { - for (int i = 0; i < context.getSize(); i++) { - out.write(String.format("%X:\t%02X\n", i, context.read(i))); - } - } - } else { - try (DataOutputStream ds = new DataOutputStream(new FileOutputStream(path.toFile()))) { - for (int i = 0; i < context.getSize(); i++) { - ds.writeByte(context.read(i) & 0xff); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws IOException { + dump(path); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + LOGGER.error("Memory dump could not be created", cause); + dialogs.showError("Memory dump could not be created: " + cause.getMessage() + ". Please see log file for more details."); } } - } catch (IOException ex) { - LOGGER.error("Memory dump could not be created", ex); - dialogs.showError("Memory dump could not be created: " + ex.getMessage() + ". Please see log file for more details."); - } + }.execute(); }); } + + private void dump(Path path) throws IOException { + if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { + try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { + for (int i = 0; i < context.getSize(); i++) { + out.write(String.format("%X:\t%02X\n", i, context.read(i))); + } + } + } else { + try (DataOutputStream ds = new DataOutputStream(new FileOutputStream(path.toFile()))) { + for (int i = 0; i < context.getSize(); i++) { + ds.writeByte(context.read(i) & 0xff); + } + } + } + } } diff --git a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/LoadImageAction.java b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/LoadImageAction.java index 002985575..b0a9e8d6c 100644 --- a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/LoadImageAction.java +++ b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/actions/LoadImageAction.java @@ -56,13 +56,27 @@ public void actionPerformed(ActionEvent e) { Loader loader = Loader.createLoader(path); Optional bank = askForMemoryBank(!loader.isMemoryAddressAware()); if (bank.isPresent()) { - try { - loader.load(path, context, bank.get()); - repaint.run(); - } catch (Exception ex) { - dialogs.showError("Could not load selected image file: " + ex.getMessage(), "Load image file"); - LOGGER.error("Could not load image file '{}'", path, ex); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + loader.load(path, context, bank.get()); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + repaint.run(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + dialogs.showError("Could not load selected image file: " + cause.getMessage(), "Load image file"); + LOGGER.error("Could not load image file '{}'", path, cause); + } + } + }.execute(); } }); } diff --git a/plugins/memory/byte-mem/src/test/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryActionTest.java b/plugins/memory/byte-mem/src/test/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryActionTest.java index 858da1cc3..44f552190 100644 --- a/plugins/memory/byte-mem/src/test/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryActionTest.java +++ b/plugins/memory/byte-mem/src/test/java/net/emustudio/plugins/memory/bytemem/gui/actions/DumpMemoryActionTest.java @@ -48,6 +48,7 @@ public void testDumpToTextFile() throws Exception { replay(dialogs); DumpMemoryAction action = new DumpMemoryAction(dialogs, context); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); String content = Files.readString(txtFile.toPath()); assertTrue(content.contains("AA")); assertTrue(content.contains("BB")); @@ -63,6 +64,7 @@ public void testDumpToBinaryFile() throws Exception { replay(dialogs); DumpMemoryAction action = new DumpMemoryAction(dialogs, context); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); byte[] bytes = Files.readAllBytes(binFile.toPath()); assertEquals(4, bytes.length); assertEquals((byte) 0xAA, bytes[0]); @@ -82,4 +84,14 @@ public void testDumpCancelledByUser() { action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); // No exception = pass } + + private void waitForCompletion(DumpMemoryAction action) throws InterruptedException { + for (int i = 0; i < 200; i++) { + if (action.isEnabled()) { + return; + } + Thread.sleep(10); + } + fail("Memory dump did not complete"); + } } diff --git a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/DumpMemoryAction.java b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/DumpMemoryAction.java index 4986edf46..4850f6c3b 100644 --- a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/DumpMemoryAction.java +++ b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/DumpMemoryAction.java @@ -49,20 +49,38 @@ public void actionPerformed(ActionEvent e) { new FileExtensionsFilter("Binary dump", "bram")); dumpPath.ifPresent(path -> { - try { - if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { - try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { - for (int i = 0; i < context.getSize(); i++) { - out.write(String.format("%X:\t%s\n", i, context.read(i))); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws IOException { + dump(path); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + LOGGER.error("Memory dump could not be created", cause); + dialogs.showError("Memory dump could not be created: " + cause.getMessage() + ". Please see log file for more details."); } - } else { - RamMemoryContext.serialize(path, context.getSnapshot()); } - } catch (IOException ex) { - LOGGER.error("Memory dump could not be created", ex); - dialogs.showError("Memory dump could not be created: " + ex.getMessage() + ". Please see log file for more details."); - } + }.execute(); }); } + + private void dump(Path path) throws IOException { + if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { + try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { + for (int i = 0; i < context.getSize(); i++) { + out.write(String.format("%X:\t%s\n", i, context.read(i))); + } + } + } else { + RamMemoryContext.serialize(path, context.getSnapshot()); + } + } } diff --git a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/LoadImageAction.java b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/LoadImageAction.java index 690c330d9..3fd528d8b 100644 --- a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/LoadImageAction.java +++ b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/gui/actions/LoadImageAction.java @@ -47,13 +47,27 @@ public void actionPerformed(ActionEvent e) { false, new FileExtensionsFilter("Memory image", "bram")); imagePath.ifPresent(path -> { recentOpenPath = path; - try { - context.deserialize(path.toString()); - repaint.run(); - } catch (Exception ex) { - dialogs.showError("Could not load selected image file: " + ex.getMessage(), "Load image file"); - LOGGER.error("Could not load image file '{}'", path, ex); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + context.deserialize(path.toString()); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + repaint.run(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + dialogs.showError("Could not load selected image file: " + cause.getMessage(), "Load image file"); + LOGGER.error("Could not load image file '{}'", path, cause); + } + } + }.execute(); }); } } diff --git a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryAction.java b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryAction.java index 780adb370..5c124fef5 100644 --- a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryAction.java +++ b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryAction.java @@ -52,20 +52,38 @@ public void actionPerformed(ActionEvent e) { new FileExtensionsFilter("Binary dump", "brasp")); dumpPath.ifPresent(path -> { - try { - if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { - try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { - for (int i = 0; i < context.getSize(); i++) { - out.write(String.format("%X:\t%02X\n", i, context.read(i))); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws IOException { + dump(path); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + LOGGER.error("Memory dump could not be created", cause); + dialogs.showError("Memory dump could not be created: " + cause.getMessage() + ". Please see log file for more details."); } - } else { - RaspMemoryContext.serialize(path, programLocation.get(), context.getSnapshot()); } - } catch (IOException ex) { - LOGGER.error("Memory dump could not be created", ex); - dialogs.showError("Memory dump could not be created: " + ex.getMessage() + ". Please see log file for more details."); - } + }.execute(); }); } + + private void dump(Path path) throws IOException { + if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { + try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { + for (int i = 0; i < context.getSize(); i++) { + out.write(String.format("%X:\t%02X\n", i, context.read(i))); + } + } + } else { + RaspMemoryContext.serialize(path, programLocation.get(), context.getSnapshot()); + } + } } diff --git a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageAction.java b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageAction.java index 590aa98d2..32a07051f 100644 --- a/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageAction.java +++ b/plugins/memory/rasp-mem/src/main/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageAction.java @@ -51,13 +51,27 @@ public void actionPerformed(ActionEvent e) { false, new FileExtensionsFilter("Memory image", "brasp")); imagePath.ifPresent(path -> { recentOpenPath = path; - try { - context.deserialize(path.toString(), setProgramLocation); - repaint.run(); - } catch (Exception ex) { - dialogs.showError("Could not load selected image file: " + ex.getMessage(), "Load image file"); - LOGGER.error("Could not load image file '{}'", path, ex); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + context.deserialize(path.toString(), setProgramLocation); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + repaint.run(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + dialogs.showError("Could not load selected image file: " + cause.getMessage(), "Load image file"); + LOGGER.error("Could not load image file '{}'", path, cause); + } + } + }.execute(); }); } } diff --git a/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryActionTest.java b/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryActionTest.java index 97b9c63aa..42dcdc54e 100644 --- a/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryActionTest.java +++ b/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/DumpMemoryActionTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.swing.*; import java.awt.event.ActionEvent; import java.io.*; import java.nio.file.Files; @@ -88,6 +89,7 @@ public void testActionPerformedDumpToTextFile() throws Exception { DumpMemoryAction action = new DumpMemoryAction(dialogs, context, () -> 0); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); // Verify the file content (hex format: "%X:\t%02X\n") String content = Files.readString(dumpFile.toPath()); @@ -111,6 +113,7 @@ public void testActionPerformedDumpToBinaryFile() throws Exception { DumpMemoryAction action = new DumpMemoryAction(dialogs, context, () -> 5); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); // Verify by deserializing MemoryContextImpl context2 = MemoryContextImplFactory.create(); @@ -143,6 +146,7 @@ public void testActionPerformedDumpToTextFileUpperCaseExtension() throws Excepti DumpMemoryAction action = new DumpMemoryAction(dialogs, context, () -> 0); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); // Should still write text format for .TXT String content = Files.readString(dumpFile.toPath()); @@ -160,6 +164,7 @@ public void testActionPerformedDumpEmptyMemoryTextFile() throws Exception { DumpMemoryAction action = new DumpMemoryAction(dialogs, context, () -> 0); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); String content = Files.readString(dumpFile.toPath()); assertEquals("", content); @@ -179,10 +184,21 @@ public void testActionPerformedIOErrorShowsErrorDialog() throws Exception { DumpMemoryAction action = new DumpMemoryAction(dialogs, context, () -> 0); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "dump")); + waitForCompletion(action); verify(dialogs); } + private void waitForCompletion(Action action) throws InterruptedException { + for (int i = 0; i < 200; i++) { + if (action.isEnabled()) { + return; + } + Thread.sleep(10); + } + fail("Memory action did not complete"); + } + private RaspLabel createLabel(int address, String label) { return new RaspLabel() { @Override @@ -197,4 +213,3 @@ public String getLabel() { }; } } - diff --git a/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageActionTest.java b/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageActionTest.java index 31caf5a7f..5fc8c2304 100644 --- a/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageActionTest.java +++ b/plugins/memory/rasp-mem/src/test/java/net/emustudio/plugins/memory/rasp/gui/actions/LoadImageActionTest.java @@ -14,6 +14,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.swing.*; import java.awt.event.ActionEvent; import java.io.File; import java.nio.file.Path; @@ -106,6 +107,7 @@ public void testActionPerformedLoadsImage() throws Exception { dialogs, context, () -> repainted.set(true), programLocation::set ); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "load")); + waitForCompletion(action); assertEquals(7, programLocation.get()); assertEquals(Integer.valueOf(1), context.read(0)); @@ -134,12 +136,13 @@ public void testActionPerformedInvalidFileShowsError() throws Exception { dialogs, context, () -> {}, i -> {} ); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "load")); + waitForCompletion(action); verify(dialogs); } @Test - public void testActionPerformedNonExistentFileShowsError() { + public void testActionPerformedNonExistentFileShowsError() throws Exception { Path nonExistent = Path.of(tmpFolder.getRoot().getAbsolutePath(), "nonexistent.brasp"); expect(dialogs.chooseFile( @@ -154,6 +157,7 @@ public void testActionPerformedNonExistentFileShowsError() { dialogs, context, () -> {}, i -> {} ); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "load")); + waitForCompletion(action); verify(dialogs); } @@ -190,13 +194,25 @@ public void testActionPerformedSecondCallUsesRecentPath() throws Exception { ); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "load")); + waitForCompletion(action); action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "load")); + waitForCompletion(action); // After second load, program location should be from second file assertEquals(1, programLocation.get()); assertEquals(Integer.valueOf(2), context.read(0)); } + private void waitForCompletion(Action action) throws InterruptedException { + for (int i = 0; i < 200; i++) { + if (action.isEnabled()) { + return; + } + Thread.sleep(10); + } + fail("Memory action did not complete"); + } + private RaspLabel createLabel(int address, String label) { return new RaspLabel() { @Override @@ -211,4 +227,3 @@ public String getLabel() { }; } } - diff --git a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryAction.java b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryAction.java index a4da7ed2b..4883d471d 100644 --- a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryAction.java +++ b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryAction.java @@ -49,27 +49,45 @@ public void actionPerformed(ActionEvent e) { new FileExtensionsFilter("Binary dump", "bssem")); dumpPath.ifPresent(path -> { - try { - if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { - try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { - for (int i = 0; i < 32; i++) { - Byte[] v = context.read(i * 4, 4); - out.write(String.format("0x%02X, 0x%02X, 0x%02X, 0x%02X\n", v[0], v[1], v[2], v[3])); - } - } - } else { - try (DataOutputStream ds = new DataOutputStream(new FileOutputStream(path.toFile()))) { - int programLocation = api.getProgramLocation() / 4; - ds.writeInt(programLocation); - for (int i = 0; i < context.getSize(); i++) { - ds.writeByte(context.read(i) & 0xff); - } + setEnabled(false); + new SwingWorker() { + @Override + protected Void doInBackground() throws IOException { + dump(path); + return null; + } + + @Override + protected void done() { + setEnabled(true); + try { + get(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + LOGGER.error("Memory dump could not be created", cause); + dialogs.showError("Memory dump could not be created: " + cause.getMessage() + ". Please see log file for more details."); } } - } catch (IOException ex) { - LOGGER.error("Memory dump could not be created", ex); - dialogs.showError("Memory dump could not be created: " + ex.getMessage() + ". Please see log file for more details."); - } + }.execute(); }); } + + private void dump(Path path) throws IOException { + if (path.toString().toLowerCase(Locale.ENGLISH).endsWith(".txt")) { + try (BufferedWriter out = new BufferedWriter(new FileWriter(path.toFile()))) { + for (int i = 0; i < 32; i++) { + Byte[] v = context.read(i * 4, 4); + out.write(String.format("0x%02X, 0x%02X, 0x%02X, 0x%02X\n", v[0], v[1], v[2], v[3])); + } + } + } else { + try (DataOutputStream ds = new DataOutputStream(new FileOutputStream(path.toFile()))) { + int programLocation = api.getProgramLocation() / 4; + ds.writeInt(programLocation); + for (int i = 0; i < context.getSize(); i++) { + ds.writeByte(context.read(i) & 0xff); + } + } + } + } } diff --git a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageAction.java b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageAction.java index 6d11cfa73..d590cf91b 100644 --- a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageAction.java +++ b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageAction.java @@ -52,24 +52,46 @@ public void actionPerformed(ActionEvent e) { "Load image file", "Load", currentDirectory, false, new FileExtensionsFilter("Memory image", "bssem")); - System.out.println(imagePath); imagePath.ifPresent(path -> { recentOpenPath = path; - try { - try (FileInputStream stream = new FileInputStream(path.toFile())) { - ByteBuffer code = ByteBuffer.wrap(stream.readAllBytes()); - int startLine = code.getInt(); - byte[] data = new byte[code.remaining()]; - code.get(data); + setEnabled(false); + new SwingWorker() { + @Override + protected Image doInBackground() throws Exception { + try (FileInputStream stream = new FileInputStream(path.toFile())) { + ByteBuffer code = ByteBuffer.wrap(stream.readAllBytes()); + int startLine = code.getInt(); + byte[] data = new byte[code.remaining()]; + code.get(data); + return new Image(startLine, data); + } + } - api.setProgramLocation(startLine * 4); - context.write(0, NumberUtils.nativeBytesToBytes(data)); + @Override + protected void done() { + setEnabled(true); + try { + Image image = get(); + api.setProgramLocation(image.startLine * 4); + context.write(0, NumberUtils.nativeBytesToBytes(image.data)); + repaint.run(); + } catch (Exception ex) { + Throwable cause = ex.getCause() == null ? ex : ex.getCause(); + dialogs.showError("Could not load selected image file: " + cause.getMessage(), "Load image file"); + LOGGER.error("Could not load image file '{}'", path, cause); + } } - repaint.run(); - } catch (Exception ex) { - dialogs.showError("Could not load selected image file: " + ex.getMessage(), "Load image file"); - LOGGER.error("Could not load image file '{}'", path, ex); - } + }.execute(); }); } + + private static class Image { + private final int startLine; + private final byte[] data; + + private Image(int startLine, byte[] data) { + this.startLine = startLine; + this.data = data; + } + } } diff --git a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryActionTest.java b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryActionTest.java index 798dd9e4f..18c8d5cc5 100644 --- a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryActionTest.java +++ b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/DumpMemoryActionTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.swing.*; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -35,10 +36,11 @@ public void testNoFileIsChosen() { } @Test - public void testHumanReadableDump() throws IOException { + public void testHumanReadableDump() throws Exception { Path output = folder.newFolder().toPath().resolve("human-readable.txt"); DumpMemoryAction action = new DumpMemoryAction(mockApi(output), prepareMemory()); action.actionPerformed(null); + waitForCompletion(action); String content = new String(read(output).array()); assertEquals("0x00, 0x01, 0x02, 0x03\n" + @@ -76,10 +78,11 @@ public void testHumanReadableDump() throws IOException { } @Test - public void testBSSEM() throws IOException { + public void testBSSEM() throws Exception { Path output = folder.newFolder().toPath().resolve("binary.bsem"); DumpMemoryAction action = new DumpMemoryAction(mockApi(output), prepareMemory()); action.actionPerformed(null); + waitForCompletion(action); ByteBuffer data = read(output); assertEquals(PROGRAM_LOCATION, data.getInt() * 4); @@ -128,4 +131,14 @@ private MemoryContext prepareMemory() { } return mem; } + + private void waitForCompletion(Action action) throws InterruptedException { + for (int i = 0; i < 200; i++) { + if (action.isEnabled()) { + return; + } + Thread.sleep(10); + } + throw new AssertionError("Memory action did not complete"); + } } diff --git a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageActionTest.java b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageActionTest.java index c672bcfc5..4dc6306cc 100644 --- a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageActionTest.java +++ b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/gui/actions/LoadImageActionTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.swing.*; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -32,7 +33,7 @@ public void testNoFileIsChosenWorks() { } @Test - public void testLoadBSSEM() throws IOException { + public void testLoadBSSEM() throws Exception { Path path = folder.newFolder().toPath().resolve("binary.bssem"); write(path); MemoryContext memory = createMock(MemoryContext.class); @@ -54,10 +55,21 @@ public void testLoadBSSEM() throws IOException { LoadImageAction action = new LoadImageAction(mockApi(path), memory, repaint); action.actionPerformed(null); + waitForCompletion(action); verify(memory, repaint); } + private void waitForCompletion(Action action) throws InterruptedException { + for (int i = 0; i < 200; i++) { + if (action.isEnabled()) { + return; + } + Thread.sleep(10); + } + throw new AssertionError("Memory action did not complete"); + } + private void write(Path path) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(33 * 4); From 41f7b34fc5898c8d134ef8d8c8ed61ca9ce626f4 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 18:43:35 +0200 Subject: [PATCH 3/9] [#408] Fix memory notify ranges, ROM check, locking byte-mem/ssem array write notified one cell past end (to is inclusive); byte-mem ROM intersects checked one cell past write range; ram-mem array write missed size-change detection (checked base address only) and getLabel read labels without read lock. Drop stream lookup in Loader, stray super.mousePressed in MouseHandler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../plugins/memory/bytemem/MemoryContextImpl.java | 4 ++-- .../plugins/memory/bytemem/gui/MouseHandler.java | 1 - .../emustudio/plugins/memory/bytemem/loaders/Loader.java | 8 +------- .../emustudio/plugins/memory/ram/MemoryContextImpl.java | 4 ++-- .../emustudio/plugins/memory/ssem/MemoryContextImpl.java | 2 +- .../plugins/memory/ssem/MemoryContextImplTest.java | 2 +- 6 files changed, 7 insertions(+), 14 deletions(-) diff --git a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/MemoryContextImpl.java b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/MemoryContextImpl.java index 2e572fa9d..960624acb 100644 --- a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/MemoryContextImpl.java +++ b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/MemoryContextImpl.java @@ -111,9 +111,9 @@ public void writeBank(int to, byte val, int bank) { } public void write(int to, Byte[] values, int count) { - if (!romRanges.intersects(to, to + count)) { + if (!romRanges.intersects(to, to + count - 1)) { System.arraycopy(values, 0, mem[bank(to)], to, count); - notifyMemoryContentChanged(to, to + values.length); + notifyMemoryContentChanged(to, to + count - 1); } } diff --git a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/MouseHandler.java b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/MouseHandler.java index ad9be44ed..bbd627d84 100644 --- a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/MouseHandler.java +++ b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/gui/MouseHandler.java @@ -35,7 +35,6 @@ public void mouseWheelMoved(MouseWheelEvent e) { @Override public void mouseClicked(MouseEvent e) { - super.mousePressed(e); updateMemoryValue.run(); } } diff --git a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/loaders/Loader.java b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/loaders/Loader.java index 6cb12ffde..52a00e428 100644 --- a/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/loaders/Loader.java +++ b/plugins/memory/byte-mem/src/main/java/net/emustudio/plugins/memory/bytemem/loaders/Loader.java @@ -58,12 +58,6 @@ static Loader createLoader(Path path) { String extension = (index == -1) ? "" : path.toString().substring(index + 1).toLowerCase(Locale.ENGLISH); - return IMAGE_LOADERS - .entrySet() - .stream() - .filter(l -> l.getKey().equals(extension)) - .findFirst() - .map(Map.Entry::getValue) - .orElse(new BinaryLoader()); // unknown/no extension + return IMAGE_LOADERS.getOrDefault(extension, new BinaryLoader()); // unknown/no extension } } diff --git a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java index dea493284..83751541b 100644 --- a/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java +++ b/plugins/memory/ram-mem/src/main/java/net/emustudio/plugins/memory/ram/MemoryContextImpl.java @@ -80,7 +80,7 @@ public void write(int address, RamInstruction[] values, int count) { AtomicBoolean sizeChanged = new AtomicBoolean(); rwl.lockWrite(() -> { for (int i = 0; i < count; i++) { - sizeChanged.set(sizeChanged.get() || !memory.containsKey(address)); + sizeChanged.set(sizeChanged.get() || !memory.containsKey(address + i)); memory.put(address + i, values[i]); } }); @@ -104,7 +104,7 @@ public void setLabels(List labels) { @Override public Optional getLabel(int address) { - return Optional.ofNullable(labels.get(address)); + return rwl.lockRead(() -> Optional.ofNullable(labels.get(address))); } @Override diff --git a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/MemoryContextImpl.java b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/MemoryContextImpl.java index 93f018dd3..35057c0eb 100644 --- a/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/MemoryContextImpl.java +++ b/plugins/memory/ssem-mem/src/main/java/net/emustudio/plugins/memory/ssem/MemoryContextImpl.java @@ -46,7 +46,7 @@ public void write(int to, Byte value) { @Override public void write(int to, Byte[] values, int count) { System.arraycopy(values, 0, memory, to, count); - notifyMemoryContentChanged(to, to + values.length); + notifyMemoryContentChanged(to, to + count - 1); } @Override diff --git a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/MemoryContextImplTest.java b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/MemoryContextImplTest.java index 33ad11f8a..025492782 100644 --- a/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/MemoryContextImplTest.java +++ b/plugins/memory/ssem-mem/src/test/java/net/emustudio/plugins/memory/ssem/MemoryContextImplTest.java @@ -166,7 +166,7 @@ public void testWriteArrayNotifiesObservers() { MemoryContextImpl context = new MemoryContextImpl(new Annotations()); MemoryContext.MemoryListener listener = createMock(MemoryContext.MemoryListener.class); - listener.memoryContentChanged(eq(4), eq(8)); + listener.memoryContentChanged(eq(4), eq(7)); expectLastCall().once(); replay(listener); From 04d53981c6ef2f695e1d36332fda1d635c04780e Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 18:46:46 +0200 Subject: [PATCH 4/9] [#408] Fix debug cell type check, automation listener leak setValueAt compared value class by identity so subclass values were silently dropped; automation never removed its CPU listener; Element built a zero-height gradient in constructor that measure() always replaces before first draw. Fix parameter typo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../net/emustudio/application/emulation/Automation.java | 6 ++++-- .../application/gui/debugtable/DebugTableModelImpl.java | 2 +- .../emustudio/application/gui/dialogs/EmulatorPanel.java | 4 ++-- .../emustudio/application/gui/schema/elements/Element.java | 5 ----- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/net/emustudio/application/emulation/Automation.java b/application/src/main/java/net/emustudio/application/emulation/Automation.java index 9dc3b136f..a3178dcae 100644 --- a/application/src/main/java/net/emustudio/application/emulation/Automation.java +++ b/application/src/main/java/net/emustudio/application/emulation/Automation.java @@ -163,7 +163,7 @@ private void autoEmulate(CPU cpu) { } resultState = CPU.RunState.STATE_RUNNING; - cpu.addCPUListener(new CPU.CPUListener() { + CPU.CPUListener stateListener = new CPU.CPUListener() { @Override public void runStateChanged(CPU.RunState state) { if (state != CPU.RunState.STATE_RUNNING) { @@ -177,7 +177,8 @@ public void runStateChanged(CPU.RunState state) { @Override public void internalStateChanged() { } - }); + }; + cpu.addCPUListener(stateListener); cpu.execute(); synchronized (resultStateLock) { @@ -199,6 +200,7 @@ public void internalStateChanged() { Thread.currentThread().interrupt(); } } + cpu.removeCPUListener(stateListener); switch (resultState) { case STATE_STOPPED_ADDR_FALLOUT: diff --git a/application/src/main/java/net/emustudio/application/gui/debugtable/DebugTableModelImpl.java b/application/src/main/java/net/emustudio/application/gui/debugtable/DebugTableModelImpl.java index 9ed388f70..9b94324ea 100644 --- a/application/src/main/java/net/emustudio/application/gui/debugtable/DebugTableModelImpl.java +++ b/application/src/main/java/net/emustudio/application/gui/debugtable/DebugTableModelImpl.java @@ -136,7 +136,7 @@ public void setValueAt(Object value, int rowIndex, int columnIndex) { if (location == -1) return; DebuggerColumn column = columns[columnIndex]; - if (value.getClass() == column.getClassType()) { + if (column.getClassType().isInstance(value)) { try { column.setValue(location, value); } catch (CannotSetDebuggerValueException ignored) { diff --git a/application/src/main/java/net/emustudio/application/gui/dialogs/EmulatorPanel.java b/application/src/main/java/net/emustudio/application/gui/dialogs/EmulatorPanel.java index a13c5354a..4ecdce29f 100644 --- a/application/src/main/java/net/emustudio/application/gui/dialogs/EmulatorPanel.java +++ b/application/src/main/java/net/emustudio/application/gui/dialogs/EmulatorPanel.java @@ -159,9 +159,9 @@ public void mouseClicked(MouseEvent e) { this.memoryListener = new MemoryContext.MemoryListener() { @Override - public void memoryContentChanged(int fromLocatiom, int toLocation) { + public void memoryContentChanged(int fromLocation, int toLocation) { runOnEdt(() -> { - debugTableModel.memoryChanged(fromLocatiom, toLocation + 1); + debugTableModel.memoryChanged(fromLocation, toLocation + 1); refreshDebugTable(); }); } diff --git a/application/src/main/java/net/emustudio/application/gui/schema/elements/Element.java b/application/src/main/java/net/emustudio/application/gui/schema/elements/Element.java index cfc7b70cb..1a71aedb7 100644 --- a/application/src/main/java/net/emustudio/application/gui/schema/elements/Element.java +++ b/application/src/main/java/net/emustudio/application/gui/schema/elements/Element.java @@ -66,11 +66,6 @@ public Element(Color backColor, P schemaPoint, String pluginId, PLUGIN_TYPE plug this.pluginName = Objects.requireNonNull(pluginName); this.pluginFileName = Objects.requireNonNull(pluginFileName); this.pluginSettings = Objects.requireNonNull(pluginSettings); - - int x = schemaPoint.ix(); - int y = schemaPoint.iy(); - - this.gradient = new GradientPaint(x, y, Color.WHITE, x, y + height, this.backColor, false); } public void draw(Graphics2D g) { From 3c99ca100ab53666f44836e519e8e91979ed6405 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 18:53:09 +0200 Subject: [PATCH 5/9] [#408] Encapsulate terminal video memory, reduce tables Terminal displays handed out live video memory arrays; getters now return snapshots taken under the write lock so renderers cannot see torn frames. Tape close skipped shutdownNow when interrupted. Replace 52-entry Ctrl keycode table and duplicate ZX palettes with loops. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../device/adm3a/interaction/DisplayImpl.java | 4 +- .../device/adm3a/interaction/KeyboardGui.java | 58 +---------- .../TapePlaybackController.java | 1 + .../device/vt100/gui/DisplayCanvas.java | 5 +- .../device/vt100/interaction/DisplayImpl.java | 12 +-- .../plugins/device/vt100/DisplayImplTest.java | 95 +++++++++++-------- .../zxspectrum/ula/gui/DisplayCanvas.java | 33 +++---- 7 files changed, 87 insertions(+), 121 deletions(-) diff --git a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/DisplayImpl.java b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/DisplayImpl.java index fc0a7c747..7350b8659 100644 --- a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/DisplayImpl.java +++ b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/DisplayImpl.java @@ -84,7 +84,9 @@ public int getColumns() { @Override public char[] getVideoMemory() { - return videoMemory; // I should be punished for this + synchronized (videoMemory) { + return Arrays.copyOf(videoMemory, videoMemory.length); + } } public void clearScreen() { diff --git a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/KeyboardGui.java b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/KeyboardGui.java index 7cc775510..475f3ad71 100644 --- a/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/KeyboardGui.java +++ b/plugins/device/adm3A-terminal/src/main/java/net/emustudio/plugins/device/adm3a/interaction/KeyboardGui.java @@ -17,64 +17,16 @@ public class KeyboardGui extends Keyboard implements ContainerListener, KeyListe private static final int[] CONTROL_KEYCODES_ALWAYS_ACTIVE = new int[256]; static { - CONTROL_KEYCODES['@'] = 0; - CONTROL_KEYCODES['A'] = 1; - CONTROL_KEYCODES['B'] = 2; - CONTROL_KEYCODES['C'] = 3; - CONTROL_KEYCODES['D'] = 4; - CONTROL_KEYCODES['E'] = 5; - CONTROL_KEYCODES['F'] = 6; - CONTROL_KEYCODES['G'] = 7; - CONTROL_KEYCODES['H'] = 8; - CONTROL_KEYCODES['I'] = 9; - CONTROL_KEYCODES['J'] = 10; - CONTROL_KEYCODES['K'] = 11; - CONTROL_KEYCODES['L'] = 12; - CONTROL_KEYCODES['M'] = 13; - CONTROL_KEYCODES['N'] = 14; - CONTROL_KEYCODES['O'] = 15; - CONTROL_KEYCODES['P'] = 16; - CONTROL_KEYCODES['Q'] = 17; - CONTROL_KEYCODES['R'] = 18; - CONTROL_KEYCODES['S'] = 19; - CONTROL_KEYCODES['T'] = 20; - CONTROL_KEYCODES['U'] = 21; - CONTROL_KEYCODES['V'] = 22; - CONTROL_KEYCODES['W'] = 23; - CONTROL_KEYCODES['X'] = 24; - CONTROL_KEYCODES['Y'] = 25; - CONTROL_KEYCODES['Z'] = 26; + // Ctrl+A..Z (upper or lower case) → control codes 1..26 + for (char c = 'A'; c <= 'Z'; c++) { + CONTROL_KEYCODES[c] = c - 'A' + 1; + CONTROL_KEYCODES[Character.toLowerCase(c)] = c - 'A' + 1; + } CONTROL_KEYCODES['['] = 27; CONTROL_KEYCODES['\\'] = 28; CONTROL_KEYCODES[']'] = 29; CONTROL_KEYCODES['^'] = 30; CONTROL_KEYCODES['-'] = 31; - CONTROL_KEYCODES['a'] = 1; - CONTROL_KEYCODES['b'] = 2; - CONTROL_KEYCODES['c'] = 3; - CONTROL_KEYCODES['d'] = 4; - CONTROL_KEYCODES['e'] = 5; - CONTROL_KEYCODES['f'] = 6; - CONTROL_KEYCODES['g'] = 7; - CONTROL_KEYCODES['h'] = 8; - CONTROL_KEYCODES['i'] = 9; - CONTROL_KEYCODES['j'] = 10; - CONTROL_KEYCODES['k'] = 11; - CONTROL_KEYCODES['l'] = 12; - CONTROL_KEYCODES['m'] = 13; - CONTROL_KEYCODES['n'] = 14; - CONTROL_KEYCODES['o'] = 15; - CONTROL_KEYCODES['p'] = 16; - CONTROL_KEYCODES['q'] = 17; - CONTROL_KEYCODES['r'] = 18; - CONTROL_KEYCODES['s'] = 19; - CONTROL_KEYCODES['t'] = 20; - CONTROL_KEYCODES['u'] = 21; - CONTROL_KEYCODES['v'] = 22; - CONTROL_KEYCODES['w'] = 23; - CONTROL_KEYCODES['x'] = 24; - CONTROL_KEYCODES['y'] = 25; - CONTROL_KEYCODES['z'] = 26; CONTROL_KEYCODES_ALWAYS_ACTIVE[KeyEvent.VK_DOWN] = 10; CONTROL_KEYCODES_ALWAYS_ACTIVE[KeyEvent.VK_UP] = 11; diff --git a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/TapePlaybackController.java b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/TapePlaybackController.java index cd3f574bc..63fd4df05 100644 --- a/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/TapePlaybackController.java +++ b/plugins/device/audiotape-player/src/main/java/net/emustudio/plugins/device/audiotape_player/TapePlaybackController.java @@ -65,6 +65,7 @@ public void close() { playPool.shutdownNow(); } } catch (InterruptedException e) { + playPool.shutdownNow(); Thread.currentThread().interrupt(); } } diff --git a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java index 90adfdffc..e41887200 100644 --- a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java +++ b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/gui/DisplayCanvas.java @@ -124,8 +124,9 @@ private boolean refreshFrame() { return false; } - videoMemory = newVideoMemory == null ? new char[0] : Arrays.copyOf(newVideoMemory, newVideoMemory.length); - attributeMemory = newAttributeMemory == null ? null : Arrays.copyOf(newAttributeMemory, newAttributeMemory.length); + // getVideoMemory/getAttributeMemory already return defensive copies + videoMemory = newVideoMemory == null ? new char[0] : newVideoMemory; + attributeMemory = newAttributeMemory; cursorPoint = new Point(newCursorPoint); dimension = new Dimension(newDimension); columns = newColumns; diff --git a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/interaction/DisplayImpl.java b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/interaction/DisplayImpl.java index 6a5dd6325..6287c283a 100644 --- a/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/interaction/DisplayImpl.java +++ b/plugins/device/vt100-terminal/src/main/java/net/emustudio/plugins/device/vt100/interaction/DisplayImpl.java @@ -25,8 +25,8 @@ public class DisplayImpl implements Display, Vt100StateMachine.Vt100Dispatcher { private final static Logger LOGGER = LoggerFactory.getLogger(DisplayImpl.class); - public char[] videoMemory; - public int[] attributeMemory; + private char[] videoMemory; + private int[] attributeMemory; private final TerminalSettings settings; private final Cursor cursor; @@ -100,13 +100,13 @@ public int getColumns() { } @Override - public char[] getVideoMemory() { - return videoMemory; + public synchronized char[] getVideoMemory() { + return Arrays.copyOf(videoMemory, videoMemory.length); } @Override - public int[] getAttributeMemory() { - return attributeMemory; + public synchronized int[] getAttributeMemory() { + return Arrays.copyOf(attributeMemory, attributeMemory.length); } public void clearScreen() { diff --git a/plugins/device/vt100-terminal/src/test/java/net/emustudio/plugins/device/vt100/DisplayImplTest.java b/plugins/device/vt100-terminal/src/test/java/net/emustudio/plugins/device/vt100/DisplayImplTest.java index f6d3e08ac..e6e947921 100644 --- a/plugins/device/vt100-terminal/src/test/java/net/emustudio/plugins/device/vt100/DisplayImplTest.java +++ b/plugins/device/vt100-terminal/src/test/java/net/emustudio/plugins/device/vt100/DisplayImplTest.java @@ -311,22 +311,23 @@ public void testResetClearsScreenAndHomesCursor() { public void testRollUp() { // Write characters on first two lines for (int i = 0; i < DEFAULT_COLUMNS; i++) { - display.getVideoMemory()[i] = 'A'; + display.write((byte) 'A'); } - for (int i = DEFAULT_COLUMNS; i < 2 * DEFAULT_COLUMNS; i++) { - display.getVideoMemory()[i] = 'B'; + for (int i = 0; i < DEFAULT_COLUMNS; i++) { + display.write((byte) 'B'); } display.rollUp(); + char[] vm = display.getVideoMemory(); // First line should now be 'B's for (int i = 0; i < DEFAULT_COLUMNS; i++) { - assertEquals('B', display.getVideoMemory()[i]); + assertEquals('B', vm[i]); } // Last line should be spaces int lastLineStart = DEFAULT_COLUMNS * (DEFAULT_ROWS - 1); for (int i = lastLineStart; i < lastLineStart + DEFAULT_COLUMNS; i++) { - assertEquals(' ', display.getVideoMemory()[i]); + assertEquals(' ', vm[i]); } } @@ -334,18 +335,19 @@ public void testRollUp() { public void testRollDown() { // Write characters on first line for (int i = 0; i < DEFAULT_COLUMNS; i++) { - display.getVideoMemory()[i] = 'X'; + display.write((byte) 'X'); } display.rollDown(); + char[] vm = display.getVideoMemory(); // First line should be spaces for (int i = 0; i < DEFAULT_COLUMNS; i++) { - assertEquals(' ', display.getVideoMemory()[i]); + assertEquals(' ', vm[i]); } // Second line should now be 'X's for (int i = DEFAULT_COLUMNS; i < 2 * DEFAULT_COLUMNS; i++) { - assertEquals('X', display.getVideoMemory()[i]); + assertEquals('X', vm[i]); } } @@ -890,6 +892,21 @@ private void writeDecstbm(int top, int bottom) { display.write((byte) 'r'); } + /** + * Helper to put a char at column 0 of the given row. + */ + private void putCharAtRow(char c, int row) { + cursor.move(0, row); + display.print(c); + } + + /** + * Helper to read the char at column 0 of the given row. + */ + private char charAtRow(int row) { + return display.getVideoMemory()[row * DEFAULT_COLUMNS]; + } + @Test public void testDecstbmMovesCursorHome() { // Move cursor somewhere first @@ -917,19 +934,19 @@ public void testDecstbmRollUpDirectlyOnlyAffectsRegion() { writeDecstbm(2, 4); // Directly fill column 0 of rows 0-4 with distinct chars - display.videoMemory[0] = 'A'; // row 0 - display.videoMemory[DEFAULT_COLUMNS] = 'B'; // row 1 - display.videoMemory[2 * DEFAULT_COLUMNS] = 'C'; // row 2 - display.videoMemory[3 * DEFAULT_COLUMNS] = 'D'; // row 3 - display.videoMemory[4 * DEFAULT_COLUMNS] = 'E'; // row 4 + putCharAtRow('A', 0); + putCharAtRow('B', 1); + putCharAtRow('C', 2); + putCharAtRow('D', 3); + putCharAtRow('E', 4); display.rollUp(); - assertEquals('A', display.videoMemory[0]); - assertEquals('C', display.videoMemory[DEFAULT_COLUMNS]); - assertEquals('D', display.videoMemory[2 * DEFAULT_COLUMNS]); - assertEquals(' ', display.videoMemory[3 * DEFAULT_COLUMNS]); - assertEquals('E', display.videoMemory[4 * DEFAULT_COLUMNS]); + assertEquals('A', charAtRow(0)); + assertEquals('C', charAtRow(1)); + assertEquals('D', charAtRow(2)); + assertEquals(' ', charAtRow(3)); + assertEquals('E', charAtRow(4)); } @Test @@ -938,11 +955,11 @@ public void testDecstbmScrollUpOnlyAffectsRegion() { writeDecstbm(2, 4); // Directly fill column 0 of rows 0-4 with distinct chars - display.videoMemory[0] = 'A'; // row 0 - display.videoMemory[DEFAULT_COLUMNS] = 'B'; // row 1 - display.videoMemory[2 * DEFAULT_COLUMNS] = 'C'; // row 2 - display.videoMemory[3 * DEFAULT_COLUMNS] = 'D'; // row 3 - display.videoMemory[4 * DEFAULT_COLUMNS] = 'E'; // row 4 + putCharAtRow('A', 0); + putCharAtRow('B', 1); + putCharAtRow('C', 2); + putCharAtRow('D', 3); + putCharAtRow('E', 4); // Move cursor to scrollBottom (row 3, 0-based) cursor.move(0, 3); @@ -950,22 +967,22 @@ public void testDecstbmScrollUpOnlyAffectsRegion() { // Line feed at bottom of scrolling region triggers rollUp within region display.write((byte) 0x0A); // LF - assertEquals('A', display.videoMemory[0]); - assertEquals('C', display.videoMemory[DEFAULT_COLUMNS]); - assertEquals('D', display.videoMemory[2 * DEFAULT_COLUMNS]); - assertEquals(' ', display.videoMemory[3 * DEFAULT_COLUMNS]); - assertEquals('E', display.videoMemory[4 * DEFAULT_COLUMNS]); + assertEquals('A', charAtRow(0)); + assertEquals('C', charAtRow(1)); + assertEquals('D', charAtRow(2)); + assertEquals(' ', charAtRow(3)); + assertEquals('E', charAtRow(4)); } @Test public void testDecstbmScrollDownOnlyAffectsRegion() { // Directly fill column 0 of rows 0-4 with distinct chars - display.videoMemory[0] = 'A'; // row 0 - display.videoMemory[DEFAULT_COLUMNS] = 'B'; // row 1 - display.videoMemory[2 * DEFAULT_COLUMNS] = 'C'; // row 2 - display.videoMemory[3 * DEFAULT_COLUMNS] = 'D'; // row 3 - display.videoMemory[4 * DEFAULT_COLUMNS] = 'E'; // row 4 + putCharAtRow('A', 0); + putCharAtRow('B', 1); + putCharAtRow('C', 2); + putCharAtRow('D', 3); + putCharAtRow('E', 4); // Set scrolling region to rows 2-4 (1-based) = rows 1-3 (0-based) writeDecstbm(2, 4); @@ -978,15 +995,15 @@ public void testDecstbmScrollDownOnlyAffectsRegion() { display.write((byte) 0x4D); // Row 0 (outside region) should be unchanged: 'A' - assertEquals('A', display.videoMemory[0]); + assertEquals('A', charAtRow(0)); // Row 1 (scrollTop) should be cleared to space (new blank line scrolled in) - assertEquals(' ', display.videoMemory[DEFAULT_COLUMNS]); + assertEquals(' ', charAtRow(1)); // Row 2 should now contain what was in row 1 (old scrollTop): 'B' - assertEquals('B', display.videoMemory[2 * DEFAULT_COLUMNS]); + assertEquals('B', charAtRow(2)); // Row 3 should now contain what was in row 2: 'C' - assertEquals('C', display.videoMemory[3 * DEFAULT_COLUMNS]); + assertEquals('C', charAtRow(3)); // Row 4 (outside region) should be unchanged: 'E' - assertEquals('E', display.videoMemory[4 * DEFAULT_COLUMNS]); + assertEquals('E', charAtRow(4)); } @Test @@ -999,7 +1016,7 @@ public void testDecstbmInvalidTopGreaterThanBottomIsIgnored() { // Put a char at last row to verify full-screen scrolling region is intact cursor.move(0, DEFAULT_ROWS - 1); display.write((byte) 'Z'); - assertEquals('Z', display.videoMemory[(DEFAULT_ROWS - 1) * DEFAULT_COLUMNS]); + assertEquals('Z', charAtRow(DEFAULT_ROWS - 1)); } // ========== DECSC/DECRC saves/restores attribute ========== diff --git a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java index 07af1cda8..9c784d5d6 100644 --- a/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java +++ b/plugins/device/zxspectrum-ula/src/main/java/net/emustudio/plugins/device/zxspectrum/ula/gui/DisplayCanvas.java @@ -23,27 +23,20 @@ public class DisplayCanvas extends Canvas implements AutoCloseable { public static final float ZOOM = 2f; - private static final Color[] COLOR_MAP = new Color[]{ - new Color(0, 0, 0), // black - new Color(0, 0, 0xD8), // blue - new Color(0xD8, 0, 0), // red - new Color(0xD8, 0, 0xD8), // magenta - new Color(0, 0xD8, 0), // green - new Color(0, 0xD8, 0xD8), // cyan - new Color(0xD8, 0xD8, 0), // yellow - new Color(0xD8, 0xD8, 0xD8) // white - }; + // ZX Spectrum palette, bit 0 = blue, bit 1 = red, bit 2 = green + private static final Color[] COLOR_MAP = palette(0xD8); + private static final Color[] BRIGHT_COLOR_MAP = palette(0xFF); - private static final Color[] BRIGHT_COLOR_MAP = new Color[]{ - new Color(0, 0, 0), // black - new Color(0, 0, 0xFF), // blue - new Color(0xFF, 0, 0), // red - new Color(0xFF, 0, 0xFF), // magenta - new Color(0, 0xFF, 0), // green - new Color(0, 0xFF, 0xFF), // cyan - new Color(0xFF, 0xFF, 0), // yellow - new Color(0xFF, 0xFF, 0xFF) // white - }; + private static Color[] palette(int intensity) { + Color[] colors = new Color[8]; + for (int i = 0; i < 8; i++) { + colors[i] = new Color( + (i & 2) == 0 ? 0 : intensity, + (i & 4) == 0 ? 0 : intensity, + (i & 1) == 0 ? 0 : intensity); + } + return colors; + } private static final Color KEYBOARD_OVERLAY_COLOR = new Color(0, 0, 0, 127); From 39518ffe8b345d48201bdbb8d03dfd28976dbc04 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 18:58:06 +0200 Subject: [PATCH 6/9] [#408] Fix assembler expr eval: identity set, div-by-zero error HashSet on Node dedupes equal-looking nodes at different addresses (Node subclasses override equals without hashCode); identity set preserves per-instance multi-pass resolution. ArithmeticException from / and %% now reported as compile error with position instead of crashing compilation thread. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../plugins/compiler/as8080/ast/expr/ExprInfix.java | 9 +++++++-- .../compiler/as8080/visitors/EvaluateExprVisitor.java | 5 +++-- .../plugins/compiler/as8080/e2e/InstrExprTest.java | 5 +++++ .../plugins/compiler/asZ80/ast/expr/ExprInfix.java | 9 +++++++-- .../compiler/asZ80/visitors/EvaluateExprVisitor.java | 5 +++-- .../plugins/compiler/asZ80/e2e/InstrExprTest.java | 5 +++++ 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprInfix.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprInfix.java index dc0df8973..5444c8135 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprInfix.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprInfix.java @@ -6,6 +6,7 @@ import net.emustudio.plugins.compiler.as8080.ast.Evaluated; import net.emustudio.plugins.compiler.as8080.ast.NameSpace; import net.emustudio.plugins.compiler.as8080.ast.Node; +import net.emustudio.plugins.compiler.as8080.exceptions.CompileException; import net.emustudio.plugins.compiler.as8080.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; @@ -22,7 +23,7 @@ public class ExprInfix extends Node { static { infixOps.put(OP_ADD, Integer::sum); infixOps.put(OP_SUBTRACT, (x, y) -> x - y); - infixOps.put(OP_DIVIDE, (x, y) -> x / y); // can throw! + infixOps.put(OP_DIVIDE, (x, y) -> x / y); infixOps.put(OP_MULTIPLY, (x, y) -> x * y); infixOps.put(OP_MOD, (x, y) -> x % y); infixOps.put(OP_MOD_2, (x, y) -> x % y); @@ -73,7 +74,11 @@ public Evaluated eval(Integer currentAddress, NameSpace env) { if (left != null && right != null) { int l = left.value; int r = right.value; - return new Evaluated(position, operation.apply(l, r)); + try { + return new Evaluated(position, operation.apply(l, r)); + } catch (ArithmeticException e) { + throw new CompileException(position, "Arithmetic error: " + e.getMessage()); + } } return null; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java index dd66c3857..365e225cd 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java @@ -41,7 +41,8 @@ public class EvaluateExprVisitor extends NodeVisitor { private int sizeBytes = 0; private boolean doNotEvaluateCurrentAddress = false; private Evaluated latestEval; - private Set needMorePassThings = new HashSet<>(); + // tracks unresolved nodes by identity: equal-looking nodes at different places must stay distinct + private Set needMorePassThings = Collections.newSetFromMap(new IdentityHashMap<>()); private String currentMacroId; @Override @@ -56,7 +57,7 @@ public void visit(Program node) { Set oldNeedMorePass; while (!needMorePassThings.isEmpty()) { oldNeedMorePass = needMorePassThings; - needMorePassThings = new HashSet<>(); + needMorePassThings = Collections.newSetFromMap(new IdentityHashMap<>()); currentAddress = 0; doNotEvaluateCurrentAddress = false; diff --git a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/e2e/InstrExprTest.java b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/e2e/InstrExprTest.java index 549e4b198..ca5e53b67 100644 --- a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/e2e/InstrExprTest.java +++ b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/e2e/InstrExprTest.java @@ -6,6 +6,11 @@ public class InstrExprTest extends AbstractCompilerTest { + @Test(expected = Exception.class) + public void testDivisionByZeroReportsError() { + compile("MVI A, 1 / 0"); + } + @Test public void testRST() { compile( diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprInfix.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprInfix.java index 32f1eb023..f74bc4735 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprInfix.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprInfix.java @@ -6,6 +6,7 @@ import net.emustudio.plugins.compiler.asZ80.ast.Evaluated; import net.emustudio.plugins.compiler.asZ80.ast.NameSpace; import net.emustudio.plugins.compiler.asZ80.ast.Node; +import net.emustudio.plugins.compiler.asZ80.exceptions.CompileException; import net.emustudio.plugins.compiler.asZ80.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; @@ -22,7 +23,7 @@ public class ExprInfix extends Node { static { infixOps.put(OP_ADD, Integer::sum); infixOps.put(OP_SUBTRACT, (x, y) -> x - y); - infixOps.put(OP_DIVIDE, (x, y) -> x / y); // can throw! + infixOps.put(OP_DIVIDE, (x, y) -> x / y); infixOps.put(OP_MULTIPLY, (x, y) -> x * y); infixOps.put(OP_MOD, (x, y) -> x % y); infixOps.put(OP_MOD_2, (x, y) -> x % y); @@ -70,7 +71,11 @@ public Evaluated eval(Integer currentAddress, NameSpace env) { if (left != null && right != null) { int l = left.value; int r = right.value; - return new Evaluated(position, operation.apply(l, r)); + try { + return new Evaluated(position, operation.apply(l, r)); + } catch (ArithmeticException e) { + throw new CompileException(position, "Arithmetic error: " + e.getMessage()); + } } return null; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java index 3e3dfea32..2a7ea7221 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java @@ -41,7 +41,8 @@ public class EvaluateExprVisitor extends NodeVisitor { private int sizeBytes = 0; private boolean doNotEvaluateCurrentAddress = false; private Evaluated latestEval; - private Set needMorePassThings = new HashSet<>(); + // tracks unresolved nodes by identity: equal-looking nodes at different places must stay distinct + private Set needMorePassThings = Collections.newSetFromMap(new IdentityHashMap<>()); private String currentMacroId; @Override @@ -56,7 +57,7 @@ public void visit(Program node) { Set oldNeedMorePass; while (!needMorePassThings.isEmpty()) { oldNeedMorePass = needMorePassThings; - needMorePassThings = new HashSet<>(); + needMorePassThings = Collections.newSetFromMap(new IdentityHashMap<>()); currentAddress = 0; doNotEvaluateCurrentAddress = false; diff --git a/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/e2e/InstrExprTest.java b/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/e2e/InstrExprTest.java index e0812e02b..f6216c8e2 100644 --- a/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/e2e/InstrExprTest.java +++ b/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/e2e/InstrExprTest.java @@ -6,6 +6,11 @@ public class InstrExprTest extends AbstractCompilerTest { + @Test(expected = Exception.class) + public void testDivisionByZeroReportsError() { + compile("LD A, 1 / 0"); + } + @Test public void testJump() { compile( From d79858b4883732a0a7e131583b6a86fb53ba514c Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 19:00:00 +0200 Subject: [PATCH 7/9] [#408] Make z80 run state volatile for cross-thread interrupts requestMaskableInterrupt reads currentRunState from device thread; stale read could drop interrupts. 8080 already volatile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java index 12467d105..c7b870106 100644 --- a/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java +++ b/plugins/cpu/z80-cpu/src/main/java/net/emustudio/plugins/cpu/zilogZ80/EmulatorEngine.java @@ -75,7 +75,7 @@ public class EmulatorEngine implements CpuEngine { private boolean interruptSkip; // when EI enabled, skip next instruction interrupt private int lastOpcode; - private RunState currentRunState = RunState.STATE_STOPPED_NORMAL; + private volatile RunState currentRunState = RunState.STATE_STOPPED_NORMAL; private volatile DispatchListener dispatchListener; From 7630b21002e7cd33eb70d35c4d4b3037d48f634d Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 19:16:27 +0200 Subject: [PATCH 8/9] [#408] Extract shared compiler support module CharArrayCharStream (6 identical copies) and ParsingUtils (4 copies) move to new :plugins:compiler:compiler-shared module. Application depends on it so the jar lands in dist lib/, where plugin manifests resolve Class-Path. ADR-0008 records the boundary decision. Not unified: per-plugin exceptions and ParserErrorListener (divergent types/formats), FlagsModel (different semantics), Breakpoint (trivial), terminal DisplayCanvas (too divergent). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- application/build.gradle | 2 + .../0008-shared-compiler-support-module.adoc | 15 ++ plugins/compiler/as-8080/build.gradle | 1 + .../compiler/as8080/LexicalAnalyzerImpl.java | 1 + .../plugins/compiler/as8080/ParsingUtils.java | 59 ------- .../compiler/as8080/ast/expr/ExprId.java | 2 +- .../compiler/as8080/ast/expr/ExprString.java | 2 +- .../as8080/ast/pseudo/PseudoInclude.java | 2 +- .../as8080/ast/pseudo/PseudoLabel.java | 2 +- .../visitors/CheckDeclarationsVisitor.java | 2 +- .../as8080/visitors/CreateExprVisitor.java | 2 +- .../as8080/visitors/EvaluateExprVisitor.java | 2 +- .../as8080/visitors/ExpandMacrosVisitor.java | 2 +- .../visitors/SortMacroArgumentsVisitor.java | 2 +- .../as8080/parser/ParsingUtilsTest.java | 2 +- plugins/compiler/as-ssem/build.gradle | 1 + .../compiler/ssem/CharArrayCharStream.java | 113 ------------ .../compiler/ssem/LexicalAnalyzerImpl.java | 1 + plugins/compiler/as-z80/build.gradle | 1 + .../compiler/asZ80/CharArrayCharStream.java | 113 ------------ .../compiler/asZ80/LexicalAnalyzerImpl.java | 1 + .../compiler/asZ80/ast/expr/ExprId.java | 2 +- .../compiler/asZ80/ast/expr/ExprString.java | 2 +- .../asZ80/ast/pseudo/PseudoInclude.java | 2 +- .../asZ80/ast/pseudo/PseudoLabel.java | 2 +- .../visitors/CheckDeclarationsVisitor.java | 2 +- .../asZ80/visitors/CreateExprVisitor.java | 2 +- .../asZ80/visitors/EvaluateExprVisitor.java | 2 +- .../asZ80/visitors/ExpandMacrosVisitor.java | 2 +- .../visitors/SortMacroArgumentsVisitor.java | 2 +- .../asZ80/parser/ParsingUtilsTest.java | 80 --------- .../compiler/brainc-brainduck/build.gradle | 1 + .../brainduck/CharArrayCharStream.java | 113 ------------ .../brainduck/LexicalAnalyzerImpl.java | 1 + plugins/compiler/compiler-shared/build.gradle | 25 +++ .../compiler/shared}/CharArrayCharStream.java | 6 +- .../compiler/shared}/ParsingUtils.java | 2 +- .../shared}/CharArrayCharStreamTest.java | 2 +- plugins/compiler/ramc-ram/build.gradle | 1 + .../compiler/ram/CharArrayCharStream.java | 113 ------------ .../compiler/ram/LexicalAnalyzerImpl.java | 1 + .../plugins/compiler/ram/ParsingUtils.java | 59 ------- .../plugins/compiler/ram/ProgramParser.java | 2 +- .../plugins/compiler/ram/ast/Program.java | 2 +- plugins/compiler/raspc-rasp/build.gradle | 1 + .../compiler/rasp/CharArrayCharStream.java | 113 ------------ .../compiler/rasp/LexicalAnalyzerImpl.java | 1 + .../plugins/compiler/rasp/ParsingUtils.java | 52 ------ .../plugins/compiler/rasp/ProgramParser.java | 2 +- .../plugins/compiler/rasp/ast/Program.java | 2 +- .../rasp/CharArrayCharStreamTest.java | 166 ------------------ settings.gradle | 1 + 52 files changed, 83 insertions(+), 1009 deletions(-) create mode 100644 docs/adr/0008-shared-compiler-support-module.adoc delete mode 100644 plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ParsingUtils.java delete mode 100644 plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/CharArrayCharStream.java delete mode 100644 plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/CharArrayCharStream.java delete mode 100644 plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/parser/ParsingUtilsTest.java delete mode 100644 plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CharArrayCharStream.java create mode 100644 plugins/compiler/compiler-shared/build.gradle rename plugins/compiler/{as-8080/src/main/java/net/emustudio/plugins/compiler/as8080 => compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared}/CharArrayCharStream.java (93%) rename plugins/compiler/{as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80 => compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared}/ParsingUtils.java (97%) rename plugins/compiler/{ramc-ram/src/test/java/net/emustudio/plugins/compiler/ram => compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared}/CharArrayCharStreamTest.java (99%) delete mode 100644 plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CharArrayCharStream.java delete mode 100644 plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ParsingUtils.java delete mode 100644 plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStream.java delete mode 100644 plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ParsingUtils.java delete mode 100644 plugins/compiler/raspc-rasp/src/test/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStreamTest.java diff --git a/application/build.gradle b/application/build.gradle index dd35a620b..5be399139 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -23,6 +23,8 @@ dependencies { implementation libs.jcipAnnotations implementation libs.antlrRuntime + // shared plugin support code must land in dist lib/ (referenced by plugin jar manifests) + implementation project(":plugins:compiler:compiler-shared") extraLibs libs.editor extraLibs libs.editorDialogs diff --git a/docs/adr/0008-shared-compiler-support-module.adoc b/docs/adr/0008-shared-compiler-support-module.adoc new file mode 100644 index 000000000..6a838c52c --- /dev/null +++ b/docs/adr/0008-shared-compiler-support-module.adoc @@ -0,0 +1,15 @@ +# ADR-0008: Shared Compiler Support Module + +## Status +Accepted + +## Context +Six bundled compiler plugins each carried identical copies of ANTLR support code (`CharArrayCharStream`, `ParsingUtils`). Bug fixes had to be repeated per copy. The code depends on the ANTLR runtime, so it does not belong in emuLib (which must stay free of parser-framework dependencies). + +## Decision +Introduce an internal Gradle module `:plugins:compiler:compiler-shared` (package `net.emustudio.plugins.compiler.shared`) holding compiler support code shared by bundled compiler plugins. Plugins depend on it via `implementation project(...)`; the application also depends on it so the jar lands in the distribution `lib/` directory, where plugin jar manifests resolve their `Class-Path` entries from. + +Plugin-specific code (exception types, error listeners tied to per-plugin grammars) stays in each plugin. + +## Consequences +One source of truth for shared parsing code; ~700 lines of duplication removed. Bundled compiler plugins are no longer standalone-buildable without this repository, which is acceptable — they are bundled and versioned together. Third-party plugins are unaffected; the emuLib API is unchanged. diff --git a/plugins/compiler/as-8080/build.gradle b/plugins/compiler/as-8080/build.gradle index 12d211b51..ccde24943 100644 --- a/plugins/compiler/as-8080/build.gradle +++ b/plugins/compiler/as-8080/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java index 480305dc3..f2560c418 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.as8080; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ParsingUtils.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ParsingUtils.java deleted file mode 100644 index 5e51a139a..000000000 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ParsingUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.as8080; - -import org.antlr.v4.runtime.Token; - -import java.util.Locale; - -public class ParsingUtils { - - public static String parseLitString(Token token) { - // LIT_STRING_1: '\'' ~[']* '\''; - // LIT_STRING_2: '"' ~["]* '"'; - String text = token.getText(); - return text.substring(1, text.length() - 1); - } - - public static int parseLitHex1(Token token) { - // LIT_HEXNUMBER_1: [\-]? '0' X [0-9a-fA-F]+; - return Integer.decode(token.getText()); - } - - public static int parseLitHex2(Token token) { - // LIT_HEXNUMBER_2: [\-]? [0-9a-fA-F]+ H; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 16); - } - - public static int parseLitOct(Token token) { - // LIT_OCTNUMBER: [\-]? [0-7]+ [oOqQ]; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 8); - } - - public static int parseLitDec(Token token) { - // LIT_NUMBER: [\-]? [0-9]+ D? - String rawText = token.getText(); - if (rawText.endsWith("d") || rawText.endsWith("D")) { - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 10); - } else { - return Integer.parseInt(rawText, 10); - } - } - - public static int parseLitBin(Token token) { - // LIT_BINNUMBER: [01]+ B; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 2); - } - - public static String parseLabel(Token token) { - String rawText = token.getText(); - return rawText.substring(0, rawText.length() - 1); - } - - public static String normalizeId(String id) { - return id.toLowerCase(Locale.ENGLISH); - } -} diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java index 2b8ef9954..c4770c924 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; public class ExprId extends Node { public final String id; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java index c972940e3..ee7bfca67 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.parseLitString; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLitString; public class ExprString extends Node { public final String string; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java index 405ea6907..236de89ec 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java @@ -3,7 +3,7 @@ package net.emustudio.plugins.compiler.as8080.ast.pseudo; import net.emustudio.emulib.plugins.compiler.SourceCodePosition; -import net.emustudio.plugins.compiler.as8080.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.as8080.ast.Node; import net.emustudio.plugins.compiler.as8080.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java index 038d38d0f..f0ec606ef 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.parseLabel; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLabel; public class PseudoLabel extends Node { public final String label; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java index 2f9dde230..0e1b35df4 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java @@ -9,7 +9,7 @@ import java.util.*; import static net.emustudio.plugins.compiler.as8080.CompileError.*; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Checks if all declarations are valid: diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java index 62bdc5eee..001ee7f59 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java @@ -5,7 +5,7 @@ import net.emustudio.emulib.plugins.compiler.SourceCodePosition; import net.emustudio.plugins.compiler.as8080.As8080Parser; import net.emustudio.plugins.compiler.as8080.As8080ParserBaseVisitor; -import net.emustudio.plugins.compiler.as8080.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.as8080.ast.Node; import net.emustudio.plugins.compiler.as8080.ast.expr.*; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java index 365e225cd..e1b9a2062 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java @@ -17,7 +17,7 @@ import static net.emustudio.plugins.compiler.as8080.CompileError.ambiguousExpression; import static net.emustudio.plugins.compiler.as8080.CompileError.expressionIsBiggerThanExpected; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * The goal is to replace all Expr* with Evaluated diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java index 2a9fbd00f..34bce2f94 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java @@ -11,7 +11,7 @@ import static net.emustudio.plugins.compiler.as8080.CompileError.infiniteLoopDetected; import static net.emustudio.plugins.compiler.as8080.CompileError.notDefined; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Expands macros. It means - find macro definitions, remove them from the parent node and put them as a child under diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java index e0aa70a38..a3a3afcfd 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java @@ -15,7 +15,7 @@ import java.util.Map; import static net.emustudio.plugins.compiler.as8080.CompileError.macroArgumentsDoNotMatch; -import static net.emustudio.plugins.compiler.as8080.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Connects macro parameters with arguments. diff --git a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java index ff2167401..6b2928b65 100644 --- a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java +++ b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.as8080.parser; -import net.emustudio.plugins.compiler.as8080.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.as8080.Utils; import org.antlr.v4.runtime.Token; import org.junit.Assert; diff --git a/plugins/compiler/as-ssem/build.gradle b/plugins/compiler/as-ssem/build.gradle index 39e67fd4f..9f4e09f93 100644 --- a/plugins/compiler/as-ssem/build.gradle +++ b/plugins/compiler/as-ssem/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/CharArrayCharStream.java b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/CharArrayCharStream.java deleted file mode 100644 index a568fd877..000000000 --- a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.ssem; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java index 7f01a7054..b1e42b9c7 100644 --- a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ssem; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-z80/build.gradle b/plugins/compiler/as-z80/build.gradle index b27d6ccf9..f9cd57898 100644 --- a/plugins/compiler/as-z80/build.gradle +++ b/plugins/compiler/as-z80/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi testImplementation libs.cpuTestSuite diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/CharArrayCharStream.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/CharArrayCharStream.java deleted file mode 100644 index df532a5d6..000000000 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.asZ80; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java index ffd6fa126..e51a7e936 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.asZ80; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java index 3afb857b8..343cea96c 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; public class ExprId extends Node { public final String id; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java index a520e0ece..7a4cebc94 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.parseLitString; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLitString; public class ExprString extends Node { public final String string; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java index 9e6bb64b6..0073c73a8 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java @@ -3,7 +3,7 @@ package net.emustudio.plugins.compiler.asZ80.ast.pseudo; import net.emustudio.emulib.plugins.compiler.SourceCodePosition; -import net.emustudio.plugins.compiler.asZ80.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.asZ80.ast.Node; import net.emustudio.plugins.compiler.asZ80.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java index 46ac93090..4805cba46 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.parseLabel; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLabel; public class PseudoLabel extends Node { public final String label; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java index da1472cf0..63cf4cc1f 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java @@ -9,7 +9,7 @@ import java.util.*; import static net.emustudio.plugins.compiler.asZ80.CompileError.*; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Checks if all declarations are valid: diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java index 159e87dfe..b54904deb 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java @@ -5,7 +5,7 @@ import net.emustudio.emulib.plugins.compiler.SourceCodePosition; import net.emustudio.plugins.compiler.asZ80.AsZ80Parser.*; import net.emustudio.plugins.compiler.asZ80.AsZ80ParserBaseVisitor; -import net.emustudio.plugins.compiler.asZ80.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.asZ80.ast.Node; import net.emustudio.plugins.compiler.asZ80.ast.expr.*; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java index 2a7ea7221..c8c93fa2b 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java @@ -17,7 +17,7 @@ import static net.emustudio.plugins.compiler.asZ80.CompileError.ambiguousExpression; import static net.emustudio.plugins.compiler.asZ80.CompileError.expressionIsBiggerThanExpected; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * The goal is to replace all Expr* with Evaluated diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java index 09407e646..9cd3677fe 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java @@ -11,7 +11,7 @@ import static net.emustudio.plugins.compiler.asZ80.CompileError.infiniteLoopDetected; import static net.emustudio.plugins.compiler.asZ80.CompileError.notDefined; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Expands macros. It means - find macro definitions, remove them from the parent node and put them as a child under diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java index a8ec9dad7..0064fff64 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java @@ -15,7 +15,7 @@ import java.util.Map; import static net.emustudio.plugins.compiler.asZ80.CompileError.macroArgumentsDoNotMatch; -import static net.emustudio.plugins.compiler.asZ80.ParsingUtils.normalizeId; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; /** * Connects macro parameters with arguments. diff --git a/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/parser/ParsingUtilsTest.java b/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/parser/ParsingUtilsTest.java deleted file mode 100644 index c9bc21c03..000000000 --- a/plugins/compiler/as-z80/src/test/java/net/emustudio/plugins/compiler/asZ80/parser/ParsingUtilsTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.asZ80.parser; - -import net.emustudio.plugins.compiler.asZ80.ParsingUtils; -import net.emustudio.plugins.compiler.asZ80.Utils; -import org.antlr.v4.runtime.Token; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; - -import static net.emustudio.plugins.compiler.asZ80.AsZ80Parser.*; -import static net.emustudio.plugins.compiler.asZ80.Utils.assertTokenTypes; -import static net.emustudio.plugins.compiler.asZ80.Utils.getTokens; -import static org.junit.Assert.assertEquals; - -public class ParsingUtilsTest { - - @Test - public void testParseLitString() { - List tokens = getTokens("'te\"x\"t1' \"te'x't2\""); - Utils.assertTokenTypes(tokens, LIT_STRING_1, WS, LIT_STRING_2, EOF); - Assert.assertEquals("te\"x\"t1", ParsingUtils.parseLitString(tokens.get(0))); - assertEquals("te'x't2", ParsingUtils.parseLitString(tokens.get(2))); - } - - @Test - public void testParseLitHex1() { - List tokens = getTokens("0x22F 0XAA55"); - assertTokenTypes(tokens, LIT_HEXNUMBER_1, WS, LIT_HEXNUMBER_1, EOF); - assertEquals(0x22F, ParsingUtils.parseLitHex1(tokens.get(0))); - assertEquals(0xAA55, ParsingUtils.parseLitHex1(tokens.get(2))); - } - - @Test - public void testParseLitHex2() { - List tokens = getTokens("022Fh AA55H"); - assertTokenTypes(tokens, LIT_HEXNUMBER_2, WS, LIT_HEXNUMBER_2, EOF); - assertEquals(0x22F, ParsingUtils.parseLitHex2(tokens.get(0))); - assertEquals(0xAA55, ParsingUtils.parseLitHex2(tokens.get(2))); - } - - @Test - public void testParseLitOct() { - List tokens = getTokens("22q 55O 77Q 001o"); - assertTokenTypes( - tokens, - LIT_OCTNUMBER, WS, LIT_OCTNUMBER, WS, LIT_OCTNUMBER, WS, LIT_OCTNUMBER, EOF - ); - assertEquals(18, ParsingUtils.parseLitOct(tokens.get(0))); - assertEquals(45, ParsingUtils.parseLitOct(tokens.get(2))); - assertEquals(63, ParsingUtils.parseLitOct(tokens.get(4))); - assertEquals(1, ParsingUtils.parseLitOct(tokens.get(6))); - } - - @Test - public void testParseLitDec() { - List tokens = getTokens("22 55 00"); - assertTokenTypes( - tokens, - LIT_NUMBER, WS, LIT_NUMBER, WS, LIT_NUMBER, EOF - ); - assertEquals(22, ParsingUtils.parseLitDec(tokens.get(0))); - assertEquals(55, ParsingUtils.parseLitDec(tokens.get(2))); - assertEquals(0, ParsingUtils.parseLitDec(tokens.get(4))); - } - - @Test - public void testParseLitBin() { - List tokens = getTokens("000b 0101101b 111b"); - assertTokenTypes( - tokens, - LIT_BINNUMBER, WS, LIT_BINNUMBER, WS, LIT_BINNUMBER, EOF - ); - assertEquals(0, ParsingUtils.parseLitBin(tokens.get(0))); - assertEquals(45, ParsingUtils.parseLitBin(tokens.get(2))); - assertEquals(7, ParsingUtils.parseLitBin(tokens.get(4))); - } -} diff --git a/plugins/compiler/brainc-brainduck/build.gradle b/plugins/compiler/brainc-brainduck/build.gradle index 7e9bd1579..526555934 100644 --- a/plugins/compiler/brainc-brainduck/build.gradle +++ b/plugins/compiler/brainc-brainduck/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CharArrayCharStream.java b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CharArrayCharStream.java deleted file mode 100644 index d0a892740..000000000 --- a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.brainduck; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java index e466c3938..1211f84d3 100644 --- a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java +++ b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.brainduck; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/compiler-shared/build.gradle b/plugins/compiler/compiler-shared/build.gradle new file mode 100644 index 000000000..146494b2c --- /dev/null +++ b/plugins/compiler/compiler-shared/build.gradle @@ -0,0 +1,25 @@ +/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo + SPDX-License-Identifier: GPL-3.0-or-later */ + +plugins { + id 'java' + id 'idea' + id 'com.adarshr.test-logger' version '4.0.0' + id 'jacoco' +} + +dependencies { + implementation libs.antlrRuntime + + testImplementation libs.junit +} + +apply from: "$rootDir/gradle/conventions/java-conventions.gradle" + +test { + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test +} diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/CharArrayCharStream.java b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java similarity index 93% rename from plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/CharArrayCharStream.java rename to plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java index 022d99530..630191955 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/CharArrayCharStream.java +++ b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java @@ -1,6 +1,6 @@ /* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.as8080; +package net.emustudio.plugins.compiler.shared; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.IntStream; @@ -24,7 +24,7 @@ * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), * which is true for assembly language source code. */ -final class CharArrayCharStream implements CharStream { +public final class CharArrayCharStream implements CharStream { private final char[] data; private final int start; private final int size; @@ -37,7 +37,7 @@ final class CharArrayCharStream implements CharStream { * @param offset first character index in the array * @param length number of characters to expose */ - CharArrayCharStream(char[] data, int offset, int length) { + public CharArrayCharStream(char[] data, int offset, int length) { this.data = data; this.start = offset; this.size = length; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ParsingUtils.java b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java similarity index 97% rename from plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ParsingUtils.java rename to plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java index e220b771f..37263406b 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ParsingUtils.java +++ b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java @@ -1,6 +1,6 @@ /* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.asZ80; +package net.emustudio.plugins.compiler.shared; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/ramc-ram/src/test/java/net/emustudio/plugins/compiler/ram/CharArrayCharStreamTest.java b/plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java similarity index 99% rename from plugins/compiler/ramc-ram/src/test/java/net/emustudio/plugins/compiler/ram/CharArrayCharStreamTest.java rename to plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java index 219ed1b7f..07cb6c867 100644 --- a/plugins/compiler/ramc-ram/src/test/java/net/emustudio/plugins/compiler/ram/CharArrayCharStreamTest.java +++ b/plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java @@ -1,6 +1,6 @@ /* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.ram; +package net.emustudio.plugins.compiler.shared; import org.antlr.v4.runtime.IntStream; import org.antlr.v4.runtime.misc.Interval; diff --git a/plugins/compiler/ramc-ram/build.gradle b/plugins/compiler/ramc-ram/build.gradle index 144c0edb3..6a5689087 100644 --- a/plugins/compiler/ramc-ram/build.gradle +++ b/plugins/compiler/ramc-ram/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation project.rootProject.project(":plugins:memory:ram-mem") implementation libs.slf4JApi diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CharArrayCharStream.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CharArrayCharStream.java deleted file mode 100644 index bf4d8f0f7..000000000 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.ram; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java index 9a57ac0bb..2dd8b63c2 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ram; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ParsingUtils.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ParsingUtils.java deleted file mode 100644 index a5b69aa7f..000000000 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ParsingUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.ram; - -import org.antlr.v4.runtime.Token; - -import java.util.Locale; - -public class ParsingUtils { - - public static String parseLitString(Token token) { - // LIT_STRING_1: '\'' ~[']* '\''; - // LIT_STRING_2: '"' ~["]* '"'; - String text = token.getText(); - return text.substring(1, text.length() - 1); - } - - public static int parseLitHex1(Token token) { - // LIT_HEXNUMBER_1: [\-]? '0' X [0-9a-fA-F]+; - return Integer.decode(token.getText()); - } - - public static int parseLitHex2(Token token) { - // LIT_HEXNUMBER_2: [\-]? [0-9a-fA-F]+ H; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 16); - } - - public static int parseLitOct(Token token) { - // LIT_OCTNUMBER: [\-]? [0-7]+ [oOqQ]; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 8); - } - - public static int parseLitDec(Token token) { - // LIT_NUMBER: [\-]? [0-9]+ D? - String rawText = token.getText(); - if (rawText.endsWith("d") || rawText.endsWith("D")) { - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 10); - } else { - return Integer.parseInt(rawText, 10); - } - } - - public static int parseLitBin(Token token) { - // LIT_BINNUMBER: [01]+ B; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 2); - } - - public static String parseLabel(Token token) { - String rawText = token.getText(); - return rawText.substring(0, rawText.length() - 1); - } - - public static String normalizeId(String id) { - return id.toLowerCase(Locale.ENGLISH); - } -} diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java index fd9fadb0a..ac97868b3 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.Objects; -import static net.emustudio.plugins.compiler.ram.ParsingUtils.*; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.*; import static net.emustudio.plugins.compiler.ram.RAMParser.*; public class ProgramParser extends RAMParserBaseVisitor { diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java index cde358b2b..c5bb04a28 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ram.ast; -import net.emustudio.plugins.compiler.ram.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.ram.exceptions.CompileException; import net.emustudio.plugins.memory.ram.api.RamInstruction; import net.emustudio.plugins.memory.ram.api.RamMemoryContext; diff --git a/plugins/compiler/raspc-rasp/build.gradle b/plugins/compiler/raspc-rasp/build.gradle index 078e996a1..330e8bdf4 100644 --- a/plugins/compiler/raspc-rasp/build.gradle +++ b/plugins/compiler/raspc-rasp/build.gradle @@ -19,6 +19,7 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib + implementation project(":plugins:compiler:compiler-shared") implementation project.rootProject.project(":plugins:memory:rasp-mem") implementation libs.slf4JApi diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStream.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStream.java deleted file mode 100644 index 2fa4f4ef1..000000000 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.rasp; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java index 4ff21d5df..fe2019d95 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java @@ -2,6 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.rasp; +import net.emustudio.plugins.compiler.shared.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ParsingUtils.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ParsingUtils.java deleted file mode 100644 index 4c5cf880e..000000000 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ParsingUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.rasp; - -import org.antlr.v4.runtime.Token; - -import java.util.Locale; - -public class ParsingUtils { - - public static int parseLitHex1(Token token) { - // LIT_HEXNUMBER_1: [\-]? '0' X [0-9a-fA-F]+; - return Integer.decode(token.getText()); - } - - public static int parseLitHex2(Token token) { - // LIT_HEXNUMBER_2: [\-]? [0-9a-fA-F]+ H; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 16); - } - - public static int parseLitOct(Token token) { - // LIT_OCTNUMBER: [\-]? [0-7]+ [oOqQ]; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 8); - } - - public static int parseLitDec(Token token) { - // LIT_NUMBER: [\-]? [0-9]+ D? - String rawText = token.getText(); - if (rawText.endsWith("d") || rawText.endsWith("D")) { - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 10); - } else { - return Integer.parseInt(rawText, 10); - } - } - - public static int parseLitBin(Token token) { - // LIT_BINNUMBER: [01]+ B; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 2); - } - - public static String parseLabel(Token token) { - String rawText = token.getText(); - return rawText.substring(0, rawText.length() - 1); - } - - public static String normalizeId(String id) { - return id.toLowerCase(Locale.ENGLISH); - } -} diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java index a73a55f51..636b47b00 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java @@ -11,7 +11,7 @@ import java.util.Map; import java.util.Objects; -import static net.emustudio.plugins.compiler.rasp.ParsingUtils.*; +import static net.emustudio.plugins.compiler.shared.ParsingUtils.*; import static net.emustudio.plugins.compiler.rasp.RASPParser.*; import static net.emustudio.plugins.memory.rasp.gui.Disassembler.HALT; diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java index 490581d0a..c8a2b2741 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.rasp.ast; -import net.emustudio.plugins.compiler.rasp.ParsingUtils; +import net.emustudio.plugins.compiler.shared.ParsingUtils; import net.emustudio.plugins.compiler.rasp.exceptions.CompileException; import net.emustudio.plugins.memory.rasp.api.RaspMemoryContext; diff --git a/plugins/compiler/raspc-rasp/src/test/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStreamTest.java b/plugins/compiler/raspc-rasp/src/test/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStreamTest.java deleted file mode 100644 index e56118b18..000000000 --- a/plugins/compiler/raspc-rasp/src/test/java/net/emustudio/plugins/compiler/rasp/CharArrayCharStreamTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.rasp; - -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class CharArrayCharStreamTest { - - @Test - public void testSizeReturnsLength() { - char[] data = "hello".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(5, stream.size()); - } - - @Test - public void testSizeWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals(5, stream.size()); - } - - @Test - public void testIndexStartsAtZero() { - char[] data = "hello".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(0, stream.index()); - } - - @Test - public void testConsume() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); - assertEquals(1, stream.index()); - stream.consume(); - assertEquals(2, stream.index()); - } - - @Test(expected = IllegalStateException.class) - public void testConsumeAtEOFThrows() { - char[] data = "a".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 = size - stream.consume(); // should throw - } - - @Test - public void testLAForward() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals('a', stream.LA(1)); - assertEquals('b', stream.LA(2)); - assertEquals('c', stream.LA(3)); - } - - @Test - public void testLAReturnsEOFBeyondSize() { - char[] data = "ab".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.EOF, stream.LA(3)); - } - - @Test - public void testLAZeroReturnsZero() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(0, stream.LA(0)); - } - - @Test - public void testLANegative() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 - assertEquals('a', stream.LA(-1)); - } - - @Test - public void testLANegativeBeyondStart() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.EOF, stream.LA(-1)); - } - - @Test - public void testSeek() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.seek(2); - assertEquals(2, stream.index()); - assertEquals('c', stream.LA(1)); - } - - @Test - public void testGetText() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("hello", stream.getText(Interval.of(0, 4))); - } - - @Test - public void testGetTextWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals("world", stream.getText(Interval.of(0, 4))); - } - - @Test - public void testGetTextBeyondSize() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("abc", stream.getText(Interval.of(0, 10))); - } - - @Test - public void testGetTextEmptyInterval() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("", stream.getText(Interval.of(5, 3))); - } - - @Test - public void testMark() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(-1, stream.mark()); - } - - @Test - public void testRelease() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.release(0); // should not throw - } - - @Test - public void testGetSourceName() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.UNKNOWN_SOURCE_NAME, stream.getSourceName()); - } - - @Test - public void testLAWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals('w', stream.LA(1)); - assertEquals('o', stream.LA(2)); - } - - @Test - public void testLANegativeAtPositionTwoReturnsCorrectChars() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 - stream.consume(); // index = 2 - assertEquals('b', stream.LA(-1)); - assertEquals('a', stream.LA(-2)); - } -} - diff --git a/settings.gradle b/settings.gradle index 0f1f00660..b3d02ff82 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ rootProject.name = 'emuStudio' include ':application' +include ':plugins:compiler:compiler-shared' include ':plugins:compiler:as-8080' include ':plugins:compiler:as-ssem' include ':plugins:compiler:as-z80' From 248cb60d106243f4e4f9c0d7ebf65367994b2a98 Mon Sep 17 00:00:00 2001 From: Peter Jakubco Date: Sat, 22 Aug 2026 19:36:16 +0200 Subject: [PATCH 9/9] [#408] Move shared compiler helpers to emuLib Replaces internal compiler-shared module. CharArrayCharStream and ParsingUtils now live in emuLib plugins.compiler.antlr (compileOnly antlr4-runtime there; plugins provide it). ADR-0008 updated. Requires emuLib 12.1.0-SNAPSHOT (emuLib commit bc00f7c). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- application/build.gradle | 2 - ...008-shared-compiler-helpers-in-emulib.adoc | 15 ++ .../0008-shared-compiler-support-module.adoc | 15 -- plugins/compiler/as-8080/build.gradle | 1 - .../compiler/as8080/LexicalAnalyzerImpl.java | 2 +- .../compiler/as8080/ast/expr/ExprId.java | 2 +- .../compiler/as8080/ast/expr/ExprString.java | 2 +- .../as8080/ast/pseudo/PseudoInclude.java | 2 +- .../as8080/ast/pseudo/PseudoLabel.java | 2 +- .../visitors/CheckDeclarationsVisitor.java | 2 +- .../as8080/visitors/CreateExprVisitor.java | 2 +- .../as8080/visitors/EvaluateExprVisitor.java | 2 +- .../as8080/visitors/ExpandMacrosVisitor.java | 2 +- .../visitors/SortMacroArgumentsVisitor.java | 2 +- .../as8080/parser/ParsingUtilsTest.java | 2 +- plugins/compiler/as-ssem/build.gradle | 1 - .../compiler/ssem/LexicalAnalyzerImpl.java | 2 +- plugins/compiler/as-z80/build.gradle | 1 - .../compiler/asZ80/LexicalAnalyzerImpl.java | 2 +- .../compiler/asZ80/ast/expr/ExprId.java | 2 +- .../compiler/asZ80/ast/expr/ExprString.java | 2 +- .../asZ80/ast/pseudo/PseudoInclude.java | 2 +- .../asZ80/ast/pseudo/PseudoLabel.java | 2 +- .../visitors/CheckDeclarationsVisitor.java | 2 +- .../asZ80/visitors/CreateExprVisitor.java | 2 +- .../asZ80/visitors/EvaluateExprVisitor.java | 2 +- .../asZ80/visitors/ExpandMacrosVisitor.java | 2 +- .../visitors/SortMacroArgumentsVisitor.java | 2 +- .../compiler/brainc-brainduck/build.gradle | 1 - .../brainduck/LexicalAnalyzerImpl.java | 2 +- plugins/compiler/compiler-shared/build.gradle | 25 --- .../compiler/shared/CharArrayCharStream.java | 113 ------------ .../plugins/compiler/shared/ParsingUtils.java | 59 ------- .../shared/CharArrayCharStreamTest.java | 166 ------------------ plugins/compiler/ramc-ram/build.gradle | 1 - .../compiler/ram/LexicalAnalyzerImpl.java | 2 +- .../plugins/compiler/ram/ProgramParser.java | 2 +- .../plugins/compiler/ram/ast/Program.java | 2 +- plugins/compiler/raspc-rasp/build.gradle | 1 - .../compiler/rasp/LexicalAnalyzerImpl.java | 2 +- .../plugins/compiler/rasp/ProgramParser.java | 2 +- .../plugins/compiler/rasp/ast/Program.java | 2 +- settings.gradle | 1 - 43 files changed, 44 insertions(+), 416 deletions(-) create mode 100644 docs/adr/0008-shared-compiler-helpers-in-emulib.adoc delete mode 100644 docs/adr/0008-shared-compiler-support-module.adoc delete mode 100644 plugins/compiler/compiler-shared/build.gradle delete mode 100644 plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java delete mode 100644 plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java delete mode 100644 plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java diff --git a/application/build.gradle b/application/build.gradle index 5be399139..dd35a620b 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -23,8 +23,6 @@ dependencies { implementation libs.jcipAnnotations implementation libs.antlrRuntime - // shared plugin support code must land in dist lib/ (referenced by plugin jar manifests) - implementation project(":plugins:compiler:compiler-shared") extraLibs libs.editor extraLibs libs.editorDialogs diff --git a/docs/adr/0008-shared-compiler-helpers-in-emulib.adoc b/docs/adr/0008-shared-compiler-helpers-in-emulib.adoc new file mode 100644 index 000000000..5dc2cf905 --- /dev/null +++ b/docs/adr/0008-shared-compiler-helpers-in-emulib.adoc @@ -0,0 +1,15 @@ +# ADR-0008: Shared Compiler Helpers Live in emuLib + +## Status +Accepted + +## Context +Six bundled compiler plugins each carried identical copies of ANTLR support code (`CharArrayCharStream`, `ParsingUtils`). Bug fixes had to be repeated per copy. emuLib owns reusable utilities shared across emuStudio repositories, so the helpers belong there rather than in an emuStudio-internal module. + +## Decision +Move the helpers to emuLib package `net.emustudio.emulib.plugins.compiler.antlr` (a subpackage, because `plugins.compiler` already defines emuStudio's own `Token`). In emuLib, `antlr4-runtime` is a `compileOnly` dependency: only compiler plugins that use these helpers must provide the ANTLR runtime, which they already do. + +Plugin-specific code (exception types, error listeners tied to per-plugin grammars) stays in each plugin. + +## Consequences +One source of truth for shared parsing code; ~700 lines of duplication removed from this repository. emuLib gains a compile-time-only ANTLR dependency; non-compiler consumers are unaffected. Bundled compiler plugins require emuLib >= 12.1.0. diff --git a/docs/adr/0008-shared-compiler-support-module.adoc b/docs/adr/0008-shared-compiler-support-module.adoc deleted file mode 100644 index 6a838c52c..000000000 --- a/docs/adr/0008-shared-compiler-support-module.adoc +++ /dev/null @@ -1,15 +0,0 @@ -# ADR-0008: Shared Compiler Support Module - -## Status -Accepted - -## Context -Six bundled compiler plugins each carried identical copies of ANTLR support code (`CharArrayCharStream`, `ParsingUtils`). Bug fixes had to be repeated per copy. The code depends on the ANTLR runtime, so it does not belong in emuLib (which must stay free of parser-framework dependencies). - -## Decision -Introduce an internal Gradle module `:plugins:compiler:compiler-shared` (package `net.emustudio.plugins.compiler.shared`) holding compiler support code shared by bundled compiler plugins. Plugins depend on it via `implementation project(...)`; the application also depends on it so the jar lands in the distribution `lib/` directory, where plugin jar manifests resolve their `Class-Path` entries from. - -Plugin-specific code (exception types, error listeners tied to per-plugin grammars) stays in each plugin. - -## Consequences -One source of truth for shared parsing code; ~700 lines of duplication removed. Bundled compiler plugins are no longer standalone-buildable without this repository, which is acceptable — they are bundled and versioned together. Third-party plugins are unaffected; the emuLib API is unchanged. diff --git a/plugins/compiler/as-8080/build.gradle b/plugins/compiler/as-8080/build.gradle index ccde24943..12d211b51 100644 --- a/plugins/compiler/as-8080/build.gradle +++ b/plugins/compiler/as-8080/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java index f2560c418..4f6d35782 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.as8080; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java index c4770c924..8cf490c5a 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprId.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; public class ExprId extends Node { public final String id; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java index ee7bfca67..eb402daa2 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/expr/ExprString.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLitString; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.parseLitString; public class ExprString extends Node { public final String string; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java index 236de89ec..949d153f6 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoInclude.java @@ -3,7 +3,7 @@ package net.emustudio.plugins.compiler.as8080.ast.pseudo; import net.emustudio.emulib.plugins.compiler.SourceCodePosition; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.as8080.ast.Node; import net.emustudio.plugins.compiler.as8080.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java index f0ec606ef..62c38fa9d 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/ast/pseudo/PseudoLabel.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLabel; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.parseLabel; public class PseudoLabel extends Node { public final String label; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java index 0e1b35df4..236339920 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CheckDeclarationsVisitor.java @@ -9,7 +9,7 @@ import java.util.*; import static net.emustudio.plugins.compiler.as8080.CompileError.*; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Checks if all declarations are valid: diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java index 001ee7f59..c924b7a8f 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/CreateExprVisitor.java @@ -5,7 +5,7 @@ import net.emustudio.emulib.plugins.compiler.SourceCodePosition; import net.emustudio.plugins.compiler.as8080.As8080Parser; import net.emustudio.plugins.compiler.as8080.As8080ParserBaseVisitor; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.as8080.ast.Node; import net.emustudio.plugins.compiler.as8080.ast.expr.*; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java index e1b9a2062..e07dbd81a 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/EvaluateExprVisitor.java @@ -17,7 +17,7 @@ import static net.emustudio.plugins.compiler.as8080.CompileError.ambiguousExpression; import static net.emustudio.plugins.compiler.as8080.CompileError.expressionIsBiggerThanExpected; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * The goal is to replace all Expr* with Evaluated diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java index 34bce2f94..75303cfee 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/ExpandMacrosVisitor.java @@ -11,7 +11,7 @@ import static net.emustudio.plugins.compiler.as8080.CompileError.infiniteLoopDetected; import static net.emustudio.plugins.compiler.as8080.CompileError.notDefined; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Expands macros. It means - find macro definitions, remove them from the parent node and put them as a child under diff --git a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java index a3a3afcfd..ac8cdacd7 100644 --- a/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java +++ b/plugins/compiler/as-8080/src/main/java/net/emustudio/plugins/compiler/as8080/visitors/SortMacroArgumentsVisitor.java @@ -15,7 +15,7 @@ import java.util.Map; import static net.emustudio.plugins.compiler.as8080.CompileError.macroArgumentsDoNotMatch; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Connects macro parameters with arguments. diff --git a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java index 6b2928b65..cf97b3d64 100644 --- a/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java +++ b/plugins/compiler/as-8080/src/test/java/net/emustudio/plugins/compiler/as8080/parser/ParsingUtilsTest.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.as8080.parser; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.as8080.Utils; import org.antlr.v4.runtime.Token; import org.junit.Assert; diff --git a/plugins/compiler/as-ssem/build.gradle b/plugins/compiler/as-ssem/build.gradle index 9f4e09f93..39e67fd4f 100644 --- a/plugins/compiler/as-ssem/build.gradle +++ b/plugins/compiler/as-ssem/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java index b1e42b9c7..4d72585b5 100644 --- a/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-ssem/src/main/java/net/emustudio/plugins/compiler/ssem/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ssem; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-z80/build.gradle b/plugins/compiler/as-z80/build.gradle index f9cd57898..b27d6ccf9 100644 --- a/plugins/compiler/as-z80/build.gradle +++ b/plugins/compiler/as-z80/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi testImplementation libs.cpuTestSuite diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java index e51a7e936..0c266de13 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.asZ80; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java index 343cea96c..35459e678 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprId.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; public class ExprId extends Node { public final String id; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java index 7a4cebc94..36744c869 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/expr/ExprString.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLitString; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.parseLitString; public class ExprString extends Node { public final String string; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java index 0073c73a8..35bfd40ac 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoInclude.java @@ -3,7 +3,7 @@ package net.emustudio.plugins.compiler.asZ80.ast.pseudo; import net.emustudio.emulib.plugins.compiler.SourceCodePosition; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.asZ80.ast.Node; import net.emustudio.plugins.compiler.asZ80.visitors.NodeVisitor; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java index 4805cba46..599099220 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/ast/pseudo/PseudoLabel.java @@ -11,7 +11,7 @@ import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.parseLabel; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.parseLabel; public class PseudoLabel extends Node { public final String label; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java index 63cf4cc1f..0cb7d931f 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CheckDeclarationsVisitor.java @@ -9,7 +9,7 @@ import java.util.*; import static net.emustudio.plugins.compiler.asZ80.CompileError.*; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Checks if all declarations are valid: diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java index b54904deb..723c2f4af 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/CreateExprVisitor.java @@ -5,7 +5,7 @@ import net.emustudio.emulib.plugins.compiler.SourceCodePosition; import net.emustudio.plugins.compiler.asZ80.AsZ80Parser.*; import net.emustudio.plugins.compiler.asZ80.AsZ80ParserBaseVisitor; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.asZ80.ast.Node; import net.emustudio.plugins.compiler.asZ80.ast.expr.*; import org.antlr.v4.runtime.Token; diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java index c8c93fa2b..af58b9177 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/EvaluateExprVisitor.java @@ -17,7 +17,7 @@ import static net.emustudio.plugins.compiler.asZ80.CompileError.ambiguousExpression; import static net.emustudio.plugins.compiler.asZ80.CompileError.expressionIsBiggerThanExpected; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * The goal is to replace all Expr* with Evaluated diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java index 9cd3677fe..23cef3fda 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/ExpandMacrosVisitor.java @@ -11,7 +11,7 @@ import static net.emustudio.plugins.compiler.asZ80.CompileError.infiniteLoopDetected; import static net.emustudio.plugins.compiler.asZ80.CompileError.notDefined; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Expands macros. It means - find macro definitions, remove them from the parent node and put them as a child under diff --git a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java index 0064fff64..44f1124da 100644 --- a/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java +++ b/plugins/compiler/as-z80/src/main/java/net/emustudio/plugins/compiler/asZ80/visitors/SortMacroArgumentsVisitor.java @@ -15,7 +15,7 @@ import java.util.Map; import static net.emustudio.plugins.compiler.asZ80.CompileError.macroArgumentsDoNotMatch; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.normalizeId; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.normalizeId; /** * Connects macro parameters with arguments. diff --git a/plugins/compiler/brainc-brainduck/build.gradle b/plugins/compiler/brainc-brainduck/build.gradle index 526555934..7e9bd1579 100644 --- a/plugins/compiler/brainc-brainduck/build.gradle +++ b/plugins/compiler/brainc-brainduck/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation libs.slf4JApi implementation libs.jcipAnnotations diff --git a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java index 1211f84d3..0c17da345 100644 --- a/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java +++ b/plugins/compiler/brainc-brainduck/src/main/java/net/emustudio/plugins/compiler/brainduck/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.brainduck; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/compiler-shared/build.gradle b/plugins/compiler/compiler-shared/build.gradle deleted file mode 100644 index 146494b2c..000000000 --- a/plugins/compiler/compiler-shared/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ - -plugins { - id 'java' - id 'idea' - id 'com.adarshr.test-logger' version '4.0.0' - id 'jacoco' -} - -dependencies { - implementation libs.antlrRuntime - - testImplementation libs.junit -} - -apply from: "$rootDir/gradle/conventions/java-conventions.gradle" - -test { - finalizedBy jacocoTestReport -} - -jacocoTestReport { - dependsOn test -} diff --git a/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java deleted file mode 100644 index 630191955..000000000 --- a/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/CharArrayCharStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.shared; - -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; - -/** - * A zero-copy {@link CharStream} backed directly by a {@code char[]} slice. - *

- * Unlike {@link org.antlr.v4.runtime.CharStreams#fromString(String)}, this - * implementation avoids: - *

    - *
  • Allocating an intermediate {@code String}
  • - *
  • Allocating a {@code CharBuffer}
  • - *
  • Allocating a {@code CodePointBuffer}
  • - *
  • Copying character data (3× in the standard path)
  • - *
- *

- * This makes it suitable for per-line tokenization in RSyntaxTextArea where - * {@code getTokenList()} is called for every visible line on every keystroke. - *

- * Limitation: Assumes all characters are in the BMP (U+0000–U+FFFF), - * which is true for assembly language source code. - */ -public final class CharArrayCharStream implements CharStream { - private final char[] data; - private final int start; - private final int size; - private int index; - - /** - * Wrap a slice of a char array as a CharStream with zero copies. - * - * @param data the backing array (NOT copied) - * @param offset first character index in the array - * @param length number of characters to expose - */ - public CharArrayCharStream(char[] data, int offset, int length) { - this.data = data; - this.start = offset; - this.size = length; - this.index = 0; - } - - @Override - public String getText(Interval interval) { - int a = interval.a; - int b = Math.min(interval.b, size - 1); - if (a > b) { - return ""; - } - return new String(data, start + a, b - a + 1); - } - - @Override - public void consume() { - if (index >= size) { - throw new IllegalStateException("cannot consume EOF"); - } - index++; - } - - @Override - public int LA(int i) { - if (i == 0) { - return 0; // undefined - } - if (i < 0) { - i++; // e.g., LA(-1) looks at data[index-1] - if (index + i - 1 < 0) { - return IntStream.EOF; - } - } - int pos = index + i - 1; - if (pos < 0 || pos >= size) { - return IntStream.EOF; - } - return data[start + pos]; - } - - @Override - public int mark() { - return -1; // no need for mark/release in per-line scanning - } - - @Override - public void release(int marker) { - // no-op - } - - @Override - public int index() { - return index; - } - - @Override - public void seek(int index) { - this.index = index; - } - - @Override - public int size() { - return size; - } - - @Override - public String getSourceName() { - return IntStream.UNKNOWN_SOURCE_NAME; - } -} - diff --git a/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java b/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java deleted file mode 100644 index 37263406b..000000000 --- a/plugins/compiler/compiler-shared/src/main/java/net/emustudio/plugins/compiler/shared/ParsingUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.shared; - -import org.antlr.v4.runtime.Token; - -import java.util.Locale; - -public class ParsingUtils { - - public static String parseLitString(Token token) { - // LIT_STRING_1: '\'' ~[']* '\''; - // LIT_STRING_2: '"' ~["]* '"'; - String text = token.getText(); - return text.substring(1, text.length() - 1); - } - - public static int parseLitHex1(Token token) { - // LIT_HEXNUMBER_1: [\-]? '0' X [0-9a-fA-F]+; - return Integer.decode(token.getText()); - } - - public static int parseLitHex2(Token token) { - // LIT_HEXNUMBER_2: [\-]? [0-9a-fA-F]+ H; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 16); - } - - public static int parseLitOct(Token token) { - // LIT_OCTNUMBER: [\-]? [0-7]+ [oOqQ]; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 8); - } - - public static int parseLitDec(Token token) { - // LIT_NUMBER: [\-]? [0-9]+ D? - String rawText = token.getText(); - if (rawText.endsWith("d") || rawText.endsWith("D")) { - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 10); - } else { - return Integer.parseInt(rawText, 10); - } - } - - public static int parseLitBin(Token token) { - // LIT_BINNUMBER: [01]+ B; - String rawText = token.getText(); - return Integer.parseInt(rawText.substring(0, rawText.length() - 1), 2); - } - - public static String parseLabel(Token token) { - String rawText = token.getText(); - return rawText.substring(0, rawText.length() - 1); - } - - public static String normalizeId(String id) { - return id.toLowerCase(Locale.ENGLISH); - } -} diff --git a/plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java b/plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java deleted file mode 100644 index 07cb6c867..000000000 --- a/plugins/compiler/compiler-shared/src/test/java/net/emustudio/plugins/compiler/shared/CharArrayCharStreamTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-FileCopyrightText: 2006-2026 Peter Jakubčo - SPDX-License-Identifier: GPL-3.0-or-later */ -package net.emustudio.plugins.compiler.shared; - -import org.antlr.v4.runtime.IntStream; -import org.antlr.v4.runtime.misc.Interval; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class CharArrayCharStreamTest { - - @Test - public void testSizeReturnsLength() { - char[] data = "hello".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(5, stream.size()); - } - - @Test - public void testSizeWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals(5, stream.size()); - } - - @Test - public void testIndexStartsAtZero() { - char[] data = "hello".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(0, stream.index()); - } - - @Test - public void testConsume() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); - assertEquals(1, stream.index()); - stream.consume(); - assertEquals(2, stream.index()); - } - - @Test(expected = IllegalStateException.class) - public void testConsumeAtEOFThrows() { - char[] data = "a".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 = size - stream.consume(); // should throw - } - - @Test - public void testLAForward() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals('a', stream.LA(1)); - assertEquals('b', stream.LA(2)); - assertEquals('c', stream.LA(3)); - } - - @Test - public void testLAReturnsEOFBeyondSize() { - char[] data = "ab".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.EOF, stream.LA(3)); - } - - @Test - public void testLAZeroReturnsZero() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(0, stream.LA(0)); - } - - @Test - public void testLANegative() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 - assertEquals('a', stream.LA(-1)); - } - - @Test - public void testLANegativeBeyondStart() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.EOF, stream.LA(-1)); - } - - @Test - public void testSeek() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.seek(2); - assertEquals(2, stream.index()); - assertEquals('c', stream.LA(1)); - } - - @Test - public void testGetText() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("hello", stream.getText(Interval.of(0, 4))); - } - - @Test - public void testGetTextWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals("world", stream.getText(Interval.of(0, 4))); - } - - @Test - public void testGetTextBeyondSize() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("abc", stream.getText(Interval.of(0, 10))); - } - - @Test - public void testGetTextEmptyInterval() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals("", stream.getText(Interval.of(5, 3))); - } - - @Test - public void testMark() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(-1, stream.mark()); - } - - @Test - public void testRelease() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.release(0); // should not throw - } - - @Test - public void testGetSourceName() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - assertEquals(IntStream.UNKNOWN_SOURCE_NAME, stream.getSourceName()); - } - - @Test - public void testLAWithOffset() { - char[] data = "hello world".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 6, 5); - assertEquals('w', stream.LA(1)); - assertEquals('o', stream.LA(2)); - } - - @Test - public void testLANegativeAtPositionOneReturnsFirstChar() { - char[] data = "abc".toCharArray(); - CharArrayCharStream stream = new CharArrayCharStream(data, 0, data.length); - stream.consume(); // index = 1 - stream.consume(); // index = 2 - assertEquals('b', stream.LA(-1)); - assertEquals('a', stream.LA(-2)); - } -} - diff --git a/plugins/compiler/ramc-ram/build.gradle b/plugins/compiler/ramc-ram/build.gradle index 6a5689087..144c0edb3 100644 --- a/plugins/compiler/ramc-ram/build.gradle +++ b/plugins/compiler/ramc-ram/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation project.rootProject.project(":plugins:memory:ram-mem") implementation libs.slf4JApi diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java index 2dd8b63c2..b72bd32c8 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ram; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java index ac97868b3..b7040ffd4 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ProgramParser.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.*; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.*; import static net.emustudio.plugins.compiler.ram.RAMParser.*; public class ProgramParser extends RAMParserBaseVisitor { diff --git a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java index c5bb04a28..cad05c5f3 100644 --- a/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java +++ b/plugins/compiler/ramc-ram/src/main/java/net/emustudio/plugins/compiler/ram/ast/Program.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.ram.ast; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.ram.exceptions.CompileException; import net.emustudio.plugins.memory.ram.api.RamInstruction; import net.emustudio.plugins.memory.ram.api.RamMemoryContext; diff --git a/plugins/compiler/raspc-rasp/build.gradle b/plugins/compiler/raspc-rasp/build.gradle index 330e8bdf4..078e996a1 100644 --- a/plugins/compiler/raspc-rasp/build.gradle +++ b/plugins/compiler/raspc-rasp/build.gradle @@ -19,7 +19,6 @@ dependencies { implementation libs.antlrRuntime implementation libs.emuLib - implementation project(":plugins:compiler:compiler-shared") implementation project.rootProject.project(":plugins:memory:rasp-mem") implementation libs.slf4JApi diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java index fe2019d95..3d681417e 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/LexicalAnalyzerImpl.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.rasp; -import net.emustudio.plugins.compiler.shared.CharArrayCharStream; +import net.emustudio.emulib.plugins.compiler.antlr.CharArrayCharStream; import net.emustudio.emulib.plugins.compiler.LexicalAnalyzer; import net.emustudio.emulib.plugins.compiler.Token; import org.antlr.v4.runtime.*; diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java index 636b47b00..0943baed3 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ProgramParser.java @@ -11,7 +11,7 @@ import java.util.Map; import java.util.Objects; -import static net.emustudio.plugins.compiler.shared.ParsingUtils.*; +import static net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils.*; import static net.emustudio.plugins.compiler.rasp.RASPParser.*; import static net.emustudio.plugins.memory.rasp.gui.Disassembler.HALT; diff --git a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java index c8a2b2741..7a7dc161a 100644 --- a/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java +++ b/plugins/compiler/raspc-rasp/src/main/java/net/emustudio/plugins/compiler/rasp/ast/Program.java @@ -2,7 +2,7 @@ SPDX-License-Identifier: GPL-3.0-or-later */ package net.emustudio.plugins.compiler.rasp.ast; -import net.emustudio.plugins.compiler.shared.ParsingUtils; +import net.emustudio.emulib.plugins.compiler.antlr.ParsingUtils; import net.emustudio.plugins.compiler.rasp.exceptions.CompileException; import net.emustudio.plugins.memory.rasp.api.RaspMemoryContext; diff --git a/settings.gradle b/settings.gradle index b3d02ff82..0f1f00660 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,7 +16,6 @@ rootProject.name = 'emuStudio' include ':application' -include ':plugins:compiler:compiler-shared' include ':plugins:compiler:as-8080' include ':plugins:compiler:as-ssem' include ':plugins:compiler:as-z80'