/******************************************************************************* * Copyright (c) 2010 Scott Stanchfield. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Scott Stanchfield - Work-in-progress - ANTLR 3.x AST Visualization *******************************************************************************/ package com.javadude.antlr3.visualizer.views; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.debug.DebugEventListener; import org.antlr.runtime.debug.DebugEventSocketProxy; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.part.ViewPart; import org.eclipse.zest.core.widgets.Graph; import org.eclipse.zest.core.widgets.GraphConnection; import org.eclipse.zest.core.widgets.GraphNode; import org.eclipse.zest.core.widgets.ZestStyles; import org.eclipse.zest.layouts.LayoutEntity; import org.eclipse.zest.layouts.LayoutStyles; import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm; import com.javadude.antlr3.visualizer.Log; import com.javadude.antlr3.visualizer.MyRemoteDebugEventSocketListener; import com.javadude.antlr3.visualizer.MyRemoteDebugEventSocketListener.ProxyTree; public class ANTLRVisualizationView extends ViewPart { private Map<String, GraphConnection> connections = new HashMap<String, GraphConnection>(); private static final Font font = new Font(Display.getDefault(), "Arial", 12, SWT.BOLD); private Graph g; private GraphNode highlightedFrom, highlightedTo; private GraphConnection highlightedConn; private Stack<StackItem> ruleStack = new Stack<StackItem>(); private final List<Token> tokens = new ArrayList<Token>(); private final List<GraphNode> nodes = new ArrayList<GraphNode>(); private class StackItem { public String ruleName; public GraphNode node; public StackItem(String ruleName) { this.ruleName = ruleName; } } /** * The ID of the view as specified by the extension. */ public static final String ID = "com.javadude.antlr3.visualizer.views.ANTLRVisualizationView"; /** * The constructor. */ public ANTLRVisualizationView() { } /** * This is a callback that will allow us * to create the viewer and initialize it. */ @Override public void createPartControl(Composite parent) { parent.setLayout(new GridLayout(6, false)); final Button connectButton = new Button(parent, SWT.PUSH); connectButton.setText("Connect to parser"); connectButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); connectButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { try { clearGraph(); clearData(); remoteListener = new MyRemoteDebugEventSocketListener(new MyDebugEventListener(), "localhost", DebugEventSocketProxy.DEFAULT_DEBUGGER_PORT); remoteListener.start(); } catch (IOException e) { Log.error(42, "Error starting socket listener", e); } } }); g = new Graph(parent, SWT.NONE); g.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 6, 1)); g.setFont(new Font(Display.getDefault(), "Arial", 24, SWT.BOLD)); g.setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED | ZestStyles.CONNECTIONS_DOT); TreeLayoutAlgorithm layoutAlgorithm = new TreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING); g.setLayoutAlgorithm(layoutAlgorithm, true); layoutAlgorithm.setComparator(new MyComparator()); } private MyRemoteDebugEventSocketListener remoteListener; private static class MyComparator implements Comparator<LayoutEntity> { public int compare(LayoutEntity o1, LayoutEntity o2) { GraphNode n1 = (GraphNode) o1.getGraphData(); GraphNode n2 = (GraphNode) o2.getGraphData(); int order1 = (Integer) n1.getData("order"); int order2 = (Integer) n2.getData("order"); return order1 - order2; } } @Override public void setFocus() { g.setFocus(); } @Override public void dispose() { g.dispose(); super.dispose(); } private class MyDebugEventListener implements DebugEventListener { public void addChild(Object root, Object child) { addConnection(root, child); } public void becomeRoot(Object newRoot, Object oldRoot) { swapRoot(newRoot, oldRoot); } public void consumeToken(Token token) { if (token.getType() != Token.EOF) { set(tokens, token.getTokenIndex(), token); } } public void createNode(Object node) { addASTNode(node); } public void createNode(Object node, org.antlr.runtime.Token token) { addASTNode(node, token); } public void enterRule(String grammarFileName, String ruleName) { ruleStack.push(new StackItem(ruleName)); } public void enterSubRule(int decisionNumber) { ruleStack.push(new StackItem(ruleStack.peek().ruleName + "-" + decisionNumber)); } public void errorNode(Object t) { addASTNode(t); } public void exitRule(String grammarFileName, String ruleName) { popStack(); } public void exitSubRule(int decisionNumber) { popStack(); } public void nilNode(Object node) { addNilNode(node); } public void terminate() { clearData(); remoteListener.stop(); } public void LT(int i, org.antlr.runtime.Token t) {} public void LT(int i, Object t) {} public void beginBacktrack(int level) {} public void beginResync() {} public void commence() {} public void consumeHiddenToken(org.antlr.runtime.Token t) {} public void consumeNode(Object t) {} public void endBacktrack(int level, boolean successful) {} public void endResync() {} public void enterAlt(int alt) {} public void enterDecision(int decisionNumber) {} public void exitDecision(int decisionNumber) {} public void location(int line, int pos) {} public void mark(int marker) {} public void recognitionException(RecognitionException e) {} public void rewind() {} public void rewind(int marker) {} public void semanticPredicate(boolean result, String predicate) {} public void setTokenBoundaries(Object t, int tokenStartIndex, int tokenStopIndex) {} } private void clearGraph() { clearHighlights(); for (GraphConnection c : connections.values()) { c.dispose(); } for (GraphNode n : nodes) { n.dispose(); } } private void clearData() { connections.clear(); nodes.clear(); ruleStack.clear(); tokens.clear(); } private void clearHighlights() { if (highlightedFrom != null) { if (!highlightedFrom.isDisposed()) highlightedFrom.unhighlight(); highlightedFrom = null; } if (highlightedTo != null) { if (!highlightedTo.isDisposed()) highlightedTo.unhighlight(); highlightedTo = null; } if (highlightedConn != null) { if (!highlightedConn.isDisposed()) highlightedConn.unhighlight(); highlightedConn = null; } } private void highlight(GraphNode from, GraphNode to, GraphConnection conn) { clearHighlights(); from.highlight(); to.highlight(); conn.highlight(); highlightedFrom = from; highlightedTo = to; highlightedConn = conn; } private void addASTNode(Object o) { ProxyTree ast = (ProxyTree) o; addNode(ast.ID, ast, ast.getText(), false); } private void addASTNode(Object o, Token token) { ProxyTree ast = (ProxyTree) o; Token realToken = tokens.get(token.getTokenIndex()); addNode(ast.ID, ast, realToken.getText(), false); } private void addNilNode(Object o) { ProxyTree ast = (ProxyTree) o; addNode(ast.ID, ruleStack.peek().ruleName, ruleStack.peek().ruleName, true); } private void popStack() { g.getDisplay().syncExec(new Runnable() { public void run() { StackItem stackItem = ruleStack.pop(); // leave the top-level rule node if (!ruleStack.isEmpty() && stackItem.node != null) { stackItem.node.dispose(); } }}); } private void addNode(final int index, final Object data, final String text, final boolean updateStackItem) { g.getDisplay().syncExec(new Runnable() { public void run() { GraphNode node = new GraphNode(g, ZestStyles.NODES_FISHEYE, data); node.setText(text); node.setFont(font); set(nodes, index, node); if (updateStackItem) ruleStack.peek().node = node; g.applyLayout(); }}); } private void swapRoot(final Object newRoot, final Object oldRoot) { g.getDisplay().syncExec(new Runnable() { public void run() { ProxyTree newRootAST = (ProxyTree) newRoot; ProxyTree oldRootAST = (ProxyTree) oldRoot; GraphNode newRootNode = nodes.get(newRootAST.ID); GraphNode oldRootNode = nodes.get(oldRootAST.ID); @SuppressWarnings("unchecked") List<GraphConnection> sourceConnections = oldRootNode.getSourceConnections(); // for each connection from the old node, add the target to the new node and then dispose it for (GraphConnection connection : sourceConnections) { GraphNode destination = connection.getDestination(); doAddConnection(newRootNode, destination, newRootAST, (ProxyTree) connection.getData("child")); connection.dispose(); } oldRootNode.dispose(); g.applyLayout(); }}); } private GraphConnection doAddConnection(GraphNode from, GraphNode to, ProxyTree fromAST, ProxyTree toAST) { GraphConnection conn = new GraphConnection(g, SWT.NONE, from, to); int num = from.getSourceConnections().size(); conn.setData("parent", fromAST); conn.setData("child", toAST); String name = fromAST.ID + "->" + toAST.ID; connections.put(name, conn); to.setData("order", num); return conn; } private void addConnection(final Object root, final Object child) { g.getDisplay().syncExec(new Runnable() { public void run() { ProxyTree fromAST = (ProxyTree) root; ProxyTree toAST = (ProxyTree) child; String name = fromAST.ID + "->" + toAST.ID; GraphNode fromNode = nodes.get(fromAST.ID); GraphNode toNode = nodes.get(toAST.ID); GraphConnection conn = connections.get(name); if (conn == null) { conn = doAddConnection(fromNode, toNode, fromAST, toAST); } highlight(fromNode, toNode, conn); g.applyLayout(); }}); } private static <T> void set(List<T> list, int index, T value) { int size = list.size(); int missing = index - size; if (missing >= 0) { for(; missing > 0; missing--) list.add(null); list.add(value); } else { list.set(index, value); } } }