| /* |
| [The "BSD license"] |
| Copyright (c) 2005-2009 Terence Parr |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions |
| are met: |
| 1. Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| 3. The name of the author may not be used to endorse or promote products |
| derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| package org.antlr.runtime.debug; |
| |
| import org.antlr.runtime.RecognitionException; |
| import org.antlr.runtime.Token; |
| import org.antlr.runtime.CharStream; |
| import org.antlr.runtime.tree.BaseTree; |
| import org.antlr.runtime.tree.Tree; |
| |
| import java.io.*; |
| import java.net.ConnectException; |
| import java.net.Socket; |
| import java.util.StringTokenizer; |
| |
| public class RemoteDebugEventSocketListener implements Runnable { |
| static final int MAX_EVENT_ELEMENTS = 8; |
| DebugEventListener listener; |
| String machine; |
| int port; |
| Socket channel = null; |
| PrintWriter out; |
| BufferedReader in; |
| String event; |
| /** Version of ANTLR (dictates events) */ |
| public String version; |
| public String grammarFileName; |
| /** Track the last token index we saw during a consume. If same, then |
| * set a flag that we have a problem. |
| */ |
| int previousTokenIndex = -1; |
| boolean tokenIndexesInvalid = false; |
| |
| public static class ProxyToken implements Token { |
| int index; |
| int type; |
| int channel; |
| int line; |
| int charPos; |
| String text; |
| public ProxyToken(int index) { this.index = index; } |
| public ProxyToken(int index, int type, int channel, |
| int line, int charPos, String text) |
| { |
| this.index = index; |
| this.type = type; |
| this.channel = channel; |
| this.line = line; |
| this.charPos = charPos; |
| this.text = text; |
| } |
| public String getText() { |
| return text; |
| } |
| public void setText(String text) { |
| this.text = text; |
| } |
| public int getType() { |
| return type; |
| } |
| public void setType(int ttype) { |
| this.type = ttype; |
| } |
| public int getLine() { |
| return line; |
| } |
| public void setLine(int line) { |
| this.line = line; |
| } |
| public int getCharPositionInLine() { |
| return charPos; |
| } |
| public void setCharPositionInLine(int pos) { |
| this.charPos = pos; |
| } |
| public int getChannel() { |
| return channel; |
| } |
| public void setChannel(int channel) { |
| this.channel = channel; |
| } |
| public int getTokenIndex() { |
| return index; |
| } |
| public void setTokenIndex(int index) { |
| this.index = index; |
| } |
| public CharStream getInputStream() { |
| return null; |
| } |
| public void setInputStream(CharStream input) { |
| } |
| public String toString() { |
| String channelStr = ""; |
| if ( channel!=Token.DEFAULT_CHANNEL ) { |
| channelStr=",channel="+channel; |
| } |
| return "["+getText()+"/<"+type+">"+channelStr+","+line+":"+getCharPositionInLine()+",@"+index+"]"; |
| } |
| } |
| |
| public static class ProxyTree extends BaseTree { |
| public int ID; |
| public int type; |
| public int line = 0; |
| public int charPos = -1; |
| public int tokenIndex = -1; |
| public String text; |
| |
| public ProxyTree(int ID, int type, int line, int charPos, int tokenIndex, String text) { |
| this.ID = ID; |
| this.type = type; |
| this.line = line; |
| this.charPos = charPos; |
| this.tokenIndex = tokenIndex; |
| this.text = text; |
| } |
| |
| public ProxyTree(int ID) { this.ID = ID; } |
| |
| public int getTokenStartIndex() { return tokenIndex; } |
| public void setTokenStartIndex(int index) { } |
| public int getTokenStopIndex() { return 0; } |
| public void setTokenStopIndex(int index) { } |
| public Tree dupNode() { return null; } |
| public int getType() { return type; } |
| public String getText() { return text; } |
| public String toString() { |
| return "fix this"; |
| } |
| } |
| |
| public RemoteDebugEventSocketListener(DebugEventListener listener, |
| String machine, |
| int port) throws IOException |
| { |
| this.listener = listener; |
| this.machine = machine; |
| this.port = port; |
| |
| if( !openConnection() ) { |
| throw new ConnectException(); |
| } |
| } |
| |
| protected void eventHandler() { |
| try { |
| handshake(); |
| event = in.readLine(); |
| while ( event!=null ) { |
| dispatch(event); |
| ack(); |
| event = in.readLine(); |
| } |
| } |
| catch (Exception e) { |
| System.err.println(e); |
| e.printStackTrace(System.err); |
| } |
| finally { |
| closeConnection(); |
| } |
| } |
| |
| protected boolean openConnection() { |
| boolean success = false; |
| try { |
| channel = new Socket(machine, port); |
| channel.setTcpNoDelay(true); |
| OutputStream os = channel.getOutputStream(); |
| OutputStreamWriter osw = new OutputStreamWriter(os, "UTF8"); |
| out = new PrintWriter(new BufferedWriter(osw)); |
| InputStream is = channel.getInputStream(); |
| InputStreamReader isr = new InputStreamReader(is, "UTF8"); |
| in = new BufferedReader(isr); |
| success = true; |
| } catch(Exception e) { |
| System.err.println(e); |
| } |
| return success; |
| } |
| |
| protected void closeConnection() { |
| try { |
| in.close(); in = null; |
| out.close(); out = null; |
| channel.close(); channel=null; |
| } |
| catch (Exception e) { |
| System.err.println(e); |
| e.printStackTrace(System.err); |
| } |
| finally { |
| if ( in!=null ) { |
| try {in.close();} catch (IOException ioe) { |
| System.err.println(ioe); |
| } |
| } |
| if ( out!=null ) { |
| out.close(); |
| } |
| if ( channel!=null ) { |
| try {channel.close();} catch (IOException ioe) { |
| System.err.println(ioe); |
| } |
| } |
| } |
| |
| } |
| |
| protected void handshake() throws IOException { |
| String antlrLine = in.readLine(); |
| String[] antlrElements = getEventElements(antlrLine); |
| version = antlrElements[1]; |
| String grammarLine = in.readLine(); |
| String[] grammarElements = getEventElements(grammarLine); |
| grammarFileName = grammarElements[1]; |
| ack(); |
| listener.commence(); // inform listener after handshake |
| } |
| |
| protected void ack() { |
| out.println("ack"); |
| out.flush(); |
| } |
| |
| protected void dispatch(String line) { |
| //System.out.println("event: "+line); |
| String[] elements = getEventElements(line); |
| if ( elements==null || elements[0]==null ) { |
| System.err.println("unknown debug event: "+line); |
| return; |
| } |
| if ( elements[0].equals("enterRule") ) { |
| listener.enterRule(elements[1], elements[2]); |
| } |
| else if ( elements[0].equals("exitRule") ) { |
| listener.exitRule(elements[1], elements[2]); |
| } |
| else if ( elements[0].equals("enterAlt") ) { |
| listener.enterAlt(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("enterSubRule") ) { |
| listener.enterSubRule(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("exitSubRule") ) { |
| listener.exitSubRule(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("enterDecision") ) { |
| listener.enterDecision(Integer.parseInt(elements[1]), elements[2].equals("true")); |
| } |
| else if ( elements[0].equals("exitDecision") ) { |
| listener.exitDecision(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("location") ) { |
| listener.location(Integer.parseInt(elements[1]), |
| Integer.parseInt(elements[2])); |
| } |
| else if ( elements[0].equals("consumeToken") ) { |
| ProxyToken t = deserializeToken(elements, 1); |
| if ( t.getTokenIndex() == previousTokenIndex ) { |
| tokenIndexesInvalid = true; |
| } |
| previousTokenIndex = t.getTokenIndex(); |
| listener.consumeToken(t); |
| } |
| else if ( elements[0].equals("consumeHiddenToken") ) { |
| ProxyToken t = deserializeToken(elements, 1); |
| if ( t.getTokenIndex() == previousTokenIndex ) { |
| tokenIndexesInvalid = true; |
| } |
| previousTokenIndex = t.getTokenIndex(); |
| listener.consumeHiddenToken(t); |
| } |
| else if ( elements[0].equals("LT") ) { |
| Token t = deserializeToken(elements, 2); |
| listener.LT(Integer.parseInt(elements[1]), t); |
| } |
| else if ( elements[0].equals("mark") ) { |
| listener.mark(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("rewind") ) { |
| if ( elements[1]!=null ) { |
| listener.rewind(Integer.parseInt(elements[1])); |
| } |
| else { |
| listener.rewind(); |
| } |
| } |
| else if ( elements[0].equals("beginBacktrack") ) { |
| listener.beginBacktrack(Integer.parseInt(elements[1])); |
| } |
| else if ( elements[0].equals("endBacktrack") ) { |
| int level = Integer.parseInt(elements[1]); |
| int successI = Integer.parseInt(elements[2]); |
| listener.endBacktrack(level, successI==DebugEventListener.TRUE); |
| } |
| else if ( elements[0].equals("exception") ) { |
| String excName = elements[1]; |
| String indexS = elements[2]; |
| String lineS = elements[3]; |
| String posS = elements[4]; |
| Class excClass = null; |
| try { |
| excClass = Class.forName(excName); |
| RecognitionException e = |
| (RecognitionException)excClass.newInstance(); |
| e.index = Integer.parseInt(indexS); |
| e.line = Integer.parseInt(lineS); |
| e.charPositionInLine = Integer.parseInt(posS); |
| listener.recognitionException(e); |
| } |
| catch (ClassNotFoundException cnfe) { |
| System.err.println("can't find class "+cnfe); |
| cnfe.printStackTrace(System.err); |
| } |
| catch (InstantiationException ie) { |
| System.err.println("can't instantiate class "+ie); |
| ie.printStackTrace(System.err); |
| } |
| catch (IllegalAccessException iae) { |
| System.err.println("can't access class "+iae); |
| iae.printStackTrace(System.err); |
| } |
| } |
| else if ( elements[0].equals("beginResync") ) { |
| listener.beginResync(); |
| } |
| else if ( elements[0].equals("endResync") ) { |
| listener.endResync(); |
| } |
| else if ( elements[0].equals("terminate") ) { |
| listener.terminate(); |
| } |
| else if ( elements[0].equals("semanticPredicate") ) { |
| Boolean result = Boolean.valueOf(elements[1]); |
| String predicateText = elements[2]; |
| predicateText = unEscapeNewlines(predicateText); |
| listener.semanticPredicate(result.booleanValue(), |
| predicateText); |
| } |
| else if ( elements[0].equals("consumeNode") ) { |
| ProxyTree node = deserializeNode(elements, 1); |
| listener.consumeNode(node); |
| } |
| else if ( elements[0].equals("LN") ) { |
| int i = Integer.parseInt(elements[1]); |
| ProxyTree node = deserializeNode(elements, 2); |
| listener.LT(i, node); |
| } |
| else if ( elements[0].equals("createNodeFromTokenElements") ) { |
| int ID = Integer.parseInt(elements[1]); |
| int type = Integer.parseInt(elements[2]); |
| String text = elements[3]; |
| text = unEscapeNewlines(text); |
| ProxyTree node = new ProxyTree(ID, type, -1, -1, -1, text); |
| listener.createNode(node); |
| } |
| else if ( elements[0].equals("createNode") ) { |
| int ID = Integer.parseInt(elements[1]); |
| int tokenIndex = Integer.parseInt(elements[2]); |
| // create dummy node/token filled with ID, tokenIndex |
| ProxyTree node = new ProxyTree(ID); |
| ProxyToken token = new ProxyToken(tokenIndex); |
| listener.createNode(node, token); |
| } |
| else if ( elements[0].equals("nilNode") ) { |
| int ID = Integer.parseInt(elements[1]); |
| ProxyTree node = new ProxyTree(ID); |
| listener.nilNode(node); |
| } |
| else if ( elements[0].equals("errorNode") ) { |
| // TODO: do we need a special tree here? |
| int ID = Integer.parseInt(elements[1]); |
| int type = Integer.parseInt(elements[2]); |
| String text = elements[3]; |
| text = unEscapeNewlines(text); |
| ProxyTree node = new ProxyTree(ID, type, -1, -1, -1, text); |
| listener.errorNode(node); |
| } |
| else if ( elements[0].equals("becomeRoot") ) { |
| int newRootID = Integer.parseInt(elements[1]); |
| int oldRootID = Integer.parseInt(elements[2]); |
| ProxyTree newRoot = new ProxyTree(newRootID); |
| ProxyTree oldRoot = new ProxyTree(oldRootID); |
| listener.becomeRoot(newRoot, oldRoot); |
| } |
| else if ( elements[0].equals("addChild") ) { |
| int rootID = Integer.parseInt(elements[1]); |
| int childID = Integer.parseInt(elements[2]); |
| ProxyTree root = new ProxyTree(rootID); |
| ProxyTree child = new ProxyTree(childID); |
| listener.addChild(root, child); |
| } |
| else if ( elements[0].equals("setTokenBoundaries") ) { |
| int ID = Integer.parseInt(elements[1]); |
| ProxyTree node = new ProxyTree(ID); |
| listener.setTokenBoundaries( |
| node, |
| Integer.parseInt(elements[2]), |
| Integer.parseInt(elements[3])); |
| } |
| else { |
| System.err.println("unknown debug event: "+line); |
| } |
| } |
| |
| protected ProxyTree deserializeNode(String[] elements, int offset) { |
| int ID = Integer.parseInt(elements[offset+0]); |
| int type = Integer.parseInt(elements[offset+1]); |
| int tokenLine = Integer.parseInt(elements[offset+2]); |
| int charPositionInLine = Integer.parseInt(elements[offset+3]); |
| int tokenIndex = Integer.parseInt(elements[offset+4]); |
| String text = elements[offset+5]; |
| text = unEscapeNewlines(text); |
| return new ProxyTree(ID, type, tokenLine, charPositionInLine, tokenIndex, text); |
| } |
| |
| protected ProxyToken deserializeToken(String[] elements, |
| int offset) |
| { |
| String indexS = elements[offset+0]; |
| String typeS = elements[offset+1]; |
| String channelS = elements[offset+2]; |
| String lineS = elements[offset+3]; |
| String posS = elements[offset+4]; |
| String text = elements[offset+5]; |
| text = unEscapeNewlines(text); |
| int index = Integer.parseInt(indexS); |
| ProxyToken t = |
| new ProxyToken(index, |
| Integer.parseInt(typeS), |
| Integer.parseInt(channelS), |
| Integer.parseInt(lineS), |
| Integer.parseInt(posS), |
| text); |
| return t; |
| } |
| |
| /** Create a thread to listen to the remote running recognizer */ |
| public void start() { |
| Thread t = new Thread(this); |
| t.start(); |
| } |
| |
| public void run() { |
| eventHandler(); |
| } |
| |
| // M i s c |
| |
| public String[] getEventElements(String event) { |
| if ( event==null ) { |
| return null; |
| } |
| String[] elements = new String[MAX_EVENT_ELEMENTS]; |
| String str = null; // a string element if present (must be last) |
| try { |
| int firstQuoteIndex = event.indexOf('"'); |
| if ( firstQuoteIndex>=0 ) { |
| // treat specially; has a string argument like "a comment\n |
| // Note that the string is terminated by \n not end quote. |
| // Easier to parse that way. |
| String eventWithoutString = event.substring(0,firstQuoteIndex); |
| str = event.substring(firstQuoteIndex+1,event.length()); |
| event = eventWithoutString; |
| } |
| StringTokenizer st = new StringTokenizer(event, "\t", false); |
| int i = 0; |
| while ( st.hasMoreTokens() ) { |
| if ( i>=MAX_EVENT_ELEMENTS ) { |
| // ErrorManager.internalError("event has more than "+MAX_EVENT_ELEMENTS+" args: "+event); |
| return elements; |
| } |
| elements[i] = st.nextToken(); |
| i++; |
| } |
| if ( str!=null ) { |
| elements[i] = str; |
| } |
| } |
| catch (Exception e) { |
| e.printStackTrace(System.err); |
| } |
| return elements; |
| } |
| |
| protected String unEscapeNewlines(String txt) { |
| // this unescape is slow but easy to understand |
| txt = txt.replaceAll("%0A","\n"); // unescape \n |
| txt = txt.replaceAll("%0D","\r"); // unescape \r |
| txt = txt.replaceAll("%25","%"); // undo escaped escape chars |
| return txt; |
| } |
| |
| public boolean tokenIndexesAreInvalid() { |
| return false; |
| //return tokenIndexesInvalid; |
| } |
| |
| } |
| |