587 lines
16 KiB
Ruby
587 lines
16 KiB
Ruby
#!/usr/bin/ruby
|
|
# encoding: utf-8
|
|
|
|
=begin LICENSE
|
|
[The "BSD licence"]
|
|
Copyright (c) 2009-2010 Kyle Yetter
|
|
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.
|
|
|
|
=end
|
|
|
|
require 'optparse'
|
|
require 'antlr3'
|
|
|
|
module ANTLR3
|
|
|
|
=begin rdoc ANTLR3::Main
|
|
|
|
Namespace module for the quick script Main classes.
|
|
|
|
=end
|
|
|
|
module Main
|
|
|
|
|
|
=begin rdoc ANTLR3::Main::Options
|
|
|
|
Defines command-line options and attribute mappings shared by all types of
|
|
Main classes.
|
|
|
|
=end
|
|
|
|
module Options
|
|
# the input encoding type; defaults to +nil+ (currently, not used)
|
|
attr_accessor :encoding
|
|
# the input stream used by the Main script; defaults to <tt>$stdin</tt>
|
|
attr_accessor :input
|
|
# a boolean flag indicating whether or not to run the Main
|
|
# script in interactive mode; defaults to +false+
|
|
attr_accessor :interactive
|
|
attr_accessor :no_output
|
|
attr_accessor :profile
|
|
attr_accessor :debug_socket
|
|
attr_accessor :ruby_prof
|
|
|
|
def initialize( options = {} )
|
|
@no_output = options.fetch( :no_output, false )
|
|
@profile = options.fetch( :profile, false )
|
|
@debug_socket = options.fetch( :debug_socket, false )
|
|
@ruby_prof = options.fetch( :ruby_prof, false )
|
|
@encoding = options.fetch( :encoding, nil )
|
|
@interactive = options.fetch( :interactive, false )
|
|
@input = options.fetch( :input, $stdin )
|
|
end
|
|
|
|
# constructs an OptionParser and parses the argument list provided by +argv+
|
|
def parse_options( argv = ARGV )
|
|
oparser = OptionParser.new do | o |
|
|
o.separator 'Input Options:'
|
|
|
|
o.on( '-i', '--input "text to process"', doc( <<-END ) ) { |val| @input = val }
|
|
| a string to use as direct input to the recognizer
|
|
END
|
|
|
|
o.on( '-I', '--interactive', doc( <<-END ) ) { @interactive = true }
|
|
| run an interactive session with the recognizer
|
|
END
|
|
end
|
|
|
|
setup_options( oparser )
|
|
return oparser.parse( argv )
|
|
end
|
|
|
|
private
|
|
|
|
def setup_options( oparser )
|
|
# overridable hook to modify / append options
|
|
end
|
|
|
|
def doc( description_string )
|
|
description_string.chomp!
|
|
description_string.gsub!( /^ *\| ?/, '' )
|
|
description_string.gsub!( /\s+/, ' ' )
|
|
return description_string
|
|
end
|
|
|
|
end
|
|
|
|
=begin rdoc ANTLR3::Main::Main
|
|
|
|
The base-class for the three primary Main script-runner classes.
|
|
It defines the skeletal structure shared by all main
|
|
scripts, but isn't particularly useful on its own.
|
|
|
|
=end
|
|
|
|
class Main
|
|
include Options
|
|
include Util
|
|
attr_accessor :output, :error
|
|
|
|
def initialize( options = {} )
|
|
@input = options.fetch( :input, $stdin )
|
|
@output = options.fetch( :output, $stdout )
|
|
@error = options.fetch( :error, $stderr )
|
|
@name = options.fetch( :name, File.basename( $0, '.rb' ) )
|
|
super
|
|
block_given? and yield( self )
|
|
end
|
|
|
|
|
|
# runs the script
|
|
def execute( argv = ARGV )
|
|
args = parse_options( argv )
|
|
setup
|
|
|
|
@interactive and return execute_interactive
|
|
|
|
in_stream =
|
|
case
|
|
when @input.is_a?( ::String ) then StringStream.new( @input )
|
|
when args.length == 1 && args.first != '-'
|
|
ANTLR3::FileStream.new( args[ 0 ] )
|
|
else ANTLR3::FileStream.new( @input )
|
|
end
|
|
case
|
|
when @ruby_prof
|
|
load_ruby_prof
|
|
profile = RubyProf.profile do
|
|
recognize( in_stream )
|
|
end
|
|
printer = RubyProf::FlatPrinter.new( profile )
|
|
printer.print( @output )
|
|
when @profile
|
|
require 'profiler'
|
|
Profiler__.start_profile
|
|
recognize( in_stream )
|
|
Profiler__.print_profile
|
|
else
|
|
recognize( in_stream )
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def recognize( *args )
|
|
# overriden by subclasses
|
|
end
|
|
|
|
def execute_interactive
|
|
@output.puts( tidy( <<-END ) )
|
|
| ===================================================================
|
|
| Ruby ANTLR Console for #{ $0 }
|
|
| ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
|
|
| * Enter source code lines
|
|
| * Enter EOF to finish up and exit
|
|
| (control+D on Mac/Linux/Unix or control+Z on Windows)
|
|
| ===================================================================
|
|
|
|
|
END
|
|
|
|
read_method =
|
|
begin
|
|
require 'readline'
|
|
line_number = 0
|
|
lambda do
|
|
begin
|
|
if line = Readline.readline( "#@name:#{ line_number += 1 }> ", true )
|
|
line << $/
|
|
else
|
|
@output.print( "\n" ) # ensures result output is on a new line after EOF is entered
|
|
nil
|
|
end
|
|
rescue Interrupt, EOFError
|
|
retry
|
|
end
|
|
line << "\n" if line
|
|
end
|
|
|
|
rescue LoadError
|
|
lambda do
|
|
begin
|
|
printf( "%s:%i> ", @name, @input.lineno )
|
|
flush
|
|
line = @input.gets or
|
|
@output.print( "\n" ) # ensures result output is on a new line after EOF is entered
|
|
line
|
|
rescue Interrupt, EOFError
|
|
retry
|
|
end
|
|
line
|
|
end
|
|
end
|
|
|
|
stream = InteractiveStringStream.new( :name => @name, &read_method )
|
|
recognize( stream )
|
|
end
|
|
|
|
def screen_width
|
|
( ENV[ 'COLUMNS' ] || 80 ).to_i
|
|
end
|
|
|
|
def attempt( lib, message = nil, exit_status = nil )
|
|
yield
|
|
rescue LoadError => error
|
|
message or raise
|
|
@error.puts( message )
|
|
report_error( error )
|
|
report_load_path
|
|
exit( exit_status ) if exit_status
|
|
rescue => error
|
|
@error.puts( "received an error while attempting to load %p" % lib )
|
|
report_error( error )
|
|
exit( exit_status ) if exit_status
|
|
end
|
|
|
|
def report_error( error )
|
|
puts!( "~ error details:" )
|
|
puts!( ' [ %s ]' % error.class.name )
|
|
message = error.to_s.gsub( /\n/, "\n " )
|
|
puts!( ' -> ' << message )
|
|
for call in error.backtrace
|
|
puts!( ' ' << call )
|
|
end
|
|
end
|
|
|
|
def report_load_path
|
|
puts!( "~ content of $LOAD_PATH: " )
|
|
for dir in $LOAD_PATH
|
|
puts!( " - #{ dir }" )
|
|
end
|
|
end
|
|
|
|
def setup
|
|
# hook
|
|
end
|
|
|
|
def fetch_class( name )
|
|
name.nil? || name.empty? and return( nil )
|
|
unless constant_exists?( name )
|
|
try_to_load( name )
|
|
constant_exists?( name ) or return( nil )
|
|
end
|
|
|
|
name.split( /::/ ).inject( Object ) do |mod, name|
|
|
# ::SomeModule splits to ['', 'SomeModule'] - so ignore empty strings
|
|
name.empty? and next( mod )
|
|
mod.const_get( name )
|
|
end
|
|
end
|
|
|
|
def constant_exists?( name )
|
|
eval( "defined?(#{ name })" ) == 'constant'
|
|
end
|
|
|
|
def try_to_load( name )
|
|
if name =~ /(\w+)::(Lexer|Parser|TreeParser)$/
|
|
retry_ok = true
|
|
module_name, recognizer_type = $1, $2
|
|
script = name.gsub( /::/, '' )
|
|
begin
|
|
return( require( script ) )
|
|
rescue LoadError
|
|
if retry_ok
|
|
script, retry_ok = module_name, false
|
|
retry
|
|
else
|
|
return( nil )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
%w(puts print printf flush).each do |method|
|
|
class_eval( <<-END, __FILE__, __LINE__ )
|
|
private
|
|
|
|
def #{ method }(*args)
|
|
@output.#{ method }(*args) unless @no_output
|
|
end
|
|
|
|
def #{ method }!( *args )
|
|
@error.#{ method }(*args) unless @no_output
|
|
end
|
|
END
|
|
end
|
|
end
|
|
|
|
|
|
=begin rdoc ANTLR3::Main::LexerMain
|
|
|
|
A class which implements a handy test script which is executed whenever an ANTLR
|
|
generated lexer file is run directly from the command line.
|
|
|
|
=end
|
|
class LexerMain < Main
|
|
def initialize( lexer_class, options = {} )
|
|
super( options )
|
|
@lexer_class = lexer_class
|
|
end
|
|
|
|
def recognize( in_stream )
|
|
lexer = @lexer_class.new( in_stream )
|
|
|
|
loop do
|
|
begin
|
|
token = lexer.next_token
|
|
if token.nil? || token.type == ANTLR3::EOF then break
|
|
else display_token( token )
|
|
end
|
|
rescue ANTLR3::RecognitionError => error
|
|
report_error( error )
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
def display_token( token )
|
|
case token.channel
|
|
when ANTLR3::DEFAULT_CHANNEL
|
|
prefix = '-->'
|
|
suffix = ''
|
|
when ANTLR3::HIDDEN_CHANNEL
|
|
prefix = '# '
|
|
suffix = ' (hidden)'
|
|
else
|
|
prefix = '~~>'
|
|
suffix = ' (channel %p)' % token.channel
|
|
end
|
|
|
|
printf( "%s %-15s %-15p @ line %-3i col %-3i%s\n",
|
|
prefix, token.name, token.text,
|
|
token.line, token.column, suffix )
|
|
end
|
|
|
|
end
|
|
|
|
=begin rdoc ANTLR3::Main::ParserMain
|
|
|
|
A class which implements a handy test script which is executed whenever an ANTLR
|
|
generated parser file is run directly from the command line.
|
|
|
|
=end
|
|
class ParserMain < Main
|
|
attr_accessor :lexer_class_name,
|
|
:lexer_class,
|
|
:parser_class,
|
|
:parser_rule,
|
|
:port,
|
|
:log
|
|
|
|
def initialize( parser_class, options = {} )
|
|
super( options )
|
|
@lexer_class_name = options[ :lexer_class_name ]
|
|
@lexer_class = options[ :lexer_class ]
|
|
@parser_class = parser_class
|
|
@parser_rule = options[ :parser_rule ]
|
|
if @debug = ( @parser_class.debug? rescue false )
|
|
@trace = options.fetch( :trace, nil )
|
|
@port = options.fetch( :port, ANTLR3::Debug::DEFAULT_PORT )
|
|
@log = options.fetch( :log, @error )
|
|
end
|
|
@profile = ( @parser_class.profile? rescue false )
|
|
end
|
|
|
|
def setup_options( opt )
|
|
super
|
|
|
|
opt.separator ""
|
|
opt.separator( "Parser Configuration:" )
|
|
|
|
opt.on( '--lexer-name CLASS_NAME', "name of the lexer class to use" ) { |val|
|
|
@lexer_class_name = val
|
|
@lexer_class = nil
|
|
}
|
|
|
|
opt.on( '--lexer-file PATH_TO_LIBRARY', "path to library defining the lexer class" ) { |val|
|
|
begin
|
|
test( ?f, val ) ? load( val ) : require( val )
|
|
rescue LoadError
|
|
warn( "unable to load the library specified by --lexer-file: #{ $! }" )
|
|
end
|
|
}
|
|
|
|
opt.on( '--rule NAME', "name of the parser rule to execute" ) { |val| @parser_rule = val }
|
|
|
|
if @debug
|
|
opt.separator ''
|
|
opt.separator "Debug Mode Options:"
|
|
|
|
opt.on( '--trace', '-t', "print rule trace instead of opening a debug socket" ) do
|
|
@trace = true
|
|
end
|
|
|
|
opt.on( '--port NUMBER', Integer, "port number to use for the debug socket" ) do |number|
|
|
@port = number
|
|
end
|
|
|
|
opt.on( '--log PATH', "path of file to use to record socket activity",
|
|
"(stderr by default)" ) do |path|
|
|
@log = open( path, 'w' )
|
|
end
|
|
end
|
|
end
|
|
|
|
def setup
|
|
unless @lexer_class ||= fetch_class( @lexer_class_name )
|
|
if @lexer_class_name
|
|
fail( "unable to locate the lexer class ``#@lexer_class_name''" )
|
|
else
|
|
unless @lexer_class = @parser_class.associated_lexer
|
|
fail( doc( <<-END ) )
|
|
| no lexer class has been specified with the --lexer-name option
|
|
| and #@parser_class does not appear to have an associated
|
|
| lexer class
|
|
END
|
|
end
|
|
end
|
|
end
|
|
@parser_rule ||= @parser_class.default_rule or
|
|
fail( "a parser rule name must be specified via --rule NAME" )
|
|
end
|
|
|
|
def recognize( in_stream )
|
|
parser_options = {}
|
|
if @debug
|
|
if @trace
|
|
parser_options[ :debug_listener ] = ANTLR3::Debug::RuleTracer.new
|
|
else
|
|
parser_options[ :port ] = @port
|
|
parser_options[ :log ] = @log
|
|
end
|
|
end
|
|
lexer = @lexer_class.new( in_stream )
|
|
# token_stream = CommonTokenStream.new( lexer )
|
|
parser = @parser_class.new( lexer, parser_options )
|
|
result = parser.send( @parser_rule ) and present( result )
|
|
@profile and puts( parser.generate_report )
|
|
end
|
|
|
|
def present( return_value )
|
|
ASTBuilder > @parser_class and return_value = return_value.tree
|
|
if return_value
|
|
text =
|
|
begin
|
|
require 'pp'
|
|
return_value.pretty_inspect
|
|
rescue LoadError, NoMethodError
|
|
return_value.inspect
|
|
end
|
|
puts( text )
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
=begin rdoc ANTLR3::Main::WalkerMain
|
|
|
|
A class which implements a handy test script which is executed whenever an ANTLR
|
|
generated tree walker (tree parser) file is run directly from the command line.
|
|
|
|
=end
|
|
|
|
class WalkerMain < Main
|
|
attr_accessor :walker_class, :lexer_class, :parser_class
|
|
|
|
def initialize( walker_class, options = {} )
|
|
super( options )
|
|
@walker_class = walker_class
|
|
@lexer_class_name = options[ :lexer_class_name ]
|
|
@lexer_class = options[ :lexer_class ]
|
|
@parser_class_name = options[ :parser_class_name ]
|
|
@parser_class = options[ :parser_class ]
|
|
if @debug = ( @parser_class.debug? rescue false )
|
|
@port = options.fetch( :port, ANTLR3::Debug::DEFAULT_PORT )
|
|
@log = options.fetch( :log, @error )
|
|
end
|
|
end
|
|
|
|
def setup_options( opt )
|
|
super
|
|
|
|
opt.separator ''
|
|
opt.separator "Tree Parser Configuration:"
|
|
|
|
opt.on( '--lexer-name CLASS_NAME', 'full name of the lexer class to use' ) { |val| @lexer_class_name = val }
|
|
opt.on(
|
|
'--lexer-file PATH_TO_LIBRARY',
|
|
'path to load to make the lexer class available'
|
|
) { |val|
|
|
begin
|
|
test( ?f, val ) ? load( val ) : require( val )
|
|
rescue LoadError
|
|
warn( "unable to load the library `#{ val }' specified by --lexer-file: #{ $! }" )
|
|
end
|
|
}
|
|
|
|
opt.on(
|
|
'--parser-name CLASS_NAME',
|
|
'full name of the parser class to use'
|
|
) { |val| @parser_class_name = val }
|
|
opt.on(
|
|
'--parser-file PATH_TO_LIBRARY',
|
|
'path to load to make the parser class available'
|
|
) { |val|
|
|
begin
|
|
test( ?f, val ) ? load( val ) : require( val )
|
|
rescue LoadError
|
|
warn( "unable to load the library specified by --parser-file: #{ $! }" )
|
|
end
|
|
}
|
|
|
|
opt.on( '--parser-rule NAME', "name of the parser rule to use on the input" ) { |val| @parser_rule = val }
|
|
opt.on( '--rule NAME', "name of the rule to invoke in the tree parser" ) { |val| @walker_rule = val }
|
|
|
|
if @debug
|
|
opt.separator ''
|
|
opt.separator "Debug Mode Options:"
|
|
|
|
opt.on( '--port NUMBER', Integer, "port number to use for the debug socket" ) do |number|
|
|
@port = number
|
|
end
|
|
opt.on( '--log PATH', "path of file to use to record socket activity",
|
|
"(stderr by default)" ) do |path|
|
|
@log = open( path, 'w' )
|
|
end
|
|
end
|
|
end
|
|
|
|
# TODO: finish the Main modules
|
|
def setup
|
|
unless @lexer_class ||= fetch_class( @lexer_class_name )
|
|
fail( "unable to locate the lexer class #@lexer_class_name" )
|
|
end
|
|
unless @parser_class ||= fetch_class( @parser_class_name )
|
|
fail( "unable to locate the parser class #@parser_class_name" )
|
|
end
|
|
end
|
|
|
|
def recognize( in_stream )
|
|
walker_options = {}
|
|
if @debug
|
|
walker_options[ :port ] = @port
|
|
walker_options[ :log ] = @log
|
|
end
|
|
@lexer = @lexer_class.new( in_stream )
|
|
@token_stream = ANTLR3::CommonTokenStream.new( @lexer )
|
|
@parser = @parser_class.new( @token_stream )
|
|
if result = @parser.send( @parser_rule )
|
|
result.respond_to?( :tree ) or fail( "Parser did not return an AST for rule #@parser_rule" )
|
|
@node_stream = ANTLR3::CommonTreeNodeStream.new( result.tree )
|
|
@node_stream.token_stream = @token_stream
|
|
@walker = @walker_class.new( @node_stream, walker_options )
|
|
if result = @walker.send( @walker_rule )
|
|
out = result.tree.inspect rescue result.inspect
|
|
puts( out )
|
|
else puts!( "walker.#@walker_rule returned nil" )
|
|
end
|
|
else puts!( "parser.#@parser_rule returned nil" )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|