Merge branch 'machines-are-launch-configs' into development
authorDaniel Kirschten <daniel.kirschten@gmx.de>
Mon, 30 Sep 2019 12:10:38 +0000 (14:10 +0200)
committerDaniel Kirschten <daniel.kirschten@gmx.de>
Mon, 30 Sep 2019 12:10:38 +0000 (14:10 +0200)
16 files changed:
plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParseException.java [new file with mode: 0644]
plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParser.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/META-INF/MANIFEST.MF
plugins/net.mograsim.plugin.core/plugin.xml
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/EclipsePreferences.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/MograsimActivator.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/editors/MemoryEditor.java
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/EmptyLaunchConfigTabGroup.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineDebugTarget.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineLaunchConfigType.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMachineLaunchConfigTab.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlock.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlockExtension.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/FileExtensionViewerFilter.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ImageDescriptorWithMargins.java [new file with mode: 0644]
plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ProjectViewerFilter.java [new file with mode: 0644]

diff --git a/plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParseException.java b/plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParseException.java
new file mode 100644 (file)
index 0000000..5c6297e
--- /dev/null
@@ -0,0 +1,28 @@
+package net.mograsim.machine.standard.memory;
+
+import net.mograsim.machine.MachineException;
+
+public class MainMemoryParseException extends MachineException
+{
+
+       /**
+        * 
+        */
+       private static final long serialVersionUID = 6820101808901789906L;
+
+       public MainMemoryParseException()
+       {
+               super();
+       }
+
+       public MainMemoryParseException(String message)
+       {
+               super(message);
+       }
+
+       public MainMemoryParseException(Throwable cause)
+       {
+               super(cause);
+       }
+
+}
diff --git a/plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParser.java b/plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/MainMemoryParser.java
new file mode 100644 (file)
index 0000000..ea87296
--- /dev/null
@@ -0,0 +1,96 @@
+package net.mograsim.machine.standard.memory;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import net.mograsim.logic.core.types.BitVector;
+import net.mograsim.machine.MainMemory;
+import net.mograsim.machine.MainMemoryDefinition;
+
+public class MainMemoryParser
+{
+       private final static String lineSeparator = System.getProperty("line.separator");
+
+       public static void parseMemory(final MainMemory memory, String inputPath) throws IOException
+       {
+               try (InputStream input = new FileInputStream(inputPath))
+               {
+                       parseMemory(memory, input);
+               }
+       }
+
+       /**
+        * @param input The input to parse must be in csv format; The stream is closed after being consumed.
+        * 
+        * @throws IOException
+        */
+       public static MainMemory parseMemory(MainMemoryDefinition memDef, InputStream input) throws IOException
+       {
+               try
+               {
+                       MainMemory memory = new WordAddressableMemory(memDef);
+                       parseMemory(memory, input);
+                       return memory;
+               }
+               catch (NullPointerException e)
+               {
+                       throw new MainMemoryParseException(e);
+               }
+       }
+
+       /**
+        *
+        * @param input The input to parse must be in csv format; The stream is closed after being consumed.
+        * 
+        * @throws IOException
+        */
+       public static void parseMemory(final MainMemory memory, InputStream input) throws IOException
+       {
+               try (BufferedReader reader = new BufferedReader(new InputStreamReader(input)))
+               {
+                       MainMemoryDefinition def = memory.getDefinition();
+
+                       long minAddr = def.getMinimalAddress();
+                       long maxAddr = def.getMaximalAddress();
+
+                       String line;
+                       long i = minAddr;
+                       try
+                       {
+                               for (; i <= maxAddr && reader.ready() && !"".equals((line = reader.readLine())); i++)
+                               {
+                                       memory.setCell(i, BitVector.parse(line));
+                               }
+                       }
+                       catch (IOException e)
+                       {
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       public static InputStream write(MainMemory memory)
+       {
+               return new InputStream()
+               {
+                       long instIndex = memory.getDefinition().getMinimalAddress(), maxAddress = memory.getDefinition().getMaximalAddress();
+                       InputStream instStream = new ByteArrayInputStream(new byte[0]);
+
+                       @Override
+                       public int read() throws IOException
+                       {
+                               int val = instStream.read();
+                               if (val == -1 && instIndex <= maxAddress)
+                               {
+                                       instStream = new ByteArrayInputStream((memory.getCell(instIndex++).toString() + lineSeparator).getBytes());
+                                       val = instStream.read();
+                               }
+                               return val;
+                       }
+               };
+       }
+}
index 3fae54f..d12f07c 100644 (file)
@@ -8,6 +8,7 @@ Export-Package: net.mograsim.plugin;uses:="org.eclipse.ui.themes,org.eclipse.swt
  net.mograsim.plugin.asm.editor,
  net.mograsim.plugin.asm.editor.rules,
  net.mograsim.plugin.editors,
+ net.mograsim.plugin.launch,
  net.mograsim.plugin.nature,
  net.mograsim.plugin.nature.properties,
  net.mograsim.plugin.tables,
@@ -32,7 +33,9 @@ Require-Bundle: org.eclipse.core.runtime,
  net.mograsim.preferences;bundle-version="0.1.0",
  net.mograsim.machine,
  org.eclipse.core.expressions,
- SWTInput
+ SWTInput,
+ org.eclipse.debug.core,
+ org.eclipse.debug.ui
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Automatic-Module-Name: net.mograsim.plugin.core
 Bundle-Vendor: %Bundle-Vendor.0
index 4f85d4a..7d7e5e1 100644 (file)
                        <selection class="org.eclipse.core.resources.IResource"/>
                </wizard>
        </extension>
+ <extension
+       point="org.eclipse.debug.core.launchConfigurationTypes">
+    <launchConfigurationType
+          delegate="net.mograsim.plugin.launch.MachineLaunchConfigType"
+          id="net.mograsim.plugin.core.launchmachine.type"
+          modes="run, debug"
+          name="Mograsim machine">
+    </launchConfigurationType>
+ </extension>
+ <extension
+       point="org.eclipse.debug.ui.launchConfigurationTabGroups">
+    <launchConfigurationTabGroup
+          class="net.mograsim.plugin.launch.EmptyLaunchConfigTabGroup"
+          id="net.mograsim.plugin.core.launchmachine.tabgroup"
+          type="net.mograsim.plugin.core.launchmachine.type">
+    </launchConfigurationTabGroup>
+ </extension>
+ <extension
+       point="org.eclipse.debug.ui.launchConfigurationTabs">
+    <tab
+          class="net.mograsim.plugin.launch.MainMachineLaunchConfigTab"
+          group="net.mograsim.plugin.core.launchmachine.tabgroup"
+          id="net.mograsim.plugin.core.launchmachine.maintab"
+          name="Common">
+    </tab>
+ </extension>
+ <extension
+       point="org.eclipse.debug.ui.launchConfigurationTypeImages">
+    <launchConfigurationTypeImage
+          configTypeID="net.mograsim.plugin.core.launchmachine.type"
+          icon="icons/mograsim/orange/icon_orange_16.png"
+          id="net.mograsim.plugin.core.launchmachine.image">
+    </launchConfigurationTypeImage>
+ </extension>
+ <extension
+       point="org.eclipse.debug.ui.memoryRenderings">
+    <renderingBindings
+          defaultIds="org.eclipse.debug.ui.rendering.raw_memory"
+          primaryId="org.eclipse.debug.ui.rendering.raw_memory"
+          renderingIds="org.eclipse.debug.ui.rendering.raw_memory">
+    </renderingBindings>
+ </extension>
 </plugin>
index 1e2c447..445cf20 100644 (file)
@@ -53,7 +53,7 @@ public class EclipsePreferences extends Preferences
                RGB rgb = getColorRegistry().getRGB(name);
                if (rgb == null)
                {
-                       StatusManager.getManager().handle(new Status(IStatus.ERROR, "net.mograsim.plugin.core", "No color for name " + name));
+                       StatusManager.getManager().handle(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "No color for name " + name));
                        return null;
                }
                return new ColorDefinition(rgb.red, rgb.green, rgb.blue);
index 9311ddd..765a63a 100644 (file)
@@ -8,6 +8,8 @@ import net.mograsim.preferences.Preferences;
 
 public final class MograsimActivator extends AbstractUIPlugin
 {
+       public static final String PLUGIN_ID = "net.mograsim.plugin.core";
+
        private static MograsimActivator instance;
 
        public MograsimActivator()
index 2b02852..507adae 100644 (file)
@@ -1,6 +1,7 @@
 package net.mograsim.plugin.editors;
 
-import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.math.BigInteger;
 
 import org.eclipse.core.resources.IFile;
@@ -24,8 +25,10 @@ import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.part.EditorPart;
 
 import net.mograsim.machine.MainMemory;
+import net.mograsim.machine.MainMemoryDefinition;
+import net.mograsim.machine.Memory.MemoryCellModifiedListener;
 import net.mograsim.machine.mi.MicroInstructionMemoryParseException;
-import net.mograsim.machine.standard.memory.WordAddressableMemory;
+import net.mograsim.machine.standard.memory.MainMemoryParser;
 import net.mograsim.plugin.asm.AsmNumberUtil;
 import net.mograsim.plugin.nature.MachineContext;
 import net.mograsim.plugin.nature.ProjectMachineContext;
@@ -49,6 +52,15 @@ public class MemoryEditor extends EditorPart
        private MemoryTableContentProvider provider;
        private DisplaySettings displaySettings;
 
+       private boolean dirty;
+
+       private final MemoryCellModifiedListener memListener;
+
+       public MemoryEditor()
+       {
+               memListener = this::cellModified;
+       }
+
        @Override
        public void createPartControl(Composite parent)
        {
@@ -178,7 +190,14 @@ public class MemoryEditor extends EditorPart
                        context.activateMachine();
 
                        setPartName(fileInput.getName());
-                       open(fileInput.getFile());
+                       try
+                       {
+                               open(fileInput.getFile());
+                       }
+                       catch (Exception e)
+                       {
+                               throw new PartInitException("Failed to read input!", e);
+                       }
                } else
                        throw new IllegalArgumentException("MemoryEditor can only be used with Files");
 
@@ -194,20 +213,40 @@ public class MemoryEditor extends EditorPart
                        SafeRunnable.getRunner().run(() -> save(((IFileEditorInput) input).getFile(), monitor));
        }
 
-       private void save(IFile file, IProgressMonitor monitor) throws CoreException
+       private void save(IFile file, IProgressMonitor monitor) throws CoreException, IOException
        {
-               file.setContents(new ByteArrayInputStream("actual contents will go here".getBytes()), 0, monitor);
+               if (memory == null)
+               {
+                       throw new MicroInstructionMemoryParseException("Failed to write MainMemory to File. No MainMemory assigned.");
+               }
+               try (InputStream toWrite = MainMemoryParser.write(memory))
+               {
+                       file.setContents(toWrite, 0, monitor);
+                       setDirty(false);
+               }
        }
 
-       private void open(IFile file)
+       private void open(IFile file) throws IOException, CoreException
        {
-               // TODO actually parse the file
-               memory = new WordAddressableMemory(context.getMachineDefinition()
-                               .orElseThrow(() -> new MicroInstructionMemoryParseException("No MachineDefinition assigned!")).getMainMemoryDefinition());
+               MainMemoryDefinition memDef = context.getMachineDefinition()
+                               .orElseThrow(() -> new MicroInstructionMemoryParseException("No MachineDefinition assigned!")).getMainMemoryDefinition();
+               memory = MainMemoryParser.parseMemory(memDef, file.getContents());
+               memory.registerCellModifiedListener(memListener);
                if (viewer != null)
                        viewer.setInput(memory);
        }
 
+       private void cellModified(@SuppressWarnings("unused") long address)
+       {
+               setDirty(true);
+       }
+
+       private void setDirty(boolean newDirty)
+       {
+               dirty = newDirty;
+               firePropertyChange(PROP_DIRTY);
+       }
+
        @Override
        public void doSaveAs()
        {
@@ -217,8 +256,7 @@ public class MemoryEditor extends EditorPart
        @Override
        public boolean isDirty()
        {
-               // TODO
-               return false;
+               return dirty;
        }
 
        @Override
@@ -236,7 +274,8 @@ public class MemoryEditor extends EditorPart
        @Override
        public void dispose()
        {
-               // TODO Auto-generated method stub
+               if (memory != null)
+                       memory.deregisterCellModifiedListener(memListener);
                super.dispose();
        }
 }
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/EmptyLaunchConfigTabGroup.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/EmptyLaunchConfigTabGroup.java
new file mode 100644 (file)
index 0000000..6f4844e
--- /dev/null
@@ -0,0 +1,19 @@
+package net.mograsim.plugin.launch;
+
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
+import org.eclipse.debug.ui.ILaunchConfigurationDialog;
+import org.eclipse.debug.ui.ILaunchConfigurationTab;
+
+/**
+ * Useful for specifying launch config tabs via the extension point <code>org.eclipse.debug.ui.launchConfigurationTabs</code>.
+ * 
+ * @author Daniel Kirschten
+ */
+public class EmptyLaunchConfigTabGroup extends AbstractLaunchConfigurationTabGroup
+{
+       @Override
+       public void createTabs(ILaunchConfigurationDialog dialog, String mode)
+       {
+               setTabs(new ILaunchConfigurationTab[0]);
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineDebugTarget.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineDebugTarget.java
new file mode 100644 (file)
index 0000000..328c589
--- /dev/null
@@ -0,0 +1,303 @@
+package net.mograsim.plugin.launch;
+
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.debug.core.model.IDebugElement;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.core.model.IMemoryBlock;
+import org.eclipse.debug.core.model.IMemoryBlockExtension;
+import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.debug.core.model.IStepFilters;
+import org.eclipse.debug.core.model.IThread;
+
+import net.mograsim.logic.model.LogicExecuter;
+import net.mograsim.machine.Machine;
+import net.mograsim.machine.MachineDefinition;
+import net.mograsim.plugin.MograsimActivator;
+
+public class MachineDebugTarget extends PlatformObject implements IDebugTarget, IMemoryBlockRetrievalExtension
+{
+       private final ILaunch launch;
+       private final Machine machine;
+       private final LogicExecuter exec;
+
+       private boolean running;
+
+       public MachineDebugTarget(ILaunch launch, MachineDefinition machineDefinition)
+       {
+               this.launch = launch;
+               this.machine = machineDefinition.createNew();
+               this.exec = new LogicExecuter(machine.getTimeline());
+
+               exec.startLiveExecution();
+               running = true;
+
+               getLaunch().addDebugTarget(this);
+               fireCreationEvent();
+       }
+
+       public Machine getMachine()
+       {
+               return machine;
+       }
+
+       @Override
+       public String getName() throws DebugException
+       {
+               return "Mograsim machine \"" + machine.getDefinition().getId() + '"';
+       }
+
+       @Override
+       public String getModelIdentifier()
+       {
+               return MograsimActivator.PLUGIN_ID;
+       }
+
+       @Override
+       public IDebugTarget getDebugTarget()
+       {
+               return this;
+       }
+
+       @Override
+       public ILaunch getLaunch()
+       {
+               return launch;
+       }
+
+       public void setExecutionSpeed(double speed)
+       {
+               exec.setSpeedFactor(speed);
+       }
+
+       @Override
+       public boolean isSuspended()
+       {
+               return exec.isPaused();
+       }
+
+       @Override
+       public boolean canSuspend()
+       {
+               return !isTerminated() && !isSuspended();
+       }
+
+       @Override
+       public void suspend() throws DebugException
+       {
+               if (isTerminated())
+                       throwDebugException("Can't suspend a terminated MachineProcess");
+               if (isSuspended())
+                       throwDebugException("Can't suspend a suspended MachineProcess");
+
+               exec.pauseLiveExecution();
+               fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
+       }
+
+       @Override
+       public boolean canResume()
+       {
+               return !isTerminated() && isSuspended();
+       }
+
+       @Override
+       public void resume() throws DebugException
+       {
+               if (isTerminated())
+                       throwDebugException("Can't resume a terminated MachineProcess");
+               if (!isSuspended())
+                       throwDebugException("Can't resume a non-suspended MachineProcess");
+
+               exec.unpauseLiveExecution();
+               fireResumeEvent(DebugEvent.CLIENT_REQUEST);
+       }
+
+       @Override
+       public boolean isTerminated()
+       {
+               return !running;
+       }
+
+       @Override
+       public boolean canTerminate()
+       {
+               return !isTerminated();
+       }
+
+       @Override
+       public void terminate() throws DebugException
+       {
+               if (isTerminated())
+                       return;
+
+               exec.stopLiveExecution();
+               running = false;
+               fireTerminateEvent();
+       }
+
+       @Override
+       public boolean supportsBreakpoint(IBreakpoint breakpoint)
+       {
+               return false;
+       }
+
+       @Override
+       public void breakpointAdded(IBreakpoint breakpoint)
+       {
+               // ignore; we don't support breakpoints
+       }
+
+       @Override
+       public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta)
+       {
+               // ignore; we don't support breakpoints
+       }
+
+       @Override
+       public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta)
+       {
+               // ignore; we don't support breakpoints
+       }
+
+       @Override
+       public boolean isDisconnected()
+       {
+               return false;
+       }
+
+       @Override
+       public boolean canDisconnect()
+       {
+               return false;
+       }
+
+       @Override
+       public void disconnect() throws DebugException
+       {
+               throw new DebugException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.NOT_SUPPORTED,
+                               "Can't disconnect from a MachineDebugTarget", null));
+       }
+
+       @Override
+       public boolean supportsStorageRetrieval()
+       {
+               return true;
+       }
+
+       @SuppressWarnings("deprecation") // TODO can we throw a DebugException instead?
+       @Override
+       public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException
+       {
+               return new MainMemoryBlock(this, startAddress, length);
+       }
+
+       @Override
+       public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException
+       {
+               return new MainMemoryBlockExtension(this, expression, context);
+       }
+
+       @Override
+       public IProcess getProcess()
+       {
+               return null;
+       }
+
+       @Override
+       public boolean hasThreads() throws DebugException
+       {
+               return false;
+       }
+
+       @Override
+       public IThread[] getThreads() throws DebugException
+       {
+               return new IThread[0];
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> T getAdapter(Class<T> adapter)
+       {
+               if (adapter == IDebugElement.class)
+                       return (T) this;
+
+               // leave this here; maybe we implement IStepFilters someday
+               if (adapter == IStepFilters.class)
+                       if (this instanceof IStepFilters)
+                               return (T) getDebugTarget();
+
+               if (adapter == IDebugTarget.class)
+                       return (T) getDebugTarget();
+
+               if (adapter == ILaunch.class)
+                       return (T) getLaunch();
+
+               // CONTEXTLAUNCHING
+               if (adapter == ILaunchConfiguration.class)
+                       return (T) getLaunch().getLaunchConfiguration();
+
+               return super.getAdapter(adapter);
+       }
+
+       /**
+        * Fires a creation event for this debug element.
+        */
+       private void fireCreationEvent()
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.CREATE));
+       }
+
+       /**
+        * Fires a resume for this debug element with the specified detail code.
+        *
+        * @param detail detail code for the resume event, such as <code>DebugEvent.STEP_OVER</code>
+        */
+       private void fireResumeEvent(int detail)
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.RESUME, detail));
+       }
+
+       /**
+        * Fires a suspend event for this debug element with the specified detail code.
+        *
+        * @param detail detail code for the suspend event, such as <code>DebugEvent.BREAKPOINT</code>
+        */
+       private void fireSuspendEvent(int detail)
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail));
+       }
+
+       /**
+        * Fires a terminate event for this debug element.
+        */
+       private void fireTerminateEvent()
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
+       }
+
+       /**
+        * Fires a debug event.
+        *
+        * @param event debug event to fire
+        */
+       private static void fireEvent(DebugEvent event)
+       {
+               DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
+       }
+
+       private static void throwDebugException(String message) throws DebugException
+       {
+               throw new DebugException(
+                               new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineLaunchConfigType.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MachineLaunchConfigType.java
new file mode 100644 (file)
index 0000000..82d7e8b
--- /dev/null
@@ -0,0 +1,201 @@
+package net.mograsim.plugin.launch;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
+import org.eclipse.ui.statushandlers.StatusManager;
+
+import net.mograsim.machine.Machine;
+import net.mograsim.machine.MachineDefinition;
+import net.mograsim.machine.MainMemory;
+import net.mograsim.machine.MainMemoryDefinition;
+import net.mograsim.machine.mi.MicroInstructionMemory;
+import net.mograsim.machine.mi.MicroInstructionMemoryDefinition;
+import net.mograsim.machine.mi.MicroInstructionMemoryParser;
+import net.mograsim.machine.standard.memory.MainMemoryParser;
+import net.mograsim.plugin.MograsimActivator;
+import net.mograsim.plugin.nature.MachineContext;
+import net.mograsim.plugin.nature.MograsimNature;
+import net.mograsim.plugin.nature.ProjectMachineContext;
+
+public class MachineLaunchConfigType extends LaunchConfigurationDelegate
+{
+       public static final String PROJECT_ATTR = MograsimActivator.PLUGIN_ID + ".project";
+       public static final String MPM_FILE_ATTR = MograsimActivator.PLUGIN_ID + ".mpm";
+       public static final String INITIAL_RAM_FILE_ATTR = MograsimActivator.PLUGIN_ID + ".initialram";
+
+       private final IResourceChangeListener resChangedListener;
+
+       public MachineLaunchConfigType()
+       {
+               this.resChangedListener = this::resourceChanged;
+               ResourcesPlugin.getWorkspace().addResourceChangeListener(resChangedListener,
+                               // IResourceChangeEvent.POST_BUILD |
+                               IResourceChangeEvent.POST_CHANGE |
+                               // IResourceChangeEvent.PRE_BUILD |
+                               // IResourceChangeEvent.PRE_CLOSE |
+                               // IResourceChangeEvent.PRE_DELETE |
+                               // IResourceChangeEvent.PRE_REFRESH |
+                                               0);
+       }
+
+       @Override
+       public boolean preLaunchCheck(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException
+       {
+               String projName = configuration.getAttribute(PROJECT_ATTR, "");
+               if ("".equals(projName))
+                       return showErrorAndReturnFalse("No project specified");
+
+               IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
+               if (!project.isAccessible())
+                       return showErrorAndReturnFalse("Project not accessible");
+               if (!project.hasNature(MograsimNature.NATURE_ID))
+                       return showErrorAndReturnFalse("Project is not a Mograsim project");
+
+               MachineContext machineContext = ProjectMachineContext.getMachineContextOf(project);
+               Optional<MachineDefinition> machDefOptional = machineContext.getMachineDefinition();
+               if (machDefOptional.isEmpty())
+                       return showErrorAndReturnFalse("No machine definition set");
+
+               MachineDefinition machineDefinition = machDefOptional.orElseThrow();
+               MicroInstructionMemoryDefinition miMemDef = machineDefinition.getMicroInstructionMemoryDefinition();
+               MainMemoryDefinition mainMemDef = machineDefinition.getMainMemoryDefinition();
+
+               String mpmFileName = configuration.getAttribute(MPM_FILE_ATTR, "");
+               if ("".equals(mpmFileName))
+                       return showErrorAndReturnFalse("No MPM file specified");
+
+               IFile mpmFile = project.getFile(mpmFileName);
+               if (mpmFile == null || !mpmFile.isAccessible())
+                       return showErrorAndReturnFalse("MPM file not accessible");
+
+               try (InputStream mpmStream = mpmFile.getContents())
+               {
+                       MicroInstructionMemoryParser.parseMemory(miMemDef, mpmStream);
+               }
+               catch (IOException e)
+               {
+                       throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
+               }
+
+               String initialRAMFileName = configuration.getAttribute(INITIAL_RAM_FILE_ATTR, "");
+               if (!"".equals(initialRAMFileName))
+               {
+                       IFile initialRAMFile = project.getFile(initialRAMFileName);
+                       if (initialRAMFile == null || !initialRAMFile.isAccessible())
+                               return showErrorAndReturnFalse("Initial RAM file not accessible");
+
+                       try (InputStream initialRAMStream = initialRAMFile.getContents())
+                       {
+                               MainMemoryParser.parseMemory(mainMemDef, initialRAMStream);
+                       }
+                       catch (IOException e)
+                       {
+                               throw new CoreException(
+                                               new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading initial RAM file", e));
+                       }
+               }
+
+               return super.preLaunchCheck(configuration, mode, monitor);
+       }
+
+       private static boolean showErrorAndReturnFalse(String message)
+       {
+               StatusManager.getManager().handle(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, message, null), StatusManager.SHOW);
+               return false;
+       }
+
+       @Override
+       public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException
+       {
+               String projName = configuration.getAttribute(PROJECT_ATTR, "");
+               IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName);
+
+               MachineContext machineContext = ProjectMachineContext.getMachineContextOf(project);
+               MachineDefinition machineDefinition = machineContext.getMachineDefinition().orElseThrow();
+               MicroInstructionMemoryDefinition miMemDef = machineDefinition.getMicroInstructionMemoryDefinition();
+               MainMemoryDefinition mainMemDef = machineDefinition.getMainMemoryDefinition();
+
+               IFile mpmFile = project.getFile(configuration.getAttribute(MPM_FILE_ATTR, ""));
+
+               MicroInstructionMemory mpm;
+               try (InputStream mpmStream = mpmFile.getContents())
+               {
+                       mpm = MicroInstructionMemoryParser.parseMemory(miMemDef, mpmStream);
+               }
+               catch (IOException e)
+               {
+                       throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
+               }
+
+               String initialRAMFileName = configuration.getAttribute(INITIAL_RAM_FILE_ATTR, "");
+               MainMemory mem;
+               if (!"".equals(initialRAMFileName))
+               {
+                       IFile initialRAMFile = project.getFile(initialRAMFileName);
+                       try (InputStream initialRAMStream = initialRAMFile.getContents())
+                       {
+                               mem = MainMemoryParser.parseMemory(mainMemDef, initialRAMStream);
+                       }
+                       catch (IOException e)
+                       {
+                               throw new CoreException(
+                                               new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading initial RAM file", e));
+                       }
+               } else
+                       mem = null;
+
+               MachineDebugTarget debugTarget = new MachineDebugTarget(launch, machineDefinition);
+               debugTarget.suspend();
+               debugTarget.setExecutionSpeed(1);
+               Machine machine = debugTarget.getMachine();
+               machine.getMicroInstructionMemory().bind(mpm);
+               if (mem != null)
+                       machine.getMainMemory().bind(mem);
+               machine.reset();
+       }
+
+       private void resourceChanged(IResourceChangeEvent event)
+       {
+               // TODO react to MPM changes
+               int type = event.getType();
+               String typeStr;
+               switch (type)
+               {
+               case IResourceChangeEvent.POST_BUILD:
+                       typeStr = "POST_BUILD";
+                       break;
+               case IResourceChangeEvent.POST_CHANGE:
+                       typeStr = "POST_CHANGE";
+                       break;
+               case IResourceChangeEvent.PRE_BUILD:
+                       typeStr = "PRE_BUILD";
+                       break;
+               case IResourceChangeEvent.PRE_CLOSE:
+                       typeStr = "PRE_CLOSE";
+                       break;
+               case IResourceChangeEvent.PRE_DELETE:
+                       typeStr = "PRE_DELETE";
+                       break;
+               case IResourceChangeEvent.PRE_REFRESH:
+                       typeStr = "PRE_REFRESH";
+                       break;
+               default:
+                       typeStr = "<unknown: " + type + ">";
+               }
+               System.out.println(typeStr + ": " + event);
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMachineLaunchConfigTab.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMachineLaunchConfigTab.java
new file mode 100644 (file)
index 0000000..836b8a9
--- /dev/null
@@ -0,0 +1,298 @@
+package net.mograsim.plugin.launch;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.PixelConverter;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.window.Window;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+
+import net.mograsim.plugin.nature.MograsimNature;
+import net.mograsim.plugin.util.FileExtensionViewerFilter;
+import net.mograsim.plugin.util.ImageDescriptorWithMargins;
+import net.mograsim.plugin.util.ProjectViewerFilter;
+
+//a big part of this class is stolen from org.eclipse.jdt.debug.ui
+public class MainMachineLaunchConfigTab extends AbstractLaunchConfigurationTab
+{
+       private Text projSelText;
+       private Text mpmFileSelText;
+       private Text initialRAMFileSelText;
+
+       @Override
+       public void createControl(Composite parent)
+       {
+               parent.setLayout(new FillLayout());
+               Composite innerParent = new Composite(parent, SWT.NONE);
+               setControl(innerParent);
+
+               innerParent.setLayout(new GridLayout(3, false));
+
+               this.projSelText = addResourceSelector(innerParent, "&Project:", this::chooseMograsimProject);
+
+               this.mpmFileSelText = addResourceSelector(innerParent, "&MPM:", this::chooseMPMFile);
+
+               this.initialRAMFileSelText = addResourceSelector(innerParent, "Initial &RAM (optional):", this::chooseInitialRAMFile);
+       }
+
+       private Text addResourceSelector(Composite innerParent, String label, Supplier<String> chooser)
+       {
+               Label swtLabel = new Label(innerParent, SWT.NONE);
+               swtLabel.setText(label);
+               swtLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+
+               Text text = new Text(innerParent, SWT.BORDER);
+               text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               text.addModifyListener(e -> updateLaunchConfigurationDialog());
+
+               swtLabel.addListener(SWT.FocusIn, e -> text.setFocus());
+
+               Button browseButton = new Button(innerParent, SWT.PUSH);
+               GridData projSelButtonData = new GridData();
+               projSelButtonData.widthHint = calculateWidthHint(browseButton);
+               projSelButtonData.horizontalAlignment = SWT.FILL;
+               browseButton.setLayoutData(projSelButtonData);
+               browseButton.setText("&Browse...");
+               browseButton.addListener(SWT.Selection, e ->
+               {
+                       String chosen = chooser.get();
+                       if (chosen != null)
+                               text.setText(chosen);
+               });
+               return text;
+       }
+
+       private static int calculateWidthHint(Control c)
+       {
+               int wHint = new PixelConverter(c).convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
+               return Math.max(wHint, c.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
+       }
+
+       private String chooseMograsimProject()
+       {
+               WorkbenchLabelProvider renderer = new WorkbenchLabelProvider()
+               {
+                       @Override
+                       protected ImageDescriptor decorateImage(ImageDescriptor input, Object element)
+                       {
+                               return new ImageDescriptorWithMargins(input, new Point(20, 16));
+                       }
+               };
+               ElementListSelectionDialog dialog = new ElementListSelectionDialog(getShell(), renderer);
+               dialog.setTitle("Project Selection");
+               dialog.setMessage("Select a Mograsim project");
+               dialog.setElements(filterOpenMograsimProjects(ResourcesPlugin.getWorkspace().getRoot().getProjects()));
+               if (dialog.open() == Window.OK)
+                       return ((IProject) dialog.getFirstResult()).getName();
+               return null;
+       }
+
+       private String chooseMPMFile()
+       {
+               ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(),
+                               new WorkbenchContentProvider());
+               dialog.setTitle("MPM Selection");
+               dialog.setMessage("Select a MPM file");
+               dialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
+               dialog.addFilter(new FileExtensionViewerFilter("mpm"));
+               dialog.addFilter(new ProjectViewerFilter(getSelectedProject()));
+
+               if (dialog.open() == Window.OK)
+                       return ((IResource) dialog.getResult()[0]).getProjectRelativePath().toPortableString();
+               return null;
+       }
+
+       private String chooseInitialRAMFile()
+       {
+               ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(),
+                               new WorkbenchContentProvider());
+               dialog.setTitle("Initial RAM Selection");
+               dialog.setMessage("Select a RAM file");
+               dialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
+               dialog.addFilter(new FileExtensionViewerFilter("mem"));
+               dialog.addFilter(new ProjectViewerFilter(getSelectedProject()));
+
+               if (dialog.open() == Window.OK)
+                       return ((IResource) dialog.getResult()[0]).getProjectRelativePath().toPortableString();
+               return null;
+       }
+
+       private IProject getSelectedProject()
+       {
+               String projName = projSelText.getText().trim();
+               IWorkspace workspace = ResourcesPlugin.getWorkspace();
+               if (workspace.validateName(projName, IResource.PROJECT).isOK())
+                       return workspace.getRoot().getProject(projName);
+               return null;
+       }
+
+       private static IProject[] filterOpenMograsimProjects(IProject[] projects)
+       {
+               return Arrays.stream(projects).filter(p ->
+               {
+                       try
+                       {
+                               return p.isAccessible() && p.hasNature(MograsimNature.NATURE_ID);
+                       }
+                       catch (CoreException e)
+                       {
+                               throw new RuntimeException(e);
+                       }
+               }).toArray(IProject[]::new);
+       }
+
+       @Override
+       public void setDefaults(ILaunchConfigurationWorkingCopy configuration)
+       {
+               // TODO don't let the user have to specify everything
+       }
+
+       @Override
+       public void initializeFrom(ILaunchConfiguration configuration)
+       {
+               projSelText.setText(getStringAttribSafe(configuration, MachineLaunchConfigType.PROJECT_ATTR, ""));
+               mpmFileSelText.setText(getStringAttribSafe(configuration, MachineLaunchConfigType.MPM_FILE_ATTR, ""));
+               initialRAMFileSelText.setText(getStringAttribSafe(configuration, MachineLaunchConfigType.INITIAL_RAM_FILE_ATTR, ""));
+       }
+
+       private String getStringAttribSafe(ILaunchConfiguration configuration, String attrib, String defaultValue)
+       {
+               try
+               {
+                       return configuration.getAttribute(attrib, defaultValue);
+               }
+               catch (CoreException e)
+               {
+                       setErrorMessage(e.getStatus().getMessage());
+               }
+               return defaultValue;
+       }
+
+       @Override
+       public void performApply(ILaunchConfigurationWorkingCopy configuration)
+       {
+               String projName = projSelText.getText().trim();
+               String mpmFileName = mpmFileSelText.getText().trim();
+               String initialRAMFileName = initialRAMFileSelText.getText().trim();
+
+               Set<IResource> associatedResources = new HashSet<>();
+               IWorkspace workspace = ResourcesPlugin.getWorkspace();
+               if (workspace.validateName(projName, IResource.PROJECT).isOK())
+               {
+                       IProject project = workspace.getRoot().getProject(projName);
+                       try
+                       {
+                               if (project != null && project.isAccessible() && project.hasNature(MograsimNature.NATURE_ID))
+                               {
+                                       associatedResources.add(project);
+
+                                       IResource mpmFile = project.findMember(mpmFileName);
+                                       if (mpmFile != null && mpmFile.exists() && mpmFile.getType() == IResource.FILE)
+                                               associatedResources.add(mpmFile);
+
+                                       IResource ramFile = project.findMember(initialRAMFileName);
+                                       if (ramFile != null && ramFile.exists() && ramFile.getType() == IResource.FILE)
+                                               associatedResources.add(ramFile);
+                               }
+                       }
+                       catch (CoreException e)
+                       {
+                               setErrorMessage(e.getStatus().getMessage());
+                       }
+               }
+               configuration.setMappedResources(associatedResources.toArray(IResource[]::new));
+               configuration.setAttribute(MachineLaunchConfigType.PROJECT_ATTR, projName);
+               configuration.setAttribute(MachineLaunchConfigType.MPM_FILE_ATTR, mpmFileName);
+               configuration.setAttribute(MachineLaunchConfigType.INITIAL_RAM_FILE_ATTR, initialRAMFileName);
+       }
+
+       @Override
+       public boolean isValid(ILaunchConfiguration launchConfig)
+       {
+               setErrorMessage(null);
+               setMessage(null);
+               String projName = projSelText.getText().trim();
+               if (projName.length() == 0)
+                       return setErrorAndReturnFalse("No project specified");
+
+               IWorkspace workspace = ResourcesPlugin.getWorkspace();
+               IStatus status = workspace.validateName(projName, IResource.PROJECT);
+               if (!status.isOK())
+                       return setErrorAndReturnFalse("Illegal project name: {0}: {1}", projName, status.getMessage());
+
+               IProject project = workspace.getRoot().getProject(projName);
+               if (!project.exists())
+                       return setErrorAndReturnFalse("Project {0} does not exist", projName);
+               if (!project.isOpen())
+                       return setErrorAndReturnFalse("Project {0} is closed", projName);
+               try
+               {
+                       if (!project.hasNature(MograsimNature.NATURE_ID))
+                               return setErrorAndReturnFalse("Project {0} is not a Mograsim project", projName);
+               }
+               catch (CoreException e)
+               {
+                       return setErrorAndReturnFalse(e.getStatus().getMessage());
+               }
+
+               String mpmFileName = mpmFileSelText.getText().trim();
+               if (mpmFileName.length() == 0)
+                       return setErrorAndReturnFalse("No MPM file specified");
+               IResource mpmResource = project.findMember(mpmFileName);
+               if (mpmResource == null || !mpmResource.exists())
+                       return setErrorAndReturnFalse("MPM file {0} does not exist", mpmFileName);
+               if (mpmResource.getType() != IResource.FILE)
+                       return setErrorAndReturnFalse("MPM file {0} is not a file", mpmFileName);
+
+               String initialRAMFileName = initialRAMFileSelText.getText().trim();
+               if (initialRAMFileName.length() > 0)
+               {
+                       IResource initialRAMResource = project.findMember(initialRAMFileName);
+                       if (initialRAMResource == null || !initialRAMResource.exists())
+                               return setErrorAndReturnFalse("Initial RAM file {0} does not exist", initialRAMFileName);
+                       if (initialRAMResource.getType() != IResource.FILE)
+                               return setErrorAndReturnFalse("Initial RAM file {0} is not a file", initialRAMFileName);
+               }
+
+               return true;
+       }
+
+       private boolean setErrorAndReturnFalse(String message, String... params)
+       {
+               setErrorMessage(NLS.bind(message, params));
+               return false;
+       }
+
+       @Override
+       public String getName()
+       {
+               return "testlaunchconfigtabname";
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlock.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlock.java
new file mode 100644 (file)
index 0000000..c2edf4e
--- /dev/null
@@ -0,0 +1,135 @@
+package net.mograsim.plugin.launch;
+
+import java.math.BigInteger;
+
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.core.model.IMemoryBlock;
+
+import net.mograsim.machine.MainMemory;
+import net.mograsim.plugin.MograsimActivator;
+
+@Deprecated
+public class MainMemoryBlock extends PlatformObject implements IMemoryBlock
+{
+       private final MachineDebugTarget debugTarget;
+       private final MainMemory mem;
+       private final long startAddress;
+       private final int length;
+
+       public MainMemoryBlock(MachineDebugTarget debugTarget, long startAddress, long length)
+       {
+               MainMemory mem = debugTarget.getMachine().getMainMemory();
+
+               if (length < 0)
+                       throw new IllegalArgumentException("Negative size");
+               if (startAddress < 0)
+                       throw new IllegalArgumentException("Negative start address");
+               if ((startAddress + length) / 2 > mem.size())
+                       throw new IllegalArgumentException("End address higher than memory size");
+               if (length > Integer.MAX_VALUE)
+                       throw new IllegalArgumentException("Memory block bigger than Integer.MAX_VALUE (" + Integer.MAX_VALUE + "\"");
+               if (startAddress % 2 != 0 || length % 2 != 0)
+                       throw new IllegalArgumentException("Unaligned memory block");
+
+               this.debugTarget = debugTarget;
+               this.mem = mem;
+               this.startAddress = startAddress;
+               this.length = (int) length;
+
+               mem.registerCellModifiedListener(e ->
+               {
+                       if (e >= startAddress / 2 && e < (startAddress + length) / 2)
+                               fireContentChangeEvent();
+               });
+       }
+
+       @Override
+       public String getModelIdentifier()
+       {
+               return MograsimActivator.PLUGIN_ID;
+       }
+
+       @Override
+       public IDebugTarget getDebugTarget()
+       {
+               return debugTarget;
+       }
+
+       @Override
+       public ILaunch getLaunch()
+       {
+               return debugTarget.getLaunch();
+       }
+
+       @Override
+       public long getStartAddress()
+       {
+               return startAddress;
+       }
+
+       @Override
+       public long getLength()
+       {
+               return length;
+       }
+
+       @Override
+       public byte[] getBytes() throws DebugException
+       {
+               byte[] bs = new byte[length];
+               int i;
+               long j;
+               for (i = 0, j = startAddress / 2; i < length; i += 2, j++)
+               {
+                       short word = mem.getCellAsBigInteger(j).shortValue();
+                       bs[i + 0] = (byte) (word & 0xFF);
+                       bs[i + 1] = (byte) (word >>> 8);
+               }
+               return bs;
+       }
+
+       @Override
+       public boolean supportsValueModification()
+       {
+               return true;
+       }
+
+       @Override
+       public void setValue(long offset, byte[] bytes) throws DebugException
+       {
+               if (offset % 2 != 0 || bytes.length % 2 != 0)
+                       throw new IllegalArgumentException("Can't write unaligned to a memory block");
+               int i;
+               long j;
+               for (i = 0, j = (startAddress + offset) / 2; i < bytes.length; i += 2, j++)
+               {
+                       short word = 0;
+                       word |= bytes[i + 0] & 0xFF;
+                       word |= bytes[i + 1] << 8;
+                       mem.setCellAsBigInteger(j, BigInteger.valueOf(word));
+               }
+       }
+
+       /**
+        * Fires a terminate event for this debug element.
+        */
+       private void fireContentChangeEvent()
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
+       }
+
+       /**
+        * Fires a debug event.
+        *
+        * @param event debug event to fire
+        */
+       private static void fireEvent(DebugEvent event)
+       {
+               DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlockExtension.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/launch/MainMemoryBlockExtension.java
new file mode 100644 (file)
index 0000000..ff633fe
--- /dev/null
@@ -0,0 +1,349 @@
+package net.mograsim.plugin.launch;
+
+import java.math.BigInteger;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.model.IDebugTarget;
+import org.eclipse.debug.core.model.IMemoryBlockExtension;
+import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
+import org.eclipse.debug.core.model.MemoryByte;
+
+import net.mograsim.machine.MainMemory;
+import net.mograsim.machine.MainMemoryDefinition;
+import net.mograsim.machine.Memory.MemoryCellModifiedListener;
+import net.mograsim.plugin.MograsimActivator;
+
+public class MainMemoryBlockExtension extends PlatformObject implements IMemoryBlockExtension
+{
+       // TODO do we want to make the memory accessible byte-wise?
+
+       private final String expression;
+       private final MachineDebugTarget debugTarget;
+       private final MainMemory mem;
+
+       private final MainMemoryDefinition memDef;
+       private final int cellWidthBits;
+       private final int cellWidthBytes;
+       private final BigInteger cellWidthBytesBI;
+       private final BigInteger minAddrWords;
+       private final BigInteger maxAddrWords;
+
+       private BigInteger baseAddrWords;
+       private BigInteger lengthWords;
+
+       private final Set<Object> clients;
+       private final MemoryCellModifiedListener memListener;
+       private final AtomicBoolean memListenerRegistered;
+
+       public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
+                       throws DebugException
+       {
+               this.expression = expression;
+               this.debugTarget = debugTarget;
+               this.mem = debugTarget.getMachine().getMainMemory();
+
+               this.memDef = mem.getDefinition();
+               this.cellWidthBits = memDef.getCellWidth();
+               this.cellWidthBytes = (cellWidthBits + 7) / 8;
+               this.cellWidthBytesBI = BigInteger.valueOf(cellWidthBytes);
+               this.minAddrWords = BigInteger.valueOf(memDef.getMinimalAddress());
+               this.maxAddrWords = BigInteger.valueOf(memDef.getMaximalAddress());
+
+               // TODO parse expression better
+               this.baseAddrWords = new BigInteger(expression, 16);
+               this.lengthWords = BigInteger.ONE;
+
+               if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("Base address out of range");
+               if (baseAddrWords.add(lengthWords).compareTo(maxAddrWords) > 0)
+                       throwDebugException("End address out of range");
+
+               this.clients = new HashSet<>();
+               // don't check whether the address is in range, because this memory block could be read outside its "range"
+               this.memListener = a -> fireContentChangeEvent();
+               this.memListenerRegistered = new AtomicBoolean();
+       }
+
+       @Override
+       public long getStartAddress()
+       {
+               return baseAddrWords.multiply(cellWidthBytesBI).longValueExact();
+       }
+
+       @Override
+       public long getLength()
+       {
+               return lengthWords.multiply(cellWidthBytesBI).longValueExact();
+       }
+
+       @Override
+       public byte[] getBytes() throws DebugException
+       {
+               BigInteger endAddrWords = baseAddrWords.add(lengthWords);
+               if (endAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("End address out of range");
+               int lengthBytes = lengthWords.multiply(cellWidthBytesBI).intValueExact();
+
+               byte[] bytes = new byte[lengthBytes];
+               int i;
+               long j;
+               for (i = 0, j = baseAddrWords.longValue(); i < lengthBytes; i += cellWidthBytes, j++)
+               {
+                       BigInteger word = mem.getCellAsBigInteger(j);
+                       System.arraycopy(word.toByteArray(), 0, bytes, i, cellWidthBytes);
+               }
+               return bytes;
+       }
+
+       @Override
+       public boolean supportsValueModification()
+       {
+               return true;
+       }
+
+       @Override
+       public void setValue(long offset, byte[] bytes) throws DebugException
+       {
+               if (offset % cellWidthBytes != 0 || bytes.length % cellWidthBytes != 0)
+                       throwDebugException("Requested unaligned memory write");
+               BigInteger startAddrWords = baseAddrWords.add(BigInteger.valueOf(offset / cellWidthBytes));
+               if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("Start address out of range");
+
+               BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
+               if (endAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("End address out of range");
+
+               int i;
+               long j;
+               for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
+               {
+                       BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
+                       mem.setCellAsBigInteger(j, word);
+               }
+       }
+
+       @Override
+       public String getModelIdentifier()
+       {
+               return MograsimActivator.PLUGIN_ID;
+       }
+
+       @Override
+       public IDebugTarget getDebugTarget()
+       {
+               return debugTarget;
+       }
+
+       @Override
+       public ILaunch getLaunch()
+       {
+               return debugTarget.getLaunch();
+       }
+
+       @Override
+       public String getExpression()
+       {
+               return expression;
+       }
+
+       @Override
+       public BigInteger getBigBaseAddress() throws DebugException
+       {
+               return baseAddrWords;
+       }
+
+       @Override
+       public BigInteger getMemoryBlockStartAddress() throws DebugException
+       {
+               return minAddrWords;
+       }
+
+       @Override
+       public BigInteger getMemoryBlockEndAddress() throws DebugException
+       {
+               return maxAddrWords;
+       }
+
+       @Override
+       public BigInteger getBigLength() throws DebugException
+       {
+               return maxAddrWords.subtract(minAddrWords);
+       }
+
+       @Override
+       public int getAddressSize() throws DebugException
+       {
+               return Long.BYTES;
+       }
+
+       @Override
+       public boolean supportBaseAddressModification() throws DebugException
+       {
+               return true;
+       }
+
+       @Override
+       public boolean supportsChangeManagement()
+       {
+               return false;
+       }
+
+       @Override
+       public void setBaseAddress(BigInteger address) throws DebugException
+       {
+               if (address.compareTo(minAddrWords) < 0 || address.compareTo(maxAddrWords) > 0)
+                       throwDebugException("Address out of range");
+               this.baseAddrWords = address;
+       }
+
+       @Override
+       public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException
+       {
+               return getBytesFromAddress(getBigBaseAddress().add(unitOffset), addressableUnits);
+       }
+
+       @Override
+       public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException
+       {
+               if (units < 0)
+                       throwDebugException("Requested negative amount of unites");
+               int lengthBytes = BigInteger.valueOf(units).multiply(cellWidthBytesBI).intValueExact();
+
+               MemoryByte[] bytes = new MemoryByte[lengthBytes];
+               int i;
+               BigInteger j;
+               for (i = 0, j = address; i < lengthBytes; i += cellWidthBytes, j = j.add(BigInteger.ONE))
+               {
+                       if (j.compareTo(minAddrWords) >= 0 && j.compareTo(maxAddrWords) <= 0)
+                       {
+                               BigInteger word = mem.getCellAsBigInteger(j.longValue());
+                               byte[] wordBytes = word.toByteArray();
+                               int l = wordBytes[0] == 0 ? 1 : 0;
+                               int k;
+                               for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
+                                       bytes[i + k] = new MemoryByte();
+                               for (; k < cellWidthBytes; k++, l++)
+                                       bytes[i + k] = new MemoryByte(wordBytes[l]);
+                       } else
+                               for (int k = 0; k < cellWidthBytes; k++)
+                                       bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
+               }
+               return bytes;
+       }
+
+       @Override
+       public void setValue(BigInteger offset, byte[] bytes) throws DebugException
+       {
+               if (bytes.length % cellWidthBytes != 0)
+                       throwDebugException("Requested unaligned memory write");
+               BigInteger startAddrWords = baseAddrWords.add(offset);
+               if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("Start address out of range");
+               BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
+               if (endAddrWords.compareTo(maxAddrWords) > 0)
+                       throwDebugException("End address out of range");
+
+               unregisterMemoryListener();
+
+               int i;
+               long j;
+               for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
+               {
+                       BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
+                       mem.setCellAsBigInteger(j, word);
+               }
+
+               if (!clients.isEmpty())
+                       registerMemoryListener();
+               fireContentChangeEvent();
+       }
+
+       @Override
+       public void connect(Object client)
+       {
+               registerMemoryListener();
+               clients.add(client);
+       }
+
+       @Override
+       public void disconnect(Object client)
+       {
+               clients.remove(client);
+
+               if (clients.isEmpty())
+                       unregisterMemoryListener();
+       }
+
+       @Override
+       public Object[] getConnections()
+       {
+
+               Set<Object> clientsLocal = clients;
+               return clientsLocal == null ? new Object[0] : clientsLocal.toArray();
+       }
+
+       private void registerMemoryListener()
+       {
+               if (!memListenerRegistered.getAndSet(true))
+                       mem.registerCellModifiedListener(memListener);
+       }
+
+       private void unregisterMemoryListener()
+       {
+               if (memListenerRegistered.getAndSet(false))
+                       mem.deregisterCellModifiedListener(memListener);
+       }
+
+       @Override
+       public void dispose() throws DebugException
+       {
+               clients.clear();
+               unregisterMemoryListener();
+       }
+
+       @Override
+       public IMemoryBlockRetrievalExtension getMemoryBlockRetrieval()
+       {
+               return debugTarget;
+       }
+
+       @Override
+       public int getAddressableSize() throws DebugException
+       {
+               return cellWidthBytes;
+       }
+
+       /**
+        * Fires a terminate event for this debug element.
+        */
+       private void fireContentChangeEvent()
+       {
+               fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
+       }
+
+       /**
+        * Fires a debug event.
+        *
+        * @param event debug event to fire
+        */
+       private static void fireEvent(DebugEvent event)
+       {
+               DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
+       }
+
+       private static void throwDebugException(String message) throws DebugException
+       {
+               throw new DebugException(
+                               new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/FileExtensionViewerFilter.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/FileExtensionViewerFilter.java
new file mode 100644 (file)
index 0000000..a702937
--- /dev/null
@@ -0,0 +1,30 @@
+package net.mograsim.plugin.util;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+
+public class FileExtensionViewerFilter extends ViewerFilter
+{
+       private final String fileext;
+
+       public FileExtensionViewerFilter(String fileext)
+       {
+               this.fileext = fileext;
+       }
+
+       @Override
+       public boolean select(Viewer viewer, Object parentElement, Object element)
+       {
+               IResource elementResource = (IResource) element;
+               switch (elementResource.getType())
+               {
+               case IResource.FILE:
+                       return elementResource.getProjectRelativePath().getFileExtension().equals(fileext);
+               case IResource.FOLDER:
+                       return true;
+               default:
+                       return true;
+               }
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ImageDescriptorWithMargins.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ImageDescriptorWithMargins.java
new file mode 100644 (file)
index 0000000..a664b09
--- /dev/null
@@ -0,0 +1,37 @@
+package net.mograsim.plugin.util;
+
+import org.eclipse.jface.resource.CompositeImageDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Point;
+
+public class ImageDescriptorWithMargins extends CompositeImageDescriptor
+{
+       private final ImageDescriptor input;
+       private final Point size;
+       private final int ox, oy;
+
+       public ImageDescriptorWithMargins(ImageDescriptor input, Point size)
+       {
+               this(input, 0, 0, size);
+       }
+
+       public ImageDescriptorWithMargins(ImageDescriptor input, int offX, int offY, Point size)
+       {
+               this.input = input;
+               this.size = size;
+               this.ox = offX;
+               this.oy = offY;
+       }
+
+       @Override
+       protected Point getSize()
+       {
+               return size;
+       }
+
+       @Override
+       protected void drawCompositeImage(int width, int height)
+       {
+               drawImage(input::getImageData, ox, oy);
+       }
+}
\ No newline at end of file
diff --git a/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ProjectViewerFilter.java b/plugins/net.mograsim.plugin.core/src/net/mograsim/plugin/util/ProjectViewerFilter.java
new file mode 100644 (file)
index 0000000..c049477
--- /dev/null
@@ -0,0 +1,24 @@
+package net.mograsim.plugin.util;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+
+public class ProjectViewerFilter extends ViewerFilter
+{
+       private final IProject project;
+
+       public ProjectViewerFilter(IProject project)
+       {
+               this.project = project;
+       }
+
+       @Override
+       public boolean select(Viewer viewer, Object parentElement, Object element)
+       {
+               if (((IResource) element).getType() == IResource.PROJECT)
+                       return element == project;
+               return true;
+       }
+}
\ No newline at end of file