/** * 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; } } ); } }