# http://ananelson.com/blog/2008/06/jruby-wrapping-for-antlr require 'java' $CLASSPATH << File.dirname(__FILE__) include_class 'org.antlr.runtime.ANTLRStringStream' include_class 'org.antlr.runtime.ANTLRFileStream' include_class 'org.antlr.runtime.CommonTokenStream' include_class 'java.io.PrintStream' include_class 'java.io.ByteArrayOutputStream' include_class 'java.lang.System' class AntlrError < RuntimeError end # How to use AntlrVelvet: # # require 'antlr_wrapper' # # class Example # include AntlrVelvet # end # # By default AntlrVelvet assumes your grammar has the same name as the # ruby class you are including it in, and therefore it assumes (e.g.) # ExampleLexer and ExampleParser to be the Java classes created in your grammar. # You can override these default class names by defining LEXER_CLASS_NAME # and/or PARSER_CLASS_NAME in your class. module AntlrVelvet attr_reader :output attr_reader :lexer attr_reader :token_stream attr_reader :parser def initialize_with_antlr initialize_without_antlr @lexer_class_name = defined?(LEXER_CLASS_NAME) ? LEXER_CLASS_NAME : "#{self.class.name}Lexer" @parser_class_name = defined?(PARSER_CLASS_NAME) ? PARSER_CLASS_NAME : "#{self.class.name}Parser" include_class @lexer_class_name unless Module.constants.include?(@lexer_class_name) include_class @parser_class_name unless Module.constants.include?(@parser_class_name) end def self.included(base_class) # Add these class methods to the class which includes AntlrVelvet. base_class.class_eval do alias_method :initialize_without_antlr, :initialize alias_method :initialize, :initialize_with_antlr # Return an ANTLRStringStream to input into a lexer. def self.string_stream(str) ANTLRStringStream.new(str) end # Return an ANTLRFileStream to input into a lexer. def self.file_stream(filename) ANTLRFileStream.new(filename) end # Return a new instance of the calling class with parser initialized # to the passed string. def self.string_parser(str) raise "You must pass a string to the string_parser!" if str.nil? p = new p.initialize_parser(string_stream(str)) p end # Return a new instance of the calling class with parser initialized # to the passed filename. def self.file_parser(filename) raise "You must pass a filename to the file_parser!" if filename.nil? p = new p.initialize_parser(file_stream(filename)) p end end end def method_missing(meth, *args) if args.empty? result, @output = capture_java_streams { begin @parser.send(meth) rescue NoMethodError if @parser.nil? raise "You may have tried to parse without intializing the parser. Create parsers using file_parser or string_parser." else # If we are here, this isn't a valid parser method. super meth end end } result else super(meth, *args) end end def initialize_parser(stream) @lexer = Kernel.const_get(@lexer_class_name).new(stream) @token_stream = CommonTokenStream.new(@lexer) @parser = Kernel.const_get(@parser_class_name).new(@token_stream) end def capture_java_streams # Save the default streams so we can restore them later. sys_err_stream = System.err sys_out_stream = System.out # Create new error and output stream. my_error_stream = ByteArrayOutputStream.new System.setErr(PrintStream.new(my_error_stream)) my_output_stream = ByteArrayOutputStream.new System.setOut(PrintStream.new(my_output_stream)) begin # Errors and messages occurring here will # be stored in our custom error/output ByteArrayOutputStreams so we can # access them later and so they don't clutter up the console. result = yield ensure # Restore the default streams. System.setErr(sys_err_stream) System.setOut(sys_out_stream) end errors = my_error_stream.to_s.chomp!.split("\n") rescue nil output = my_output_stream.to_s.chomp!.split("\n") rescue nil raise(AntlrError, errors.join(", ")) if errors [result, output] end # Just return the result, ignore the output. def suppress_java_streams capture_java_streams { yield }[0] end end class ByteArrayOutputStream def to_s String.from_java_bytes(self.to_byte_array) end end