diff --git a/ext/ripper/tools/generate.rb b/ext/ripper/tools/generate.rb new file mode 100755 index 0000000000..a93d6cd839 --- /dev/null +++ b/ext/ripper/tools/generate.rb @@ -0,0 +1,180 @@ +# $Id$ + +require 'stringio' +require 'optparse' + +def main + mode = nil + ids1src = nil + ids2src = nil + template = nil + output = nil + + parser = @parser = OptionParser.new + parser.banner = "Usage: #{File.basename($0)} --mode=MODE [--ids1src=PATH] [--ids2src=PATH] [--template=PATH] [--output=PATH]" + parser.on('--mode=MODE', '"ripper/core" or "eventids1".') {|m| + mode = m + } + parser.on('--ids1src=PATH', 'A source file of event-IDs 1 (parse.y).') {|path| + ids1src = path + } + parser.on('--ids2src=PATH', 'A source file of event-IDs 2 (eventids2.c).') {|path| + ids2src = path + } + parser.on('--template=PATH', 'A template file of ripper/core.rb.') {|path| + template = path + } + parser.on('--output=PATH', 'An output file.') {|path| + output = path + } + parser.on('--help', 'Prints this message and quit.') { + puts parser.help + exit 0 + } + begin + parser.parse! + rescue OptionParser::ParseError => err + usage err.message + end + usage 'no mode given' unless mode + case mode + when 'ripper/core', 'ripper/core.rb' + usage 'no --ids1src' unless ids1src + usage 'no --ids2src' unless ids2src + usage 'no --template' unless template + result = generate_ripper_core(template, read_ids1(ids1src), read_ids2(ids2src)) + when 'eventids1', 'eventids1.c' + usage 'no --ids1src' unless ids1src + result = generate_eventids1(read_ids1(ids1src)) + end + if output + File.open(output, 'w') {|f| + f.write result + } + else + puts result + end +end + +def usage(msg) + $stderr.puts msg + $stderr.puts @parser.help + exit 1 +end + +def generate_ripper_core(template, ids1, ids2) + f = StringIO.new + f.print <
#{arity}" + end + f.puts + when /\A\#include ids2/ + comma = '' + ids2.each do |id| + f.print comma; comma = ",\n" + f.print " #{id.intern.inspect} => 1" + end + f.puts + when /\A\#include handlers1/ + ids1.each do |id, arity| + f.puts + f.puts " def on_#{id}#{paramdecl(arity)}" + f.puts " #{arity == 0 ? 'nil' : 'a'}" + f.puts " end" + end + when /\A\#include handlers2/ + ids2.each do |id| + f.puts + f.puts " def on_#{id}(token)" + f.puts " token" + f.puts " end" + end + when /\A\#include (.*)/ + raise "unknown operation: #include #{$1}" + else + f.print line + end + end + f.string +end + +def paramdecl(n) + return '' if n == 0 + '(' + ('a'..'z').to_a[0, n].join(', ') + ')' +end + +def generate_eventids1(ids) + f = StringIO.new + ids.each do |id, arity| + f.puts "static ID ripper_id_#{id};" + end + f.puts + f.puts 'static void' + f.puts 'ripper_init_eventids1()' + f.puts '{' + ids.each do |id, arity| + f.puts %Q[ ripper_id_#{id} = rb_intern("on_#{id}");] + end + f.puts '}' + f.string +end + +def read_ids1(path) + check_arity path + ids = {} + File.open(path) {|f| + f.each do |line| + next if /\A\#\s*define\s+s?dispatch/ =~ line + next if /ripper_dispatch/ =~ line + if a = line.scan(/dispatch(\d)\((\w+)/) + a.each do |arity, event| + ids[event] = arity.to_i + end + end + end + } + ids.to_a.sort_by {|event, arity| event.to_s } +end + +def check_arity(path) + invalid = false + table = {} + File.open(path) {|f| + File.foreach(path) do |line| + next if /\A\#\s*define\s+s?dispatch\d/ =~ line + next if /ripper_dispatch\d/ =~ line + line.scan(/dispatch(\d)\((\w+)/) do |num, ev| + num = num.to_i + if table.key?(ev) + locations, arity = *table[ev] + unless num == arity + invalid = true + $stderr.puts "arity differ [event=#{ev}]: #{f.lineno}=#{num}; #{locations.join(',')}=#{arity}" + end + locations.push f.lineno + else + table[ev] = [[f.lineno], num.to_i] + end + end + end + } + exit 1 if invalid +end + +def read_ids2(path) + File.open(path) {|f| + return f.read.scan(/ripper_id_(\w+)/).flatten.uniq.sort + } +end + +main