Home | History | Annotate | Download | only in antlr3
      1 #!/usr/bin/ruby
      2 # encoding: utf-8
      3 
      4 =begin LICENSE
      5 [The "BSD licence"]
      6 Copyright (c) 2009-2010 Kyle Yetter
      7 All rights reserved.
      8 
      9 Redistribution and use in source and binary forms, with or without
     10 modification, are permitted provided that the following conditions
     11 are met:
     12 
     13  1. Redistributions of source code must retain the above copyright
     14     notice, this list of conditions and the following disclaimer.
     15  2. Redistributions in binary form must reproduce the above copyright
     16     notice, this list of conditions and the following disclaimer in the
     17     documentation and/or other materials provided with the distribution.
     18  3. The name of the author may not be used to endorse or promote products
     19     derived from this software without specific prior written permission.
     20 
     21 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     22 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     23 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     24 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     25 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     26 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     30 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31 
     32 =end
     33 
     34 require 'optparse'
     35 require 'antlr3'
     36 
     37 module ANTLR3
     38 
     39 =begin rdoc ANTLR3::Main
     40 
     41 Namespace module for the quick script Main classes.
     42 
     43 =end
     44 
     45 module Main
     46 
     47 
     48 =begin rdoc ANTLR3::Main::Options
     49 
     50 Defines command-line options and attribute mappings shared by all types of
     51 Main classes.
     52 
     53 =end
     54 
     55 module Options
     56   # the input encoding type; defaults to +nil+ (currently, not used)
     57   attr_accessor :encoding
     58   # the input stream used by the Main script; defaults to <tt>$stdin</tt>
     59   attr_accessor :input
     60   # a boolean flag indicating whether or not to run the Main
     61   # script in interactive mode; defaults to +false+
     62   attr_accessor :interactive
     63   attr_accessor :no_output
     64   attr_accessor :profile
     65   attr_accessor :debug_socket
     66   attr_accessor :ruby_prof
     67   
     68   def initialize( options = {} )
     69     @no_output    = options.fetch( :no_output, false )
     70     @profile      = options.fetch( :profile, false )
     71     @debug_socket = options.fetch( :debug_socket, false )
     72     @ruby_prof    = options.fetch( :ruby_prof, false )
     73     @encoding     = options.fetch( :encoding, nil )
     74     @interactive  = options.fetch( :interactive, false )
     75     @input        = options.fetch( :input, $stdin )
     76   end
     77   
     78   # constructs an OptionParser and parses the argument list provided by +argv+
     79   def parse_options( argv = ARGV )
     80     oparser = OptionParser.new do | o |
     81       o.separator 'Input Options:'
     82       
     83       o.on( '-i', '--input "text to process"', doc( <<-END ) ) { |val| @input = val }
     84       | a string to use as direct input to the recognizer
     85       END
     86       
     87       o.on( '-I', '--interactive', doc( <<-END ) ) { @interactive = true }
     88       | run an interactive session with the recognizer
     89       END
     90     end
     91     
     92     setup_options( oparser )
     93     return oparser.parse( argv )
     94   end
     95   
     96 private
     97   
     98   def setup_options( oparser )
     99     # overridable hook to modify / append options
    100   end
    101   
    102   def doc( description_string )
    103     description_string.chomp!
    104     description_string.gsub!( /^ *\| ?/, '' )
    105     description_string.gsub!( /\s+/, ' ' )
    106     return description_string
    107   end
    108   
    109 end
    110 
    111 =begin rdoc ANTLR3::Main::Main
    112 
    113 The base-class for the three primary Main script-runner classes.
    114 It defines the skeletal structure shared by all main
    115 scripts, but isn't particularly useful on its own.
    116 
    117 =end
    118 
    119 class Main
    120   include Options
    121   include Util
    122   attr_accessor :output, :error
    123   
    124   def initialize( options = {} )
    125     @input  = options.fetch( :input, $stdin )
    126     @output = options.fetch( :output, $stdout )
    127     @error  = options.fetch( :error, $stderr )
    128     @name   = options.fetch( :name, File.basename( $0, '.rb' ) )
    129     super
    130     block_given? and yield( self )
    131   end
    132   
    133   
    134   # runs the script
    135   def execute( argv = ARGV )
    136     args = parse_options( argv )
    137     setup
    138     
    139     @interactive and return execute_interactive
    140     
    141     in_stream = 
    142       case
    143       when @input.is_a?( ::String ) then StringStream.new( @input )
    144       when args.length == 1 && args.first != '-'
    145         ANTLR3::FileStream.new( args[ 0 ] )
    146       else ANTLR3::FileStream.new( @input )
    147       end
    148     case
    149     when @ruby_prof
    150       load_ruby_prof
    151       profile = RubyProf.profile do
    152         recognize( in_stream )
    153       end
    154       printer = RubyProf::FlatPrinter.new( profile )
    155       printer.print( @output )
    156     when @profile
    157       require 'profiler'
    158       Profiler__.start_profile
    159       recognize( in_stream )
    160       Profiler__.print_profile
    161     else
    162       recognize( in_stream )
    163     end
    164   end
    165   
    166 private
    167   
    168   def recognize( *args )
    169     # overriden by subclasses
    170   end
    171   
    172   def execute_interactive
    173     @output.puts( tidy( <<-END ) )
    174     | ===================================================================
    175     | Ruby ANTLR Console for #{ $0 }
    176     | ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
    177     | * Enter source code lines 
    178     | * Enter EOF to finish up and exit
    179     |   (control+D on Mac/Linux/Unix or control+Z on Windows)
    180     | ===================================================================
    181     | 
    182     END
    183     
    184     read_method = 
    185       begin
    186         require 'readline'
    187         line_number = 0
    188         lambda do
    189           begin
    190             if line = Readline.readline( "#@name:#{ line_number += 1 }> ", true )
    191               line << $/
    192             else
    193               @output.print( "\n" ) # ensures result output is on a new line after EOF is entered
    194               nil
    195             end
    196           rescue Interrupt, EOFError
    197             retry
    198           end
    199           line << "\n" if line
    200         end
    201         
    202       rescue LoadError
    203         lambda do
    204           begin
    205             printf( "%s:%i> ", @name, @input.lineno )
    206             flush
    207             line = @input.gets or
    208               @output.print( "\n" ) # ensures result output is on a new line after EOF is entered
    209             line
    210           rescue Interrupt, EOFError
    211             retry
    212           end
    213           line
    214         end
    215       end
    216     
    217     stream = InteractiveStringStream.new( :name => @name, &read_method )
    218     recognize( stream )
    219   end
    220   
    221   def screen_width
    222     ( ENV[ 'COLUMNS' ] || 80 ).to_i
    223   end
    224   
    225   def attempt( lib, message = nil, exit_status = nil )
    226     yield
    227   rescue LoadError => error
    228     message or raise
    229     @error.puts( message )
    230     report_error( error )
    231     report_load_path
    232     exit( exit_status ) if exit_status
    233   rescue => error
    234     @error.puts( "received an error while attempting to load %p" % lib )
    235     report_error( error )
    236     exit( exit_status ) if exit_status
    237   end
    238   
    239   def report_error( error )
    240     puts!( "~ error details:" )
    241     puts!( '  [ %s ]' % error.class.name )
    242     message = error.to_s.gsub( /\n/, "\n     " )
    243     puts!( '  -> ' << message )
    244     for call in error.backtrace
    245       puts!( '     ' << call )
    246     end
    247   end
    248   
    249   def report_load_path
    250     puts!( "~ content of $LOAD_PATH: " )
    251     for dir in $LOAD_PATH
    252       puts!( "  - #{ dir }" )
    253     end
    254   end
    255   
    256   def setup
    257     # hook
    258   end
    259   
    260   def fetch_class( name )
    261     name.nil? || name.empty? and return( nil )
    262     unless constant_exists?( name )
    263       try_to_load( name )
    264       constant_exists?( name ) or return( nil )
    265     end
    266     
    267     name.split( /::/ ).inject( Object ) do |mod, name|
    268       # ::SomeModule splits to ['', 'SomeModule'] - so ignore empty strings
    269       name.empty? and next( mod ) 
    270       mod.const_get( name )
    271     end
    272   end
    273   
    274   def constant_exists?( name )
    275     eval( "defined?(#{ name })" ) == 'constant'
    276   end
    277   
    278   def try_to_load( name )
    279     if name =~ /(\w+)::(Lexer|Parser|TreeParser)$/
    280       retry_ok = true
    281       module_name, recognizer_type = $1, $2
    282       script = name.gsub( /::/, '' )
    283       begin
    284         return( require( script ) )
    285       rescue LoadError
    286         if retry_ok
    287           script, retry_ok = module_name, false
    288           retry
    289         else
    290           return( nil )
    291         end
    292       end
    293     end
    294   end
    295   
    296   %w(puts print printf flush).each do |method|
    297     class_eval( <<-END, __FILE__, __LINE__ )
    298       private
    299       
    300       def #{ method }(*args)
    301         @output.#{ method }(*args) unless @no_output
    302       end
    303       
    304       def #{ method }!( *args )
    305         @error.#{ method }(*args) unless @no_output
    306       end
    307     END
    308   end
    309 end
    310 
    311 
    312 =begin rdoc ANTLR3::Main::LexerMain
    313 
    314 A class which implements a handy test script which is executed whenever an ANTLR
    315 generated lexer file is run directly from the command line.
    316 
    317 =end
    318 class LexerMain < Main
    319   def initialize( lexer_class, options = {} )
    320     super( options )
    321     @lexer_class = lexer_class
    322   end
    323   
    324   def recognize( in_stream )
    325     lexer = @lexer_class.new( in_stream )
    326     
    327     loop do
    328       begin
    329         token = lexer.next_token
    330         if token.nil? || token.type == ANTLR3::EOF then break
    331         else display_token( token )
    332         end
    333       rescue ANTLR3::RecognitionError => error
    334         report_error( error )
    335         break
    336       end
    337     end
    338   end
    339   
    340   def display_token( token )
    341     case token.channel
    342     when ANTLR3::DEFAULT_CHANNEL
    343       prefix = '-->'
    344       suffix = ''
    345     when ANTLR3::HIDDEN_CHANNEL
    346       prefix = '#  '
    347       suffix = ' (hidden)'
    348     else
    349       prefix = '~~>'
    350       suffix = ' (channel %p)' % token.channel
    351     end
    352     
    353     printf( "%s %-15s %-15p @ line %-3i col %-3i%s\n",
    354            prefix, token.name, token.text,
    355            token.line, token.column, suffix )
    356   end
    357   
    358 end
    359 
    360 =begin rdoc ANTLR3::Main::ParserMain
    361 
    362 A class which implements a handy test script which is executed whenever an ANTLR
    363 generated parser file is run directly from the command line.
    364 
    365 =end
    366 class ParserMain < Main
    367   attr_accessor :lexer_class_name,
    368                 :lexer_class,
    369                 :parser_class,
    370                 :parser_rule,
    371                 :port,
    372                 :log
    373   
    374   def initialize( parser_class, options = {} )
    375     super( options )
    376     @lexer_class_name = options[ :lexer_class_name ]
    377     @lexer_class      = options[ :lexer_class ]
    378     @parser_class     = parser_class
    379     @parser_rule = options[ :parser_rule ]
    380     if @debug = ( @parser_class.debug? rescue false )
    381       @trace = options.fetch( :trace, nil )
    382       @port = options.fetch( :port, ANTLR3::Debug::DEFAULT_PORT )
    383       @log  = options.fetch( :log, @error )
    384     end
    385     @profile = ( @parser_class.profile? rescue false )
    386   end
    387   
    388   def setup_options( opt )
    389     super
    390     
    391     opt.separator ""
    392     opt.separator( "Parser Configuration:" )
    393     
    394     opt.on( '--lexer-name CLASS_NAME', "name of the lexer class to use" ) { |val|
    395       @lexer_class_name = val
    396       @lexer_class = nil
    397     }
    398     
    399     opt.on( '--lexer-file PATH_TO_LIBRARY', "path to library defining the lexer class" ) { |val|
    400       begin
    401         test( ?f, val ) ? load( val ) : require( val )
    402       rescue LoadError
    403         warn( "unable to load the library specified by --lexer-file: #{ $! }" )
    404       end
    405     }
    406     
    407     opt.on( '--rule NAME', "name of the parser rule to execute" ) { |val| @parser_rule = val }
    408     
    409     if @debug
    410       opt.separator ''
    411       opt.separator "Debug Mode Options:"
    412       
    413       opt.on( '--trace', '-t', "print rule trace instead of opening a debug socket" ) do
    414         @trace = true
    415       end
    416       
    417       opt.on( '--port NUMBER', Integer, "port number to use for the debug socket" ) do |number|
    418         @port = number
    419       end
    420       
    421       opt.on( '--log PATH', "path of file to use to record socket activity",
    422              "(stderr by default)" ) do |path|
    423         @log = open( path, 'w' )
    424       end
    425     end
    426   end
    427   
    428   def setup
    429     unless @lexer_class ||= fetch_class( @lexer_class_name )
    430       if @lexer_class_name
    431         fail( "unable to locate the lexer class ``#@lexer_class_name''" )
    432       else
    433         unless @lexer_class = @parser_class.associated_lexer
    434           fail( doc( <<-END ) )
    435           | no lexer class has been specified with the --lexer-name option
    436           | and #@parser_class does not appear to have an associated
    437           | lexer class
    438           END
    439         end
    440       end
    441     end
    442     @parser_rule ||= @parser_class.default_rule or
    443       fail( "a parser rule name must be specified via --rule NAME" )
    444   end
    445   
    446   def recognize( in_stream )
    447     parser_options = {}
    448     if @debug
    449       if @trace
    450         parser_options[ :debug_listener ] = ANTLR3::Debug::RuleTracer.new
    451       else
    452         parser_options[ :port ] = @port
    453         parser_options[ :log ]  = @log
    454       end
    455     end
    456     lexer = @lexer_class.new( in_stream )
    457     # token_stream = CommonTokenStream.new( lexer )
    458     parser = @parser_class.new( lexer, parser_options )
    459     result = parser.send( @parser_rule ) and present( result )
    460     @profile and puts( parser.generate_report )
    461   end
    462   
    463   def present( return_value )
    464     ASTBuilder > @parser_class and return_value = return_value.tree
    465     if return_value
    466       text = 
    467         begin
    468           require 'pp'
    469           return_value.pretty_inspect
    470         rescue LoadError, NoMethodError
    471           return_value.inspect
    472         end
    473       puts( text )
    474     end
    475   end
    476   
    477 end
    478 
    479 =begin rdoc ANTLR3::Main::WalkerMain
    480 
    481 A class which implements a handy test script which is executed whenever an ANTLR
    482 generated tree walker (tree parser) file is run directly from the command line.
    483 
    484 =end
    485 
    486 class WalkerMain < Main
    487   attr_accessor :walker_class, :lexer_class, :parser_class
    488   
    489   def initialize( walker_class, options = {} )
    490     super( options )
    491     @walker_class = walker_class
    492     @lexer_class_name = options[ :lexer_class_name ]
    493     @lexer_class  = options[ :lexer_class ]
    494     @parser_class_name = options[ :parser_class_name ]
    495     @parser_class = options[ :parser_class ]
    496     if @debug = ( @parser_class.debug? rescue false )
    497       @port = options.fetch( :port, ANTLR3::Debug::DEFAULT_PORT )
    498       @log  = options.fetch( :log, @error )
    499     end
    500   end
    501   
    502   def setup_options( opt )
    503     super
    504     
    505     opt.separator ''
    506     opt.separator "Tree Parser Configuration:"
    507     
    508     opt.on( '--lexer-name CLASS_NAME', 'full name of the lexer class to use' ) { |val| @lexer_class_name = val }
    509     opt.on(
    510       '--lexer-file PATH_TO_LIBRARY',
    511       'path to load to make the lexer class available'
    512     ) { |val|
    513       begin
    514         test( ?f, val ) ? load( val ) : require( val )
    515       rescue LoadError
    516         warn( "unable to load the library `#{ val }' specified by --lexer-file: #{ $! }" )
    517       end
    518     }
    519     
    520     opt.on(
    521       '--parser-name CLASS_NAME',
    522       'full name of the parser class to use'
    523     ) { |val| @parser_class_name = val }
    524     opt.on(
    525       '--parser-file PATH_TO_LIBRARY',
    526       'path to load to make the parser class available'
    527     ) { |val|
    528       begin
    529         test( ?f, val ) ? load( val ) : require( val )
    530       rescue LoadError
    531         warn( "unable to load the library specified by --parser-file: #{ $! }" )
    532       end
    533     }
    534     
    535     opt.on( '--parser-rule NAME', "name of the parser rule to use on the input" ) { |val| @parser_rule = val }
    536     opt.on( '--rule NAME', "name of the rule to invoke in the tree parser" ) { |val| @walker_rule = val }
    537     
    538     if @debug
    539       opt.separator ''
    540       opt.separator "Debug Mode Options:"
    541       
    542       opt.on( '--port NUMBER', Integer, "port number to use for the debug socket" ) do |number|
    543         @port = number
    544       end
    545       opt.on( '--log PATH', "path of file to use to record socket activity",
    546              "(stderr by default)" ) do |path|
    547         @log = open( path, 'w' )
    548       end
    549     end
    550   end
    551   
    552   # TODO: finish the Main modules
    553   def setup
    554     unless @lexer_class ||= fetch_class( @lexer_class_name )
    555       fail( "unable to locate the lexer class #@lexer_class_name" )
    556     end
    557     unless @parser_class ||= fetch_class( @parser_class_name )
    558       fail( "unable to locate the parser class #@parser_class_name" )
    559     end
    560   end
    561   
    562   def recognize( in_stream )
    563     walker_options = {}
    564     if @debug
    565       walker_options[ :port ] = @port
    566       walker_options[ :log ] = @log
    567     end
    568     @lexer = @lexer_class.new( in_stream )
    569     @token_stream = ANTLR3::CommonTokenStream.new( @lexer )
    570     @parser = @parser_class.new( @token_stream )
    571     if result = @parser.send( @parser_rule )
    572       result.respond_to?( :tree ) or fail( "Parser did not return an AST for rule #@parser_rule" )
    573       @node_stream = ANTLR3::CommonTreeNodeStream.new( result.tree )
    574       @node_stream.token_stream = @token_stream
    575       @walker = @walker_class.new( @node_stream, walker_options )
    576       if result = @walker.send( @walker_rule )
    577         out = result.tree.inspect rescue result.inspect
    578         puts( out )
    579       else puts!( "walker.#@walker_rule returned nil" )
    580       end
    581     else puts!( "parser.#@parser_rule returned nil" )
    582     end
    583   end
    584 end
    585 end
    586 end
    587