Java tutorial
/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.utils; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.swing.ComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListDataListener; import javax.swing.filechooser.FileFilter; import javax.swing.table.AbstractTableModel; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; import org.apache.commons.lang.StringUtils; import de.codesourcery.jasm16.ast.AST; import de.codesourcery.jasm16.ast.ASTNode; import de.codesourcery.jasm16.ast.CommentNode; import de.codesourcery.jasm16.ast.EquationNode; import de.codesourcery.jasm16.ast.IncludeBinaryFileNode; import de.codesourcery.jasm16.ast.InitializedMemoryNode; import de.codesourcery.jasm16.ast.InstructionNode; import de.codesourcery.jasm16.ast.NumberNode; import de.codesourcery.jasm16.ast.OperatorNode; import de.codesourcery.jasm16.ast.OriginNode; import de.codesourcery.jasm16.ast.RegisterReferenceNode; import de.codesourcery.jasm16.ast.StatementNode; import de.codesourcery.jasm16.ast.SymbolReferenceNode; import de.codesourcery.jasm16.ast.UninitializedMemoryNode; import de.codesourcery.jasm16.compiler.CompilationListener; import de.codesourcery.jasm16.compiler.CompilationUnit; import de.codesourcery.jasm16.compiler.Compiler; import de.codesourcery.jasm16.compiler.ICompilationError; import de.codesourcery.jasm16.compiler.ICompilationListener; import de.codesourcery.jasm16.compiler.ICompilationUnit; import de.codesourcery.jasm16.compiler.ICompiler; import de.codesourcery.jasm16.compiler.ICompiler.CompilerOption; import de.codesourcery.jasm16.compiler.ICompilerPhase; import de.codesourcery.jasm16.compiler.Severity; import de.codesourcery.jasm16.compiler.SourceLocation; import de.codesourcery.jasm16.compiler.io.AbstractResource; import de.codesourcery.jasm16.compiler.io.FileResource; import de.codesourcery.jasm16.compiler.io.FileResourceResolver; import de.codesourcery.jasm16.compiler.io.IResource; import de.codesourcery.jasm16.compiler.io.IResource.ResourceType; import de.codesourcery.jasm16.compiler.io.NullObjectCodeWriterFactory; import de.codesourcery.jasm16.exceptions.ResourceNotFoundException; import de.codesourcery.jasm16.ide.ui.utils.ASTTableModelWrapper; /** * Crude editor to test the compiler's inner workings. * * @author tobias.gierke@code-sourcery.de */ public class ASTInspector { // time to wait until recompiling after the user edited the source code private static final int RECOMPILATION_DELAY_MILLIS = 300; // UI widgets private JFrame frame; private JFrame astInspector; private final JTree astTree = new JTree(); private final JTable statusArea = new JTable(); private final StatusModel statusModel = new StatusModel(); private final JComboBox<String> comboBox = new JComboBox<String>(); private final JTextField cursorPosition = new JTextField(); private JTextPane editorPane; private JScrollPane editorScrollPane; private final JButton fileChooser = new JButton("Open..."); private final SimpleAttributeSet registerStyle; private final SimpleAttributeSet commentStyle; private final SimpleAttributeSet instructionStyle; private final SimpleAttributeSet labelStyle; private final SimpleAttributeSet preProcessorStyle; private final SimpleAttributeSet errorStyle; private final SimpleAttributeSet defaultStyle; // compiler private ICompiler compiler; private File lastOpenDirectory = new File("/home/tobi/schulungs_workspace/jASM_16"); private File file; private ICompilationUnit currentUnit; private CompilationThread compilationThread = null; /* * * WAIT_FOR_EDIT-------> WAIT_FOR_TIMEOUT ------>( do compilation ) ----+ * ^ ^ | | * | | | | * | +---RESTART_TIMEOUT---+ | * +-----------------------------------------------------------------+ * */ private enum WaitState { WAIT_FOR_EDIT, WAIT_FOR_TIMEOUT, RESTART_TIMEOUT; } protected static final class StatusMessage { private final Severity severity; private final ITextRegion location; private final String message; @SuppressWarnings("unused") private final Throwable cause; private final ICompilationError error; public StatusMessage(Severity severity, String message) { this(severity, null, message, null, null); } public StatusMessage(Severity severity, ITextRegion location, String message) { this(severity, location, message, null, null); } public StatusMessage(Severity severity, ICompilationError error) { this(severity, error.getLocation(), error.getMessage(), error, error.getCause()); } public StatusMessage(Severity severity, ITextRegion location, String message, ICompilationError error, Throwable cause) { if (severity == null) { throw new IllegalArgumentException("severity must not be NULL."); } if (StringUtils.isBlank(message)) { throw new IllegalArgumentException("message must not be NULL/blank."); } this.severity = severity; this.location = location; this.message = message; if (cause == null) { this.cause = error != null ? error.getCause() : null; } else { this.cause = cause; } this.error = error; } public StatusMessage(Severity severity, String message, Throwable e) { this(severity, null, message, null, e); } public Severity getSeverity() { return severity; } public ITextRegion getLocation() { return location; } public String getMessage() { return message; } public ICompilationError getError() { return error; } } protected class StatusModel extends AbstractTableModel { private final List<StatusMessage> messages = new ArrayList<StatusMessage>(); private final int COL_SEVERITY = 0; private final int COL_LOCATION = 1; private final int COL_MESSAGE = 2; public StatusModel() { super(); } @Override public int getRowCount() { return messages.size(); } public StatusMessage getMessage(int row) { return messages.get(row); } public void addMessage(StatusMessage msg) { if (msg == null) { throw new IllegalArgumentException("msg must not be NULL."); } int index = messages.size(); messages.add(msg); fireTableRowsInserted(index, index); } public void setMessage(StatusMessage msg) { if (msg == null) { throw new IllegalArgumentException("msg must not be NULL."); } messages.clear(); messages.add(msg); fireTableDataChanged(); } @Override public int getColumnCount() { return 3; } @Override public String getColumnName(int columnIndex) { switch (columnIndex) { case COL_SEVERITY: return "Severity"; case COL_LOCATION: return "Location"; case COL_MESSAGE: return "Message"; default: return "no column name?"; } } @Override public Class<?> getColumnClass(int columnIndex) { return String.class; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } @Override public Object getValueAt(int rowIndex, int columnIndex) { final StatusMessage msg = messages.get(rowIndex); switch (columnIndex) { case COL_SEVERITY: return msg.getSeverity().toString(); case COL_LOCATION: if (msg.getLocation() != null) { SourceLocation location; try { location = getSourceLocation(msg.getLocation()); return "Line " + location.getLineNumber() + " , column " + location.getColumnNumber(); } catch (NoSuchElementException e) { // ok, can't help it } } return "<unknown>"; case COL_MESSAGE: return msg.getMessage(); default: return "no column name?"; } } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { throw new UnsupportedOperationException(""); } public void addError(String message, IOException e1) { addMessage(new StatusMessage(Severity.ERROR, message, e1)); } public void addInfo(String message) { addMessage(new StatusMessage(Severity.INFO, message)); } public void clearMessages() { messages.clear(); fireTableDataChanged(); } } protected class CompilationThread extends Thread { private final Object LOCK = new Object(); // @GuardedBy( LOCK ) private WaitState currentState = WaitState.WAIT_FOR_EDIT; public CompilationThread() { setDaemon(true); } @Override public void run() { while (true) { try { internalRun(); } catch (Exception e) { e.printStackTrace(); } } } private void internalRun() throws InterruptedException, InvocationTargetException { synchronized (LOCK) { switch (currentState) { case WAIT_FOR_EDIT: LOCK.wait(); return; case RESTART_TIMEOUT: currentState = WaitState.WAIT_FOR_TIMEOUT; // $FALL-THROUGH$ return; case WAIT_FOR_TIMEOUT: LOCK.wait(RECOMPILATION_DELAY_MILLIS); if (currentState != WaitState.WAIT_FOR_TIMEOUT) { return; } try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { try { compile(); } catch (IOException e) { e.printStackTrace(); } finally { } } }); } finally { currentState = WaitState.WAIT_FOR_EDIT; } } } } public void documentChanged() { synchronized (LOCK) { currentState = WaitState.RESTART_TIMEOUT; LOCK.notifyAll(); } } } private DocumentListener recompilationListener = new DocumentListener() { private void textChanged(DocumentEvent e) { if (compilationThread == null) { compilationThread = new CompilationThread(); compilationThread.start(); } compilationThread.documentChanged(); } @Override public void removeUpdate(DocumentEvent e) { textChanged(e); } @Override public void insertUpdate(DocumentEvent e) { textChanged(e); } @Override public void changedUpdate(DocumentEvent e) { textChanged(e); } }; private final CaretListener listener = new CaretListener() { @Override public void caretUpdate(CaretEvent e) { if (currentUnit != null && currentUnit.getAST() != null && currentUnit.getAST().getTextRegion() != null) { try { final SourceLocation location = getSourceLocation(e.getDot()); cursorPosition.setHorizontalAlignment(JTextField.RIGHT); cursorPosition.setText("Line " + location.getLineNumber() + " , column " + location.getColumnNumber() + " (offset " + e.getDot() + ")"); } catch (NoSuchElementException e2) { // ok, user clicked on unknown location } ASTNode n = currentUnit.getAST().getNodeInRange(e.getDot()); if (n != null && astInspector != null && astInspector.isVisible()) { TreePath path = new TreePath(n.getPathToRoot()); astTree.setSelectionPath(path); astTree.scrollPathToVisible(path); } } } }; public ASTInspector() { defaultStyle = new SimpleAttributeSet(); errorStyle = createStyle(Color.RED); registerStyle = createStyle(Color.ORANGE); commentStyle = createStyle(Color.DARK_GRAY); instructionStyle = createStyle(Color.BLUE); labelStyle = createStyle(Color.GREEN); preProcessorStyle = createStyle(new Color(200, 200, 200)); } private static SimpleAttributeSet createStyle(Color color) { SimpleAttributeSet result = new SimpleAttributeSet(); StyleConstants.setForeground(result, color); return result; } public static void main(final String[] args) throws IOException { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { new ASTInspector().run(args); } catch (IOException e) { e.printStackTrace(); } } }); } private void run(String[] args) throws IOException { setupCompiler(); setupUI(); if (args.length > 0) { openFile(new File(args[0])); } else { final File tmpFile = File.createTempFile("prefix", "suffix"); tmpFile.deleteOnExit(); openFile(tmpFile); } compile(); frame.setVisible(true); } private SourceLocation getSourceLocation(ITextRegion range) { return getSourceLocation(range.getStartingOffset()); } private SourceLocation getSourceLocation(int offset) { final Line line = currentUnit.getLineForOffset(offset); return new SourceLocation(currentUnit, line, new TextRegion(offset, 0)); } private void setupUI() throws MalformedURLException { // editor pane editorPane = new JTextPane(); editorScrollPane = new JScrollPane(editorPane); editorPane.addCaretListener(listener); editorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); editorScrollPane.setPreferredSize(new Dimension(400, 600)); editorScrollPane.setMinimumSize(new Dimension(100, 100)); final AdjustmentListener adjustmentListener = new AdjustmentListener() { @Override public void adjustmentValueChanged(AdjustmentEvent e) { if (!e.getValueIsAdjusting()) { if (currentUnit != null) { doSemanticHighlighting(currentUnit); } } } }; editorScrollPane.getVerticalScrollBar().addAdjustmentListener(adjustmentListener); editorScrollPane.getHorizontalScrollBar().addAdjustmentListener(adjustmentListener); // button panel final JPanel topPanel = new JPanel(); final JToolBar toolbar = new JToolBar(); final JButton showASTButton = new JButton("Show AST"); showASTButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean currentlyVisible = astInspector != null ? astInspector.isVisible() : false; if (currentlyVisible) { showASTButton.setText("Show AST"); } else { showASTButton.setText("Hide AST"); } if (currentlyVisible) { astInspector.setVisible(false); } else { showASTInspector(); } } }); fileChooser.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final JFileChooser chooser; if (lastOpenDirectory != null && lastOpenDirectory.isDirectory()) { chooser = new JFileChooser(lastOpenDirectory); } else { lastOpenDirectory = null; chooser = new JFileChooser(); } final FileFilter filter = new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } return f.isFile() && (f.getName().endsWith(".asm") || f.getName().endsWith(".dasm") || f.getName().endsWith(".dasm16")); } @Override public String getDescription() { return "DCPU-16 assembler sources"; } }; chooser.setFileFilter(filter); int returnVal = chooser.showOpenDialog(frame); if (returnVal == JFileChooser.APPROVE_OPTION) { File newFile = chooser.getSelectedFile(); if (newFile.isFile()) { lastOpenDirectory = newFile.getParentFile(); try { openFile(newFile); } catch (IOException e1) { statusModel.addError("Failed to read from file " + newFile.getAbsolutePath(), e1); } } } } }); toolbar.add(fileChooser); toolbar.add(showASTButton); final ComboBoxModel<String> model = new ComboBoxModel<String>() { private ICompilerPhase selected; private final List<String> realModel = new ArrayList<String>(); { for (ICompilerPhase p : compiler.getCompilerPhases()) { realModel.add(p.getName()); if (p.getName().equals(ICompilerPhase.PHASE_GENERATE_CODE)) { selected = p; } } } @Override public Object getSelectedItem() { return selected != null ? selected.getName() : null; } private ICompilerPhase getPhaseByName(String name) { for (ICompilerPhase p : compiler.getCompilerPhases()) { if (p.getName().equals(name)) { return p; } } return null; } @Override public void setSelectedItem(Object name) { selected = getPhaseByName((String) name); } @Override public void addListDataListener(ListDataListener l) { } @Override public String getElementAt(int index) { return realModel.get(index); } @Override public int getSize() { return realModel.size(); } @Override public void removeListDataListener(ListDataListener l) { } }; comboBox.setModel(model); comboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (model.getSelectedItem() != null) { ICompilerPhase oldPhase = findDisabledPhase(); if (oldPhase != null) { oldPhase.setStopAfterExecution(false); } compiler.getCompilerPhaseByName((String) model.getSelectedItem()).setStopAfterExecution(true); try { compile(); } catch (IOException e1) { e1.printStackTrace(); } } } private ICompilerPhase findDisabledPhase() { for (ICompilerPhase p : compiler.getCompilerPhases()) { if (p.isStopAfterExecution()) { return p; } } return null; } }); toolbar.add(new JLabel("Stop compilation after: ")); toolbar.add(comboBox); cursorPosition.setSize(new Dimension(400, 15)); cursorPosition.setEditable(false); statusArea.setPreferredSize(new Dimension(400, 100)); statusArea.setModel(statusModel); /** * TOOLBAR * SOURCE * cursor position * status area */ topPanel.setLayout(new GridBagLayout()); GridBagConstraints cnstrs = constraints(0, 0, GridBagConstraints.HORIZONTAL); cnstrs.gridwidth = GridBagConstraints.REMAINDER; cnstrs.weighty = 0; topPanel.add(toolbar, cnstrs); cnstrs = constraints(0, 1, GridBagConstraints.BOTH); cnstrs.gridwidth = GridBagConstraints.REMAINDER; topPanel.add(editorScrollPane, cnstrs); cnstrs = constraints(0, 2, GridBagConstraints.HORIZONTAL); cnstrs.gridwidth = GridBagConstraints.REMAINDER; cnstrs.weighty = 0; topPanel.add(cursorPosition, cnstrs); cnstrs = constraints(0, 3, GridBagConstraints.HORIZONTAL); cnstrs.gridwidth = GridBagConstraints.REMAINDER; cnstrs.weighty = 0; final JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new GridBagLayout()); statusArea.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); statusArea.addMouseListener(new MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { final int row = statusArea.rowAtPoint(e.getPoint()); StatusMessage message = statusModel.getMessage(row); if (message.getLocation() != null) { moveCursorTo(message.getLocation()); } } }; }); statusArea.setFillsViewportHeight(true); statusArea.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final JScrollPane statusPane = new JScrollPane(statusArea); statusPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); statusPane.setPreferredSize(new Dimension(400, 100)); statusPane.setMinimumSize(new Dimension(100, 20)); cnstrs = constraints(0, 0, GridBagConstraints.BOTH); cnstrs.weightx = 1; cnstrs.weighty = 1; cnstrs.gridwidth = GridBagConstraints.REMAINDER; cnstrs.gridheight = GridBagConstraints.REMAINDER; bottomPanel.add(statusPane, cnstrs); // setup frame frame = new JFrame( "DCPU-16 assembler " + Compiler.VERSION + " (c) 2012 by tobias.gierke@code-sourcery.de"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topPanel, bottomPanel); splitPane.setBackground(Color.WHITE); frame.getContentPane().add(splitPane); frame.pack(); frame.setVisible(true); splitPane.setDividerLocation(0.9); } private GridBagConstraints constraints(int x, int y, int fill) { GridBagConstraints result = new GridBagConstraints(); result.fill = fill; result.weightx = 1.0; result.weighty = 1.0; result.gridheight = 1; result.gridwidth = 1; result.gridx = x; result.gridy = y; result.insets = new Insets(1, 1, 1, 1); return result; } protected void setStatusMessage(String message) { } private void showASTInspector() { if (astInspector == null) { setupASTInspector(); } if (!astInspector.isVisible()) { astInspector.setVisible(true); } } private void setupASTInspector() { astInspector = new JFrame("AST"); astTree.setCellRenderer(new ASTTreeCellRenderer()); final JScrollPane pane = new JScrollPane(astTree); pane.setPreferredSize(new Dimension(400, 600)); astInspector.getContentPane().add(pane); astInspector.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); astInspector.pack(); } private class ASTTreeCellRenderer extends DefaultTreeCellRenderer { public ASTTreeCellRenderer() { } @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Component result = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); if (!(value instanceof ASTNode)) { return result; } final ASTNode n = (ASTNode) value; String txt; try { txt = getLabelFor(n); } catch (IOException e) { txt = e.getMessage(); } setText(txt); return result; } private String getLabelFor(ASTNode n) throws IOException { String name = n.getClass().getSimpleName(); ITextRegion range = n.getTextRegion(); String source = range == null ? "<no source location>" : currentUnit.getSource(range); String txt = name + " " + source + " ( " + n.getTextRegion() + " )"; if (n instanceof StatementNode) { final List<Line> linesForRange = currentUnit.getLinesForRange(n.getTextRegion()); return "Statement " + StringUtils.join(linesForRange, ","); } else if (n instanceof AST) { return "AST"; } else if (n instanceof OperatorNode) { return "Operator " + ((OperatorNode) n).getOperator(); } else if (n instanceof NumberNode) { return "Number (" + ((NumberNode) n).getValue() + ")"; } return txt; } } private void openFile(final File file) throws IOException { FileInputStream in = new FileInputStream(file); final String source; try { source = Misc.readSource(in); } finally { in.close(); } disableDocumentListener(); final Document doc = editorPane.getDocument(); doc.putProperty(Document.StreamDescriptionProperty, null); editorPane.setText(source); final IResource resource = new AbstractResource(ResourceType.UNKNOWN) { @Override public String readText(ITextRegion range) throws IOException { return range.apply(getSourceFromEditor()); } private String getSourceFromEditor() throws IOException { try { return editorPane.getDocument().getText(0, editorPane.getDocument().getLength()); } catch (BadLocationException e) { throw new IOException("Internal error", e); } } @Override public long getAvailableBytes() throws IOException { return editorPane.getDocument().getLength(); } @Override public OutputStream createOutputStream(boolean append) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public InputStream createInputStream() throws IOException { return new ByteArrayInputStream(getSourceFromEditor().getBytes()); } @Override public String getIdentifier() { return file.getAbsolutePath(); } }; this.file = file; this.currentUnit = CompilationUnit.createInstance(file.getAbsolutePath(), resource); enableDocumentListener(); frame.setTitle(Compiler.VERSION + " / " + file.getName()); compile(); } private void compile() throws IOException { final List<ICompilationUnit> units = new ArrayList<ICompilationUnit>(); units.add(currentUnit); final ICompilationListener listener = new CompilationListener() { private long compileTime; @Override public void onCompileStart(ICompilerPhase firstPhase) { System.out.print("Compiling..."); clearCompilationErrors(currentUnit); compileTime = -System.currentTimeMillis(); } @Override public void afterCompile(ICompilerPhase lastPhase) { compileTime += System.currentTimeMillis(); System.out.println("Compilation finished."); statusModel.clearMessages(); if (currentUnit.getAST() != null) { final ASTTableModelWrapper astModel = new ASTTableModelWrapper(currentUnit.getAST()); astTree.setModel(astModel); doSemanticHighlighting(currentUnit); } if (currentUnit.hasErrors()) { statusModel.addInfo("Compilation stopped with errors after phase '" + lastPhase.getName() + "' (" + compileTime + " ms)"); showCompilationErrors(currentUnit); } else { final int lines = currentUnit.getParsedLineCount(); final float speed = lines / (compileTime / 1000.0f); statusModel.addInfo( "Source compiled without errors up to and including phase '" + lastPhase.getName() + "' ( " + lines + " lines , " + compileTime + " ms , " + speed + " lines/s )"); } } }; compiler.compile(units, listener); } private void doSemanticHighlighting(ICompilationUnit unit) { if (unit.getAST() == null) { return; } // changing character styles triggers // change events that in turn would // again trigger recompilation...we don't want that... disableDocumentListener(); try { final ITextRegion visible = getVisibleTextRegion(); if (visible != null) { long time = -System.currentTimeMillis(); final List<ASTNode> nodes = unit.getAST().getNodesInRange(visible); for (ASTNode child : nodes) { doSemanticHighlighting(unit, child); } time += System.currentTimeMillis(); System.out.println("Syntax highlighting " + visible + " took " + time + " millis."); } } finally { enableDocumentListener(); } } private void doSemanticHighlighting(ICompilationUnit unit, ASTNode node) { highlight(node); for (ASTNode child : node.getChildren()) { doSemanticHighlighting(unit, child); } } private void highlight(ASTNode node) { if (node instanceof InstructionNode) { ITextRegion children = null; for (ASTNode child : node.getChildren()) { if (children == null) { children = child.getTextRegion(); } else { children.merge(child.getTextRegion()); } } ITextRegion whole = new TextRegion(node.getTextRegion()); whole.subtract(children); highlight(whole, instructionStyle); } else if (node instanceof EquationNode || node instanceof UninitializedMemoryNode || node instanceof InitializedMemoryNode || node instanceof OriginNode || node instanceof IncludeBinaryFileNode) { highlight(node, preProcessorStyle); } if (node instanceof SymbolReferenceNode) { highlight(node, labelStyle); } else if (node instanceof CommentNode) { highlight(node, commentStyle); } else if (node instanceof RegisterReferenceNode) { highlight(node, registerStyle); } } private void highlight(ASTNode node, AttributeSet attributes) { highlight(node.getTextRegion(), attributes); } private void highlight(ITextRegion range, AttributeSet attributes) { editorPane.getStyledDocument().setCharacterAttributes(range.getStartingOffset(), range.getLength(), attributes, true); } private void moveCursorTo(ITextRegion location) { if (currentUnit == null || currentUnit.getAST() == null) { return; } editorPane.setCaretPosition(location.getStartingOffset()); centerCurrentLineInScrollPane(); editorPane.requestFocus(); } public void centerCurrentLineInScrollPane() { final Container container = SwingUtilities.getAncestorOfClass(JViewport.class, editorPane); if (container == null) { return; } try { final Rectangle r = editorPane.modelToView(editorPane.getCaretPosition()); final JViewport viewport = (JViewport) container; final int extentHeight = viewport.getExtentSize().height; final int viewHeight = viewport.getViewSize().height; int y = Math.max(0, r.y - (extentHeight / 2)); y = Math.min(y, viewHeight - extentHeight); viewport.setViewPosition(new Point(0, y)); } catch (BadLocationException ble) { } } private ITextRegion getVisibleTextRegion() { final Point startPoint = editorScrollPane.getViewport().getViewPosition(); final Dimension size = editorScrollPane.getViewport().getExtentSize(); final Point endPoint = new Point(startPoint.x + size.width, startPoint.y + size.height); try { final int start = editorPane.viewToModel(startPoint); final int end = editorPane.viewToModel(endPoint); return new TextRegion(start, end - start); } catch (NullPointerException e) { System.out.println("startPoint: " + startPoint + " / size: " + size); e.printStackTrace(); return null; } } private void clearCompilationErrors(ICompilationUnit unit) { StyledDocument doc = editorPane.getStyledDocument(); disableDocumentListener(); try { doc.setCharacterAttributes(0, doc.getLength(), defaultStyle, true); } finally { enableDocumentListener(); } } private void disableDocumentListener() { editorPane.getDocument().removeDocumentListener(recompilationListener); } private void enableDocumentListener() { editorPane.getDocument().addDocumentListener(recompilationListener); } private void showCompilationErrors(ICompilationUnit unit) { disableDocumentListener(); try { for (ICompilationError error : unit.getErrors()) { final ITextRegion location; if (error.getLocation() != null) { location = error.getLocation(); } else { if (error.getErrorOffset() != -1) { location = new TextRegion(error.getErrorOffset(), 1); } else { location = null; } } if (location != null) { System.out.println("Highlighting error at " + location); highlight(location, errorStyle); } statusModel.addMessage(new StatusMessage(Severity.ERROR, error)); } } finally { enableDocumentListener(); } } private void setupCompiler() { compiler = new Compiler(); compiler.setCompilerOption(CompilerOption.DEBUG_MODE, true); compiler.setCompilerOption(CompilerOption.RELAXED_PARSING, true); compiler.setObjectCodeWriterFactory(new NullObjectCodeWriterFactory()); compiler.setResourceResolver(new FileResourceResolver() { @Override public IResource resolveRelative(String identifier, IResource parent) throws ResourceNotFoundException { if (parent instanceof FileResource) { return super.resolveRelative(identifier, parent); } return new FileResource(new File(file.getParentFile(), identifier), ResourceType.UNKNOWN); } @Override public IResource resolve(String identifier) throws ResourceNotFoundException { return new FileResource(new File(identifier), ResourceType.UNKNOWN); } @Override protected ResourceType determineResourceType(File file) { return ResourceType.UNKNOWN; } }); } }