diff options
Diffstat (limited to 'runtime/Ruby/lib/antlr3/debug/socket.rb')
-rw-r--r-- | runtime/Ruby/lib/antlr3/debug/socket.rb | 360 |
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 |