1#!/usr/bin/ruby
2# encoding: utf-8
3
4require 'antlr3'
5require 'antlr3/test/core-extensions'
6require 'antlr3/test/grammar'
7require 'antlr3/test/call-stack'
8
9require 'test/unit'
10require 'spec'
11
12module ANTLR3
13module Test
14module Location
15  attr_accessor :test_path
16  
17  def test_group
18    File.basename( test_path, '.rb' )
19  end
20  
21  def test_directory
22    File.dirname( test_path )
23  end
24  
25  def local_path( *parts )
26    File.join( test_directory, *parts )
27  end
28  
29  def output_directory( name = test_group )
30    local_path( name )
31  end
32  
33end # module Location
34
35module NameSpace
36  
37  #
38  # import( ruby_file )   => [ new constants, ... ]
39  # Read the source code from the path given by +ruby_file+ and
40  # evaluate it within the class body. Return new constants
41  # created in the class after the evaluation.
42  # 
43  def import( ruby_file )
44    constants_before = constants
45    class_eval( File.read( ruby_file ), ruby_file, 1 )
46    constants - constants_before
47  end
48  
49  def import_grammar_targets( grammar )
50    for file in grammar.target_files
51      import( file )
52    end
53  end
54end
55
56module GrammarManager
57  include Location
58  include NameSpace
59  
60  DEFAULT_COMPILE_OPTIONS = {}
61  
62  def add_default_compile_option( name, value )
63    DEFAULT_COMPILE_OPTIONS[ name ] = value
64  end
65  module_function :add_default_compile_option
66  
67  if ANTLR_JAR = ENV[ 'ANTLR_JAR' ] || ANTLR3.antlr_jar
68    add_default_compile_option( :antlr_jar, ANTLR_JAR )
69    
70    Grammar.global_dependency( ANTLR_JAR )
71  end
72  
73  #
74  # Compile and load inline grammars on demand when their constant name
75  # is referenced in the code. This makes it easier to catch big errors
76  # quickly as test cases are run, instead of waiting a few minutes
77  # for all grammars to compile, and then discovering there's a big dumb
78  # error ruining most of the grammars.
79  # 
80  def const_missing( name )
81    if g = grammars[ name.to_s ]
82      compile( g )
83      grammars.delete( name.to_s )
84      const_get( name )
85    elsif superclass.respond_to?( :grammars )
86      superclass.const_missing( name )
87      # ^-- for some reason, in ruby 1.9, rspec runs examples as instances of
88      # anonymous subclasses, of the actual test class, which messes up the
89      # assumptions made in the test code. Grammars are stored in @grammars belonging
90      # to the test class, so in 1.9, this method is called with @grammars = {}
91      # since it's a subclass
92    else
93      super
94    end
95  end
96  
97  # 
98  # An index of grammar file objects created in the test class
99  # (defined inline or loaded from a file)
100  # 
101  def grammars
102    @grammars ||= {}
103  end
104  
105  def grammar_count
106    grammars.length
107  end
108  
109  def load_grammar( name )
110    path = local_path( name.to_s )
111    path =~ /\.g$/ or path << '.g'
112    grammar = Grammar.new( path, :output_directory => output_directory )
113    register_grammar( grammar )
114    return grammar
115  end
116  
117  def inline_grammar( source, options = {} )
118    call = call_stack.find { |call| call.file != __FILE__ }
119    grammar = Grammar.inline source,
120                :output_directory => output_directory,
121                :file => ( call.file rescue nil ),
122                :line => ( call.line rescue nil )
123    register_grammar( grammar )
124    return grammar
125  end
126  
127  def compile_options( defaults = nil )
128    @compile_options ||= DEFAULT_COMPILE_OPTIONS.clone
129    @compile_options.update( defaults ) if defaults
130    return @compile_options
131  end
132  
133  def compile( grammar, options = {} )
134    grammar.compile( compile_options.merge( options ) )
135    import_grammar_targets( grammar )
136    return grammar
137  end
138  
139private
140  
141  def register_grammar( grammar )
142    name = grammar.name
143    @grammars ||= {}
144    
145    if conflict = @grammars[ name ] and conflict.source != grammar.source
146      message = "Multiple grammars exist with the name ``#{ name }''"
147      raise NameError, message
148    else
149      @grammars[ name ] = grammar
150    end
151  end
152end # module GrammarManager
153
154class Functional < ::Test::Unit::TestCase
155  extend GrammarManager
156  
157  def self.inherited( klass )
158    super
159    klass.test_path = call_stack[ 0 ].file
160  end
161  
162  def local_path( *args )
163    self.class.local_path( *args )
164  end
165  
166  def test_path
167    self.class.test_path
168  end
169  
170  def output_directory
171    self.class.output_directory
172  end
173  
174  def inline_grammar( source )
175    call = call_stack.find { |call| call.file != __FILE__ }
176    grammar = Grammar.inline source,
177                :output_directory => output_directory,
178                :file => call.file,
179                :line => call.line
180  end
181  
182  def compile_and_load( grammar, options = {} )
183    self.class.compile( grammar, options )
184  end
185end # class Functional
186
187
188
189module CaptureOutput
190  require 'stringio'
191  def output_buffer
192    defined?( @output_buffer ) or @output_buffer = StringIO.new( '' )
193    @output_buffer
194  end
195  
196  def output
197    output_buffer.string
198  end
199  
200  def say( *args )
201    output_buffer.puts( *args )
202  end
203  
204  def capture( *args )
205    output_buffer.write( *args )
206  end
207end
208
209module RaiseErrors
210  def emit_error_message( msg )
211    # do nothing
212  end
213  
214  def report_error( error )
215    raise error
216  end
217end
218
219module CollectErrors
220  def reported_errors
221    defined?( @reported_errors ) or @reported_errors = []
222    return @reported_errors
223  end
224  
225  def emit_error_message( msg )
226    reported_errors << msg
227  end
228end
229
230end # module Test
231end # module ANTLR3
232