361 lines
9.0 KiB
Ruby
361 lines
9.0 KiB
Ruby
#!/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
|