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