de.codesourcery.jasm16.utils.ASTInspector.java Source code

Java tutorial

Introduction

Here is the source code for de.codesourcery.jasm16.utils.ASTInspector.java

Source

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