aboutsummaryrefslogtreecommitdiff
path: root/runtime/Ruby/lib/antlr3/debug/socket.rb
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/Ruby/lib/antlr3/debug/socket.rb')
-rw-r--r--runtime/Ruby/lib/antlr3/debug/socket.rb360
1 files changed, 360 insertions, 0 deletions
diff --git a/runtime/Ruby/lib/antlr3/debug/socket.rb b/runtime/Ruby/lib/antlr3/debug/socket.rb
new file mode 100644
index 0000000..49c8779
--- /dev/null
+++ b/runtime/Ruby/lib/antlr3/debug/socket.rb
@@ -0,0 +1,360 @@
+#!/usr/bin/ruby
+# encoding: utf-8
+
+require 'socket'
+
+module ANTLR3
+module Debug
+
+
+=begin rdoc ANTLR3::Debug::EventSocketProxy
+
+A proxy debug event listener that forwards events over a socket to
+a debugger (or any other listener) using a simple text-based protocol;
+one event per line. ANTLRWorks listens on server socket with a
+RemoteDebugEventSocketListener instance. These two objects must therefore
+be kept in sync. New events must be handled on both sides of socket.
+
+=end
+class EventSocketProxy
+ include EventListener
+
+ SOCKET_ADDR_PACK = 'snCCCCa8'.freeze
+
+ def initialize( recognizer, options = {} )
+ super()
+ @grammar_file_name = recognizer.grammar_file_name
+ @adaptor = options[ :adaptor ]
+ @port = options[ :port ] || DEFAULT_PORT
+ @log = options[ :log ]
+ @socket = nil
+ @connection = nil
+ end
+
+ def log!( message, *interpolation_arguments )
+ @log and @log.printf( message, *interpolation_arguments )
+ end
+
+ def handshake
+ unless @socket
+ begin
+ @socket = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
+ @socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
+ @socket.bind( Socket.pack_sockaddr_in( @port, '' ) )
+ @socket.listen( 1 )
+ log!( "waiting for incoming connection on port %i\n", @port )
+
+ @connection, addr = @socket.accept
+ port, host = Socket.unpack_sockaddr_in( addr )
+ log!( "Accepted connection from %s:%s\n", host, port )
+
+ @connection.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
+
+ write( 'ANTLR %s', PROTOCOL_VERSION )
+ write( 'grammar %p', @grammar_file_name )
+ ack
+ rescue IOError => error
+ log!( "handshake failed due to an IOError:\n" )
+ log!( " %s: %s", error.class, error.message )
+ log!( " Backtrace: " )
+ log!( " - %s", error.backtrace.join( "\n - " ) )
+ @connection and @connection.close
+ @socket and @socket.close
+ @socket = nil
+ raise
+ end
+ end
+ return self
+ end
+
+ def write( message, *interpolation_arguments )
+ message << ?\n
+ log!( "---> #{ message }", *interpolation_arguments )
+ @connection.printf( message, *interpolation_arguments )
+ @connection.flush
+ end
+
+ def ack
+ line = @connection.readline
+ log!( "<--- %s", line )
+ line
+ end
+
+ def transmit( event, *interpolation_arguments )
+ write( event, *interpolation_arguments )
+ ack()
+ rescue IOError
+ @connection.close
+ raise
+ end
+
+ def commence
+ # don't bother sending event; listener will trigger upon connection
+ end
+
+ def terminate
+ transmit 'terminate'
+ @connection.close
+ @socket.close
+ end
+
+ def enter_rule( grammar_file_name, rule_name )
+ transmit "%s\t%s\t%s", :enter_rule, grammar_file_name, rule_name
+ end
+
+ def enter_alternative( alt )
+ transmit "%s\t%s", :enter_alternative, alt
+ end
+
+ def exit_rule( grammar_file_name, rule_name )
+ transmit "%s\t%s\t%s", :exit_rule, grammar_file_name, rule_name
+ end
+
+ def enter_subrule( decision_number )
+ transmit "%s\t%i", :enter_subrule, decision_number
+ end
+
+ def exit_subrule( decision_number )
+ transmit "%s\t%i", :exit_subrule, decision_number
+ end
+
+ def enter_decision( decision_number )
+ transmit "%s\t%i", :enter_decision, decision_number
+ end
+
+ def exit_decision( decision_number )
+ transmit "%s\t%i", :exit_decision, decision_number
+ end
+
+ def consume_token( token )
+ transmit "%s\t%s", :consume_token, serialize_token( token )
+ end
+
+ def consume_hidden_token( token )
+ transmit "%s\t%s", :consume_hidden_token, serialize_token( token )
+ end
+
+ def look( i, item )
+ case item
+ when AST::Tree
+ look_tree( i, item )
+ when nil
+ else
+ transmit "%s\t%i\t%s", :look, i, serialize_token( item )
+ end
+ end
+
+ def mark( i )
+ transmit "%s\t%i", :mark, i
+ end
+
+ def rewind( i = nil )
+ i ? transmit( "%s\t%i", :rewind, i ) : transmit( '%s', :rewind )
+ end
+
+ def begin_backtrack( level )
+ transmit "%s\t%i", :begin_backtrack, level
+ end
+ def end_backtrack( level, successful )
+ transmit "%s\t%i\t%p", :end_backtrack, level, ( successful ? true : false )
+ end
+
+ def location( line, position )
+ transmit "%s\t%i\t%i", :location, line, position
+ end
+
+ def recognition_exception( exception )
+ transmit "%s\t%p\t%i\t%i\t%i", :recognition_exception, exception.class,
+ exception.index, exception.line, exception.column
+ end
+
+ def begin_resync
+ transmit '%s', :begin_resync
+ end
+
+ def end_resync
+ transmit '%s', :end_resync
+ end
+
+ def semantic_predicate( result, predicate )
+ pure_boolean = !( !result )
+ transmit "%s\t%s\t%s", :semantic_predicate, pure_boolean, escape_newlines( predicate )
+ end
+
+ def consume_node( tree )
+ transmit "%s\t%s", :consume_node, serialize_node( tree )
+ end
+
+ def adaptor
+ @adaptor ||= ANTLR3::CommonTreeAdaptor.new
+ end
+
+ def look_tree( i, tree )
+ transmit "%s\t%s\t%s", :look_tree, i, serialize_node( tree )
+ end
+
+ def flat_node( tree )
+ transmit "%s\t%i", :flat_node, adaptor.unique_id( tree )
+ end
+
+ def error_node( tree )
+ transmit "%s\t%i\t%i\t%p", :error_node, adaptor.unique_id( tree ),
+ Token::INVALID_TOKEN_TYPE, escape_newlines( tree.to_s )
+ end
+
+ def create_node( node, token = nil )
+ if token
+ transmit "%s\t%i\t%i", :create_node, adaptor.unique_id( node ),
+ token.token_index
+ else
+ transmit "%s\t%i\t%i\t%p", :create_node, adaptor.unique_id( node ),
+ adaptor.type_of( node ), adaptor.text_of( node )
+ end
+ end
+
+ def become_root( new_root, old_root )
+ transmit "%s\t%i\t%i", :become_root, adaptor.unique_id( new_root ),
+ adaptor.unique_id( old_root )
+ end
+
+ def add_child( root, child )
+ transmit "%s\t%i\t%i", :add_child, adaptor.unique_id( root ),
+ adaptor.unique_id( child )
+ end
+
+ def set_token_boundaries( t, token_start_index, token_stop_index )
+ transmit "%s\t%i\t%i\t%i", :set_token_boundaries, adaptor.unique_id( t ),
+ token_start_index, token_stop_index
+ end
+
+ attr_accessor :adaptor
+
+ def serialize_token( token )
+ [ token.token_index, token.type, token.channel,
+ token.line, token.column,
+ escape_newlines( token.text ) ].join( "\t" )
+ end
+
+ def serialize_node( node )
+ adaptor ||= ANTLR3::AST::CommonTreeAdaptor.new
+ id = adaptor.unique_id( node )
+ type = adaptor.type_of( node )
+ token = adaptor.token( node )
+ line = token.line rescue -1
+ col = token.column rescue -1
+ index = adaptor.token_start_index( node )
+ [ id, type, line, col, index ].join( "\t" )
+ end
+
+
+ def escape_newlines( text )
+ text.inspect.tap do |t|
+ t.gsub!( /%/, '%%' )
+ end
+ end
+end
+
+=begin rdoc ANTLR3::Debug::RemoteEventSocketListener
+
+A debugging event listener which intercepts debug event messages sent by a EventSocketProxy
+over an IP socket.
+
+=end
+class RemoteEventSocketListener < ::Thread
+ autoload :StringIO, 'stringio'
+ ESCAPE_MAP = Hash.new { |h, k| k }
+ ESCAPE_MAP.update(
+ ?n => ?\n, ?t => ?\t, ?a => ?\a, ?b => ?\b, ?e => ?\e,
+ ?f => ?\f, ?r => ?\r, ?v => ?\v
+ )
+
+ attr_reader :host, :port
+
+ def initialize( options = {} )
+ @listener = listener
+ @host = options.fetch( :host, 'localhost' )
+ @port = options.fetch( :port, DEFAULT_PORT )
+ @buffer = StringIO.new
+ super do
+ connect do
+ handshake
+ loop do
+ yield( read_event )
+ end
+ end
+ end
+ end
+
+private
+
+ def handshake
+ @version = @socket.readline.split( "\t" )[ -1 ]
+ @grammar_file = @socket.readline.split( "\t" )[ -1 ]
+ ack
+ end
+
+ def ack
+ @socket.puts( "ack" )
+ @socket.flush
+ end
+
+ def unpack_event( event )
+ event.nil? and raise( StopIteration )
+ event.chomp!
+ name, *elements = event.split( "\t",-1 )
+ name = name.to_sym
+ name == :terminate and raise StopIteration
+ elements.map! do |elem|
+ elem.empty? and next( nil )
+ case elem
+ when /^\d+$/ then Integer( elem )
+ when /^\d+\.\d+$/ then Float( elem )
+ when /^true$/ then true
+ when /^false$/ then false
+ when /^"(.*)"$/ then parse_string( $1 )
+ end
+ end
+ elements.unshift( name )
+ return( elements )
+ end
+
+ def read_event
+ event = @socket.readline or raise( StopIteration )
+ ack
+ return unpack_event( event )
+ end
+
+ def connect
+ TCPSocket.open( @host, @port ) do |socket|
+ @socket = socket
+ yield
+ end
+ end
+
+ def parse_string( string )
+ @buffer.string = string
+ @buffer.rewind
+ out = ''
+ until @buffer.eof?
+ case c = @buffer.getc
+ when ?\\ # escape
+ nc = @buffer.getc
+ out <<
+ if nc.between?( ?0, ?9 ) # octal integer
+ @buffer.ungetc( nc )
+ @buffer.read( 3 ).to_i( 8 ).chr
+ elsif nc == ?x
+ @buffer.read( 2 ).to_i( 16 ).chr
+ else
+ ESCAPE_MAP[ nc ]
+ end
+ else
+ out << c
+ end
+ end
+ return out
+ end
+
+end # class RemoteEventSocketListener
+end # module Debug
+end # module ANTLR3