1324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver#!/usr/bin/ruby
2324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver# encoding: utf-8
3324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
4324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverrequire 'antlr3'
5324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverrequire 'antlr3/test/core-extensions'
6324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverrequire 'antlr3/test/call-stack'
7324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
8324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverif RUBY_VERSION =~ /^1\.9/
9324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  require 'digest/md5'
10324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  MD5 = Digest::MD5
11324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverelse
12324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  require 'md5'
13324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend
14324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
15324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruvermodule ANTLR3
16324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruvermodule Test
17324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruvermodule DependantFile
18324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_accessor :path, :force
19324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  alias force? force
20324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
21324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  GLOBAL_DEPENDENCIES = []
22324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
23324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def dependencies
24324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @dependencies ||= GLOBAL_DEPENDENCIES.clone
25324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
26324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
27324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def depends_on( path )
28324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    path = File.expand_path path.to_s
29324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    dependencies << path if test( ?f, path )
30324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return path
31324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
32324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
33324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def stale?
34324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    force and return( true )
35324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    target_files.any? do |target|
36324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      not test( ?f, target ) or
37324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver        dependencies.any? { |dep| test( ?>, dep, target ) }
38324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
39324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
40324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend # module DependantFile
41324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
42324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverclass Grammar
43324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  include DependantFile
44324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
45324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  GRAMMAR_TYPES = %w(lexer parser tree combined)
46324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  TYPE_TO_CLASS = { 
47324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    'lexer'  => 'Lexer',
48324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    'parser' => 'Parser',
49324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    'tree'   => 'TreeParser'
50324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  }
51324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  CLASS_TO_TYPE = TYPE_TO_CLASS.invert
52324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
53324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def self.global_dependency( path )
54324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    path = File.expand_path path.to_s
55324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    GLOBAL_DEPENDENCIES << path if test( ?f, path )
56324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return path
57324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
58324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
59324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def self.inline( source, *args )
60324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    InlineGrammar.new( source, *args )
61324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
62324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
63324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
64324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ######## CONSTRUCTOR #############################################
65324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
66324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def initialize( path, options = {} )
67324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @path = path.to_s
68324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source = File.read( @path )
69324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @output_directory = options.fetch( :output_directory, '.' )
70324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @verbose = options.fetch( :verbose, $VERBOSE )
71324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    study
72324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    build_dependencies
73324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
74324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    yield( self ) if block_given?
75324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
76324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
77324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
78324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ######## ATTRIBUTES AND ATTRIBUTE-ISH METHODS ####################
79324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
80324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_reader :type, :name, :source
81324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_accessor :output_directory, :verbose
82324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
83324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def lexer_class_name
84324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    self.name + "::Lexer"
85324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
86324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
87324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def lexer_file_name
88324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    if lexer? then base = name
89324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    elsif combined? then base = name + 'Lexer'
90324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    else return( nil )
91324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
92324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return( base + '.rb' )
93324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
94324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
95324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def parser_class_name
96324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    name + "::Parser"
97324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
98324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
99324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def parser_file_name
100324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    if parser? then base = name
101324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    elsif combined? then base = name + 'Parser'
102324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    else return( nil )
103324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
104324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return( base + '.rb' )
105324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
106324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
107324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def tree_parser_class_name
108324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    name + "::TreeParser"
109324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
110324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
111324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def tree_parser_file_name
112324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    tree? and name + '.rb'
113324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
114324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
115324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def has_lexer?
116324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == 'combined' || @type == 'lexer'
117324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
118324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
119324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def has_parser?
120324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == 'combined' || @type == 'parser'
121324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
122324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
123324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def lexer?
124324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == "lexer"
125324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
126324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
127324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def parser?
128324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == "parser"
129324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
130324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
131324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def tree?
132324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == "tree"
133324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
134324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
135324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  alias has_tree? tree?
136324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
137324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def combined?
138324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type == "combined"
139324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
140324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
141324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def target_files( include_imports = true )
142324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    targets = []
143324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
144324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    for target_type in %w(lexer parser tree_parser)
145324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      target_name = self.send( :"#{ target_type }_file_name" ) and
146324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver        targets.push( output_directory / target_name )
147324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
148324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
149324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    targets.concat( imported_target_files ) if include_imports
150324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return targets
151324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
152324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
153324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def imports
154324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source.scan( /^\s*import\s+(\w+)\s*;/ ).
155324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      tap { |list| list.flatten! }
156324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
157324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
158324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def imported_target_files
159324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    imports.map! do |delegate|
160324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      output_directory / "#{ @name }_#{ delegate }.rb"
161324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
162324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
163324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
164324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
165324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##### COMMAND METHODS ############################################
166324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  ##################################################################
167324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def compile( options = {} )
168324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    if options[ :force ] or stale?
169324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      compile!( options )
170324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
171324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
172324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
173324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def compile!( options = {} )
174324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    command = build_command( options )
175324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
176324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    blab( command )
177324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    output = IO.popen( command ) do |pipe|
178324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      pipe.read
179324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
180324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
181324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    case status = $?.exitstatus
182324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    when 0, 130
183324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      post_compile( options )
184324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    else compilation_failure!( command, status, output )
185324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
186324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
187324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return target_files
188324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
189324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
190324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def clean!
191324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    deleted = []
192324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    for target in target_files
193324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      if test( ?f, target )
194324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver        File.delete( target )
195324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver        deleted << target
196324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      end
197324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
198324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return deleted
199324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
200324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
201324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def inspect
202324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    sprintf( "grammar %s (%s)", @name, @path )
203324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
204324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
205324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverprivate
206324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
207324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def post_compile( options )
208324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    # do nothing for now
209324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
210324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
211324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def blab( string, *args )
212324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    $stderr.printf( string + "\n", *args ) if @verbose
213324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
214324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
215324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def default_antlr_jar
216324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    ENV[ 'ANTLR_JAR' ] || ANTLR3.antlr_jar
217324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
218324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
219324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def compilation_failure!( command, status, output )
220324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    for f in target_files
221324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      test( ?f, f ) and File.delete( f )
222324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
223324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    raise CompilationFailure.new( self, command, status, output )
224324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
225324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
226324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def build_dependencies
227324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    depends_on( @path )
228324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
229324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    if @source =~ /tokenVocab\s*=\s*(\S+)\s*;/
230324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      foreign_grammar_name = $1
231324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      token_file = output_directory / foreign_grammar_name + '.tokens'
232324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      grammar_file = File.dirname( path ) / foreign_grammar_name << '.g'
233324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      depends_on( token_file )
234324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      depends_on( grammar_file )
235324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end    
236324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
237324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
238324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def shell_escape( token )
239324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    token = token.to_s.dup
240324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    token.empty? and return "''"
241324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    token.gsub!( /([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\1' )
242324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    token.gsub!( /\n/, "'\n'" )
243324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    return token
244324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
245324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
246324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def build_command( options )
247324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts = %w(java)
248324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    jar_path = options.fetch( :antlr_jar, default_antlr_jar )
249324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts.push( '-cp', jar_path )
250324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts << 'org.antlr.Tool'
251324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts.push( '-fo', output_directory )
252324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    options[ :profile ] and parts << '-profile'
253324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    options[ :debug ]   and parts << '-debug'
254324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    options[ :trace ]   and parts << '-trace'
255324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    options[ :debug_st ] and parts << '-XdbgST'
256324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts << File.expand_path( @path )
257324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    parts.map! { |part| shell_escape( part ) }.join( ' ' ) << ' 2>&1'
258324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
259324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
260324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def study
261324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source =~ /^\s*(lexer|parser|tree)?\s*grammar\s*(\S+)\s*;/ or
262324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      raise Grammar::FormatError[ source, path ]
263324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @name = $2
264324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @type = $1 || 'combined'
265324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
266324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend # class Grammar
267324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
268324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverclass Grammar::InlineGrammar < Grammar
269324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_accessor :host_file, :host_line
270324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
271324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def initialize( source, options = {} )
272324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    host = call_stack.find { |call| call.file != __FILE__ }
273324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
274324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @host_file = File.expand_path( options[ :file ] || host.file )
275324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @host_line = ( options[ :line ] || host.line )
276324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @output_directory = options.fetch( :output_directory, File.dirname( @host_file ) )
277324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @verbose = options.fetch( :verbose, $VERBOSE )
278324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
279324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source = source.to_s.fixed_indent( 0 )
280324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source.strip!
281324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
282324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    study
283324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    write_to_disk
284324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    build_dependencies
285324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
286324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    yield( self ) if block_given?
287324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
288324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
289324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def output_directory
290324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @output_directory and return @output_directory
291324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    File.basename( @host_file )
292324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
293324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
294324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def path=( v )
295324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    previous, @path = @path, v.to_s
296324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    previous == @path or write_to_disk
297324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
298324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
299324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def inspect
300324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    sprintf( 'inline grammar %s (%s:%s)', name, @host_file, @host_line )
301324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
302324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
303324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverprivate
304324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
305324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def write_to_disk
306324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @path ||= output_directory / @name + '.g'
307324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    test( ?d, output_directory ) or Dir.mkdir( output_directory )
308324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    unless test( ?f, @path ) and MD5.digest( @source ) == MD5.digest( File.read( @path ) )
309324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      open( @path, 'w' ) { |f| f.write( @source ) }
310324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
311324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
312324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend # class Grammar::InlineGrammar
313324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
314324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverclass Grammar::CompilationFailure < StandardError
315324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  JAVA_TRACE = /^(org\.)?antlr\.\S+\(\S+\.java:\d+\)\s*/
316324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_reader :grammar, :command, :status, :output
317324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
318324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def initialize( grammar, command, status, output )
319324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @command = command
320324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @status = status
321324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @output = output.gsub( JAVA_TRACE, '' )
322324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
323324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    message = <<-END.here_indent! % [ command, status, grammar, @output ]
324324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    | command ``%s'' failed with status %s
325324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    | %p
326324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    | ~ ~ ~ command output ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
327324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    | %s
328324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    END
329324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    
330324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    super( message.chomp! || message )
331324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
332324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend # error Grammar::CompilationFailure
333324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
334324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverclass Grammar::FormatError < StandardError
335324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  attr_reader :file, :source
336324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
337324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def self.[]( *args )
338324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    new( *args )
339324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
340324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  
341324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  def initialize( source, file = nil )
342324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @file = file
343324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    @source = source
344324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    message = ''
345324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    if file.nil? # inline
346324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << "bad inline grammar source:\n"
347324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << ( "-" * 80 ) << "\n"
348324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << @source
349324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message[ -1 ] == ?\n or message << "\n"
350324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << ( "-" * 80 ) << "\n"
351324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << "could not locate a grammar name and type declaration matching\n"
352324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << "/^\s*(lexer|parser|tree)?\s*grammar\s*(\S+)\s*;/"
353324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    else
354324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << 'bad grammar source in file %p' % @file
355324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << ( "-" * 80 ) << "\n"
356324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << @source
357324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message[ -1 ] == ?\n or message << "\n"
358324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << ( "-" * 80 ) << "\n"
359324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << "could not locate a grammar name and type declaration matching\n"
360324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver      message << "/^\s*(lexer|parser|tree)?\s*grammar\s*(\S+)\s*;/"
361324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    end
362324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver    super( message )
363324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver  end
364324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend # error Grammar::FormatError
365324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruver
366324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend
367324c4644fee44b9898524c09511bd33c3f12e2dfBen Gruverend
368