summaryrefslogtreecommitdiff
path: root/tool
diff options
context:
space:
mode:
Diffstat (limited to 'tool')
-rw-r--r--tool/asm_parse.rb53
-rwxr-xr-xtool/bisect.sh45
-rwxr-xr-xtool/build-transcode16
-rwxr-xr-xtool/change_maker.rb47
-rwxr-xr-xtool/checksum.rb72
-rw-r--r--tool/colorize.rb41
-rw-r--r--tool/downloader.rb331
-rw-r--r--tool/enc-emoji-citrus-gen.rb131
-rw-r--r--tool/enc-emoji4unicode.rb133
-rwxr-xr-xtool/enc-unicode.rb556
-rw-r--r--tool/eval.rb160
-rwxr-xr-xtool/expand-config.rb35
-rwxr-xr-xtool/extlibs.rb186
-rw-r--r--tool/fake.rb70
-rwxr-xr-xtool/fetch-bundled_gems.rb27
-rwxr-xr-xtool/file2lastrev.rb98
-rwxr-xr-xtool/gem-unpack.rb18
-rwxr-xr-xtool/gen_dummy_probes.rb32
-rwxr-xr-xtool/gen_ruby_tapset.rb106
-rw-r--r--tool/generate-backport-changelog.rb99
-rw-r--r--tool/generic_erb.rb58
-rwxr-xr-xtool/git-refresh43
-rw-r--r--tool/gperf.sed22
-rwxr-xr-xtool/id2token.rb27
-rwxr-xr-xtool/ifchange87
-rwxr-xr-xtool/insns2vm.rb18
-rw-r--r--tool/install-sh17
-rwxr-xr-xtool/instruction.rb1249
-rw-r--r--tool/jisx0208.rb86
-rwxr-xr-xtool/make-snapshot467
-rw-r--r--tool/make_hgraph.rb95
-rwxr-xr-xtool/mdoc2man.rb505
-rwxr-xr-xtool/merger.rb312
-rw-r--r--tool/mk_call_iseq_optimized.rb73
-rwxr-xr-xtool/mkconfig.rb325
-rwxr-xr-xtool/mkrunnable.rb137
-rwxr-xr-xtool/node_name.rb10
-rw-r--r--tool/parse.rb16
-rw-r--r--tool/prereq.status43
-rw-r--r--tool/probes_to_wiki.rb16
-rwxr-xr-xtool/pull-latest-mspec-spec18
-rwxr-xr-xtool/rbinstall.rb920
-rwxr-xr-xtool/rbuninstall.rb71
-rwxr-xr-xtool/redmine-backporter.rb608
-rwxr-xr-xtool/release.sh38
-rwxr-xr-xtool/rmdirs14
-rw-r--r--tool/run-gcov.rb54
-rw-r--r--tool/run-lcov.rb164
-rwxr-xr-xtool/runruby.rb159
-rwxr-xr-xtool/strip-rdoc.rb26
-rw-r--r--tool/sync_default_gems.rb198
-rw-r--r--tool/test-coverage.rb103
-rw-r--r--tool/test/test_jisx0208.rb40
-rw-r--r--tool/transcode-tblgen.rb1114
-rwxr-xr-xtool/update-deps625
-rw-r--r--tool/vcs.rb495
-rw-r--r--tool/vpath.rb87
-rw-r--r--tool/vtlh.rb17
-rwxr-xr-xtool/ytab.sed61
59 files changed, 10674 insertions, 0 deletions
diff --git a/tool/asm_parse.rb b/tool/asm_parse.rb
new file mode 100644
index 0000000000..32882be3ad
--- /dev/null
+++ b/tool/asm_parse.rb
@@ -0,0 +1,53 @@
+# YARV tool to parse assembly output.
+
+stat = {}
+
+while line = ARGF.gets
+ if /\[start\] (\w+)/ =~ line
+ name = $1
+ puts '--------------------------------------------------------------'
+ puts line
+ size = 0
+ len = 0
+
+ while line = ARGF.gets
+ if /\[start\] (\w+)/ =~ line
+ puts "\t; # length: #{len}, size: #{size}"
+ puts "\t; # !!"
+ stat[name] = [len, size]
+ #
+ name = $1
+ puts '--------------------------------------------------------------'
+ puts line
+ size = 0
+ len = 0
+ next
+ end
+
+ unless /(\ALM)|(\ALB)|(\A\.)|(\A\/)/ =~ line
+ puts line
+ if /\[length = (\d+)\]/ =~ line
+ len += $1.to_i
+ size += 1
+ end
+ end
+
+
+ if /__NEXT_INSN__/ !~ line && /\[end \] (\w+)/ =~ line
+ ename = $1
+ if name != ename
+ puts "!! start with #{name}, but end with #{ename}"
+ end
+ stat[ename] = [len, size]
+ puts "\t; # length: #{len}, size: #{size}"
+ break
+ end
+ end
+ end
+end
+
+stat.sort_by{|a, b| -b[0] * 1000 - a[0]}.each{|a, b|
+ puts "#{a}\t#{b.join("\t")}"
+}
+puts "total length :\t#{stat.inject(0){|r, e| r+e[1][0]}}"
+puts "total size :\t#{stat.inject(0){|r, e| r+e[1][1]}}"
diff --git a/tool/bisect.sh b/tool/bisect.sh
new file mode 100755
index 0000000000..d486c9cfc6
--- /dev/null
+++ b/tool/bisect.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# usage:
+# edit $(srcdir)/test.rb
+# git bisect start `git svn find-rev <rBADREV>` `git svn find-rev <rGOODREV>`
+# cd <builddir>
+# make bisect (or bisect-ruby for full ruby)
+
+if [ "x" = "x$MAKE" ]; then
+ MAKE=make
+fi
+
+case $1 in
+ miniruby | ruby ) # (miniruby|ruby) <srcdir>
+ srcdir="$2"
+ builddir=`pwd` # assume pwd is builddir
+ path="$builddir/_bisect.sh"
+ echo "path: $path"
+ cp "$0" "$path"
+ cd "$srcdir"
+ set -x
+ exec git bisect run "$path" "run-$1"
+ ;;
+ run-miniruby )
+ $MAKE srcs || exit 125
+ cd "${0%/*}" || exit 125 # assume a copy of this script is in builddir
+ $MAKE Makefile || exit 125
+ $MAKE mini || exit 125
+ $MAKE run || exit 1
+ ;;
+ run-ruby )
+ $MAKE srcs || exit 125
+ cd "${0%/*}" || exit 125 # assume a copy of this script is in builddir
+ $MAKE Makefile || exit 125
+ $MAKE program || exit 125
+ $MAKE runruby || exit 1
+ ;;
+ "" )
+ echo foo bar
+ ;;
+ * )
+ echo unknown command "'$1'" 1>&2
+ exit 1
+ ;;
+esac
+exit 0
diff --git a/tool/build-transcode b/tool/build-transcode
new file mode 100755
index 0000000000..fa71155530
--- /dev/null
+++ b/tool/build-transcode
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+[ "$1" -a -d "$1" ] && { cd "$1" || exit $?; } && shift
+[ "$#" = 0 ] && set enc/trans/*.trans
+for src; do
+ case "$src" in
+ *.trans)
+ c="`dirname $src`/`basename $src .trans`.c"
+ ${BASERUBY-ruby} tool/transcode-tblgen.rb -vo "$c" "$src"
+ ;;
+ *)
+ echo "$0: don't know how to deal with $src"
+ continue
+ ;;
+ esac
+done
diff --git a/tool/change_maker.rb b/tool/change_maker.rb
new file mode 100755
index 0000000000..395bd34990
--- /dev/null
+++ b/tool/change_maker.rb
@@ -0,0 +1,47 @@
+#! ./miniruby
+
+# Used by "make change" to generate a list of files for a Changelog entry.
+# Run it via "make change" in the Ruby root directory.
+
+$:.unshift(File.expand_path("../../lib", __FILE__))
+require File.expand_path("../vcs", __FILE__)
+
+def diff2index(cmd, *argv)
+ lines = []
+ path = nil
+ output = `#{cmd} #{argv.join(" ")}`
+ if defined? Encoding::BINARY
+ output.force_encoding Encoding::BINARY
+ end
+ output.each_line do |line|
+ case line
+ when /^Index: (\S*)/, /^diff --git [a-z]\/(\S*) [a-z]\/\1/
+ path = $1
+ when /^@@\s*-[,\d]+ +\+(\d+)[,\d]*\s*@@(?: +([A-Za-z_][A-Za-z_0-9 ]*[A-Za-z_0-9]))?/
+ line = $1.to_i
+ ent = "\t* #{path}"
+ ent << " (#{$2})" if $2
+ lines << "#{ent}:"
+ end
+ end
+ lines.uniq!
+ lines.empty? ? nil : lines
+end
+
+vcs = begin
+ VCS.detect(".")
+rescue VCS::NotFoundError
+ nil
+end
+
+case vcs
+when VCS::SVN
+ cmd = "svn diff --diff-cmd=diff -x-pU0"
+ change = diff2index(cmd, ARGV)
+when VCS::GIT
+ cmd = "git diff -U0"
+ change = diff2index(cmd, ARGV) || diff2index(cmd, "--cached", ARGV)
+else
+ abort "does not seem to be under a vcs"
+end
+puts change if change
diff --git a/tool/checksum.rb b/tool/checksum.rb
new file mode 100755
index 0000000000..0de54a314d
--- /dev/null
+++ b/tool/checksum.rb
@@ -0,0 +1,72 @@
+#!ruby
+
+require_relative 'vpath'
+
+class Checksum
+ def initialize(vpath)
+ @vpath = vpath
+ end
+
+ attr_reader :source, :target
+
+ def source=(source)
+ @source = source
+ @checksum = File.basename(source, ".*") + ".chksum"
+ end
+
+ def target=(target)
+ @target = target
+ end
+
+ def update?
+ src = @vpath.read(@source)
+ @len = src.length
+ @sum = src.sum
+ return false unless @vpath.search(File.method(:exist?), @target)
+ begin
+ data = @vpath.read(@checksum)
+ rescue
+ return false
+ else
+ return false unless data[/src="([0-9a-z_.-]+)",/, 1] == @source
+ return false unless @len == data[/\blen=(\d+)/, 1].to_i
+ return false unless @sum == data[/\bchecksum=(\d+)/, 1].to_i
+ return true
+ end
+ end
+
+ def update!
+ open(@checksum, "wb") {|f|
+ f.puts("src=\"#{@source}\", len=#{@len}, checksum=#{@sum}")
+ }
+ end
+
+ def update
+ return true if update?
+ update! if ret = yield(self)
+ ret
+ end
+
+ def copy(name)
+ @vpath.open(name, "rb") {|f|
+ IO.copy_stream(f, name)
+ }
+ true
+ end
+
+ def make(*args)
+ system(@make, *args)
+ end
+
+ def def_options(opt = (require 'optparse'; OptionParser.new))
+ @vpath.def_options(opt)
+ opt.on("--make=PATH") {|v| @make = v}
+ opt
+ end
+
+ def self.update(argv)
+ k = new(VPath.new)
+ k.source, k.target, *argv = k.def_options.parse(*argv)
+ k.update {|k| yield(k, *argv)}
+ end
+end
diff --git a/tool/colorize.rb b/tool/colorize.rb
new file mode 100644
index 0000000000..1cbbeef543
--- /dev/null
+++ b/tool/colorize.rb
@@ -0,0 +1,41 @@
+# frozen-string-literal: true
+
+class Colorize
+ def initialize(color = nil)
+ @colors = @reset = nil
+ if color or (color == nil && STDOUT.tty?)
+ if (/\A\e\[.*m\z/ =~ IO.popen("tput smso", "r", err: IO::NULL, &:read) rescue nil)
+ @beg = "\e["
+ @colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {}
+ @reset = "#{@beg}m"
+ end
+ end
+ self
+ end
+
+ DEFAULTS = {
+ "pass"=>"32", "fail"=>"31;1", "skip"=>"33;1",
+ "black"=>"30", "red"=>"31", "green"=>"32", "yellow"=>"33",
+ "blue"=>"34", "magenta"=>"35", "cyan"=>"36", "white"=>"37",
+ }
+
+ def decorate(str, name)
+ if @colors and color = (@colors[name] || DEFAULTS[name])
+ "#{@beg}#{color}m#{str}#{@reset}"
+ else
+ str
+ end
+ end
+
+ DEFAULTS.each_key do |name|
+ define_method(name) {|str|
+ decorate(str, name)
+ }
+ end
+end
+
+if $0 == __FILE__
+ colorize = Colorize.new
+ col = ARGV.shift
+ ARGV.each {|str| puts colorize.decorate(str, col)}
+end
diff --git a/tool/downloader.rb b/tool/downloader.rb
new file mode 100644
index 0000000000..335fe97762
--- /dev/null
+++ b/tool/downloader.rb
@@ -0,0 +1,331 @@
+# Used by configure and make to download or update mirrored Ruby and GCC
+# files. This will use HTTPS if possible, falling back to HTTP.
+
+require 'fileutils'
+require 'open-uri'
+require 'pathname'
+begin
+ require 'net/https'
+rescue LoadError
+ https = 'http'
+else
+ https = 'https'
+
+ # open-uri of ruby 2.2.0 accept an array of PEMs as ssl_ca_cert, but old
+ # versions are not. so, patching OpenSSL::X509::Store#add_file instead.
+ class OpenSSL::X509::Store
+ alias orig_add_file add_file
+ def add_file(pems)
+ Array(pems).each do |pem|
+ if File.directory?(pem)
+ add_path pem
+ else
+ orig_add_file pem
+ end
+ end
+ end
+ end
+ # since open-uri internally checks ssl_ca_cert using File.directory?,
+ # allow to accept an array.
+ class <<File
+ alias orig_directory? directory?
+ def File.directory? files
+ files.is_a?(Array) ? false : orig_directory?(files)
+ end
+ end
+end
+
+class Downloader
+ def self.https=(https)
+ @@https = https
+ end
+
+ def self.https?
+ @@https == 'https'
+ end
+
+ def self.https
+ @@https
+ end
+
+ class GNU < self
+ def self.download(name, *rest)
+ if https?
+ super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest)
+ else
+ super("https://repo.or.cz/official-gcc.git/blob_plain/HEAD:/#{name}", name, *rest)
+ end
+ end
+ end
+
+ class RubyGems < self
+ def self.download(name, dir = nil, since = true, options = {})
+ require 'rubygems'
+ options = options.dup
+ options[:ssl_ca_cert] = Dir.glob(File.expand_path("../lib/rubygems/ssl_certs/**/*.pem", File.dirname(__FILE__)))
+ super("https://rubygems.org/downloads/#{name}", name, dir, since, options)
+ end
+ end
+
+ Gems = RubyGems
+
+ class Unicode < self
+ def self.download(name, *rest)
+ super("http://www.unicode.org/Public/#{name}", name, *rest)
+ end
+ end
+
+ def self.mode_for(data)
+ /\A#!/ =~ data ? 0755 : 0644
+ end
+
+ def self.http_options(file, since)
+ options = {}
+ if since
+ case since
+ when true
+ since = (File.mtime(file).httpdate rescue nil)
+ when Time
+ since = since.httpdate
+ end
+ if since
+ options['If-Modified-Since'] = since
+ end
+ end
+ options['Accept-Encoding'] = '*' # to disable Net::HTTP::GenericRequest#decode_content
+ options
+ end
+
+ # Downloader.download(url, name, [dir, [since]])
+ #
+ # Update a file from url if newer version is available.
+ # Creates the file if the file doesn't yet exist; however, the
+ # directory where the file is being created has to exist already.
+ # The +since+ parameter can take the following values, with associated meanings:
+ # true ::
+ # Take the last-modified time of the current file on disk, and only download
+ # if the server has a file that was modified later. Download unconditionally
+ # if we don't have the file yet. Default.
+ # +some time value+ ::
+ # Use this time value instead of the time of modification of the file on disk.
+ # nil ::
+ # Only download the file if it doesn't exist yet.
+ # false ::
+ # always download url regardless of whether we already have a file,
+ # and regardless of modification times. (This is essentially just a waste of
+ # network resources, except in the case that the file we have is somehow damaged.
+ # Please note that using this recurringly might create or be seen as a
+ # denial of service attack.)
+ #
+ # Example usage:
+ # download 'http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt',
+ # 'UnicodeData.txt', 'enc/unicode/data'
+ def self.download(url, name, dir = nil, since = true, options = {})
+ options = options.dup
+ url = URI(url)
+ dryrun = options.delete(:dryrun)
+ if name
+ file = Pathname.new(under(dir, name))
+ else
+ name = File.basename(url.path)
+ end
+ cache_save = options.delete(:cache_save) {
+ ENV["CACHE_SAVE"] != "no"
+ }
+ cache = cache_file(url, name, options.delete(:cache_dir))
+ file ||= cache
+ if since.nil? and file.exist?
+ if $VERBOSE
+ $stdout.puts "#{file} already exists"
+ $stdout.flush
+ end
+ if cache_save
+ save_cache(cache, file, name)
+ end
+ return file.to_path
+ end
+ if dryrun
+ puts "Download #{url} into #{file}"
+ return
+ end
+ if link_cache(cache, file, name, $VERBOSE)
+ return file.to_path
+ end
+ if !https? and URI::HTTPS === url
+ warn "*** using http instead of https ***"
+ url.scheme = 'http'
+ url = URI(url.to_s)
+ end
+ if $VERBOSE
+ $stdout.print "downloading #{name} ... "
+ $stdout.flush
+ end
+ begin
+ data = url.read(options.merge(http_options(file, since.nil? ? true : since)))
+ rescue OpenURI::HTTPError => http_error
+ if http_error.message =~ /^304 / # 304 Not Modified
+ if $VERBOSE
+ $stdout.puts "#{name} not modified"
+ $stdout.flush
+ end
+ return file.to_path
+ end
+ raise
+ rescue Timeout::Error
+ if since.nil? and file.exist?
+ puts "Request for #{url} timed out, using old version."
+ return file.to_path
+ end
+ raise
+ rescue SocketError
+ if since.nil? and file.exist?
+ puts "No network connection, unable to download #{url}, using old version."
+ return file.to_path
+ end
+ raise
+ end
+ mtime = nil
+ dest = (cache_save && cache && !cache.exist? ? cache : file)
+ dest.parent.mkpath
+ dest.open("wb", 0600) do |f|
+ f.write(data)
+ f.chmod(mode_for(data))
+ mtime = data.meta["last-modified"]
+ end
+ if mtime
+ mtime = Time.httpdate(mtime)
+ dest.utime(mtime, mtime)
+ end
+ if $VERBOSE
+ $stdout.puts "done"
+ $stdout.flush
+ end
+ if dest.eql?(cache)
+ link_cache(cache, file, name)
+ elsif cache_save
+ save_cache(cache, file, name)
+ end
+ return file.to_path
+ rescue => e
+ raise "failed to download #{name}\n#{e.message}: #{url}"
+ end
+
+ def self.under(dir, name)
+ dir ? File.join(dir, File.basename(name)) : name
+ end
+
+ def self.cache_file(url, name, cache_dir = nil)
+ case cache_dir
+ when false
+ return nil
+ when nil
+ cache_dir = ENV['CACHE_DIR']
+ if !cache_dir or cache_dir.empty?
+ cache_dir = ".downloaded-cache"
+ end
+ end
+ Pathname.new(cache_dir) + (name || File.basename(URI(url).path))
+ end
+
+ def self.link_cache(cache, file, name, verbose = false)
+ return false unless cache and cache.exist?
+ return true if cache.eql?(file)
+ if /cygwin/ !~ RUBY_PLATFORM or /winsymlink:nativestrict/ =~ ENV['CYGWIN']
+ begin
+ file.make_symlink(cache.relative_path_from(file.parent))
+ rescue SystemCallError
+ else
+ if verbose
+ $stdout.puts "made symlink #{name} to #{cache}"
+ $stdout.flush
+ end
+ return true
+ end
+ end
+ begin
+ file.make_link(cache)
+ rescue SystemCallError
+ else
+ if verbose
+ $stdout.puts "made link #{name} to #{cache}"
+ $stdout.flush
+ end
+ return true
+ end
+ end
+
+ def self.save_cache(cache, file, name)
+ if cache and !cache.eql?(file) and !cache.exist?
+ begin
+ file.rename(cache)
+ rescue
+ else
+ link_cache(cache, file, name)
+ end
+ end
+ end
+end
+
+Downloader.https = https.freeze
+
+if $0 == __FILE__
+ since = true
+ options = {}
+ until ARGV.empty?
+ case ARGV[0]
+ when '-d'
+ destdir = ARGV[1]
+ ARGV.shift
+ when '-p'
+ # strip directory names from the name to download, and add the
+ # prefix instead.
+ prefix = ARGV[1]
+ ARGV.shift
+ when '-e'
+ since = nil
+ when '-a'
+ since = false
+ when '-n', '--dryrun'
+ options[:dryrun] = true
+ when '--cache-dir'
+ options[:cache_dir] = ARGV[1]
+ ARGV.shift
+ when /\A--cache-dir=(.*)/m
+ options[:cache_dir] = $1
+ when /\A-/
+ abort "#{$0}: unknown option #{ARGV[0]}"
+ else
+ break
+ end
+ ARGV.shift
+ end
+ dl = Downloader.constants.find do |name|
+ ARGV[0].casecmp(name.to_s) == 0
+ end unless ARGV.empty?
+ $VERBOSE = true
+ if dl
+ dl = Downloader.const_get(dl)
+ ARGV.shift
+ ARGV.each do |name|
+ dir = destdir
+ if prefix
+ name = name.sub(/\A\.\//, '')
+ if name.start_with?(destdir+"/")
+ name = name[(destdir.size+1)..-1]
+ if (dir = File.dirname(name)) == '.'
+ dir = destdir
+ else
+ dir = File.join(destdir, dir)
+ end
+ else
+ name = File.basename(name)
+ end
+ name = "#{prefix}/#{name}"
+ end
+ dl.download(name, dir, since, options)
+ end
+ else
+ abort "usage: #{$0} url name" unless ARGV.size == 2
+ Downloader.download(ARGV[0], ARGV[1], destdir, since, options)
+ end
+end
diff --git a/tool/enc-emoji-citrus-gen.rb b/tool/enc-emoji-citrus-gen.rb
new file mode 100644
index 0000000000..5037cbde1e
--- /dev/null
+++ b/tool/enc-emoji-citrus-gen.rb
@@ -0,0 +1,131 @@
+require File.expand_path('../jisx0208', __FILE__)
+
+ENCODES = [
+ {
+ :name => "SHIFT_JIS-DOCOMO",
+ :src_zone => [0xF8..0xFC, 0x40..0xFC, 8],
+ :dst_ilseq => 0xFFFE,
+ :map => [
+ [0xE63E..0xE757, JISX0208::Char.from_sjis(0xF89F)],
+ ],
+ },
+ {
+ :name => "ISO-2022-JP-KDDI",
+ :src_zone => [0x21..0x7E, 0x21..0x7E, 8],
+ :dst_ilseq => 0xFFFE,
+ :map => [
+ [0xE468..0xE5B4, JISX0208::Char.new(0x7521)],
+ [0xE5B5..0xE5CC, JISX0208::Char.new(0x7867)],
+ [0xE5CD..0xE5DF, JISX0208::Char.new(0x7921)],
+ [0xEA80..0xEAFA, JISX0208::Char.new(0x7934)],
+ [0xEAFB..0xEB0D, JISX0208::Char.new(0x7854)],
+ [0xEB0E..0xEB8E, JISX0208::Char.new(0x7A51)],
+ ],
+ },
+ {
+ :name => "SHIFT_JIS-KDDI",
+ :src_zone => [0xF3..0xFC, 0x40..0xFC, 8],
+ :dst_ilseq => 0xFFFE,
+ :map => [
+ [0xE468..0xE5B4, JISX0208::Char.from_sjis(0xF640)],
+ [0xE5B5..0xE5CC, JISX0208::Char.from_sjis(0xF7E5)],
+ [0xE5CD..0xE5DF, JISX0208::Char.from_sjis(0xF340)],
+ [0xEA80..0xEAFA, JISX0208::Char.from_sjis(0xF353)],
+ [0xEAFB..0xEB0D, JISX0208::Char.from_sjis(0xF7D2)],
+ [0xEB0E..0xEB8E, JISX0208::Char.from_sjis(0xF3CF)],
+ ],
+ },
+ {
+ :name => "SHIFT_JIS-SOFTBANK",
+ :src_zone => [0xF3..0xFC, 0x40..0xFC, 8],
+ :dst_ilseq => 0xFFFE,
+ :map => [
+ [0xE001..0xE05A, JISX0208::Char.from_sjis(0xF941)],
+ [0xE101..0xE15A, JISX0208::Char.from_sjis(0xF741)],
+ [0xE201..0xE25A, JISX0208::Char.from_sjis(0xF7A1)],
+ [0xE301..0xE34D, JISX0208::Char.from_sjis(0xF9A1)],
+ [0xE401..0xE44C, JISX0208::Char.from_sjis(0xFB41)],
+ [0xE501..0xE53E, JISX0208::Char.from_sjis(0xFBA1)],
+ ],
+ },
+]
+
+def zone(*args)
+ bits = args.pop
+ [*args.map{|range| "0x%02X-0x%02X" % [range.begin, range.end] }, bits].join(' / ')
+end
+
+def header(params)
+ (<<END_HEADER_TEMPLATE % [params[:name], zone(*params[:src_zone]), params[:dst_ilseq]])
+# DO NOT EDIT THIS FILE DIRECTLY
+
+TYPE ROWCOL
+NAME %s
+SRC_ZONE %s
+OOB_MODE ILSEQ
+DST_ILSEQ 0x%04X
+DST_UNIT_BITS 16
+END_HEADER_TEMPLATE
+end
+
+def generate_to_ucs(params, pairs)
+ pairs.sort_by! {|u, c| c }
+ name = "EMOJI_#{params[:name]}%UCS"
+ open("#{name}.src", "w") do |io|
+ io.print header(params.merge(name: name.tr('%', '/')))
+ io.puts
+ io.puts "BEGIN_MAP"
+ io.print pairs.inject("") {|acc, uc| acc += "0x%04X = 0x%04X\n" % uc.reverse }
+ io.puts "END_MAP"
+ end
+end
+
+def generate_from_ucs(params, pairs)
+ pairs.sort_by! {|u, c| u }
+ name = "UCS%EMOJI_#{params[:name]}"
+ open("#{name}.src", "w") do |io|
+ io.print header(params.merge(name: name.tr('%', '/')))
+ io.puts
+ io.puts "BEGIN_MAP"
+ io.print pairs.inject("") {|acc, uc| acc += "0x%04X = 0x%04X\n" % uc }
+ io.puts "END_MAP"
+ end
+end
+
+def make_pairs(code_map)
+ pairs = code_map.inject([]) {|acc, (range, ch)|
+ acc += range.map{|uni| pair = [uni, Integer(ch)]; ch = ch.succ; next pair }
+ }
+end
+
+ENCODES.each do |params|
+ pairs = make_pairs(params[:map], &params[:conv])
+ generate_to_ucs(params, pairs)
+ generate_from_ucs(params, pairs)
+end
+
+# generate KDDI-UNDOC for Shift_JIS-KDDI
+kddi_sjis_map = ENCODES.select{|enc| enc[:name] == "SHIFT_JIS-KDDI"}.first[:map]
+pairs = kddi_sjis_map.inject([]) {|acc, (range, ch)|
+ acc += range.map{|uni| pair = [ch.to_sjis - 0x700, Integer(ch)]; ch = ch.succ; next pair }
+}
+params = {
+ :name => "SHIFT_JIS-KDDI-UNDOC",
+ :src_zone => [0xF3..0xFC, 0x40..0xFC, 8],
+ :dst_ilseq => 0xFFFE,
+}
+generate_from_ucs(params, pairs)
+generate_to_ucs(params, pairs)
+
+# generate KDDI-UNDOC for ISO-2022-JP-KDDI
+kddi_2022_map = ENCODES.select{|enc| enc[:name] == "ISO-2022-JP-KDDI"}.first[:map]
+pairs = kddi_2022_map.each_with_index.inject([]) {|acc, ((range, ch), i)|
+ sjis = kddi_sjis_map[i][1]
+ acc += range.map{|uni| pair = [sjis.to_sjis - 0x700, Integer(ch)]; ch = ch.succ; sjis = sjis.succ; next pair }
+}
+params = {
+ :name => "ISO-2022-JP-KDDI-UNDOC",
+ :src_zone => [0x21..0x7E, 0x21..0x7E, 8],
+ :dst_ilseq => 0xFFFE,
+}
+generate_from_ucs(params, pairs)
diff --git a/tool/enc-emoji4unicode.rb b/tool/enc-emoji4unicode.rb
new file mode 100644
index 0000000000..1e7d45901f
--- /dev/null
+++ b/tool/enc-emoji4unicode.rb
@@ -0,0 +1,133 @@
+#!/usr/bin/env ruby
+
+# example:
+# ./enc-emoji4unicode.rb emoji4unicode.xml > ../enc/trans/emoji-exchange-tbl.rb
+
+require 'rexml/document'
+require File.expand_path("../transcode-tblgen", __FILE__)
+
+class EmojiTable
+ VERBOSE_MODE = false
+
+ def initialize(xml_path)
+ @doc = REXML::Document.new File.open(xml_path)
+ @kddi_undoc = make_kddi_undoc_map()
+ end
+
+ def conversion(from_carrier, to_carrier, &block)
+ REXML::XPath.each(@doc.root, '//e') do |e|
+ from = e.attribute(from_carrier.downcase).to_s
+ to = e.attribute(to_carrier.downcase).to_s
+ text_fallback = e.attribute('text_fallback').to_s
+ name = e.attribute('name').to_s
+ if from =~ /^(?:\*|\+)(.+)$/ # proposed or unified
+ from = $1
+ end
+ if from.empty? || from !~ /^[0-9A-F]+$/
+ # do nothing
+ else
+ from_utf8 = [from.hex].pack("U").unpack("H*").first
+ if to =~ /^(?:&gt;|\*)?([0-9A-F\+]+)$/
+ str_to = $1
+ if str_to =~ /^\+/ # unicode "proposed" begins at "+"
+ proposal = true
+ str_to.sub!(/^\+/, '')
+ else
+ proposal = false
+ end
+ tos = str_to.split('+')
+ to_utf8 = tos.map(&:hex).pack("U*").unpack("H*").first
+ comment = "[%s] U+%X -> %s" % [name, from.hex, tos.map{|c| "U+%X"%c.hex}.join(' ')]
+ block.call(:from => from_utf8,
+ :to => to_utf8,
+ :comment => comment,
+ :fallback => false,
+ :proposal => proposal)
+ elsif to.empty?
+ if text_fallback.empty?
+ comment = "[%s] U+%X -> U+3013 (GETA)" % [name, from.hex]
+ block.call(:from => from_utf8,
+ :to => "\u{3013}".unpack("H*").first,
+ :comment => comment, # geta
+ :fallback => true,
+ :proposal => false)
+ else
+ to_utf8 = text_fallback.unpack("H*").first
+ comment = %([%s] U+%X -> "%s") % [name, from.hex, text_fallback]
+ block.call(:from => from_utf8,
+ :to => to_utf8,
+ :comment => comment,
+ :fallback => true,
+ :proposal => false)
+ end
+ else
+ raise "something wrong: %s -> %s" % [from, to]
+ end
+ end
+ end
+ end
+
+ def generate(io, from_carrier, to_carrier)
+ from_encoding = (from_carrier == "Unicode") ? "UTF-8" : "UTF8-"+from_carrier
+ to_encoding = (to_carrier == "Unicode" ) ? "UTF-8" : "UTF8-"+to_carrier
+ io.puts "EMOJI_EXCHANGE_TBL['#{from_encoding}']['#{to_encoding}'] = ["
+ io.puts " # for documented codepoints" if from_carrier == "KDDI"
+ self.conversion(from_carrier, to_carrier) do |params|
+ from, to = params[:from], %Q{"#{params[:to]}"}
+ to = ":undef" if params[:fallback] || params[:proposal]
+ io.puts %{ ["#{from}", #{to}], # #{params[:comment]}}
+ end
+ if from_carrier == "KDDI"
+ io.puts " # for undocumented codepoints"
+ self.conversion(from_carrier, to_carrier) do |params|
+ from, to = params[:from], %Q{"#{params[:to]}"}
+ to = ":undef" if params[:fallback] || params[:proposal]
+ unicode = utf8_to_ucs(from)
+ undoc = ucs_to_utf8(@kddi_undoc[unicode])
+ io.puts %{ ["#{undoc}", #{to}], # #{params[:comment]}}
+ end
+ end
+ io.puts "]"
+ io.puts
+ end
+
+ private
+
+ def utf8_to_ucs(cp)
+ return [cp].pack("H*").unpack("U*").first
+ end
+
+ def ucs_to_utf8(cp)
+ return [cp].pack("U*").unpack("H*").first
+ end
+
+ def make_kddi_undoc_map()
+ pub_to_sjis = citrus_decode_mapsrc(
+ "mskanji", 2, "UCS/EMOJI_SHIFT_JIS-KDDI").sort_by{|u, s| s}
+ sjis_to_undoc = citrus_decode_mapsrc(
+ "mskanji", 2, "EMOJI_SHIFT_JIS-KDDI-UNDOC/UCS").sort_by{|s, u| s}
+ return pub_to_sjis.zip(sjis_to_undoc).inject({}) {|h, rec|
+ raise "no match sjis codepoint" if rec[0][1] != rec[1][0]
+ h[rec[0][0]] = rec[1][1]
+ next h
+ }
+ end
+end
+
+if ARGV.empty?
+ puts "usage: #$0 [emoji4unicode.xml]"
+ exit 1
+end
+$srcdir = File.expand_path("../../enc/trans", __FILE__)
+emoji_table = EmojiTable.new(ARGV[0])
+
+companies = %w(DoCoMo KDDI SoftBank Unicode)
+
+io = STDOUT
+io.puts "EMOJI_EXCHANGE_TBL = Hash.new{|h,k| h[k] = {}}"
+companies.each do |from_company|
+ companies.each do |to_company|
+ next if from_company == to_company
+ emoji_table.generate(io, from_company, to_company)
+ end
+end
diff --git a/tool/enc-unicode.rb b/tool/enc-unicode.rb
new file mode 100755
index 0000000000..2ba0b73606
--- /dev/null
+++ b/tool/enc-unicode.rb
@@ -0,0 +1,556 @@
+#!/usr/bin/env ruby
+
+# Creates the data structures needed by Oniguruma to map Unicode codepoints to
+# property names and POSIX character classes
+#
+# To use this, get UnicodeData.txt, Scripts.txt, PropList.txt,
+# PropertyAliases.txt, PropertyValueAliases.txt, DerivedCoreProperties.txt,
+# DerivedAge.txt and Blocks.txt from unicode.org.
+# (http://unicode.org/Public/UNIDATA/) And run following command.
+# ruby1.9 tool/enc-unicode.rb data_dir > enc/unicode/name2ctype.kwd
+# You can get source file for gperf. After this, simply make ruby.
+
+if ARGV[0] == "--header"
+ header = true
+ ARGV.shift
+end
+unless ARGV.size == 2
+ abort "Usage: #{$0} data_directory emoji_data_directory"
+end
+
+$unicode_version = File.basename(ARGV[0])[/\A[.\d]+\z/]
+
+POSIX_NAMES = %w[NEWLINE Alpha Blank Cntrl Digit Graph Lower Print XPosixPunct Space Upper XDigit Word Alnum ASCII Punct]
+
+def pair_codepoints(codepoints)
+
+ # We have a sorted Array of codepoints that we wish to partition into
+ # ranges such that the start- and endpoints form an inclusive set of
+ # codepoints with property _property_. Note: It is intended that some ranges
+ # will begin with the value with which they end, e.g. 0x0020 -> 0x0020
+
+ codepoints.sort!
+ last_cp = codepoints.first
+ pairs = [[last_cp, nil]]
+ codepoints[1..-1].each do |codepoint|
+ next if last_cp == codepoint
+
+ # If the current codepoint does not follow directly on from the last
+ # codepoint, the last codepoint represents the end of the current range,
+ # and the current codepoint represents the start of the next range.
+ if last_cp.next != codepoint
+ pairs[-1][-1] = last_cp
+ pairs << [codepoint, nil]
+ end
+ last_cp = codepoint
+ end
+
+ # The final pair has as its endpoint the last codepoint for this property
+ pairs[-1][-1] = codepoints.last
+ pairs
+end
+
+def parse_unicode_data(file)
+ last_cp = 0
+ data = {'Any' => (0x0000..0x10ffff).to_a, 'Assigned' => [],
+ 'ASCII' => (0..0x007F).to_a, 'NEWLINE' => [0x0a], 'Cn' => []}
+ beg_cp = nil
+ IO.foreach(file) do |line|
+ fields = line.split(';')
+ cp = fields[0].to_i(16)
+
+ case fields[1]
+ when /\A<(.*),\s*First>\z/
+ beg_cp = cp
+ next
+ when /\A<(.*),\s*Last>\z/
+ cps = (beg_cp..cp).to_a
+ else
+ beg_cp = cp
+ cps = [cp]
+ end
+
+ # The Cn category represents unassigned characters. These are not listed in
+ # UnicodeData.txt so we must derive them by looking for 'holes' in the range
+ # of listed codepoints. We increment the last codepoint seen and compare it
+ # with the current codepoint. If the current codepoint is less than
+ # last_cp.next we have found a hole, so we add the missing codepoint to the
+ # Cn category.
+ data['Cn'].concat((last_cp.next...beg_cp).to_a)
+
+ # Assigned - Defined in unicode.c; interpreted as every character in the
+ # Unicode range minus the unassigned characters
+ data['Assigned'].concat(cps)
+
+ # The third field denotes the 'General' category, e.g. Lu
+ (data[fields[2]] ||= []).concat(cps)
+
+ # The 'Major' category is the first letter of the 'General' category, e.g.
+ # 'Lu' -> 'L'
+ (data[fields[2][0,1]] ||= []).concat(cps)
+ last_cp = cp
+ end
+
+ # The last Cn codepoint should be 0x10ffff. If it's not, append the missing
+ # codepoints to Cn and C
+ cn_remainder = (last_cp.next..0x10ffff).to_a
+ data['Cn'] += cn_remainder
+ data['C'] += data['Cn']
+
+ # Special case for LC (Cased_Letter). LC = Ll + Lt + Lu
+ data['LC'] = data['Ll'] + data['Lt'] + data['Lu']
+
+ # Define General Category properties
+ gcps = data.keys.sort - POSIX_NAMES
+
+ # Returns General Category Property names and the data
+ [gcps, data]
+end
+
+def define_posix_props(data)
+ # We now derive the character classes (POSIX brackets), e.g. [[:alpha:]]
+ #
+
+ data['Alpha'] = data['Alphabetic']
+ data['Upper'] = data['Uppercase']
+ data['Lower'] = data['Lowercase']
+ data['Punct'] = data['Punctuation']
+ data['XPosixPunct'] = data['Punctuation'] + [0x24, 0x2b, 0x3c, 0x3d, 0x3e, 0x5e, 0x60, 0x7c, 0x7e]
+ data['Digit'] = data['Decimal_Number']
+ data['XDigit'] = (0x0030..0x0039).to_a + (0x0041..0x0046).to_a +
+ (0x0061..0x0066).to_a
+ data['Alnum'] = data['Alpha'] + data['Digit']
+ data['Space'] = data['White_Space']
+ data['Blank'] = data['Space_Separator'] + [0x0009]
+ data['Cntrl'] = data['Cc']
+ data['Word'] = data['Alpha'] + data['Mark'] + data['Digit'] + data['Connector_Punctuation']
+ data['Graph'] = data['Any'] - data['Space'] - data['Cntrl'] -
+ data['Surrogate'] - data['Unassigned']
+ data['Print'] = data['Graph'] + data['Space_Separator']
+end
+
+def parse_scripts(data, categories)
+ files = [
+ {:fn => 'DerivedCoreProperties.txt', :title => 'Derived Property'},
+ {:fn => 'Scripts.txt', :title => 'Script'},
+ {:fn => 'PropList.txt', :title => 'Binary Property'},
+ {:fn => 'emoji-data.txt', :title => 'Emoji'}
+ ]
+ current = nil
+ cps = []
+ names = {}
+ files.each do |file|
+ data_foreach(file[:fn]) do |line|
+ if /^# Total (?:code points|elements): / =~ line
+ data[current] = cps
+ categories[current] = file[:title]
+ (names[file[:title]] ||= []) << current
+ cps = []
+ elsif /^([0-9a-fA-F]+)(?:\.\.([0-9a-fA-F]+))?\s*;\s*(\w+)/ =~ line
+ current = $3
+ $2 ? cps.concat(($1.to_i(16)..$2.to_i(16)).to_a) : cps.push($1.to_i(16))
+ end
+ end
+ end
+ # All code points not explicitly listed for Script
+ # have the value Unknown (Zzzz).
+ data['Unknown'] = (0..0x10ffff).to_a - data.values_at(*names['Script']).flatten
+ categories['Unknown'] = 'Script'
+ names.values.flatten << 'Unknown'
+end
+
+def parse_aliases(data)
+ kv = {}
+ data_foreach('PropertyAliases.txt') do |line|
+ next unless /^(\w+)\s*; (\w+)/ =~ line
+ data[$1] = data[$2]
+ kv[normalize_propname($1)] = normalize_propname($2)
+ end
+ data_foreach('PropertyValueAliases.txt') do |line|
+ next unless /^(sc|gc)\s*; (\w+)\s*; (\w+)(?:\s*; (\w+))?/ =~ line
+ if $1 == 'gc'
+ data[$3] = data[$2]
+ data[$4] = data[$2]
+ kv[normalize_propname($3)] = normalize_propname($2)
+ kv[normalize_propname($4)] = normalize_propname($2) if $4
+ else
+ data[$2] = data[$3]
+ data[$4] = data[$3]
+ kv[normalize_propname($2)] = normalize_propname($3)
+ kv[normalize_propname($4)] = normalize_propname($3) if $4
+ end
+ end
+ kv
+end
+
+# According to Unicode6.0.0/ch03.pdf, Section 3.1, "An update version
+# never involves any additions to the character repertoire." Versions
+# in DerivedAge.txt should always be /\d+\.\d+/
+def parse_age(data)
+ current = nil
+ last_constname = nil
+ cps = []
+ ages = []
+ data_foreach('DerivedAge.txt') do |line|
+ if /^# Total code points: / =~ line
+ constname = constantize_agename(current)
+ # each version matches all previous versions
+ cps.concat(data[last_constname]) if last_constname
+ data[constname] = cps
+ make_const(constname, cps, "Derived Age #{current}")
+ ages << current
+ last_constname = constname
+ cps = []
+ elsif /^([0-9a-fA-F]+)(?:\.\.([0-9a-fA-F]+))?\s*;\s*(\d+\.\d+)/ =~ line
+ current = $3
+ $2 ? cps.concat(($1.to_i(16)..$2.to_i(16)).to_a) : cps.push($1.to_i(16))
+ end
+ end
+ ages
+end
+
+def parse_GraphemeBreakProperty(data)
+ current = nil
+ cps = []
+ ages = []
+ data_foreach('auxiliary/GraphemeBreakProperty.txt') do |line|
+ if /^# Total code points: / =~ line
+ constname = constantize_Grapheme_Cluster_Break(current)
+ data[constname] = cps
+ make_const(constname, cps, "Grapheme_Cluster_Break=#{current}")
+ ages << current
+ cps = []
+ elsif /^([0-9a-fA-F]+)(?:\.\.([0-9a-fA-F]+))?\s*;\s*(\w+)/ =~ line
+ current = $3
+ $2 ? cps.concat(($1.to_i(16)..$2.to_i(16)).to_a) : cps.push($1.to_i(16))
+ end
+ end
+ ages
+end
+
+def parse_block(data)
+ current = nil
+ cps = []
+ blocks = []
+ data_foreach('Blocks.txt') do |line|
+ if /^([0-9a-fA-F]+)\.\.([0-9a-fA-F]+);\s*(.*)/ =~ line
+ cps = ($1.to_i(16)..$2.to_i(16)).to_a
+ constname = constantize_blockname($3)
+ data[constname] = cps
+ make_const(constname, cps, "Block")
+ blocks << constname
+ end
+ end
+
+ # All code points not belonging to any of the named blocks
+ # have the value No_Block.
+ no_block = (0..0x10ffff).to_a - data.values_at(*blocks).flatten
+ constname = constantize_blockname("No_Block")
+ make_const(constname, no_block, "Block")
+ blocks << constname
+end
+
+# shim for Ruby 1.8
+unless {}.respond_to?(:key)
+ class Hash
+ alias key index
+ end
+end
+
+$const_cache = {}
+# make_const(property, pairs, name): Prints a 'static const' structure for a
+# given property, group of paired codepoints, and a human-friendly name for
+# the group
+def make_const(prop, data, name)
+ if name.empty?
+ puts "\n/* '#{prop}' */"
+ else
+ puts "\n/* '#{prop}': #{name} */"
+ end
+ if origprop = $const_cache.key(data)
+ puts "#define CR_#{prop} CR_#{origprop}"
+ else
+ $const_cache[prop] = data
+ pairs = pair_codepoints(data)
+ puts "static const OnigCodePoint CR_#{prop}[] = {"
+ # The first element of the constant is the number of pairs of codepoints
+ puts "\t#{pairs.size},"
+ pairs.each do |pair|
+ pair.map! { |c| c == 0 ? '0x0000' : sprintf("%0#6x", c) }
+ puts "\t#{pair.first}, #{pair.last},"
+ end
+ puts "}; /* CR_#{prop} */"
+ end
+end
+
+def normalize_propname(name)
+ name = name.downcase
+ name.delete!('- _')
+ name
+end
+
+def constantize_agename(name)
+ "Age_#{name.sub(/\./, '_')}"
+end
+
+def constantize_Grapheme_Cluster_Break(name)
+ "Grapheme_Cluster_Break_#{name}"
+end
+
+def constantize_blockname(name)
+ "In_#{name.gsub(/\W/, '_')}"
+end
+
+def get_file(name)
+ File.join(ARGV[name.start_with?("emoji-") ? 1 : 0], name)
+end
+
+def data_foreach(name, &block)
+ fn = get_file(name)
+ warn "Reading #{name}"
+ pat = /^# #{File.basename(name).sub(/\./, '-([\\d.]+)\\.')}/
+ File.open(fn, 'rb') do |f|
+ line = f.gets
+ unless /^emoji-/ =~ name
+ unless pat =~ line
+ raise ArgumentError, "#{name}: no Unicode version"
+ end
+ if !$unicode_version
+ $unicode_version = $1
+ elsif $unicode_version != $1
+ raise ArgumentError, "#{name}: Unicode version mismatch: #$1"
+ end
+ end
+ f.each(&block)
+ end
+end
+
+# Write Data
+class Unifdef
+ attr_accessor :output, :top, :stack, :stdout, :kwdonly
+ def initialize(out)
+ @top = @output = []
+ @stack = []
+ $stdout, @stdout = self, out
+ end
+ def restore
+ $stdout = @stdout
+ end
+ def ifdef(sym)
+ if @kwdonly
+ @stdout.puts "#ifdef #{sym}"
+ else
+ @stack << @top
+ @top << tmp = [sym]
+ @top = tmp
+ end
+ if block_given?
+ begin
+ return yield
+ ensure
+ endif(sym)
+ end
+ end
+ end
+ def endif(sym)
+ if @kwdonly
+ @stdout.puts "#endif /* #{sym} */"
+ else
+ unless sym == @top[0]
+ restore
+ raise ArgumentError, "#{sym} unmatch to #{@top[0]}"
+ end
+ @top = @stack.pop
+ end
+ end
+ def show(dest, *syms)
+ _show(dest, @output, syms)
+ end
+ def _show(dest, ary, syms)
+ if Symbol === (sym = ary[0])
+ unless syms.include?(sym)
+ return
+ end
+ end
+ ary.each do |e|
+ case e
+ when Array
+ _show(dest, e, syms)
+ when String
+ dest.print e
+ end
+ end
+ end
+ def write(str)
+ if @kwdonly
+ @stdout.write(str)
+ else
+ @top << str
+ end
+ self
+ end
+ alias << write
+end
+
+output = Unifdef.new($stdout)
+output.kwdonly = !header
+
+puts '%{'
+props, data = parse_unicode_data(get_file('UnicodeData.txt'))
+categories = {}
+props.concat parse_scripts(data, categories)
+aliases = parse_aliases(data)
+ages = blocks = graphemeBreaks = nil
+define_posix_props(data)
+POSIX_NAMES.each do |name|
+ if name == 'XPosixPunct'
+ make_const(name, data[name], "[[:Punct:]]")
+ elsif name == 'Punct'
+ make_const(name, data[name], "")
+ else
+ make_const(name, data[name], "[[:#{name}:]]")
+ end
+end
+output.ifdef :USE_UNICODE_PROPERTIES do
+ props.each do |name|
+ category = categories[name] ||
+ case name.size
+ when 1 then 'Major Category'
+ when 2 then 'General Category'
+ else '-'
+ end
+ make_const(name, data[name], category)
+ end
+ output.ifdef :USE_UNICODE_AGE_PROPERTIES do
+ ages = parse_age(data)
+ end
+ graphemeBreaks = parse_GraphemeBreakProperty(data)
+ blocks = parse_block(data)
+end
+puts(<<'__HEREDOC')
+
+static const OnigCodePoint* const CodeRanges[] = {
+__HEREDOC
+POSIX_NAMES.each{|name|puts" CR_#{name},"}
+output.ifdef :USE_UNICODE_PROPERTIES do
+ props.each{|name| puts" CR_#{name},"}
+ output.ifdef :USE_UNICODE_AGE_PROPERTIES do
+ ages.each{|name| puts" CR_#{constantize_agename(name)},"}
+ end
+ graphemeBreaks.each{|name| puts" CR_#{constantize_Grapheme_Cluster_Break(name)},"}
+ blocks.each{|name|puts" CR_#{name},"}
+end
+
+puts(<<'__HEREDOC')
+};
+struct uniname2ctype_struct {
+ short name;
+ unsigned short ctype;
+};
+#define uniname2ctype_offset(str) offsetof(struct uniname2ctype_pool_t, uniname2ctype_pool_##str)
+
+#if !(/*ANSI*/+0)
+static const struct uniname2ctype_struct *uniname2ctype_p(const char *, unsigned int);
+#endif
+%}
+struct uniname2ctype_struct;
+%%
+__HEREDOC
+
+i = -1
+name_to_index = {}
+POSIX_NAMES.each do |name|
+ i += 1
+ next if name == 'NEWLINE'
+ name = normalize_propname(name)
+ name_to_index[name] = i
+ puts"%-40s %3d" % [name + ',', i]
+end
+output.ifdef :USE_UNICODE_PROPERTIES do
+ props.each do |name|
+ i += 1
+ name = normalize_propname(name)
+ name_to_index[name] = i
+ puts "%-40s %3d" % [name + ',', i]
+ end
+ aliases.each_pair do |k, v|
+ next if name_to_index[k]
+ next unless v = name_to_index[v]
+ puts "%-40s %3d" % [k + ',', v]
+ end
+ output.ifdef :USE_UNICODE_AGE_PROPERTIES do
+ ages.each do |name|
+ i += 1
+ name = "age=#{name}"
+ name_to_index[name] = i
+ puts "%-40s %3d" % [name + ',', i]
+ end
+ end
+ graphemeBreaks.each do |name|
+ i += 1
+ name = "graphemeclusterbreak=#{name.delete('_').downcase}"
+ name_to_index[name] = i
+ puts "%-40s %3d" % [name + ',', i]
+ end
+ blocks.each do |name|
+ i += 1
+ name = normalize_propname(name)
+ name_to_index[name] = i
+ puts "%-40s %3d" % [name + ',', i]
+ end
+end
+puts(<<'__HEREDOC')
+%%
+static int
+uniname2ctype(const UChar *name, unsigned int len)
+{
+ const struct uniname2ctype_struct *p = uniname2ctype_p((const char *)name, len);
+ if (p) return p->ctype;
+ return -1;
+}
+__HEREDOC
+versions = $unicode_version.scan(/\d+/)
+print("#if defined ONIG_UNICODE_VERSION_STRING && !( \\\n")
+%w[MAJOR MINOR TEENY].zip(versions) do |n, v|
+ print(" ONIG_UNICODE_VERSION_#{n} == #{v} && \\\n")
+end
+print(" 1)\n")
+print("# error ONIG_UNICODE_VERSION_STRING mismatch\n")
+print("#endif\n")
+print("#define ONIG_UNICODE_VERSION_STRING #{$unicode_version.dump}\n")
+%w[MAJOR MINOR TEENY].zip(versions) do |n, v|
+ print("#define ONIG_UNICODE_VERSION_#{n} #{v}\n")
+end
+
+output.restore
+
+if header
+ require 'tempfile'
+
+ NAME2CTYPE = %w[gperf -7 -c -j1 -i1 -t -C -P -T -H uniname2ctype_hash -Q uniname2ctype_pool -N uniname2ctype_p]
+
+ fds = []
+ syms = %i[USE_UNICODE_PROPERTIES USE_UNICODE_AGE_PROPERTIES]
+ begin
+ fds << (tmp = Tempfile.new(%w"name2ctype .h"))
+ IO.popen([*NAME2CTYPE, out: tmp], "w") {|f| output.show(f, *syms)}
+ end while syms.pop
+ fds.each(&:close)
+ IO.popen(%W[diff -DUSE_UNICODE_AGE_PROPERTIES #{fds[1].path} #{fds[0].path}], "r") {|age|
+ IO.popen(%W[diff -DUSE_UNICODE_PROPERTIES #{fds[2].path} -], "r", in: age) {|f|
+ ansi = false
+ f.each {|line|
+ if /ANSI-C code produced by gperf/ =~ line
+ ansi = true
+ end
+ line.sub!(/\/\*ANSI\*\//, '1') if ansi
+ line.gsub!(/\(int\)\((?:long|size_t)\)&\(\(struct uniname2ctype_pool_t \*\)0\)->uniname2ctype_pool_(str\d+),\s+/,
+ 'uniname2ctype_offset(\1), ')
+ if (/^(uniname2ctype_hash) /=~line)..(/^\}/=~line)
+ line.sub!(/^( *(?:register\s+)?(.*\S)\s+hval\s*=\s*)(?=len;)/, '\1(\2)')
+ end
+ puts line
+ }
+ }
+ }
+end
diff --git a/tool/eval.rb b/tool/eval.rb
new file mode 100644
index 0000000000..981f72733b
--- /dev/null
+++ b/tool/eval.rb
@@ -0,0 +1,160 @@
+# VM checking and benchmarking code
+
+require './rbconfig'
+require 'fileutils'
+require 'pp'
+
+Ruby = ENV['RUBY'] || RbConfig.ruby
+#
+
+OPTIONS = %w{
+ opt-direct-threaded-code
+ opt-basic-operations
+ opt-operands-unification
+ opt-instructions-unification
+ opt-inline-method-cache
+ opt-stack-caching
+}.map{|opt|
+ '--disable-' + opt
+}
+
+opts = OPTIONS.dup
+Configs = OPTIONS.map{|opt|
+ o = opts.dup
+ opts.delete(opt)
+ o
+} + [[]]
+
+pp Configs if $DEBUG
+
+
+def exec_cmd(cmd)
+ puts cmd
+ unless system(cmd)
+ p cmd
+ raise "error"
+ end
+end
+
+def dirname idx
+ "ev-#{idx}"
+end
+
+def build
+ Configs.each_with_index{|config, idx|
+ dir = dirname(idx)
+ FileUtils.rm_rf(dir) if FileTest.exist?(dir)
+ Dir.mkdir(dir)
+ FileUtils.cd(dir){
+ exec_cmd("#{Ruby} ../extconf.rb " + config.join(" "))
+ exec_cmd("make clean test-all")
+ }
+ }
+end
+
+def check
+ Configs.each_with_index{|c, idx|
+ puts "= #{idx}"
+ system("#{Ruby} -r ev-#{idx}/yarvcore -e 'puts YARVCore::OPTS'")
+ }
+end
+
+def bench_each idx
+ puts "= #{idx}"
+ 5.times{|count|
+ print count
+ FileUtils.cd(dirname(idx)){
+ exec_cmd("make benchmark OPT=-y ITEMS=#{ENV['ITEMS']} > ../b#{idx}-#{count}")
+ }
+ }
+ puts
+end
+
+def bench
+ # return bench_each(6)
+ Configs.each_with_index{|c, idx|
+ bench_each idx
+ }
+end
+
+def parse_result data
+ flag = false
+ stat = []
+ data.each{|line|
+ if flag
+ if /(\w+)\t([\d\.]+)/ =~ line
+ stat << [$1, $2.to_f]
+ else
+ raise "not a data"
+ end
+
+ end
+ if /benchmark summary/ =~ line
+ flag = true
+ end
+ }
+ stat
+end
+
+def calc_each data
+ data.sort!
+ data.pop # remove max
+ data.shift # remove min
+
+ data.inject(0.0){|res, e|
+ res += e
+ } / data.size
+end
+
+def calc_stat stats
+ stat = []
+ stats[0].each_with_index{|e, idx|
+ bm = e[0]
+ vals = stats.map{|st|
+ st[idx][1]
+ }
+ [bm, calc_each(vals)]
+ }
+end
+
+def stat
+ total = []
+ Configs.each_with_index{|c, idx|
+ stats = []
+ 5.times{|count|
+ file = "b#{idx}-#{count}"
+ # p file
+ open(file){|f|
+ stats << parse_result(f.read)
+ }
+ }
+ # merge stats
+ total << calc_stat(stats)
+ total
+ }
+ # pp total
+ total[0].each_with_index{|e, idx|
+ bm = e[0]
+ # print "#{bm}\t"
+ total.each{|st|
+ print st[idx][1], "\t"
+ }
+ puts
+ }
+end
+
+ARGV.each{|cmd|
+ case cmd
+ when 'build'
+ build
+ when 'check'
+ check
+ when 'bench'
+ bench
+ when 'stat'
+ stat
+ else
+ raise
+ end
+}
+
diff --git a/tool/expand-config.rb b/tool/expand-config.rb
new file mode 100755
index 0000000000..d34f29f586
--- /dev/null
+++ b/tool/expand-config.rb
@@ -0,0 +1,35 @@
+#!./miniruby -s
+
+# Used to expand Ruby config entries for Win32 Makefiles.
+
+config = File.read(conffile = $config)
+config.sub!(/^(\s*)RUBY_VERSION\b.*(\sor\s*)$/, '\1true\2')
+rbconfig = Module.new {module_eval(config, conffile)}::RbConfig
+config = $expand ? rbconfig::CONFIG : rbconfig::MAKEFILE_CONFIG
+config["RUBY_RELEASE_DATE"] ||=
+ File.read(File.expand_path("../../version.h", __FILE__))[/^\s*#\s*define\s+RUBY_RELEASE_DATE\s+"(.*)"/, 1]
+
+while /\A(\w+)=(.*)/ =~ ARGV[0]
+ config[$1] = $2
+ config[$1].tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
+ ARGV.shift
+end
+
+re = /@(#{config.keys.map {|k| Regexp.quote(k)}.join('|')})@/
+
+if $output
+ output = open($output, "wb", $mode &&= $mode.oct)
+ output.chmod($mode) if $mode
+else
+ output = STDOUT
+ output.binmode
+end
+
+ARGF.each do |line|
+ line.gsub!(/@([a-z_]\w*)@/i) {
+ s = config.fetch($1, $expand ? $& : "")
+ s = s.gsub(/\$\((.+?)\)/, %Q[${\\1}]) unless $expand
+ s
+ }
+ output.puts line
+end
diff --git a/tool/extlibs.rb b/tool/extlibs.rb
new file mode 100755
index 0000000000..a840724794
--- /dev/null
+++ b/tool/extlibs.rb
@@ -0,0 +1,186 @@
+#!/usr/bin/ruby
+
+# Used to download, extract and patch extension libraries (extlibs)
+# for Ruby. See common.mk for Ruby's usage.
+
+require 'digest'
+require_relative 'downloader'
+
+class ExtLibs
+ def cache_file(url, cache_dir)
+ Downloader.cache_file(url, nil, :cache_dir => cache_dir)
+ end
+
+ def do_download(url, cache_dir)
+ Downloader.download(url, nil, nil, nil, :cache_dir => cache_dir)
+ end
+
+ def do_checksum(cache, chksums)
+ chksums.each do |sum|
+ name, sum = sum.split(/:/)
+ if $VERBOSE
+ $stdout.print "checking #{name} of #{cache} ..."
+ $stdout.flush
+ end
+ hd = Digest(name.upcase).file(cache).hexdigest
+ if hd == sum
+ if $VERBOSE
+ $stdout.puts " OK"
+ $stdout.flush
+ end
+ else
+ if $VERBOSE
+ $stdout.puts " NG"
+ $stdout.flush
+ end
+ raise "checksum mismatch: #{cache}, #{name}:#{hd}, expected #{sum}"
+ end
+ end
+ end
+
+ def do_extract(cache, dir)
+ if $VERBOSE
+ $stdout.puts "extracting #{cache} into #{dir}"
+ $stdout.flush
+ end
+ ext = File.extname(cache)
+ case ext
+ when '.gz', '.tgz'
+ f = IO.popen(["gzip", "-dc", cache])
+ cache = cache.chomp('.gz')
+ when '.bz2', '.tbz'
+ f = IO.popen(["bzip2", "-dc", cache])
+ cache = cache.chomp('.bz2')
+ when '.xz', '.txz'
+ f = IO.popen(["xz", "-dc", cache])
+ cache = cache.chomp('.xz')
+ else
+ inp = cache
+ end
+ inp ||= f.binmode
+ ext = File.extname(cache)
+ case ext
+ when '.tar', /\A\.t[gbx]z\z/
+ pid = Process.spawn("tar", "xpf", "-", in: inp, chdir: dir)
+ when '.zip'
+ pid = Process.spawn("unzip", inp, "-d", dir)
+ end
+ f.close if f
+ Process.wait(pid)
+ $?.success? or raise "failed to extract #{cache}"
+ end
+
+ def do_patch(dest, patch, args)
+ if $VERBOSE
+ $stdout.puts "applying #{patch} under #{dest}"
+ $stdout.flush
+ end
+ Process.wait(Process.spawn("patch", "-d", dest, "-i", patch, *args))
+ $?.success? or raise "failed to patch #{patch}"
+ end
+
+ def do_command(mode, dest, url, cache_dir, chksums)
+ extracted = false
+ base = /.*(?=\.tar(?:\.\w+)?\z)/
+
+ case mode
+ when :download
+ cache = do_download(url, cache_dir)
+ do_checksum(cache, chksums)
+ when :extract
+ cache = cache_file(url, cache_dir)
+ target = File.join(dest, File.basename(cache)[base])
+ unless File.directory?(target)
+ do_checksum(cache, chksums)
+ extracted = do_extract(cache, dest)
+ end
+ when :all
+ cache = do_download(url, cache_dir)
+ target = File.join(dest, File.basename(cache)[base])
+ unless File.directory?(target)
+ do_checksum(cache, chksums)
+ extracted = do_extract(cache, dest)
+ end
+ end
+ extracted
+ end
+
+ def run(argv)
+ cache_dir = nil
+ mode = :all
+ until argv.empty?
+ case argv[0]
+ when '--download'
+ mode = :download
+ when '--extract'
+ mode = :extract
+ when '--patch'
+ mode = :patch
+ when '--all'
+ mode = :all
+ when '--cache'
+ argv.shift
+ cache_dir = argv[0]
+ when /\A--cache=/
+ cache_dir = $'
+ when '--'
+ argv.shift
+ break
+ when /\A-/
+ warn "unknown option: #{argv[0]}"
+ return false
+ else
+ break
+ end
+ argv.shift
+ end
+
+ success = true
+ argv.each do |dir|
+ Dir.glob("#{dir}/**/extlibs") do |list|
+ if $VERBOSE
+ $stdout.puts "downloading for #{list}"
+ $stdout.flush
+ end
+ extracted = false
+ dest = File.dirname(list)
+ url = chksums = nil
+ IO.foreach(list) do |line|
+ line.sub!(/\s*#.*/, '')
+ if chksums
+ chksums.concat(line.split)
+ elsif /^\t/ =~ line
+ if extracted and (mode == :all or mode == :patch)
+ patch, *args = line.split
+ do_patch(dest, patch, args)
+ end
+ next
+ else
+ url, *chksums = line.split(' ')
+ end
+ if chksums.last == '\\'
+ chksums.pop
+ next
+ end
+ next unless url
+ begin
+ extracted = do_command(mode, dest, url, cache_dir, chksums)
+ rescue => e
+ warn e.inspect
+ success = false
+ end
+ url = chksums = nil
+ end
+ end
+ end
+ success
+ end
+
+ def self.run(argv)
+ self.new.run(argv)
+ end
+end
+
+if $0 == __FILE__
+ exit ExtLibs.run(ARGV)
+end
diff --git a/tool/fake.rb b/tool/fake.rb
new file mode 100644
index 0000000000..99fc24e775
--- /dev/null
+++ b/tool/fake.rb
@@ -0,0 +1,70 @@
+# Used by Makefile and configure for building Ruby.
+# See common.mk and Makefile.in for details.
+
+class File
+ sep = ("\\" if RUBY_PLATFORM =~ /mswin|bccwin|mingw/)
+ if sep != ALT_SEPARATOR
+ remove_const :ALT_SEPARATOR
+ ALT_SEPARATOR = sep
+ end
+end
+
+static = !!(defined?($static) && $static)
+$:.unshift(builddir)
+posthook = proc do
+ config = RbConfig::CONFIG
+ mkconfig = RbConfig::MAKEFILE_CONFIG
+ extout = File.expand_path(mkconfig["EXTOUT"], builddir)
+ [
+ ["top_srcdir", $top_srcdir],
+ ["topdir", $topdir],
+ ].each do |var, val|
+ next unless val
+ mkconfig[var] = config[var] = val
+ t = /\A#{Regexp.quote(val)}(?=\/)/
+ $hdrdir.sub!(t) {"$(#{var})"}
+ mkconfig.keys.grep(/dir\z/) do |k|
+ mkconfig[k] = "$(#{var})#$'" if t =~ mkconfig[k]
+ end
+ end
+ if $extmk
+ $ruby = "$(topdir)/miniruby -I'$(topdir)' -I'$(top_srcdir)/lib' -I'$(extout)/$(arch)' -I'$(extout)/common'"
+ else
+ $ruby = baseruby
+ end
+ $static = static
+ untrace_var(:$ruby, posthook)
+end
+prehook = proc do |extmk|
+ pat = %r[(?:\A(?:\w:|//[^/]+)|\G)/[^/]*]
+ dir = builddir.scan(pat)
+ pwd = Dir.pwd.scan(pat)
+ if dir[0] == pwd[0]
+ while dir[0] and dir[0] == pwd[0]
+ dir.shift
+ pwd.shift
+ end
+ builddir = File.join((pwd.empty? ? ["."] : [".."]*pwd.size) + dir)
+ builddir = "." if builddir.empty?
+ end
+ join = proc {|*args| File.join(*args).sub!(/\A(?:\.\/)*/, '')}
+ $topdir ||= builddir
+ $top_srcdir ||= (File.identical?(top_srcdir, dir = join[$topdir, srcdir]) ?
+ dir : top_srcdir)
+ $extout = '$(topdir)/.ext'
+ $extout_prefix = '$(extout)$(target_prefix)/'
+ config = RbConfig::CONFIG
+ mkconfig = RbConfig::MAKEFILE_CONFIG
+ mkconfig["builddir"] = config["builddir"] = builddir
+ mkconfig["buildlibdir"] = config["buildlibdir"] = builddir
+ mkconfig["top_srcdir"] = $top_srcdir if $top_srcdir
+ mkconfig["extout"] ||= $extout
+ config["top_srcdir"] = File.expand_path($top_srcdir ||= top_srcdir)
+ config["rubyhdrdir"] = join[$top_srcdir, "include"]
+ config["rubyarchhdrdir"] = join[builddir, config["EXTOUT"], "include", config["arch"]]
+ config["extout"] ||= join[$topdir, ".ext"]
+ mkconfig["libdirname"] = "buildlibdir"
+ trace_var(:$ruby, posthook)
+ untrace_var(:$extmk, prehook)
+end
+trace_var(:$extmk, prehook)
diff --git a/tool/fetch-bundled_gems.rb b/tool/fetch-bundled_gems.rb
new file mode 100755
index 0000000000..ae3068d35c
--- /dev/null
+++ b/tool/fetch-bundled_gems.rb
@@ -0,0 +1,27 @@
+#!ruby -an
+BEGIN {
+ require 'fileutils'
+
+ dir = ARGV.shift
+ ARGF.eof?
+ FileUtils.mkdir_p(dir)
+ Dir.chdir(dir)
+}
+
+n, v, u = $F
+case n
+when "minitest"
+ v = "master"
+when "test-unit"
+else
+ v = "v" + v
+end
+
+if File.directory?(n)
+ puts "updating #{n} ..."
+ system(*%W"git fetch", chdir: n) or abort
+else
+ puts "retrieving #{n} ..."
+ system(*%W"git clone #{u} #{n}") or abort
+end
+system(*%W"git checkout #{v}", chdir: n) or abort
diff --git a/tool/file2lastrev.rb b/tool/file2lastrev.rb
new file mode 100755
index 0000000000..8d68da9f88
--- /dev/null
+++ b/tool/file2lastrev.rb
@@ -0,0 +1,98 @@
+#!/usr/bin/env ruby
+
+# Gets the most recent revision of a file in a VCS-agnostic way.
+# Used by Doxygen, Makefiles and merger.rb.
+
+require 'optparse'
+
+# this file run with BASERUBY, which may be older than 1.9, so no
+# require_relative
+require File.expand_path('../vcs', __FILE__)
+
+Program = $0
+
+@output = nil
+def self.output=(output)
+ if @output and @output != output
+ raise "you can specify only one of --changed, --revision.h and --doxygen"
+ end
+ @output = output
+end
+@suppress_not_found = false
+
+format = '%Y-%m-%dT%H:%M:%S%z'
+srcdir = nil
+parser = OptionParser.new {|opts|
+ opts.on("--srcdir=PATH", "use PATH as source directory") do |path|
+ srcdir = path
+ end
+ opts.on("--changed", "changed rev") do
+ self.output = :changed
+ end
+ opts.on("--revision.h", "RUBY_REVISION macro") do
+ self.output = :revision_h
+ end
+ opts.on("--doxygen", "Doxygen format") do
+ self.output = :doxygen
+ end
+ opts.on("--modified[=FORMAT]", "modified time") do |fmt|
+ self.output = :modified
+ format = fmt if fmt
+ end
+ opts.on("-q", "--suppress_not_found") do
+ @suppress_not_found = true
+ end
+}
+parser.parse! rescue abort "#{File.basename(Program)}: #{$!}\n#{parser}"
+
+@output =
+ case @output
+ when :changed, nil
+ Proc.new {|last, changed|
+ changed
+ }
+ when :revision_h
+ Proc.new {|last, changed, modified, branch, title|
+ [
+ "#define RUBY_REVISION #{changed || 0}",
+ if branch
+ e = '..'
+ limit = 16
+ name = branch.sub(/\A(.{#{limit-e.size}}).{#{e.size+1},}/o) {$1+e}
+ "#define RUBY_BRANCH_NAME #{name.dump}"
+ end,
+ if title
+ "#define RUBY_LAST_COMMIT_TITLE #{title.dump}"
+ end,
+ ].compact
+ }
+ when :doxygen
+ Proc.new {|last, changed|
+ "r#{changed}/r#{last}"
+ }
+ when :modified
+ Proc.new {|last, changed, modified|
+ modified.strftime(format)
+ }
+ else
+ raise "unknown output format `#{@output}'"
+ end
+
+srcdir ||= File.dirname(File.dirname(Program))
+begin
+ vcs = VCS.detect(srcdir)
+rescue VCS::NotFoundError => e
+ abort "#{File.basename(Program)}: #{e.message}" unless @suppress_not_found
+else
+ ok = true
+ (ARGV.empty? ? [nil] : ARGV).each do |arg|
+ begin
+ puts @output[*vcs.get_revisions(arg)]
+ rescue => e
+ next if @suppress_not_found and VCS::NotFoundError === e
+ warn "#{File.basename(Program)}: #{e.message}"
+ ok = false
+ end
+ end
+ exit ok
+end
diff --git a/tool/gem-unpack.rb b/tool/gem-unpack.rb
new file mode 100755
index 0000000000..0ddcea0704
--- /dev/null
+++ b/tool/gem-unpack.rb
@@ -0,0 +1,18 @@
+require 'rubygems'
+require 'rubygems/package'
+
+# This library is used by "make extract-gems" to
+# unpack bundled gem files.
+
+def Gem.unpack(file, dir = nil)
+ pkg = Gem::Package.new(file)
+ spec = pkg.spec
+ target = spec.full_name
+ target = File.join(dir, target) if dir
+ pkg.extract_files target
+ spec_file = File.join(target, "#{spec.name}.gemspec")
+ open(spec_file, 'wb') do |f|
+ f.print spec.to_ruby
+ end
+ puts "Unpacked #{file}"
+end
diff --git a/tool/gen_dummy_probes.rb b/tool/gen_dummy_probes.rb
new file mode 100755
index 0000000000..45222830f3
--- /dev/null
+++ b/tool/gen_dummy_probes.rb
@@ -0,0 +1,32 @@
+#!/usr/bin/ruby
+# -*- coding: us-ascii -*-
+
+# Used to create dummy probes (as for systemtap and DTrace) by Makefiles.
+# See common.mk.
+
+text = ARGF.read
+
+# remove comments
+text.gsub!(%r'(?:^ *)?/\*.*?\*/\n?'m, '')
+
+# remove the pragma declarations and ifdefs
+text.gsub!(/^#(?:pragma|include|if|endif).*\n/, '')
+
+# replace the provider section with the start of the header file
+text.gsub!(/provider ruby \{/, "#ifndef\t_PROBES_H\n#define\t_PROBES_H\n#define DTRACE_PROBES_DISABLED 1\n")
+
+# finish up the #ifndef sandwich
+text.gsub!(/\};/, "\n#endif\t/* _PROBES_H */")
+
+# expand probes to DTRACE macros
+text.gsub!(/^ *probe ([^\(]*)\(([^\)]*)\);/) {
+ name, args = $1, $2
+ name.upcase!
+ name.gsub!(/__/, '_')
+ args.gsub!(/(\A|, *)[^,]*\b(?=\w+(?=,|\z))/, '\1')
+ "#define RUBY_DTRACE_#{name}_ENABLED() 0\n" \
+ "#define RUBY_DTRACE_#{name}(#{args}) do {} while (0)"
+}
+
+puts "/* -*- c -*- */"
+print text
diff --git a/tool/gen_ruby_tapset.rb b/tool/gen_ruby_tapset.rb
new file mode 100755
index 0000000000..ec8650204f
--- /dev/null
+++ b/tool/gen_ruby_tapset.rb
@@ -0,0 +1,106 @@
+#!/usr/bin/ruby
+# -*- coding: us-ascii -*-
+# Create a tapset for systemtap and DTrace
+# usage: ./ruby gen_ruby_tapset.rb --ruby-path=/path/to/ruby probes.d > output
+
+require "optparse"
+
+def set_argument(argname, nth)
+ # remove C style type info
+ argname.gsub!(/.+ (.+)/, '\1') # e.g. char *hoge -> *hoge
+ argname.gsub!(/^\*/, '') # e.g. *filename -> filename
+
+ "#{argname} = $arg#{nth}"
+end
+
+ruby_path = "/usr/local/ruby"
+
+opts = OptionParser.new
+opts.on("--ruby-path=PATH"){|v| ruby_path = v}
+opts.parse!(ARGV)
+
+text = ARGF.read
+
+# remove preprocessor directives
+text.gsub!(/^#.*$/, '')
+
+# remove provider name
+text.gsub!(/^provider ruby \{/, "")
+text.gsub!(/^\};/, "")
+
+# probename()
+text.gsub!(/probe (.+)\( *\);/) {
+ probe_name = $1
+ probe = <<-End
+ probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}")
+ {
+ }
+ End
+}
+
+# probename(arg1)
+text.gsub!(/ *probe (.+)\(([^,)]+)\);/) {
+ probe_name = $1
+ arg1 = $2
+
+ probe = <<-End
+ probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}")
+ {
+ #{set_argument(arg1, 1)}
+ }
+ End
+}
+
+# probename(arg1, arg2)
+text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+)\);/) {
+ probe_name = $1
+ arg1 = $2
+ arg2 = $3
+
+ probe = <<-End
+ probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
+ {
+ #{set_argument(arg1, 1)}
+ #{set_argument(arg2, 2)}
+ }
+ End
+}
+
+# probename(arg1, arg2, arg3)
+text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+)\);/) {
+ probe_name = $1
+ arg1 = $2
+ arg2 = $3
+ arg3 = $4
+
+ probe = <<-End
+ probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
+ {
+ #{set_argument(arg1, 1)}
+ #{set_argument(arg2, 2)}
+ #{set_argument(arg3, 3)}
+ }
+ End
+}
+
+# probename(arg1, arg2, arg3, arg4)
+text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+),([^,)]+)\);/) {
+ probe_name = $1
+ arg1 = $2
+ arg2 = $3
+ arg3 = $4
+ arg4 = $5
+
+ probe = <<-End
+ probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}")
+ {
+ #{set_argument(arg1, 1)}
+ #{set_argument(arg2, 2)}
+ #{set_argument(arg3, 3)}
+ #{set_argument(arg4, 4)}
+ }
+ End
+}
+
+print text
+
diff --git a/tool/generate-backport-changelog.rb b/tool/generate-backport-changelog.rb
new file mode 100644
index 0000000000..0e8676a085
--- /dev/null
+++ b/tool/generate-backport-changelog.rb
@@ -0,0 +1,99 @@
+#!ruby
+require "time"
+
+def usage!
+ STDERR.puts <<-EOS
+Usage: #$0 [--trunk=<dir>] [--target=<dir>] <revision(s)>
+
+Generate ChangeLog entries for backporting.
+The entries are output to STDOUT, and the messages of this tool are output to
+STDERR. So you can simply redirect STDOUT to get the entries.
+
+You should specify the path of trunk by `--trunk`. If not, assumed cwd.
+You also should specify the path of the target branch by `--target`. If not,
+assumed cwd.
+This means that you have to specify at least one of `--trunk` or `--target`.
+
+revision(s) can be below or their combinations:
+ 12345 # means r12345
+ 12345,54321 # means r12345 and r54321
+ 12345-12347 # means r12345, r12346 and r12347 (of course, if available)
+
+Note that the revisions is backported branch's ones, not trunk's.
+
+The target of this tool is *not* to generate ChangeLog automatically, but to
+generate the draft of ChangeLog.
+You have to check and modify the output.
+ EOS
+ exit
+end
+
+Majors = {
+ "eregon" => "Benoit Daloze <eregontp@gmail.com>",
+ "kazu" => "Kazuhiro NISHIYAMA <zn@mbf.nifty.com>",
+ "ko1" => "Koichi Sasada <ko1@atdot.net>",
+ "marcandre" => "Marc-Andre Lafortune <ruby-core@marc-andre.ca>",
+ "naruse" => "NARUSE, Yui <naruse@ruby-lang.org>",
+ "nobu" => "Nobuyoshi Nakada <nobu@ruby-lang.org>",
+ "normal" => "Eric Wong <normalperson@yhbt.net>",
+ "rhe" => "Kazuki Yamaguchi <k@rhe.jp>",
+ "shugo" => "Shugo Maeda <shugo@ruby-lang.org>",
+ "stomar" => "Marcus Stollsteimer <sto.mar@web.de>",
+ "usa" => "NAKAMURA Usaku <usa@ruby-lang.org>",
+ "zzak" => "Zachary Scott <e@zzak.io>",
+}
+
+trunk = "."
+target = "."
+ARGV.delete_if{|e| /^--trunk=(.*)/ =~ e && trunk = $1}
+ARGV.delete_if{|e| /^--target=(.*)/ =~ e && target = $1}
+usage! if ARGV.size == 0 || trunk == target
+
+revisions = []
+ARGV.each do |a|
+ a.split(/,/).each do |b|
+ if /-/ =~ b
+ revisions += Range.new(*b.split(/-/, 2).map{|e| Integer(e)}).to_a
+ else
+ revisions << Integer(b)
+ end
+ end
+end
+revisions.sort!
+revisions.reverse!
+
+revisions.each do |rev|
+ if /^Index: ChangeLog$/ =~ `svn diff -c #{rev} #{target}`
+ STDERR.puts "#{rev} already has ChangeLog. Skip."
+ else
+ lines = `svn log -r #{rev} #{target}`.lines[1..-2]
+ if lines.empty?
+ STDERR.puts "#{rev} does not exist. Skip."
+ next
+ end
+ unless /^merge revision\(s\) (\d+)/ =~ lines[2]
+ STDERR.puts "#{rev} is not seems to be a merge commit. Skip."
+ next
+ end
+ original = $1
+ committer = `svn log -r #{original} #{trunk}`.lines[1].split(/\|/)[1].strip
+ if Majors[committer]
+ committer = Majors[committer]
+ else
+ committer = "#{committer} <#{committer}@ruby-lang.org>"
+ end
+ time = Time.parse(lines.shift.split(/\|/)[2]).getlocal("+09:00")
+ puts "#{time.asctime} #{committer}"
+ puts
+ lines.shift(2) # skip "merge" line
+ lines.shift while lines.first == "\n"
+ lines.pop while lines.last == "\n"
+ lines.each do |line|
+ line.chomp!
+ line = "\t#{line}" if line[0] != "\t" && line != ""
+ puts line
+ end
+ puts
+ STDERR.puts "#{rev} is processed."
+ end
+end
diff --git a/tool/generic_erb.rb b/tool/generic_erb.rb
new file mode 100644
index 0000000000..c9986c7284
--- /dev/null
+++ b/tool/generic_erb.rb
@@ -0,0 +1,58 @@
+# -*- coding: us-ascii -*-
+
+# Used to expand Ruby template files by common.mk, uncommon.mk and
+# some Ruby extension libraries.
+
+require 'erb'
+require 'optparse'
+require 'fileutils'
+$:.unshift(File.dirname(__FILE__))
+require 'vpath'
+require 'colorize'
+
+vpath = VPath.new
+timestamp = nil
+output = nil
+ifchange = nil
+source = false
+color = nil
+templates = []
+
+ARGV.options do |o|
+ o.on('-t', '--timestamp[=PATH]') {|v| timestamp = v || true}
+ o.on('-i', '--input=PATH') {|v| template << v}
+ o.on('-o', '--output=PATH') {|v| output = v}
+ o.on('-c', '--[no-]if-change') {|v| ifchange = v}
+ o.on('-x', '--source') {source = true}
+ o.on('--color') {color = true}
+ vpath.def_options(o)
+ o.order!(ARGV)
+ templates << (ARGV.shift or abort o.to_s) if templates.empty?
+end
+color = Colorize.new(color)
+unchanged = color.pass("unchanged")
+updated = color.fail("updated")
+
+result = templates.map do |template|
+ erb = ERB.new(File.read(template), nil, '%-')
+ erb.filename = template
+ source ? erb.src : proc{erb.result(binding)}.call
+end
+result = result.size == 1 ? result[0] : result.join("")
+if output
+ if ifchange and (vpath.open(output, "rb") {|f| f.read} rescue nil) == result
+ puts "#{output} #{unchanged}"
+ else
+ open(output, "wb") {|f| f.print result}
+ puts "#{output} #{updated}"
+ end
+ if timestamp
+ if timestamp == true
+ dir, base = File.split(output)
+ timestamp = File.join(dir, ".time." + base)
+ end
+ FileUtils.touch(timestamp)
+ end
+else
+ print result
+end
diff --git a/tool/git-refresh b/tool/git-refresh
new file mode 100755
index 0000000000..d36c4d80bc
--- /dev/null
+++ b/tool/git-refresh
@@ -0,0 +1,43 @@
+#!/bin/sh
+set -e
+
+if (cd -P .) 2>/dev/null; then
+ CHDIR='cd -P'
+else
+ CHDIR='cd'
+fi
+
+quiet=
+branch=
+
+until [ $# = 0 ]; do
+ case "$1" in
+ --) shift; break;;
+ -C|--directory) shift; $CHDIR "$1";;
+ -C*) $CHDIR `expr "$1" : '-C\(.*\)'`;;
+ --directory=*) $CHDIR `expr "$1" : '[^=]*=\(.*\)'`;;
+ -q) quiet=1;;
+ -b|--branch) shift; branch="$1";;
+ -b*) branch=`expr "$1" : '-b\(.*\)'`;;
+ --branch=*) branch=`expr "$1" : '[^=]*=\(.*\)'`;;
+ -*) echo "unknown option: $1" 1>&2; exit 1;;
+ *) break;;
+ esac
+ shift
+done
+
+url="$1"
+dir="$2"
+shift 2
+[ x"$branch" = x ] && unset branch || :
+if [ -d "$dir" ]; then
+ echo updating `expr "/$dir/" : '.*/\([^/][^/]*\)/'` ...
+ [ $quiet ] || set -x
+ $CHDIR "$dir"
+ ${branch+git} ${branch+fetch} ${branch+"$@"}
+ exec git ${branch+checkout} "${branch-pull}" "$@"
+else
+ echo retrieving `expr "/$dir/" : '.*/\([^/][^/]*\)/'` ...
+ [ $quiet ] || set -x
+ exec git clone ${branch+--branch} ${branch+"$branch"} "$url" "$dir" "$@"
+fi
diff --git a/tool/gperf.sed b/tool/gperf.sed
new file mode 100644
index 0000000000..6b3e1980be
--- /dev/null
+++ b/tool/gperf.sed
@@ -0,0 +1,22 @@
+/ANSI-C code/{
+ h
+ s/.*/ANSI:offset:/
+ x
+}
+/\/\*!ANSI{\*\//{
+ G
+ s/\/\*!ANSI{\*\/\(.*\)\/\*}!ANSI\*\/\(.*\)\nANSI:.*/\/\*\1\*\/\2/
+}
+s/(int)([a-z_]*)&((struct \([a-zA-Z_0-9][a-zA-Z_0-9]*\)_t *\*)0)->\1_str\([1-9][0-9]*\),/gperf_offsetof(\1, \2),/g
+/^#line/{
+ G
+ x
+ s/:offset:/:/
+ x
+ s/\(.*\)\(\n\).*:offset:.*/#define gperf_offsetof(s, n) (short)offsetof(struct s##_t, s##_str##n)\2\1/
+ s/\n[^#].*//
+}
+/^[a-zA-Z_0-9]*hash/,/^}/{
+ s/ hval = / hval = (unsigned int)/
+ s/ return / return (unsigned int)/
+}
diff --git a/tool/id2token.rb b/tool/id2token.rb
new file mode 100755
index 0000000000..706154ecfb
--- /dev/null
+++ b/tool/id2token.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/ruby -p
+# -*- coding: us-ascii -*-
+
+# Used to build the Ruby parsing code in common.mk and Ripper.
+
+BEGIN {
+ require 'optparse'
+ $:.unshift(File.dirname(__FILE__))
+ require 'vpath'
+ vpath = VPath.new
+ header = nil
+
+ opt = OptionParser.new do |o|
+ vpath.def_options(o)
+ header = o.order!(ARGV).shift
+ end or abort opt.opt_s
+
+ TOKENS = {}
+ h = vpath.read(header) rescue abort("#{header} not found in #{vpath.inspect}")
+ h.scan(/^#define\s+RUBY_TOKEN_(\w+)\s+(\d+)/) do |token, id|
+ TOKENS[token] = id
+ end
+
+ TOKENS_RE = /\bRUBY_TOKEN\((#{TOKENS.keys.join('|')})\)\s*(?=\s)/
+}
+
+$_.gsub!(TOKENS_RE) {TOKENS[$1]} if /^%token/ =~ $_
diff --git a/tool/ifchange b/tool/ifchange
new file mode 100755
index 0000000000..396a7a0bcf
--- /dev/null
+++ b/tool/ifchange
@@ -0,0 +1,87 @@
+#!/bin/sh
+# usage: ifchange target temporary
+
+# Used in generating revision.h via Makefiles.
+
+set -e
+timestamp=
+keepsuffix=
+empty=
+color=auto
+until [ $# -eq 0 ]; do
+ case "$1" in
+ --timestamp)
+ timestamp=.
+ ;;
+ --timestamp=*)
+ timestamp=`expr \( "$1" : '[^=]*=\(.*\)' \)`
+ ;;
+ --keep)
+ keepsuffix=.old
+ ;;
+ --keep=*)
+ keepsuffix=`expr \( "$1" : '[^=]*=\(.*\)' \)`
+ ;;
+ --empty)
+ empty=yes
+ ;;
+ --color)
+ color=always
+ ;;
+ --color=*)
+ color=`expr \( "$1" : '[^=]*=\(.*\)' \)`
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+target="$1"
+temp="$2"
+if [ "$temp" = - ]; then
+ temp="tmpdata$$.tmp~"
+ cat > "$temp"
+ trap 'rm -f "$temp"' 0
+fi
+
+msg_begin= msg_unchanged= msg_updated= msg_reset=
+if [ "$color" = always -o \( "$color" = auto -a -t 1 \) ]; then
+ msg_begin="["
+ case "`tput smso 2>/dev/null`" in
+ "$msg_begin"*m)
+ if [ ${TEST_COLORS:+set} ]; then
+ msg_unchanged=`expr ":$TEST_COLORS:" : ".*:pass=\([^:]*\):"` || :
+ msg_updated=`expr ":$TEST_COLORS:" : ".*:fail=\([^:]*\):"` || :
+ fi
+ msg_unchanged="${msg_begin}${msg_unchanged:-32}m"
+ msg_updated="${msg_begin}${msg_updated:-31;1}m"
+ msg_reset="${msg_begin}m"
+ ;;
+ esac
+ unset msg_begin
+fi
+
+targetdir=
+case "$target" in */*) targetdir=`dirname "$target"`;; esac
+if [ -f "$target" -a ! -${empty:+f}${empty:-s} "$temp" ] || cmp "$target" "$temp" >/dev/null 2>&1; then
+ echo "$target ${msg_unchanged}unchanged${msg_reset}"
+ rm -f "$temp"
+else
+ echo "$target ${msg_updated}updated${msg_reset}"
+ [ x"${targetdir}" = x -o -d "${targetdir}" ] || mkdir -p "${targetdir}"
+ [ x"${keepsuffix}" != x -a -f "$target" ] && mv -f "$target" "${target}${keepsuffix}"
+ mv -f "$temp" "$target"
+fi
+
+if [ -n "${timestamp}" ]; then
+ if [ x"${timestamp}" = x. ]; then
+ if [ x"$targetdir" = x ]; then
+ timestamp=.time."$target"
+ else
+ timestamp="$targetdir"/.time.`basename "$target"`
+ fi
+ fi
+ : > "$timestamp"
+fi
diff --git a/tool/insns2vm.rb b/tool/insns2vm.rb
new file mode 100755
index 0000000000..ecbbb52643
--- /dev/null
+++ b/tool/insns2vm.rb
@@ -0,0 +1,18 @@
+#!ruby
+
+# This is used by Makefile.in to generate .inc files.
+# See Makefile.in for details.
+
+require 'optparse'
+
+Version = %w$Revision: 11626 $[1..-1]
+
+require "#{File.join(File.dirname(__FILE__), 'instruction')}"
+
+if $0 == __FILE__
+ opts = ARGV.options
+ maker = RubyVM::SourceCodeGenerator.def_options(opts)
+ files = opts.parse!
+ generator = maker.call
+ generator.generate(files)
+end
diff --git a/tool/install-sh b/tool/install-sh
new file mode 100644
index 0000000000..11e502f56d
--- /dev/null
+++ b/tool/install-sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Just only for using AC_PROG_INSTALL in configure.ac.
+# See autoconf.info for more detail.
+
+cat <<EOF >&2
+Ruby uses a BSD-compatible install(1) if possible. If not, Ruby
+provides its own install(1) alternative.
+
+This script is a place holder for AC_PROG_INSTALL in configure.ac.
+
+Please report a bug in Ruby to http://bugs.ruby-lang.org if you see
+this message.
+
+Thank you.
+EOF
+exit 1
diff --git a/tool/instruction.rb b/tool/instruction.rb
new file mode 100755
index 0000000000..447fed5948
--- /dev/null
+++ b/tool/instruction.rb
@@ -0,0 +1,1249 @@
+#!./miniruby
+# -*- coding: us-ascii -*-
+#
+# This library is used by insns2vm.rb as part of autogenerating
+# instruction files with .inc extensions like insns.inc and vm.inc.
+
+require 'erb'
+$:.unshift(File.dirname(__FILE__))
+require 'vpath'
+
+class RubyVM
+ class Instruction
+ def initialize name, opes, pops, rets, comm, body, tvars, sp_inc,
+ orig = self, defopes = [], type = nil,
+ nsc = [], psc = [[], []]
+
+ @name = name
+ @opes = opes # [[type, name], ...]
+ @pops = pops # [[type, name], ...]
+ @rets = rets # [[type, name], ...]
+ @comm = comm # {:c => category, :e => en desc, :j => ja desc}
+ @body = body # '...'
+
+ @orig = orig
+ @defopes = defopes
+ @type = type
+ @tvars = tvars
+
+ @nextsc = nsc
+ @pushsc = psc
+ @sc = []
+ @unifs = []
+ @optimized = []
+ @is_sc = false
+ @sp_inc = sp_inc
+ @trace = trace
+ end
+
+ def add_sc sci
+ @sc << sci
+ sci.set_sc
+ end
+
+ attr_reader :name, :opes, :pops, :rets
+ attr_reader :body, :comm
+ attr_reader :nextsc, :pushsc
+ attr_reader :orig, :defopes, :type
+ attr_reader :sc
+ attr_reader :unifs, :optimized
+ attr_reader :is_sc
+ attr_reader :tvars
+ attr_reader :sp_inc
+ attr_accessor :trace
+
+ def set_sc
+ @is_sc = true
+ end
+
+ def add_unif insns
+ @unifs << insns
+ end
+
+ def add_optimized insn
+ @optimized << insn
+ end
+
+ def sp_increase_c_expr
+ if(pops.any?{|t, v| v == '...'} ||
+ rets.any?{|t, v| v == '...'})
+ # user definition
+ raise "no sp increase definition" if @sp_inc.nil?
+ ret = "int inc = 0;\n"
+
+ @opes.each_with_index{|(t, v), i|
+ if (t == 'rb_num_t' && ((re = /\b#{v}\b/n) =~ @sp_inc)) ||
+ (@defopes.any?{|t, val| re =~ val})
+ ret << " int #{v} = FIX2INT(opes[#{i}]);\n"
+ elsif (t == 'CALL_INFO' && ((re = /\b#{v}\b/n) =~ @sp_inc))
+ ret << " CALL_INFO #{v} = (CALL_INFO)(opes[#{i}]);\n"
+ end
+ }
+
+ @defopes.each_with_index{|((t, var), val), i|
+ if t == 'rb_num_t' && val != '*' && /\b#{var}\b/ =~ @sp_inc
+ ret << " #{t} #{var} = #{val};\n"
+ end
+ }
+
+ ret << " #{@sp_inc};\n"
+ ret << " return depth + inc;"
+ ret
+ else
+ "return depth + #{rets.size - pops.size};"
+ end
+ end
+
+ def inspect
+ "#<Instruction:#{@name}>"
+ end
+ end
+
+ class InstructionsLoader
+ def initialize opts = {}
+ @insns = []
+ @insn_map = {}
+
+ @vpath = opts[:VPATH] || File
+ @use_const = opts[:use_const]
+ @verbose = opts[:verbose]
+ @destdir = opts[:destdir]
+
+ (@vm_opts = load_vm_opts).each {|k, v|
+ @vm_opts[k] = opts[k] if opts.key?(k)
+ }
+
+ load_insns_def opts[:"insns.def"] || 'insns.def'
+
+ load_opt_operand_def opts[:"opope.def"] || 'defs/opt_operand.def'
+ load_insn_unification_def opts[:"unif.def"] || 'defs/opt_insn_unif.def'
+ make_stackcaching_insns if vm_opt?('STACK_CACHING')
+ make_trace_insns
+ end
+
+ attr_reader :vpath
+ attr_reader :destdir
+
+ %w[use_const verbose].each do |attr|
+ attr_reader attr
+ alias_method "#{attr}?", attr
+ remove_method attr
+ end
+
+ def [](s)
+ @insn_map[s.to_s]
+ end
+
+ def each
+ @insns.each{|insn|
+ yield insn
+ }
+ end
+
+ def size
+ @insns.size
+ end
+
+ ###
+ private
+
+ def vm_opt? name
+ @vm_opts[name]
+ end
+
+ def load_vm_opts file = nil
+ file ||= 'vm_opts.h'
+ opts = {}
+ vpath.open(file) do |f|
+ f.grep(/^\#define\s+OPT_([A-Z_]+)\s+(\d+)/) do
+ opts[$1] = !$2.to_i.zero?
+ end
+ end
+ opts
+ end
+
+ SKIP_COMMENT_PATTERN = Regexp.compile(Regexp.escape('/** ##skip'))
+
+ include Enumerable
+
+ def add_insn insn
+ @insns << insn
+ @insn_map[insn.name] = insn
+ end
+
+ def make_insn name, opes, pops, rets, comm, body, sp_inc
+ add_insn Instruction.new(name, opes, pops, rets, comm, body, [], sp_inc)
+ end
+
+ # str -> [[type, var], ...]
+ def parse_vars line
+ raise unless /\((.*?)\)/ =~ line
+ vars = $1.split(',')
+ vars.map!{|v|
+ if /\s*(\S+)\s+(\S+)\s*/ =~ v
+ type = $1
+ var = $2
+ elsif /\s*\.\.\.\s*/ =~ v
+ type = var = '...'
+ else
+ raise
+ end
+ [type, var]
+ }
+ vars
+ end
+
+ def parse_comment comm
+ c = 'others'
+ j = ''
+ e = ''
+ comm.each_line{|line|
+ case line
+ when /@c (.+)/
+ c = $1
+ when /@e (.+)/
+ e = $1
+ when /@e\s*$/
+ e = ''
+ when /@j (.+)$/
+ j = $1
+ when /@j\s*$/
+ j = ''
+ end
+ }
+ { :c => c,
+ :e => e,
+ :j => j,
+ }
+ end
+
+ def load_insns_def file
+ body = insn = opes = pops = rets = nil
+ comment = ''
+
+ vpath.open(file) {|f|
+ f.instance_variable_set(:@line_no, 0)
+ class << f
+ def line_no
+ @line_no
+ end
+ def gets
+ @line_no += 1
+ super
+ end
+ end
+
+ while line = f.gets
+ line.chomp!
+ case line
+
+ when SKIP_COMMENT_PATTERN
+ while line = f.gets.chomp
+ if /\s+\*\/$/ =~ line
+ break
+ end
+ end
+
+ # collect instruction comment
+ when /^\/\*\*$/
+ while line = f.gets
+ if /\s+\*\/\s*$/ =~ line
+ break
+ else
+ comment << line
+ end
+ end
+
+ # start instruction body
+ when /^DEFINE_INSN$/
+ insn = f.gets.chomp
+ opes = parse_vars(f.gets.chomp)
+ pops = parse_vars(f.gets.chomp).reverse
+ rets_str = f.gets.chomp
+ rets = parse_vars(rets_str).reverse
+ comment = parse_comment(comment)
+ insn_in = true
+ body = ''
+
+ sp_inc = rets_str[%r"//\s*(.+)", 1]
+
+ raise unless /^\{$/ =~ f.gets.chomp
+ line_no = f.line_no
+
+ # end instruction body
+ when /^\}/
+ if insn_in
+ body.instance_variable_set(:@line_no, line_no)
+ body.instance_variable_set(:@file, f.path)
+ insn = make_insn(insn, opes, pops, rets, comment, body, sp_inc)
+ insn_in = false
+ comment = ''
+ end
+
+ else
+ if insn_in
+ body << line + "\n"
+ end
+ end
+ end
+ }
+ end
+
+ ## opt op
+ def load_opt_operand_def file
+ vpath.foreach(file) {|line|
+ line = line.gsub(/\#.*/, '').strip
+ next if line.length == 0
+ break if /__END__/ =~ line
+ /(\S+)\s+(.+)/ =~ line
+ insn = $1
+ opts = $2
+ add_opt_operand insn, opts.split(/,/).map{|e| e.strip}
+ } if file
+ end
+
+ def label_escape label
+ label.gsub(/\(/, '_O_').
+ gsub(/\)/, '_C_').
+ gsub(/\*/, '_WC_')
+ end
+
+ def add_opt_operand insn_name, opts
+ insn = @insn_map[insn_name]
+ opes = insn.opes
+
+ if opes.size != opts.size
+ raise "operand size mismatch for #{insn.name} (opes: #{opes.size}, opts: #{opts.size})"
+ end
+
+ ninsn = insn.name + '_OP_' + opts.map{|e| label_escape(e)}.join('_')
+ nopes = []
+ defv = []
+
+ opts.each_with_index{|e, i|
+ if e == '*'
+ nopes << opes[i]
+ end
+ defv << [opes[i], e]
+ }
+
+ make_insn_operand_optimized(insn, ninsn, nopes, defv)
+ end
+
+ def make_insn_operand_optimized orig_insn, name, opes, defopes
+ comm = orig_insn.comm.dup
+ comm[:c] = 'optimize'
+ add_insn insn = Instruction.new(
+ name, opes, orig_insn.pops, orig_insn.rets, comm,
+ orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
+ orig_insn, defopes)
+ orig_insn.add_optimized insn
+ end
+
+ ## insn unif
+ def load_insn_unification_def file
+ vpath.foreach(file) {|line|
+ line = line.gsub(/\#.*/, '').strip
+ next if line.length == 0
+ break if /__END__/ =~ line
+ make_unified_insns line.split.map{|e|
+ raise "unknown insn: #{e}" unless @insn_map[e]
+ @insn_map[e]
+ }
+ } if file
+ end
+
+ def all_combination sets
+ ret = sets.shift.map{|e| [e]}
+
+ sets.each{|set|
+ prev = ret
+ ret = []
+ prev.each{|ary|
+ set.each{|e|
+ eary = ary.dup
+ eary << e
+ ret << eary
+ }
+ }
+ }
+ ret
+ end
+
+ def make_unified_insns insns
+ if vm_opt?('UNIFY_ALL_COMBINATION')
+ insn_sets = insns.map{|insn|
+ [insn] + insn.optimized
+ }
+
+ all_combination(insn_sets).each{|insns_set|
+ make_unified_insn_each insns_set
+ }
+ else
+ make_unified_insn_each insns
+ end
+ end
+
+ def mk_private_val vals, i, redef
+ vals.dup.map{|v|
+ # v[0] : type
+ # v[1] : var name
+
+ v = v.dup
+ if v[0] != '...'
+ redef[v[1]] = v[0]
+ v[1] = "#{v[1]}_#{i}"
+ end
+ v
+ }
+ end
+
+ def mk_private_val2 vals, i, redef
+ vals.dup.map{|v|
+ # v[0][0] : type
+ # v[0][1] : var name
+ # v[1] : default val
+
+ pv = v.dup
+ v = pv[0] = pv[0].dup
+ if v[0] != '...'
+ redef[v[1]] = v[0]
+ v[1] = "#{v[1]}_#{i}"
+ end
+ pv
+ }
+ end
+
+ def make_unified_insn_each insns
+ names = []
+ opes = []
+ pops = []
+ rets = []
+ comm = {
+ :c => 'optimize',
+ :e => 'unified insn',
+ :j => 'unified insn',
+ }
+ body = ''
+ passed = []
+ tvars = []
+ defopes = []
+ sp_inc = ''
+
+ insns.each_with_index{|insn, i|
+ names << insn.name
+ redef_vars = {}
+
+ e_opes = mk_private_val(insn.opes, i, redef_vars)
+ e_pops = mk_private_val(insn.pops, i, redef_vars)
+ e_rets = mk_private_val(insn.rets, i, redef_vars)
+ # ToDo: fix it
+ e_defs = mk_private_val2(insn.defopes, i, redef_vars)
+
+ passed_vars = []
+ while pvar = e_pops.pop
+ rvar = rets.pop
+ if rvar
+ raise "unsupported unif insn: #{insns.inspect}" if rvar[0] == '...'
+ passed_vars << [pvar, rvar]
+ tvars << rvar
+ else
+ e_pops.push pvar
+ break
+ end
+ end
+
+ opes.concat e_opes
+ pops.concat e_pops
+ rets.concat e_rets
+ defopes.concat e_defs
+ sp_inc << "#{insn.sp_inc}"
+
+ body << "{ /* unif: #{i} */\n" +
+ passed_vars.map{|rpvars|
+ pv = rpvars[0]
+ rv = rpvars[1]
+ "#define #{pv[1]} #{rv[1]}"
+ }.join("\n") +
+ "\n" +
+ redef_vars.map{|v, type|
+ "#{type} #{v} = #{v}_#{i};"
+ }.join("\n") + "\n"
+ if line = insn.body.instance_variable_get(:@line_no)
+ file = insn.body.instance_variable_get(:@file)
+ body << "#line #{line+1} \"#{file}\"\n"
+ body << insn.body
+ body << "\n#line __CURRENT_LINE__ \"__CURRENT_FILE__\"\n"
+ else
+ body << insn.body
+ end
+ body << redef_vars.keys.map{|v|
+ "#{v}_#{i} = #{v};"
+ }.join("\n") +
+ "\n" +
+ passed_vars.map{|rpvars|
+ "#undef #{rpvars[0][1]}"
+ }.join("\n") +
+ "\n}\n"
+ }
+
+ tvars_ary = []
+ tvars.each{|tvar|
+ unless opes.any?{|var|
+ var[1] == tvar[1]
+ } || defopes.any?{|pvar|
+ pvar[0][1] == tvar[1]
+ }
+ tvars_ary << tvar
+ end
+ }
+ add_insn insn = Instruction.new("UNIFIED_" + names.join('_'),
+ opes, pops, rets.reverse, comm, body,
+ tvars_ary, sp_inc)
+ insn.defopes.replace defopes
+ insns[0].add_unif [insn, insns]
+ end
+
+ ## sc
+ SPECIAL_INSN_FOR_SC_AFTER = {
+ /\Asend/ => [:a],
+ /\Aend/ => [:a],
+ /\Ayield/ => [:a],
+ /\Aclassdef/ => [:a],
+ /\Amoduledef/ => [:a],
+ }
+ FROM_SC = [[], [:a], [:b], [:a, :b], [:b, :a]]
+
+ def make_stackcaching_insns
+ pops = rets = nil
+
+ @insns.dup.each{|insn|
+ opops = insn.pops
+ orets = insn.rets
+ oopes = insn.opes
+ ocomm = insn.comm
+ oname = insn.name
+
+ after = SPECIAL_INSN_FOR_SC_AFTER.find {|k, v| k =~ oname}
+
+ insns = []
+ FROM_SC.each{|from|
+ name, pops, rets, pushs1, pushs2, nextsc =
+ *calc_stack(insn, from, after, opops, orets)
+
+ make_insn_sc(insn, name, oopes, pops, rets, [pushs1, pushs2], nextsc)
+ }
+ }
+ end
+
+ def make_trace_insns
+ @insns.dup.each{|insn|
+ body = <<-EOS
+ vm_trace(ec, GET_CFP(), GET_PC());
+ DISPATCH_ORIGINAL_INSN(#{insn.name});
+ EOS
+
+ trace_insn = Instruction.new(name = "trace_#{insn.name}",
+ insn.opes, insn.pops, insn.rets, insn.comm,
+ body, insn.tvars, insn.sp_inc)
+ trace_insn.trace = true
+ add_insn trace_insn
+ }
+ end
+
+ def make_insn_sc orig_insn, name, opes, pops, rets, pushs, nextsc
+ comm = orig_insn.comm.dup
+ comm[:c] = 'optimize(sc)'
+
+ scinsn = Instruction.new(
+ name, opes, pops, rets, comm,
+ orig_insn.body, orig_insn.tvars, orig_insn.sp_inc,
+ orig_insn, orig_insn.defopes, :sc, nextsc, pushs)
+
+ add_insn scinsn
+ orig_insn.add_sc scinsn
+ end
+
+ def self.complement_name st
+ "#{st[0] ? st[0] : 'x'}#{st[1] ? st[1] : 'x'}"
+ end
+
+ def add_stack_value st
+ len = st.length
+ if len == 0
+ st[0] = :a
+ [nil, :a]
+ elsif len == 1
+ if st[0] == :a
+ st[1] = :b
+ else
+ st[1] = :a
+ end
+ [nil, st[1]]
+ else
+ st[0], st[1] = st[1], st[0]
+ [st[1], st[1]]
+ end
+ end
+
+ def calc_stack insn, ofrom, oafter, opops, orets
+ from = ofrom.dup
+ pops = opops.dup
+ rets = orets.dup
+ rest_scr = ofrom.dup
+
+ pushs_before = []
+ pushs= []
+
+ pops.each_with_index{|e, i|
+ if e[0] == '...'
+ pushs_before = from
+ from = []
+ end
+ r = from.pop
+ break unless r
+ pops[i] = pops[i].dup << r
+ }
+
+ if oafter
+ from = oafter
+ from.each_with_index{|r, i|
+ rets[i] = rets[i].dup << r if rets[i]
+ }
+ else
+ rets = rets.reverse
+ rets.each_with_index{|e, i|
+ break if e[0] == '...'
+ pushed, r = add_stack_value from
+ rets[i] = rets[i].dup << r
+ if pushed
+ if rest_scr.pop
+ pushs << pushed
+ end
+
+ if i - 2 >= 0
+ rets[i-2].pop
+ end
+ end
+ }
+ end
+
+ if false #|| insn.name =~ /test3/
+ p ofrom
+ p pops
+ p rets
+ p pushs_before
+ p pushs
+ p from
+ exit
+ end
+
+ ret = ["#{insn.name}_SC_#{InstructionsLoader.complement_name(ofrom)}_#{complement_name(from)}",
+ pops, rets, pushs_before, pushs, from]
+ end
+ end
+
+ class SourceCodeGenerator
+ def initialize insns
+ @insns = insns
+ end
+
+ attr_reader :insns
+
+ def generate
+ raise "should not reach here"
+ end
+
+ def vpath
+ @insns.vpath
+ end
+
+ def verbose?
+ @insns.verbose?
+ end
+
+ def use_const?
+ @insns.use_const?
+ end
+
+ def template(name)
+ ERB.new(vpath.read("template/#{name}"), nil, '%-')
+ end
+
+ def build_string
+ @lines = []
+ yield
+ @lines.join("\n")
+ end
+
+ EMPTY_STRING = ''.freeze
+
+ def commit str = EMPTY_STRING
+ @lines << str
+ end
+
+ def comment str
+ @lines << str if verbose?
+ end
+
+ def output_path(fn)
+ d = @insns.destdir
+ fn = File.join(d, fn) if d
+ fn
+ end
+ end
+
+ ###################################################################
+ # vm.inc
+ class VmBodyGenerator < SourceCodeGenerator
+ # vm.inc
+ def generate
+ template('vm.inc.tmpl').result(binding)
+ end
+
+ def generate_from_insnname insnname
+ make_insn_def @insns[insnname.to_s]
+ end
+
+ #######
+ private
+
+ def make_header_prepare_stack insn
+ comment " /* prepare stack status */"
+
+ push_ba = insn.pushsc
+ raise "unsupport" if push_ba[0].size > 0 && push_ba[1].size > 0
+
+ n = 0
+ push_ba.each {|pushs| n += pushs.length}
+ commit " CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, #{n});" if n > 0
+ push_ba.each{|pushs|
+ pushs.each{|r|
+ commit " PUSH(SCREG(#{r}));"
+ }
+ }
+ end
+
+ def make_header_operands insn
+ comment " /* declare and get from iseq */"
+
+ vars = insn.opes
+ n = 0
+ ops = []
+
+ vars.each_with_index{|(type, var), i|
+ if type == '...'
+ break
+ end
+
+ # skip make operands when body has no reference to this operand
+ # TODO: really needed?
+ re = /\b#{var}\b/n
+ if re =~ insn.body or re =~ insn.sp_inc or insn.rets.any?{|t, v| re =~ v} or re =~ 'ic' or re =~ 'ci' or re =~ 'cc'
+ ops << " #{type} #{var} = (#{type})GET_OPERAND(#{i+1});"
+ end
+
+ n += 1
+ }
+ @opn = n
+
+ # reverse or not?
+ # ops.join
+ commit ops.reverse
+ end
+
+ def make_header_default_operands insn
+ vars = insn.defopes
+
+ vars.each{|e|
+ next if e[1] == '*'
+ if use_const?
+ commit " const #{e[0][0]} #{e[0][1]} = #{e[1]};"
+ else
+ commit " #define #{e[0][1]} #{e[1]}"
+ end
+ }
+ end
+
+ def make_footer_default_operands insn
+ comment " /* declare and initialize default opes */"
+ if use_const?
+ commit
+ else
+ vars = insn.defopes
+
+ vars.each{|e|
+ next if e[1] == '*'
+ commit "#undef #{e[0][1]}"
+ }
+ end
+ end
+
+ def make_header_stack_pops insn
+ comment " /* declare and pop from stack */"
+
+ n = 0
+ pops = []
+ vars = insn.pops
+ vars.each_with_index{|iter, i|
+ type, var, r = *iter
+ if type == '...'
+ break
+ end
+ if r
+ pops << " #{type} #{var} = SCREG(#{r});"
+ else
+ pops << " #{type} #{var} = TOPN(#{n});"
+ n += 1
+ end
+ }
+ @popn = n
+
+ # reverse or not?
+ commit pops.reverse
+ end
+
+ def make_header_temporary_vars insn
+ comment " /* declare temporary vars */"
+
+ insn.tvars.each{|var|
+ commit " #{var[0]} #{var[1]};"
+ }
+ end
+
+ def make_header_stack_val insn
+ comment "/* declare stack push val */"
+
+ vars = insn.opes + insn.pops + insn.defopes.map{|e| e[0]}
+
+ insn.rets.each{|var|
+ if vars.all?{|e| e[1] != var[1]} && var[1] != '...'
+ commit " #{var[0]} #{var[1]};"
+ end
+ }
+ end
+
+ def make_header_analysis insn
+ commit " COLLECT_USAGE_INSN(BIN(#{insn.name}));"
+ insn.opes.each_with_index{|op, i|
+ commit " COLLECT_USAGE_OPERAND(BIN(#{insn.name}), #{i}, #{op[1]});"
+ }
+ end
+
+ def make_header_pc insn
+ commit " ADD_PC(1+#{@opn});"
+ commit " PREFETCH(GET_PC());"
+ end
+
+ def make_header_popn insn
+ comment " /* management */"
+ commit " POPN(#{@popn});" if @popn > 0
+ end
+
+ def make_header_debug insn
+ comment " /* for debug */"
+ commit " DEBUG_ENTER_INSN(\"#{insn.name}\");"
+ end
+
+ def make_header_defines insn
+ commit " #define CURRENT_INSN_#{insn.name} 1"
+ commit " #define INSN_IS_SC() #{insn.sc ? 0 : 1}"
+ commit " #define INSN_LABEL(lab) LABEL_#{insn.name}_##lab"
+ commit " #define LABEL_IS_SC(lab) LABEL_##lab##_###{insn.sc.size == 0 ? 't' : 'f'}"
+ end
+
+ def each_footer_stack_val insn
+ insn.rets.reverse_each{|v|
+ break if v[1] == '...'
+ yield v
+ }
+ end
+
+ def make_footer_stack_val insn
+ comment " /* push stack val */"
+
+ n = 0
+ each_footer_stack_val(insn){|v|
+ n += 1 unless v[2]
+ }
+ commit " CHECK_VM_STACK_OVERFLOW_FOR_INSN(VM_REG_CFP, #{n});" if n > 0
+ each_footer_stack_val(insn){|v|
+ if v[2]
+ commit " SCREG(#{v[2]}) = #{v[1]};"
+ else
+ commit " PUSH(#{v[1]});"
+ end
+ }
+ end
+
+ def make_footer_undefs insn
+ commit "#undef CURRENT_INSN_#{insn.name}"
+ commit "#undef INSN_IS_SC"
+ commit "#undef INSN_LABEL"
+ commit "#undef LABEL_IS_SC"
+ end
+
+ def make_header insn
+ label = insn.trace ? '' : "START_OF_ORIGINAL_INSN(#{insn.name});"
+ commit "INSN_ENTRY(#{insn.name}){#{label}"
+ make_header_prepare_stack insn
+ commit "{"
+ unless insn.trace
+ make_header_stack_val insn
+ make_header_default_operands insn
+ make_header_operands insn
+ make_header_stack_pops insn
+ make_header_temporary_vars insn
+ #
+ make_header_debug insn
+ make_header_pc insn
+ make_header_popn insn
+ make_header_defines insn
+ make_header_analysis insn
+ end
+ commit "{"
+ end
+
+ def make_footer insn
+ unless insn.trace
+ make_footer_stack_val insn
+ make_footer_default_operands insn
+ make_footer_undefs insn
+ end
+ commit " END_INSN(#{insn.name});}}}"
+ end
+
+ def make_insn_def insn
+ build_string do
+ make_header insn
+ if line = insn.body.instance_variable_get(:@line_no)
+ file = insn.body.instance_variable_get(:@file)
+ commit "#line #{line+1} \"#{file}\""
+ commit insn.body
+ commit '#line __CURRENT_LINE__ "__CURRENT_FILE__"'
+ else
+ commit insn.body
+ end
+ make_footer(insn)
+ end
+ end
+ end
+
+ ###################################################################
+ # vmtc.inc
+ class VmTCIncGenerator < SourceCodeGenerator
+ def generate
+ template('vmtc.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # insns_info.inc
+ class InsnsInfoIncGenerator < SourceCodeGenerator
+ def generate
+ template('insns_info.inc.tmpl').result(binding)
+ end
+
+ ###
+ private
+
+ def op2typesig op
+ case op
+ when /^OFFSET/
+ "TS_OFFSET"
+ when /^rb_num_t/
+ "TS_NUM"
+ when /^lindex_t/
+ "TS_LINDEX"
+ when /^VALUE/
+ "TS_VALUE"
+ when /^ID/
+ "TS_ID"
+ when /GENTRY/
+ "TS_GENTRY"
+ when /^IC/
+ "TS_IC"
+ when /^CALL_INFO/
+ "TS_CALLINFO"
+ when /^CALL_CACHE/
+ "TS_CALLCACHE"
+ when /^\.\.\./
+ "TS_VARIABLE"
+ when /^CDHASH/
+ "TS_CDHASH"
+ when /^ISEQ/
+ "TS_ISEQ"
+ when /rb_insn_func_t/
+ "TS_FUNCPTR"
+ else
+ raise "unknown op type: #{op}"
+ end
+ end
+
+ TYPE_CHARS = {
+ 'TS_OFFSET' => 'O',
+ 'TS_NUM' => 'N',
+ 'TS_LINDEX' => 'L',
+ 'TS_VALUE' => 'V',
+ 'TS_ID' => 'I',
+ 'TS_GENTRY' => 'G',
+ 'TS_IC' => 'K',
+ 'TS_CALLINFO' => 'C',
+ 'TS_CALLCACHE' => 'E',
+ 'TS_CDHASH' => 'H',
+ 'TS_ISEQ' => 'S',
+ 'TS_VARIABLE' => '.',
+ 'TS_FUNCPTR' => 'F',
+ }
+
+ def max_length(array)
+ max = 0
+ array.each do |i|
+ if (n = i.length) > max
+ max = n
+ end
+ end
+ max
+ end
+ end
+
+ ###################################################################
+ # insns.inc
+ class InsnsIncGenerator < SourceCodeGenerator
+ def generate
+ template('insns.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # minsns.inc
+ class MInsnsIncGenerator < SourceCodeGenerator
+ def generate
+ template('minsns.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # optinsn.inc
+ class OptInsnIncGenerator < SourceCodeGenerator
+ def generate
+ optinsn_inc
+ end
+
+ ###
+ private
+
+ def val_as_type op
+ type = op[0][0]
+ val = op[1]
+
+ case type
+ when /^long/, /^rb_num_t/, /^lindex_t/
+ "INT2FIX(#{val})"
+ when /^VALUE/
+ val
+ when /^ID/
+ "INT2FIX(#{val})"
+ when /^ISEQ/, /^rb_insn_func_t/
+ val
+ when /GENTRY/
+ raise
+ when /^\.\.\./
+ raise
+ else
+ raise "type: #{type}"
+ end
+ end
+
+ # optinsn.inc
+ def optinsn_inc
+ opt_insns_map = Hash.new{[]}
+
+ @insns.each{|insn|
+ next if insn.defopes.size == 0
+ next if insn.type == :sc
+ next if /^UNIFIED/ =~ insn.name.to_s
+
+ originsn = insn.orig
+ opt_insns_map[originsn] <<= insn
+ }
+
+ opt_insns_map.each_value do |optinsns|
+ sorted = optinsns.sort_by {|opti|
+ opti.defopes.find_all{|e| e[1] == '*'}.size
+ }
+ optinsns.replace(sorted)
+ end
+
+ template('optinsn.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # optunifs.inc
+ class OptUnifsIncGenerator < SourceCodeGenerator
+ def generate
+ template('optunifs.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # opt_sc.inc
+ class OptSCIncGenerator < SourceCodeGenerator
+ def generate
+ sc_insn_info = []
+ @insns.each{|insn|
+ insns = insn.sc
+ if insns.size > 0
+ insns = ['SC_ERROR'] + insns.map{|e| " BIN(#{e.name})"}
+ else
+ insns = Array.new(6){'SC_ERROR'}
+ end
+ sc_insn_info << " {\n#{insns.join(",\n")}}"
+ }
+ sc_insn_info = sc_insn_info.join(",\n")
+
+ sc_insn_next = @insns.map{|insn|
+ " SCS_#{InstructionsLoader.complement_name(insn.nextsc).upcase}" +
+ (verbose? ? " /* #{insn.name} */" : '')
+ }.join(",\n")
+ template('opt_sc.inc.tmpl').result(binding)
+ end
+ end
+
+ ###################################################################
+ # yasmdata.rb
+ class YASMDataRbGenerator < SourceCodeGenerator
+ def generate
+ insn_id2no = ''
+ @insns.each_with_index{|insn, i|
+ insn_id2no << " :#{insn.name} => #{i},\n"
+ }
+ template('yasmdata.rb').result(binding)
+ end
+ end
+
+ ###################################################################
+ # yarvarch.*
+ class YARVDocGenerator < SourceCodeGenerator
+ def generate
+
+ end
+
+ def desc lang
+ d = ''
+ i = 0
+ cat = nil
+ @insns.each{|insn|
+ seq = insn.opes.map{|t,v| v}.join(' ')
+ before = insn.pops.reverse.map{|t,v| v}.join(' ')
+ after = insn.rets.reverse.map{|t,v| v}.join(' ')
+
+ if cat != insn.comm[:c]
+ d << "** #{insn.comm[:c]}\n\n"
+ cat = insn.comm[:c]
+ end
+
+ d << "*** #{insn.name}\n"
+ d << "\n"
+ d << insn.comm[lang] << "\n\n"
+ d << ":instruction sequence: 0x%02x " % i << seq << "\n"
+ d << ":stack: #{before} => #{after}\n\n"
+ i+=1
+ }
+ d
+ end
+
+ def desc_ja
+ d = desc :j
+ template('yarvarch.ja').result(binding)
+ end
+
+ def desc_en
+ d = desc :e
+ template('yarvarch.en').result(binding)
+ end
+ end
+
+ class SourceCodeGenerator
+ Files = { # codes
+ 'vm.inc' => VmBodyGenerator,
+ 'vmtc.inc' => VmTCIncGenerator,
+ 'insns.inc' => InsnsIncGenerator,
+ 'insns_info.inc' => InsnsInfoIncGenerator,
+ # 'minsns.inc' => MInsnsIncGenerator,
+ 'optinsn.inc' => OptInsnIncGenerator,
+ 'optunifs.inc' => OptUnifsIncGenerator,
+ 'opt_sc.inc' => OptSCIncGenerator,
+ 'yasmdata.rb' => YASMDataRbGenerator,
+ }
+
+ def generate args = []
+ args = Files.keys if args.empty?
+ args.each{|fn|
+ s = Files[fn].new(@insns).generate
+ open(output_path(fn), 'w') {|f| f.puts(s)}
+ }
+ end
+
+ def self.def_options(opt)
+ opts = {
+ :"insns.def" => 'insns.def',
+ :"opope.def" => 'defs/opt_operand.def',
+ :"unif.def" => 'defs/opt_insn_unif.def',
+ }
+
+ opt.on("-Dname", /\AOPT_(\w+)\z/, "enable VM option") {|s, v|
+ opts[v] = true
+ }
+ opt.on("--enable=name[,name...]", Array,
+ "enable VM options (without OPT_ prefix)") {|*a|
+ a.each {|v| opts[v] = true}
+ }
+ opt.on("-Uname", /\AOPT_(\w+)\z/, "disable VM option") {|s, v|
+ opts[v] = false
+ }
+ opt.on("--disable=name[,name...]", Array,
+ "disable VM options (without OPT_ prefix)") {|*a|
+ a.each {|v| opts[v] = false}
+ }
+ opt.on("-i", "--insnsdef=FILE", "--instructions-def",
+ "instructions definition file") {|n|
+ opts[:insns_def] = n
+ }
+ opt.on("-o", "--opt-operanddef=FILE", "--opt-operand-def",
+ "vm option: operand definition file") {|n|
+ opts[:opope_def] = n
+ }
+ opt.on("-u", "--opt-insnunifdef=FILE", "--opt-insn-unif-def",
+ "vm option: instruction unification file") {|n|
+ opts[:unif_def] = n
+ }
+ opt.on("-C", "--[no-]use-const",
+ "use consts for default operands instead of macros") {|v|
+ opts[:use_const] = v
+ }
+ opt.on("-d", "--destdir", "--output-directory=DIR",
+ "make output file underneath DIR") {|v|
+ opts[:destdir] = v
+ }
+ opt.on("-V", "--[no-]verbose") {|v|
+ opts[:verbose] = v
+ }
+
+ vpath = VPath.new
+ vpath.def_options(opt)
+
+ proc {
+ opts[:VPATH] = vpath
+ build opts
+ }
+ end
+
+ def self.build opts, vpath = ['./']
+ opts[:VPATH] ||= VPath.new(*vpath)
+ self.new InstructionsLoader.new(opts)
+ end
+ end
+end
+
diff --git a/tool/jisx0208.rb b/tool/jisx0208.rb
new file mode 100644
index 0000000000..30185fb81b
--- /dev/null
+++ b/tool/jisx0208.rb
@@ -0,0 +1,86 @@
+# Library used by tools/enc-emoji-citrus-gen.rb
+
+module JISX0208
+ class Char
+ class << self
+ def from_sjis(sjis)
+ unless 0x8140 <= sjis && sjis <= 0xFCFC
+ raise ArgumentError, "out of the range of JIS X 0208: 0x#{sjis.to_s(16)}"
+ end
+ sjis_hi, sjis_lo = sjis >> 8, sjis & 0xFF
+ sjis_hi = (sjis_hi - ((sjis_hi <= 0x9F) ? 0x80 : 0xC0)) << 1
+ if sjis_lo <= 0x9E
+ sjis_hi -= 1
+ sjis_lo -= (sjis_lo <= 0x7E) ? 0x3F : 0x40
+ else
+ sjis_lo -= 0x9E
+ end
+ return self.new(sjis_hi, sjis_lo)
+ end
+ end
+
+ def initialize(row, cell=nil)
+ if cell
+ @code = row_cell_to_code(row, cell)
+ else
+ @code = row.to_int
+ end
+ end
+
+ def ==(other)
+ if self.class === other
+ return Integer(self) == Integer(other)
+ end
+ return super(other)
+ end
+
+ def to_int
+ return @code
+ end
+
+ def hi
+ Integer(self) >> 8
+ end
+
+ def lo
+ Integer(self) & 0xFF
+ end
+
+ def row
+ self.hi - 0x20
+ end
+
+ def cell
+ self.lo - 0x20
+ end
+
+ def succ
+ succ_hi, succ_lo = self.hi, self.lo + 1
+ if succ_lo > 0x7E
+ succ_lo = 0x21
+ succ_hi += 1
+ end
+ return self.class.new(succ_hi << 8 | succ_lo)
+ end
+
+ def to_sjis
+ h, l = self.hi, self.lo
+ h = (h + 1) / 2 + ((0x21..0x5E).include?(h) ? 0x70 : 0xB0)
+ l += self.hi.odd? ? 0x1F + ((l >= 0x60) ? 1 : 0) : 0x7E
+ return h << 8 | l
+ end
+
+ def inspect
+ "#<JISX0208::Char:#{self.object_id.to_s(16)} sjis=#{self.to_sjis.to_s(16)} jis=#{self.to_int.to_s(16)}>"
+ end
+
+ private
+
+ def row_cell_to_code(row, cell)
+ unless 0 < row && (1..94).include?(cell)
+ raise ArgumentError, "out of row-cell range: #{row}-#{cell}"
+ end
+ return (row + 0x20) << 8 | (cell + 0x20)
+ end
+ end
+end
diff --git a/tool/make-snapshot b/tool/make-snapshot
new file mode 100755
index 0000000000..b040aa73f8
--- /dev/null
+++ b/tool/make-snapshot
@@ -0,0 +1,467 @@
+#!/usr/bin/ruby -s
+# -*- coding: us-ascii -*-
+require 'uri'
+require 'digest/sha1'
+require 'digest/sha2'
+require 'fileutils'
+require 'shellwords'
+require 'tmpdir'
+require File.expand_path("../vcs", __FILE__)
+require File.expand_path("../colorize", __FILE__)
+STDOUT.sync = true
+
+$srcdir ||= nil
+$exported = nil if ($exported ||= nil) == ""
+$archname = nil if ($archname ||= nil) == ""
+$keep_temp ||= nil
+$patch_file ||= nil
+$packages ||= nil
+$digests ||= nil
+$tooldir = File.expand_path("..", __FILE__)
+$unicode_version = nil if ($unicode_version ||= nil) == ""
+$colorize = Colorize.new
+
+def usage
+ <<USAGE
+usage: #{File.basename $0} [option...] new-directory-to-save [version ...]
+options:
+ -srcdir=PATH source directory path
+ -exported=PATH make snapshot from already exported working directory
+ -archname=NAME make the basename of snapshots NAME
+ -keep_temp keep temporary working directory
+ -patch_file=PATCH apply PATCH file after export
+ -packages=PKG[,...] make PKG packages (#{PACKAGES.keys.join(", ")})
+ -digests=ALG[,...] show ALG digests (#{DIGESTS.join(", ")})
+version:
+ trunk, stable, branches/*, tags/*, X.Y, X.Y.Z, X.Y.Z-pL
+each versions may be followed by optional @revision.
+USAGE
+end
+
+DIGESTS = %w[SHA1 SHA256 SHA512]
+PACKAGES = {
+ "bzip" => %w".tar.bz2 bzip2 -c",
+ "gzip" => %w".tar.gz gzip -c",
+ "xz" => %w".tar.xz xz -c",
+ "zip" => %w".zip zip -qr",
+}
+
+if system("7z", out: IO::NULL)
+ PACKAGES["gzip"] = %w".tar.gz 7z a dummy -tgzip -mx -so"
+ PACKAGES["zip"] = %w".zip 7z a -tzip -mx" << {out: IO::NULL}
+end
+if gzip = ENV.delete("GZIP")
+ PACKAGES["gzip"].concat(gzip.shellsplit)
+end
+
+ENV["LC_ALL"] = ENV["LANG"] = "C"
+SVNURL = URI.parse("http://svn.ruby-lang.org/repos/ruby/")
+GITURL = URI.parse("git://github.com/ruby/ruby.git")
+RUBY_VERSION_PATTERN = /^\#define\s+RUBY_VERSION\s+"([\d.]+)"/
+
+ENV["VPATH"] ||= "include/ruby"
+YACC = ENV["YACC"] ||= "bison"
+ENV["BASERUBY"] ||= "ruby"
+ENV["RUBY"] ||= "ruby"
+ENV["MV"] ||= "mv"
+ENV["RM"] ||= "rm -f"
+ENV["MINIRUBY"] ||= "ruby"
+ENV["PROGRAM"] ||= "ruby"
+ENV["AUTOCONF"] ||= "autoconf"
+ENV["BUILTIN_TRANSOBJS"] ||= "newline.o"
+
+class String
+ # for older ruby
+ alias bytesize size unless method_defined?(:bytesize)
+end
+
+class Dir
+ def self.mktmpdir(path)
+ path = File.join(tmpdir, path+"-#{$$}-#{rand(100000)}")
+ begin
+ mkdir(path)
+ rescue Errno::EEXIST
+ path.succ!
+ retry
+ end
+ path
+ end unless respond_to?(:mktmpdir)
+end
+
+$packages &&= $packages.split(/[, ]+/).tap {|pkg|
+ if all = pkg.index("all")
+ pkg[all, 1] = PACKAGES.keys - pkg
+ end
+ pkg -= PACKAGES.keys
+ pkg.empty? or abort "#{File.basename $0}: unknown packages - #{pkg.join(", ")}"
+}
+$packages ||= PACKAGES.keys
+
+$digests &&= $digests.split(/[, ]+/).tap {|dig|
+ dig -= DIGESTS
+ dig.empty? or abort "#{File.basename $0}: unknown digests - #{dig.join(", ")}"
+}
+$digests ||= DIGESTS
+
+$patch_file &&= File.expand_path($patch_file)
+path = ENV["PATH"].split(File::PATH_SEPARATOR)
+%w[YACC BASERUBY RUBY MV MINIRUBY].each do |var|
+ cmd, = ENV[var].shellsplit
+ unless path.any? {|dir|
+ file = File.expand_path(cmd, dir)
+ File.file?(file) and File.executable?(file)
+ }
+ abort "#{File.basename $0}: #{var} command not found - #{cmd}"
+ end
+end
+
+%w[BASERUBY RUBY MINIRUBY].each do |var|
+ `#{ENV[var]} --disable-gem -e1 2>&1`
+ if $?.success?
+ ENV[var] += ' --disable-gem'
+ end
+end
+
+if defined?($help) or defined?($_help)
+ puts usage
+ exit
+end
+unless destdir = ARGV.shift
+ abort usage
+end
+revisions = ARGV.empty? ? ["trunk"] : ARGV
+unless tmp = $exported
+ FileUtils.mkpath(destdir)
+ destdir = File.expand_path(destdir)
+ tmp = Dir.mktmpdir("ruby-snapshot")
+ FileUtils.mkpath(tmp)
+ at_exit {
+ Dir.chdir "/"
+ FileUtils.rm_rf(tmp)
+ } unless $keep_temp
+end
+
+def package(vcs, rev, destdir, tmp = nil)
+ patchlevel = false
+ prerelease = false
+ if revision = rev[/@(\d+)\z/, 1]
+ rev = $`
+ end
+ case rev
+ when /\Atrunk\z/
+ url = vcs.trunk
+ when /\Abranches\//
+ url = vcs.branch($')
+ when /\Atags\//
+ url = vcs.tag($')
+ when /\Astable\z/
+ vcs.branch_list("ruby_[0-9]*") {|n| url = n[/\Aruby_\d+_\d+\z/]}
+ url &&= vcs.branch(url)
+ when /\A(.*)\.(.*)\.(.*)-(preview|rc)(\d+)/
+ prerelease = true
+ tag = "#{$4}#{$5}"
+ url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}#{$5}")
+ when /\A(.*)\.(.*)\.(.*)-p(\d+)/
+ patchlevel = true
+ tag = "p#{$4}"
+ url = vcs.tag("v#{$1}_#{$2}_#{$3}_#{$4}")
+ when /\A(\d+)\.(\d+)(?:\.(\d+))?\z/
+ if $3 && ($1 > "2" || $1 == "2" && $2 >= "1")
+ patchlevel = true
+ tag = ""
+ url = vcs.tag("v#{$1}_#{$2}_#{$3}")
+ else
+ url = vcs.branch("ruby_#{rev.tr('.', '_')}")
+ end
+ else
+ warn "#{$0}: unknown version - #{rev}"
+ return
+ end
+ revision ||= vcs.get_revisions(url)[1]
+ version = nil
+ unless revision
+ url = vcs.trunk
+ vcs.grep(RUBY_VERSION_PATTERN, url, "version.h") {version = $1}
+ unless rev == version
+ warn "#{$0}: #{rev} not found"
+ return
+ end
+ revision = vcs.get_revisions(url)[1]
+ end
+ v = nil
+ if $exported
+ if String === $exported
+ v = $exported
+ end
+ else
+ v = "ruby"
+ puts "Exporting #{rev}@#{revision}"
+ exported = tmp ? File.join(tmp, v) : v
+ unless vcs.export(revision, url, exported, true) {|line| print line}
+ warn("Export failed")
+ return
+ end
+ if $srcdir
+ Dir.glob($srcdir + "/{tool/config.{guess,sub},gems/*.gem,.downloaded-cache/*,enc/unicode/data/**/*.txt}") do |file|
+ puts "copying #{file}"
+ dest = exported + file[$srcdir.size..-1]
+ FileUtils.mkpath(File.dirname(dest))
+ begin
+ FileUtils.ln(file, dest, force: true)
+ next unless File.symlink?(dest)
+ File.unlink(dest)
+ rescue SystemCallError
+ end
+ begin
+ FileUtils.cp_r(file, dest, preserve: true)
+ rescue SystemCallError
+ end
+ end
+ end
+ end
+
+ status = IO.read(File.dirname(__FILE__) + "/prereq.status")
+ Dir.chdir(tmp) if tmp
+
+ if !File.directory?(v)
+ v = Dir.glob("ruby-*").select(&File.method(:directory?))
+ v.size == 1 or abort "#{File.basename $0}: not exported"
+ v = v[0]
+ end
+
+ unless File.exist?("#{v}/ChangeLog")
+ # get last revision from previous ChangeLog archive
+ last_ChangeLog = Dir["#{v}/doc/ChangeLog-*"].grep(/-(\d+)\z/) {|n| [$1.to_i, n]}.max[1]
+ open(last_ChangeLog) do |f|
+ f.readline
+ unless /\Ar(\d+) / =~ f.readline
+ abort "#{File.basename $0}: Cannot find revision from '#{last_ChangeLog}'"
+ end
+ vcs.export_changelog(url, $1.to_i, revision.to_i, "#{v}/ChangeLog")
+ end
+ end
+
+ open("#{v}/revision.h", "wb") {|f| f.puts "#define RUBY_REVISION #{revision}"}
+ version ||= (versionhdr = IO.read("#{v}/version.h"))[RUBY_VERSION_PATTERN, 1]
+ version or return
+ if patchlevel
+ unless tag.empty?
+ versionhdr ||= IO.read("#{v}/version.h")
+ patchlevel = versionhdr[/^\#define\s+RUBY_PATCHLEVEL\s+(\d+)/, 1]
+ tag = (patchlevel ? "p#{patchlevel}" : "r#{revision}")
+ end
+ elsif prerelease
+ versionhdr ||= IO.read("#{v}/version.h")
+ versionhdr.sub!(/^\#define\s+RUBY_PATCHLEVEL_STR\s+"\K.+?(?=")/, tag)
+ IO.write("#{v}/version.h", versionhdr)
+ else
+ tag ||= "r#{revision}"
+ end
+ unless v == $exported
+ if $archname
+ n = $archname
+ elsif tag.empty?
+ n = "ruby-#{version}"
+ else
+ n = "ruby-#{version}-#{tag}"
+ end
+ File.directory?(n) or File.rename v, n
+ v = n
+ end
+ system(*%W"patch -d #{v} -p0 -i #{$patch_file}") if $patch_file
+ if !$exported or $patch_file
+ colors = %w[red yellow green cyan blue magenta]
+ "take a breath, and go ahead".scan(/./) do |c|
+ if c == ' '
+ print c
+ else
+ colors.push(color = colors.shift)
+ print $colorize.decorate(c, color)
+ end
+ sleep(c == "," ? 0.7 : 0.05)
+ end
+ puts
+ end
+ def (clean = []).add(n) push(n); n end
+ Dir.chdir(v) do
+ File.open(clean.add("cross.rb"), "w") do |f|
+ f.puts "Object.__send__(:remove_const, :CROSS_COMPILING) if defined?(CROSS_COMPILING)"
+ f.puts "CROSS_COMPILING=true"
+ f.puts "Object.__send__(:remove_const, :RUBY_PLATFORM)"
+ f.puts "RUBY_PLATFORM='none'"
+ f.puts "Object.__send__(:remove_const, :RUBY_VERSION)"
+ f.puts "RUBY_VERSION='#{version}'"
+ end
+ unless File.exist?("configure")
+ print "creating configure..."
+ unless system([ENV["AUTOCONF"]]*2)
+ puts $colorize.fail(" failed")
+ return
+ end
+ puts $colorize.pass(" done")
+ end
+ clean.add("autom4te.cache")
+ clean.add("enc/unicode/data")
+ print "creating prerequisites..."
+ if File.file?("common.mk") && /^prereq/ =~ commonmk = IO.read("common.mk")
+ puts
+ extout = clean.add('tmp')
+ begin
+ status = IO.read("tool/prereq.status")
+ rescue Errno::ENOENT
+ # use fallback file
+ end
+ File.open(clean.add("config.status"), "w") {|f|
+ f.print status
+ }
+ FileUtils.mkpath(hdrdir = "#{extout}/include/ruby")
+ File.open("#{hdrdir}/config.h", "w") {}
+ FileUtils.mkpath(defaults = "#{extout}/rubygems/defaults")
+ File.open("#{defaults}/operating_system.rb", "w") {}
+ File.open("#{defaults}/ruby.rb", "w") {}
+ miniruby = ENV['MINIRUBY'] + " -I. -I#{extout} -rcross"
+ baseruby = ENV["BASERUBY"]
+ mk = IO.read("Makefile.in").gsub(/^@.*\n/, '')
+ vars = {
+ "EXTOUT"=>extout,
+ "PATH_SEPARATOR"=>File::PATH_SEPARATOR,
+ "MINIRUBY"=>miniruby,
+ "RUBY"=>ENV["RUBY"],
+ "BASERUBY"=>baseruby,
+ "PWD"=>Dir.pwd,
+ "ruby_version"=>version,
+ }
+ status.scan(/^s([%,])@([A-Za-z_][A-Za-z_0-9]*)@\1(.*?)\1g$/) do
+ vars[$2] ||= $3
+ end
+ vars["UNICODE_VERSION"] = $unicode_version if $unicode_version
+ args = vars.dup
+ mk.gsub!(/@([A-Za-z_]\w*)@/) {args.delete($1); vars[$1] || ENV[$1]}
+ mk << commonmk.gsub(/\{\$([^(){}]*)[^{}]*\}/, "")
+ mk << <<-'APPEND'
+
+prereq: clean-cache $(CLEAN_CACHE)
+clean-cache $(CLEAN_CACHE): after-update
+touch-unicode-files: _touch-unicode-files
+update-download:: touch-unicode-files
+update-download:: update-gems
+after-update:: extract-gems
+extract-gems: update-gems
+update-gems:
+_touch-unicode-files:
+ $(MAKEDIRS) $(UNICODE_SRC_DATA_DIR)
+ touch $(UNICODE_SRC_DATA_DIR)/.unicode-tables.time $(UNICODE_DATA_HEADERS)
+ APPEND
+ open(clean.add("Makefile"), "w") do |f|
+ f.puts "prereq: update-download"
+ f.puts mk
+ end
+ ENV["CACHE_SAVE"] = "no"
+ system(ENV["MAKE"] || ENV["make"] || "make", "prereq", *args.map {|arg| arg.join("=")})
+ clean.push("rbconfig.rb", ".rbconfig.time", "enc.mk")
+ print "prerequisites"
+ else
+ system(*%W"#{YACC} -o parse.c parse.y")
+ end
+ vcs.after_export(".") if exported
+ FileUtils.rm_rf(clean) unless $keep_temp
+ FileUtils.rm_rf(".downloaded-cache")
+ if File.exist?("gems/bundled_gems")
+ gems = Dir.glob("gems/*.gem")
+ gems -= File.readlines("gems/bundled_gems").map {|line|
+ name, version, _ = line.split(' ')
+ "gems/#{name}-#{version}.gem"
+ }
+ FileUtils.rm_f(gems)
+ else
+ FileUtils.rm_rf("gems")
+ end
+ unless $?.success?
+ puts $colorize.fail(" failed")
+ return
+ end
+ puts $colorize.pass(" done")
+ end
+
+ if v == "."
+ v = File.basename(Dir.pwd)
+ Dir.chdir ".."
+ else
+ Dir.chdir(File.dirname(v))
+ v = File.basename(v)
+ end
+
+ tarball = nil
+ return $packages.collect do |mesg|
+ (ext, *cmd) = PACKAGES[mesg]
+ File.directory?(destdir) or FileUtils.mkpath(destdir)
+ file = File.join(destdir, "#{$archname||v}#{ext}")
+ case ext
+ when /\.tar/
+ if tarball
+ next if tarball.empty?
+ else
+ tarball = "#{$archname||v}.tar"
+ print "creating tarball... #{tarball}"
+ if system("tar", "cf", tarball, v)
+ puts $colorize.pass(" done")
+ else
+ puts $colorize.fail(" failed")
+ tarball = ""
+ next
+ end
+ end
+ print "creating #{mesg} tarball... #{file}"
+ done = system(*cmd, tarball, out: file)
+ else
+ print "creating #{mesg} archive... #{file}"
+ if Hash === cmd.last
+ *cmd, opt = *cmd
+ cmd << file << v << opt
+ else
+ (cmd = cmd.dup) << file << v
+ end
+ done = system(*cmd)
+ end
+ if done
+ puts $colorize.pass(" done")
+ file
+ else
+ puts $colorize.fail(" failed")
+ nil
+ end
+ end.compact
+ensure
+ FileUtils.rm_rf(v) if v and !$exported and !$keep_temp
+end
+
+if [$srcdir, ($svn||=nil), ($git||=nil)].compact.size > 1
+ abort "#{File.basename $0}: -srcdir, -svn, and -git are exclusive"
+end
+if $srcdir
+ vcs = VCS.detect($srcdir)
+elsif $svn
+ vcs = VCS::SVN.new($svn == true ? SVNURL : URI.parse($svn))
+elsif $git
+ vcs = VCS::GIT.new($git == true ? GITURL : URI.parse($git))
+else
+ vcs = VCS::SVN.new(SVNURL)
+end
+
+success = true
+revisions.collect {|rev| package(vcs, rev, destdir, tmp)}.flatten.each do |name|
+ if !name
+ success = false
+ next
+ end
+ str = open(name, "rb") {|f| f.read}
+ puts "* #{$colorize.pass(name)}"
+ puts " SIZE: #{str.bytesize} bytes"
+ $digests.each do |alg|
+ printf " %-8s%s\n", "#{alg}:", Digest.const_get(alg).hexdigest(str)
+ end
+end
+
+exit false if !success
+
+# vim:fileencoding=US-ASCII sw=2 ts=4 noexpandtab ff=unix
diff --git a/tool/make_hgraph.rb b/tool/make_hgraph.rb
new file mode 100644
index 0000000000..0f388814dd
--- /dev/null
+++ b/tool/make_hgraph.rb
@@ -0,0 +1,95 @@
+#
+# Make dot file of internal class/module hierarchy graph.
+#
+
+require 'objspace'
+
+module ObjectSpace
+ def self.object_id_of obj
+ if obj.kind_of?(ObjectSpace::InternalObjectWrapper)
+ obj.internal_object_id
+ else
+ obj.object_id
+ end
+ end
+
+ T_ICLASS_NAME = {}
+
+ def self.class_name_of klass
+ case klass
+ when Class, Module
+ # (singleton class).name returns nil
+ klass.name || klass.inspect
+ when InternalObjectWrapper # T_ICLASS
+ if klass.type == :T_ICLASS
+ "#<I:#{class_name_of(ObjectSpace.internal_class_of(klass))}>"
+ else
+ klass.inspect
+ end
+ else
+ klass.inspect
+ end
+ end
+
+ def self.module_refenreces klass
+ h = {} # object_id -> [klass, class_of, super]
+ stack = [klass]
+ while klass = stack.pop
+ obj_id = ObjectSpace.object_id_of(klass)
+ next if h.has_key?(obj_id)
+ cls = ObjectSpace.internal_class_of(klass)
+ sup = ObjectSpace.internal_super_of(klass)
+ stack << cls if cls
+ stack << sup if sup
+ h[obj_id] = [klass, cls, sup].map{|e| ObjectSpace.class_name_of(e)}
+ end
+ h.values
+ end
+
+ def self.module_refenreces_dot klass
+ result = []
+ rank_set = {}
+
+ result << "digraph mod_h {"
+ # result << " rankdir=LR;"
+ module_refenreces(klass).each{|(m, k, s)|
+ # next if /singleton/ =~ m
+ result << "#{m.dump} -> #{s.dump} [label=\"super\"];"
+ result << "#{m.dump} -> #{k.dump} [label=\"klass\"];"
+
+ unless rank = rank_set[m]
+ rank = rank_set[m] = 0
+ end
+ unless rank_set[s]
+ rank_set[s] = rank + 1
+ end
+ unless rank_set[k]
+ rank_set[k] = rank
+ end
+ }
+
+ rs = [] # [[mods...], ...]
+ rank_set.each{|m, r|
+ rs[r] = [] unless rs[r]
+ rs[r] << m
+ }
+
+ rs.each{|ms|
+ result << "{rank = same; #{ms.map{|m| m.dump}.join(", ")}};"
+ }
+ result << "}"
+ result.join("\n")
+ end
+
+ def self.module_refenreces_image klass, file
+ dot = module_refenreces_dot(klass)
+ img = nil
+ IO.popen("dot -Tpng", 'r+'){|io|
+ #
+ io.puts dot
+ io.close_write
+ img = io.read
+ }
+ open(File.expand_path(file), 'w+'){|f| f.puts img}
+ end
+end
diff --git a/tool/mdoc2man.rb b/tool/mdoc2man.rb
new file mode 100755
index 0000000000..e005fcf19a
--- /dev/null
+++ b/tool/mdoc2man.rb
@@ -0,0 +1,505 @@
+#!/usr/bin/env ruby
+###
+### mdoc2man - mdoc to man converter
+###
+### Quick usage: mdoc2man.rb < mdoc_manpage.8 > man_manpage.8
+###
+### Ported from Perl by Akinori MUSHA.
+###
+### Copyright (c) 2001 University of Illinois Board of Trustees
+### Copyright (c) 2001 Mark D. Roth
+### Copyright (c) 2002, 2003 Akinori MUSHA
+### All rights reserved.
+###
+### Redistribution and use in source and binary forms, with or without
+### modification, are permitted provided that the following conditions
+### are met:
+### 1. Redistributions of source code must retain the above copyright
+### notice, this list of conditions and the following disclaimer.
+### 2. Redistributions in binary form must reproduce the above copyright
+### notice, this list of conditions and the following disclaimer in the
+### documentation and/or other materials provided with the distribution.
+### 3. All advertising materials mentioning features or use of this software
+### must display the following acknowledgement:
+### This product includes software developed by the University of
+### Illinois at Urbana, and their contributors.
+### 4. The University nor the names of their
+### contributors may be used to endorse or promote products derived from
+### this software without specific prior written permission.
+###
+### THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
+### ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+### IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+### ARE DISCLAIMED. IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
+### FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+### DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+### OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+### HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+### LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+### OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+### SUCH DAMAGE.
+###
+### $Id$
+###
+
+class Mdoc2Man
+ ANGLE = 1
+ OPTION = 2
+ PAREN = 3
+
+ RE_PUNCT = /^[!"'),\.\/:;>\?\]`]$/
+
+ def initialize
+ @name = @date = @id = nil
+ @refauthors = @reftitle = @refissue = @refdate = @refopt = nil
+
+ @optlist = 0 ### 1 = bullet, 2 = enum, 3 = tag, 4 = item
+ @oldoptlist = 0
+ @nospace = 0 ### 0, 1, 2
+ @enum = 0
+ @synopsis = true
+ @reference = false
+ @ext = false
+ @extopt = false
+ @literal = false
+ end
+
+ def mdoc2man(i, o)
+ i.each { |line|
+ if /^\./ !~ line
+ o.print line
+ o.print ".br\n" if @literal
+ next
+ end
+
+ line.slice!(0, 1)
+
+ next if /\\"/ =~ line
+
+ line = parse_macro(line) and o.print line
+ }
+
+ initialize
+ end
+
+ def shift_arg(words)
+ case words[0]
+ when nil, RE_PUNCT
+ nil
+ when /\A"(.+)/
+ words.shift
+ word = $1
+ loop {
+ break if word.chomp!('"')
+ token = words.shift or break
+ word << ' ' << token
+ }
+ word
+ else
+ words.shift
+ end
+ end
+
+ def parse_macro(line)
+ words = line.split
+ retval = ''
+
+ quote = []
+ dl = false
+
+ while word = words.shift
+ case word
+ when RE_PUNCT
+ next retval << word if word == ':'
+ while q = quote.pop
+ case q
+ when OPTION
+ retval << ']'
+ when PAREN
+ retval << ')'
+ when ANGLE
+ retval << '>'
+ end
+ end
+ retval << word
+ next
+ when 'Li', 'Pf'
+ @nospace = 1
+ next
+ when 'Xo'
+ @ext = true
+ retval << ' ' unless retval.empty? || /[\n ]\z/ =~ retval
+ next
+ when 'Xc'
+ @ext = false
+ retval << "\n" unless @extopt
+ break
+ when 'Bd'
+ @literal = true if words[0] == '-literal'
+ retval << "\n"
+ break
+ when 'Ed'
+ @literal = false
+ break
+ when 'Ns'
+ @nospace = 1 if @nospace == 0
+ retval.chomp!(' ')
+ next
+ when 'No'
+ retval.chomp!(' ')
+ retval << words.shift
+ next
+ when 'Dq'
+ retval << '``'
+ begin
+ retval << words.shift << ' '
+ end until words.empty? || RE_PUNCT =~ words[0]
+ retval.chomp!(' ')
+ retval << '\'\''
+ @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ when 'Sq', 'Ql'
+ retval << '`' << words.shift << '\''
+ @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ # when 'Ic'
+ # retval << '\\fB' << words.shift << '\\fP'
+ # next
+ when 'Oo'
+ #retval << "[\\c\n"
+ @extopt = true
+ @nospace = 1 if @nospace == 0
+ retval << '['
+ next
+ when 'Oc'
+ @extopt = false
+ retval << ']'
+ next
+ when 'Ao'
+ @nospace = 1 if @nospace == 0
+ retval << '<'
+ next
+ when 'Ac'
+ retval << '>'
+ next
+ end
+
+ retval << ' ' if @nospace == 0 && !(retval.empty? || /[\n ]\z/ =~ retval)
+ @nospace = 0 if @nospace == 1
+
+ case word
+ when 'Dd'
+ @date = words.join(' ')
+ return nil
+ when 'Dt'
+ if words.size >= 2 && words[1] == '""' &&
+ /^(.*)\(([0-9])\)$/ =~ words[0]
+ words[0] = $1
+ words[1] = $2
+ end
+ @id = words.join(' ')
+ return nil
+ when 'Os'
+ retval << '.TH ' << @id << ' "' << @date << '" "' <<
+ words.join(' ') << '"'
+ break
+ when 'Sh'
+ retval << '.SH'
+ @synopsis = (words[0] == 'SYNOPSIS')
+ next
+ when 'Xr'
+ retval << '\\fB' << words.shift <<
+ '\\fP(' << words.shift << ')' << (words.shift||'')
+ break
+ when 'Rs'
+ @refauthors = []
+ @reftitle = ''
+ @refissue = ''
+ @refdate = ''
+ @refopt = ''
+ @reference = true
+ break
+ when 'Re'
+ retval << "\n"
+
+ # authors
+ while @refauthors.size > 1
+ retval << @refauthors.shift << ', '
+ end
+ retval << 'and ' unless retval.empty?
+ retval << @refauthors.shift
+
+ # title
+ retval << ', \\fI' << @reftitle << '\\fP'
+
+ # issue
+ retval << ', ' << @refissue unless @refissue.empty?
+
+ # date
+ retval << ', ' << @refdate unless @refdate.empty?
+
+ # optional info
+ retval << ', ' << @refopt unless @refopt.empty?
+
+ retval << ".\n"
+
+ @reference = false
+ break
+ when 'An'
+ next
+ when 'Dl'
+ retval << ".nf\n" << '\\& '
+ dl = true
+ next
+ when 'Ux'
+ retval << "UNIX"
+ next
+ when 'Bro'
+ retval << '{'
+ @nospace = 1 if @nospace == 0
+ next
+ when 'Brc'
+ retval.sub!(/ *\z/, '}')
+ next
+ end
+
+ if @reference
+ case word
+ when '%A'
+ @refauthors.unshift(words.join(' '))
+ break
+ when '%T'
+ @reftitle = words.join(' ')
+ @reftitle.sub!(/^"/, '')
+ @reftitle.sub!(/"$/, '')
+ break
+ when '%N'
+ @refissue = words.join(' ')
+ break
+ when '%D'
+ @refdate = words.join(' ')
+ break
+ when '%O'
+ @refopt = words.join(' ')
+ break
+ end
+ end
+
+ case word
+ when 'Nm'
+ name = words.empty? ? @name : words.shift
+ @name ||= name
+ retval << ".br\n" if @synopsis
+ retval << "\\fB" << name << "\\fP"
+ @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ when 'Nd'
+ retval << '\\-'
+ next
+ when 'Fl'
+ retval << '\\fB\\-' << words.shift << '\\fP'
+ @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ when 'Ar'
+ retval << '\\fI'
+ if words.empty?
+ retval << 'file ...\\fP'
+ else
+ retval << words.shift << '\\fP'
+ while words[0] == '|'
+ retval << ' ' << words.shift << ' \\fI' << words.shift << '\\fP'
+ end
+ @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ end
+ when 'Cm'
+ retval << '\\fB' << words.shift << '\\fP'
+ while RE_PUNCT =~ words[0]
+ retval << words.shift
+ end
+ next
+ when 'Op'
+ quote << OPTION
+ @nospace = 1 if @nospace == 0
+ retval << '['
+ # words.push(words.pop + ']')
+ next
+ when 'Aq'
+ quote << ANGLE
+ @nospace = 1 if @nospace == 0
+ retval << '<'
+ # words.push(words.pop + '>')
+ next
+ when 'Pp'
+ retval << "\n"
+ next
+ when 'Ss'
+ retval << '.SS'
+ next
+ end
+
+ case word
+ when 'Pa'
+ if !quote.include?(OPTION)
+ retval << '\\fI'
+ retval << '\\&' if /^\./ =~ words[0]
+ retval << words.shift << '\\fP'
+ while RE_PUNCT =~ words[0]
+ retval << words.shift
+ end
+ # @nospace = 1 if @nospace == 0 && RE_PUNCT =~ words[0]
+ next
+ end
+ when 'Lk'
+ if !quote.include?(OPTION)
+ url = words.shift
+ if name = shift_arg(words)
+ retval << '\\fI' << name << ':\\fP '
+ end
+ retval << '\\fB'
+ retval << '\\&' if /\A\./ =~ url
+ retval << url << '\\fP'
+ next
+ end
+ end
+
+ case word
+ when 'Dv'
+ retval << '.BR'
+ next
+ when 'Em', 'Ev'
+ retval << '.IR'
+ next
+ when 'Pq'
+ retval << '('
+ @nospace = 1
+ quote << PAREN
+ next
+ when 'Sx', 'Sy'
+ retval << '.B ' << words.join(' ')
+ break
+ when 'Ic'
+ retval << '\\fB'
+ until words.empty? || RE_PUNCT =~ words[0]
+ case words[0]
+ when 'Op'
+ words.shift
+ retval << '['
+ words.push(words.pop + ']')
+ next
+ when 'Aq'
+ words.shift
+ retval << '<'
+ words.push(words.pop + '>')
+ next
+ when 'Ar'
+ words.shift
+ retval << '\\fI' << words.shift << '\\fP'
+ else
+ retval << words.shift
+ end
+
+ retval << ' ' if @nospace == 0
+ end
+
+ retval.chomp!(' ')
+ retval << '\\fP'
+ retval << words.shift unless words.empty?
+ break
+ when 'Bl'
+ @oldoptlist = @optlist
+
+ case words[0]
+ when '-bullet'
+ @optlist = 1
+ when '-enum'
+ @optlist = 2
+ @enum = 0
+ when '-tag'
+ @optlist = 3
+ when '-item'
+ @optlist = 4
+ end
+
+ break
+ when 'El'
+ @optlist = @oldoptlist
+ next
+ end
+
+ if @optlist != 0 && word == 'It'
+ case @optlist
+ when 1
+ # bullets
+ retval << '.IP \\(bu'
+ when 2
+ # enum
+ @enum += 1
+ retval << '.IP ' << @enum << '.'
+ when 3
+ # tags
+ retval << ".TP\n"
+ case words[0]
+ when 'Pa', 'Ev', 'Lk'
+ words.shift
+ retval << '.B'
+ end
+ when 4
+ # item
+ retval << ".IP\n"
+ end
+
+ next
+ end
+
+ case word
+ when 'Sm'
+ case words[0]
+ when 'off'
+ @nospace = 2
+ when 'on'
+ # retval << "\n"
+ @nospace = 0
+ end
+ words.shift
+ next
+ end
+
+ retval << word
+ end
+
+ return nil if retval == '.'
+
+ retval.sub!(/\A\.([^a-zA-Z])/, "\\1")
+ # retval.chomp!(' ')
+
+ while q = quote.pop
+ case q
+ when OPTION
+ retval << ']'
+ when PAREN
+ retval << ')'
+ when ANGLE
+ retval << '>'
+ end
+ end
+
+ # retval << ' ' unless @nospace == 0 || retval.empty? || /\n\z/ =~ retval
+
+ retval << ' ' unless !@ext || @extopt || / $/ =~ retval
+
+ retval << "\n" unless @ext || @extopt || retval.empty? || /\n\z/ =~ retval
+
+ retval << ".fi\n" if dl
+
+ return retval
+ end
+
+ def self.mdoc2man(i, o)
+ new.mdoc2man(i, o)
+ end
+end
+
+if $0 == __FILE__
+ Mdoc2Man.mdoc2man(ARGF, STDOUT)
+end
diff --git a/tool/merger.rb b/tool/merger.rb
new file mode 100755
index 0000000000..f7630cd02b
--- /dev/null
+++ b/tool/merger.rb
@@ -0,0 +1,312 @@
+#!/bin/sh
+# -*- ruby -*-
+exec "${RUBY-ruby}" "-x" "$0" "$@" && [ ] if false
+#!ruby
+# This needs ruby 1.9 and subversion.
+# As a Ruby committer, run this in an SVN repository
+# to commit a change.
+
+require 'fileutils'
+require 'tempfile'
+
+$repos = 'svn+ssh://svn@ci.ruby-lang.org/ruby/'
+ENV['LC_ALL'] = 'C'
+
+def help
+ puts <<-end
+\e[1msimple backport\e[0m
+ ruby #$0 1234
+
+\e[1mrange backport\e[0m
+ ruby #$0 1234:5678
+
+\e[1mbackport from other branch\e[0m
+ ruby #$0 17502 mvm
+
+\e[1mrevision increment\e[0m
+ ruby #$0 revisionup
+
+\e[1mteeny increment\e[0m
+ ruby #$0 teenyup
+
+\e[1mtagging major release\e[0m
+ ruby #$0 tag 2.2.0
+
+\e[1mtagging patch release\e[0m (about 2.1.0 or later, it means X.Y.Z (Z > 0) release)
+ ruby #$0 tag
+
+\e[1mtagging preview/RC\e[0m
+ ruby #$0 tag 2.2.0-preview1
+
+\e[1mremove tag\e[0m
+ ruby #$0 removetag 2.2.9
+
+\e[33;1m* all operations shall be applied to the working directory.\e[0m
+end
+end
+
+# Prints the version of Ruby found in version.h
+
+def version
+ v = p = nil
+ open 'version.h', 'rb' do |f|
+ f.each_line do |l|
+ case l
+ when /^#define RUBY_VERSION "(\d+)\.(\d+)\.(\d+)"$/
+ v = $~.captures
+ when /^#define RUBY_PATCHLEVEL (-?\d+)$/
+ p = $1
+ end
+ end
+ end
+ return v, p
+end
+
+def interactive str, editfile = nil
+ loop do
+ yield
+ STDERR.puts "\e[1;33m#{str} ([y]es|[a]bort|[r]etry#{'|[e]dit' if editfile})\e[0m"
+ case STDIN.gets
+ when /\Aa/i then exit
+ when /\Ar/i then redo
+ when /\Ay/i then break
+ when /\Ae/i then system(ENV["EDITOR"], editfile)
+ else exit
+ end
+ end
+end
+
+def version_up(inc=nil)
+ d = Time.now
+ d = d.localtime(9*60*60) # server is Japan Standard Time +09:00
+ system(*%w'svn revert version.h')
+ v, pl = version
+
+ if inc == :teeny
+ v[2].succ!
+ end
+ # patchlevel
+ if pl != "-1"
+ pl.succ!
+ end
+
+ str = open 'version.h', 'rb' do |f| f.read end
+ [%W[RUBY_VERSION "#{v.join '.'}"],
+ %W[RUBY_VERSION_CODE #{v.join ''}],
+ %W[RUBY_VERSION_MAJOR #{v[0]}],
+ %W[RUBY_VERSION_MINOR #{v[1]}],
+ %W[RUBY_VERSION_TEENY #{v[2]}],
+ %W[RUBY_RELEASE_DATE "#{d.strftime '%Y-%m-%d'}"],
+ %W[RUBY_RELEASE_CODE #{d.strftime '%Y%m%d'}],
+ %W[RUBY_PATCHLEVEL #{pl}],
+ %W[RUBY_RELEASE_YEAR #{d.year}],
+ %W[RUBY_RELEASE_MONTH #{d.month}],
+ %W[RUBY_RELEASE_DAY #{d.day}],
+ ].each do |(k, i)|
+ str.sub!(/^(#define\s+#{k}\s+).*$/, "\\1#{i}")
+ end
+ str.sub!(/\s+\z/m, '')
+ fn = sprintf 'version.h.tmp.%032b', rand(1 << 31)
+ File.rename 'version.h', fn
+ open 'version.h', 'wb' do |f|
+ f.puts str
+ end
+ File.unlink fn
+end
+
+def tag intv_p = false, relname=nil
+ # relname:
+ # * 2.2.0-preview1
+ # * 2.2.0-rc1
+ # * 2.2.0
+ v, pl = version
+ x = v.join('_')
+ if relname
+ abort "patchlevel is not -1 but '#{pl}' for preview or rc" if pl != '-1' && /-(?:preview|rc)/ =~ relname
+ abort "patchlevel is not 0 but '#{pl}' for the first release" if pl != '0' && /-(?:preview|rc)/ !~ relname
+ pl = relname[/-(.*)\z/, 1]
+ curver = v.join('.') + (pl ? '-' + pl : '')
+ if relname != curver
+ abort "given relname '#{relname}' conflicts current version '#{curver}'"
+ end
+ branch_url = `svn info`[/URL: (.*)/, 1]
+ else
+ if pl == '-1'
+ abort "no relname is given and not in a release branch even if this is patch release"
+ end
+ branch_url = $repos + 'branches/ruby_'
+ if v[0] < "2" || (v[0] == "2" && v[1] < "1")
+ abort "patchlevel must be greater than 0 for patch release" if pl == "0"
+ branch_url << x
+ else
+ abort "teeny must be greater than 0 for patch release" if v[2] == "0"
+ branch_url << x.sub(/_\d+$/, '')
+ end
+ end
+ tagname = 'v' + x + (v[0] < "2" || (v[0] == "2" && v[1] < "1") || /^(?:preview|rc)/ =~ pl ? '_' + pl : '')
+ tag_url = $repos + 'tags/' + tagname
+ system(*%w'svn info', tag_url, out: IO::NULL, err: IO::NULL)
+ if $?.success?
+ abort "specfied tag already exists. check tag name and remove it if you want to force re-tagging"
+ end
+ if intv_p
+ interactive "OK? svn cp -m \"add tag #{tagname}\" #{branch_url} #{tag_url}" do
+ # nothing to do here
+ end
+ end
+ system(*%w'svn cp -m', "add tag #{tagname}", branch_url, tag_url)
+ puts "run following command in git-svn working directory to push the tag into GitHub:"
+ puts "git tag #{tagname} origin/tags/#{tagname} && git push ruby #{tagname}"
+end
+
+def remove_tag intv_p = false, relname
+ # relname:
+ # * 2.2.0-preview1
+ # * 2.2.0-rc1
+ # * 2.2.0
+ # * v2_2_0_preview1
+ # * v2_2_0_rc1
+ # * v2_2_0
+ if !relname && !intv_p.is_a?(String)
+ raise ArgumentError, "relname is not specified"
+ end
+ intv_p, relname = false, intv_p if !relname && intv_p.is_a?(String)
+
+ if /^v/ !~ relname
+ tagname = 'v' + relname.tr(".-", "_")
+ else
+ tagname = relname
+ end
+ tag_url = $repos + 'tags/' + tagname
+ if intv_p
+ interactive "OK? svn rm -m \"remove tag #{tagname}\" #{tag_url}" do
+ # nothing to do here
+ end
+ end
+ system(*%w'svn rm -m', "remove tag #{tagname}", tag_url)
+end
+
+def default_merge_branch
+ %r{^URL: .*/branches/ruby_1_8_} =~ `svn info` ? 'branches/ruby_1_8' : 'trunk'
+end
+
+case ARGV[0]
+when "teenyup"
+ version_up(:teeny)
+ system 'svn diff version.h'
+when "up", /\A(ver|version|rev|revision|lv|level|patch\s*level)\s*up/
+ version_up
+ system 'svn diff version.h'
+when "tag"
+ tag :interactive, ARGV[1]
+when /\A(?:remove|rm|del)_?tag\z/
+ remove_tag :interactive, ARGV[1]
+when nil, "-h", "--help"
+ help
+ exit
+else
+ system 'svn up'
+ system 'ruby tool/file2lastrev.rb --revision.h . > revision.tmp'
+ system 'tool/ifchange "--timestamp=.revision.time" "revision.h" "revision.tmp"'
+ FileUtils.rm_f('revision.tmp')
+
+ case ARGV[0]
+ when /--ticket=(.*)/
+ tickets = $1.split(/,/).map{|num| " [Backport ##{num}]"}.join
+ ARGV.shift
+ when /merge revision\(s\) ([\d,\-]+):( \[.*)/
+ tickets = $2
+ ARGV[0] = $1
+ else
+ tickets = ''
+ end
+
+ q = $repos + (ARGV[1] || default_merge_branch)
+ revstr = ARGV[0].delete('^, :\-0-9')
+ revs = revstr.split(/[,\s]+/)
+ log = ''
+ log_svn = ''
+
+ revs.each do |rev|
+ case rev
+ when /\A\d+:\d+\z/
+ r = ['-r', rev]
+ when /\A(\d+)-(\d+)\z/
+ rev = "#{$1.to_i-1}:#$2"
+ r = ['-r', rev]
+ when /\A\d+\z/
+ r = ['-c', rev]
+ when nil then
+ puts "#$0 revision"
+ exit
+ else
+ puts "invalid revision part '#{rev}' in '#{ARGV[0]}'"
+ exit
+ end
+
+ l = IO.popen %w'svn diff' + r + %w'--diff-cmd=diff -x -pU0' + [File.join(q, 'ChangeLog')] do |f|
+ f.read
+ end
+
+ log << l
+ l = l.lines.grep(/^\+\t/).join.gsub(/^\+/, '').gsub(/^\t\*/, "\n\t\*")
+
+ if l.empty?
+ l = IO.popen %w'svn log ' + r + [q] do |f|
+ f.read
+ end.sub(/\A-+\nr.*/, '').sub(/\n-+\n\z/, '').gsub(/^./, "\t\\&")
+ end
+ log_svn << l
+
+ a = %w'svn merge --accept=postpone' + r + [q]
+ STDERR.puts a.join(' ')
+
+ system(*a)
+ system(*%w'svn revert ChangeLog') if /^\+/ =~ l
+ end
+
+ if `svn diff --diff-cmd=diff -x -upw`.empty?
+ interactive 'Only ChangeLog is modified, right?' do
+ end
+ end
+
+ if /^\+/ =~ log
+ system(*%w'svn revert ChangeLog')
+ IO.popen %w'patch -p0', 'wb' do |f|
+ f.write log.gsub(/\+(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ 123][0-9] [012][0-9]:[0-5][0-9]:[0-5][0-9] \d\d\d\d/,
+ # this format-time-string was from the file local variables of ChangeLog
+ '+'+Time.now.strftime('%a %b %e %H:%M:%S %Y'))
+ end
+ system(*%w'touch ChangeLog') # needed somehow, don't know why...
+ else
+ STDERR.puts '*** You should write ChangeLog NOW!!! ***'
+ end
+
+ version_up
+ f = Tempfile.new 'merger.rb'
+ f.printf "merge revision(s) %s:%s", revstr, tickets
+ f.write log_svn
+ f.flush
+ f.close
+
+ interactive 'conflicts resolved?', f.path do
+ IO.popen(ENV["PAGER"] || "less", "w") do |g|
+ g << `svn stat`
+ g << "\n\n"
+ f.open
+ g << f.read
+ f.close
+ g << "\n\n"
+ g << `svn diff --diff-cmd=diff -x -upw`
+ end
+ end
+
+ if system(*%w'svn ci -F', f.path)
+ # tag :interactive # no longer needed.
+ system 'rm -f subversion.commitlog'
+ else
+ puts 'commit failed; try again.'
+ end
+
+ f.close(true)
+end
diff --git a/tool/mk_call_iseq_optimized.rb b/tool/mk_call_iseq_optimized.rb
new file mode 100644
index 0000000000..9b88c5eeb6
--- /dev/null
+++ b/tool/mk_call_iseq_optimized.rb
@@ -0,0 +1,73 @@
+
+puts <<EOS
+/* -*- c -*- */
+#if 1 /* enable or disable this optimization */
+
+/* DO NOT EDIT THIS FILE DIRECTLY
+ *
+ * This file is generated by tool/mk_call_iseq_optimized.rb
+ */
+
+EOS
+
+P = (0..3)
+L = (0..5)
+
+def fname param, local
+ "vm_call_iseq_setup_normal_0start_#{param}params_#{local}locals"
+end
+
+P.each{|param|
+ L.each{|local|
+ puts <<EOS
+static VALUE
+#{fname(param, local)}(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc)
+{
+ return vm_call_iseq_setup_normal(ec, cfp, calling, ci, cc, 0, #{param}, #{local});
+}
+
+EOS
+ #
+ }
+}
+
+puts <<EOS
+/* vm_call_iseq_handlers[param][local] */
+static const vm_call_handler vm_call_iseq_handlers[][#{L.to_a.size}] = {
+#{P.map{|param| '{' + L.map{|local| fname(param, local)}.join(",\n ") + '}'}.join(",\n")}
+};
+
+static inline vm_call_handler
+vm_call_iseq_setup_func(const struct rb_call_info *ci, const int param_size, const int local_size)
+{
+ if (UNLIKELY(ci->flag & VM_CALL_TAILCALL)) {
+ return &vm_call_iseq_setup_tailcall_0start;
+ }
+ else if (0) { /* to disable optimize */
+ return &vm_call_iseq_setup_normal_0start;
+ }
+ else {
+ if (param_size <= #{P.end} &&
+ local_size <= #{L.end}) {
+ VM_ASSERT(local_size >= 0);
+ return vm_call_iseq_handlers[param_size][local_size];
+ }
+ return &vm_call_iseq_setup_normal_0start;
+ }
+}
+
+#else
+
+
+static inline vm_call_handler
+vm_call_iseq_setup_func(const struct rb_call_info *ci, struct rb_call_cache *cc)
+{
+ if (UNLIKELY(ci->flag & VM_CALL_TAILCALL)) {
+ return &vm_call_iseq_setup_tailcall_0start;
+ }
+ else {
+ return &vm_call_iseq_setup_normal_0start;
+ }
+}
+#endif
+EOS
diff --git a/tool/mkconfig.rb b/tool/mkconfig.rb
new file mode 100755
index 0000000000..52ec6a85d7
--- /dev/null
+++ b/tool/mkconfig.rb
@@ -0,0 +1,325 @@
+#!./miniruby -s
+
+# This script, which is run when ruby is built, generates rbconfig.rb by
+# parsing information from config.status. rbconfig.rb contains build
+# information for ruby (compiler flags, paths, etc.) and is used e.g. by
+# mkmf to build compatible native extensions.
+
+# avoid warnings with -d.
+$install_name ||= nil
+$so_name ||= nil
+$unicode_version ||= nil
+arch = $arch or raise "missing -arch"
+version = $version or raise "missing -version"
+
+srcdir = File.expand_path('../..', __FILE__)
+$:.unshift(".")
+
+require "fileutils"
+mkconfig = File.basename($0)
+
+fast = {'prefix'=>true, 'ruby_install_name'=>true, 'INSTALL'=>true, 'EXEEXT'=>true}
+
+win32 = /mswin/ =~ arch
+universal = /universal.*darwin/ =~ arch
+v_fast = []
+v_others = []
+vars = {}
+continued_name = nil
+continued_line = nil
+install_name = nil
+so_name = nil
+File.foreach "config.status" do |line|
+ next if /^#/ =~ line
+ name = nil
+ case line
+ when /^s([%,])@(\w+)@\1(?:\|\#_!!_\#\|)?(.*)\1/
+ name = $2
+ val = $3.gsub(/\\(?=,)/, '')
+ when /^S\["(\w+)"\]\s*=\s*"(.*)"\s*(\\)?$/
+ name = $1
+ val = $2
+ if $3
+ continued_line = [val]
+ continued_name = name
+ next
+ end
+ when /^"(.*)"\s*(\\)?$/
+ next if !continued_line
+ continued_line << $1
+ next if $2
+ continued_line.each {|s| s.sub!(/\\n\z/, "\n")}
+ val = continued_line.join
+ name = continued_name
+ continued_line = nil
+ when /^(?:ac_given_)?INSTALL=(.*)/
+ v_fast << " CONFIG[\"INSTALL\"] = " + $1 + "\n"
+ end
+
+ if name
+ case name
+ when /^(?:ac_.*|configure_input|(?:top_)?srcdir|\w+OBJS)$/; next
+ when /^(?:X|(?:MINI|RUN|(?:HAVE_)?BASE|BOOTSTRAP|BTEST)RUBY(?:_COMMAND)?$)/; next
+ when /^INSTALLDOC|TARGET$/; next
+ when /^DTRACE/; next
+ when /^(?:MAJOR|MINOR|TEENY)$/; vars[name] = val; next
+ when /^LIBRUBY_D?LD/; next
+ when /^RUBY_INSTALL_NAME$/; next vars[name] = (install_name = val).dup if $install_name
+ when /^RUBY_SO_NAME$/; next vars[name] = (so_name = val).dup if $so_name
+ when /^arch$/; if val.empty? then val = arch else arch = val end
+ when /^sitearch$/; val = '$(arch)' if val.empty?
+ when /^DESTDIR$/; next
+ end
+ case val
+ when /^\$\(ac_\w+\)$/; next
+ when /^\$\{ac_\w+\}$/; next
+ when /^\$ac_\w+$/; next
+ end
+ if /^program_transform_name$/ =~ name
+ val.sub!(/\As(\\?\W)(?:\^|\${1,2})\1\1(;|\z)/, '')
+ if val.empty?
+ $install_name ||= "ruby"
+ next
+ end
+ unless $install_name
+ $install_name = "ruby"
+ val.gsub!(/\$\$/, '$')
+ val.scan(%r[\G[\s;]*(/(?:\\.|[^/])*/)?([sy])(\\?\W)((?:(?!\3)(?:\\.|.))*)\3((?:(?!\3)(?:\\.|.))*)\3([gi]*)]) do
+ |addr, cmd, sep, pat, rep, opt|
+ if addr
+ Regexp.new(addr[/\A\/(.*)\/\z/, 1]) =~ $install_name or next
+ end
+ case cmd
+ when 's'
+ pat = Regexp.new(pat, opt.include?('i'))
+ if opt.include?('g')
+ $install_name.gsub!(pat, rep)
+ else
+ $install_name.sub!(pat, rep)
+ end
+ when 'y'
+ $install_name.tr!(Regexp.quote(pat), rep)
+ end
+ end
+ end
+ end
+ eq = win32 && vars[name] ? '<< "\n"' : '='
+ vars[name] = val
+ if name == "configure_args"
+ val.gsub!(/--with-out-ext/, "--without-ext")
+ end
+ val = val.gsub(/\$(?:\$|\{?(\w+)\}?)/) {$1 ? "$(#{$1})" : $&}.dump
+ case name
+ when /^prefix$/
+ val = "(TOPDIR || DESTDIR + #{val})"
+ when /^ARCH_FLAG$/
+ val = "arch_flag || #{val}" if universal
+ when /^UNIVERSAL_ARCHNAMES$/
+ universal, val = val, 'universal' if universal
+ when /^arch$/
+ if universal
+ val.sub!(/universal/, %q[#{arch && universal[/(?:\A|\s)#{Regexp.quote(arch)}=(\S+)/, 1] || '\&'}])
+ end
+ when /^oldincludedir$/
+ val = '"$(SDKROOT)"'+val if /darwin/ =~ arch
+ end
+ v = " CONFIG[\"#{name}\"] #{eq} #{val}\n"
+ if fast[name]
+ v_fast << v
+ else
+ v_others << v
+ end
+ case name
+ when "RUBY_PROGRAM_VERSION"
+ version = val[/\A"(.*)"\z/, 1]
+ end
+ end
+# break if /^CEOF/
+end
+
+drive = File::PATH_SEPARATOR == ';'
+
+def vars.expand(val, config = self)
+ newval = val.gsub(/\$\$|\$\(([^()]+)\)|\$\{([^{}]+)\}/) {
+ var = $&
+ if !(v = $1 || $2)
+ '$'
+ elsif key = config[v = v[/\A[^:]+(?=(?::(.*?)=(.*))?\z)/]]
+ pat, sub = $1, $2
+ config[v] = false
+ config[v] = expand(key, config)
+ key = key.gsub(/#{Regexp.quote(pat)}(?=\s|\z)/n) {sub} if pat
+ key
+ else
+ var
+ end
+ }
+ val.replace(newval) unless newval == val
+ val
+end
+prefix = vars.expand(vars["prefix"] ||= "")
+rubyarchdir = vars.expand(vars["rubyarchdir"] ||= "")
+relative_archdir = rubyarchdir.rindex(prefix, 0) ? rubyarchdir[prefix.size..-1] : rubyarchdir
+puts %[\
+# encoding: ascii-8bit
+# frozen-string-literal: false
+#
+# The module storing Ruby interpreter configurations on building.
+#
+# This file was created by #{mkconfig} when ruby was built. It contains
+# build information for ruby which is used e.g. by mkmf to build
+# compatible native extensions. Any changes made to this file will be
+# lost the next time ruby is built.
+
+module RbConfig
+ RUBY_VERSION.start_with?("#{version[/^[0-9]+\.[0-9]+\./] || version}") or
+ raise "ruby lib version (#{version}) doesn't match executable version (\#{RUBY_VERSION})"
+
+]
+print " # Ruby installed directory.\n"
+print " TOPDIR = File.dirname(__FILE__).chomp!(#{relative_archdir.dump})\n"
+print " # DESTDIR on make install.\n"
+print " DESTDIR = ", (drive ? "TOPDIR && TOPDIR[/\\A[a-z]:/i] || " : ""), "'' unless defined? DESTDIR\n"
+print <<'ARCH' if universal
+ arch_flag = ENV['ARCHFLAGS'] || ((e = ENV['RC_ARCHS']) && e.split.uniq.map {|a| "-arch #{a}"}.join(' '))
+ arch = arch_flag && arch_flag[/\A\s*-arch\s+(\S+)\s*\z/, 1]
+ARCH
+print " universal = #{universal}\n" if universal
+print " # The hash configurations stored.\n"
+print " CONFIG = {}\n"
+print " CONFIG[\"DESTDIR\"] = DESTDIR\n"
+
+versions = {}
+IO.foreach(File.join(srcdir, "version.h")) do |l|
+ m = /^\s*#\s*define\s+RUBY_(PATCHLEVEL)\s+(-?\d+)/.match(l)
+ if m
+ versions[m[1]] = m[2]
+ break if versions.size == 4
+ next
+ end
+ m = /^\s*#\s*define\s+RUBY_VERSION\s+\W?([.\d]+)/.match(l)
+ if m
+ versions['MAJOR'], versions['MINOR'], versions['TEENY'] = m[1].split('.')
+ break if versions.size == 4
+ next
+ end
+end
+%w[MAJOR MINOR TEENY PATCHLEVEL].each do |v|
+ print " CONFIG[#{v.dump}] = #{versions[v].dump}\n"
+end
+
+dest = drive ? %r'= "(?!\$[\(\{])(?i:[a-z]:)' : %r'= "(?!\$[\(\{])'
+v_disabled = {}
+v_others.collect! do |x|
+ if /^\s*CONFIG\["((?!abs_|old)[a-z]+(?:_prefix|dir))"\]/ === x
+ name = $1
+ if /= "no"$/ =~ x
+ v_disabled[name] = true
+ v_others.delete(name)
+ next
+ end
+ x.sub(dest, '= "$(DESTDIR)')
+ else
+ x
+ end
+end
+v_others.compact!
+
+if $install_name
+ if install_name and vars.expand("$(RUBY_INSTALL_NAME)") == $install_name
+ $install_name = install_name
+ end
+ v_fast << " CONFIG[\"ruby_install_name\"] = \"" + $install_name + "\"\n"
+ v_fast << " CONFIG[\"RUBY_INSTALL_NAME\"] = \"" + $install_name + "\"\n"
+end
+if $so_name
+ if so_name and vars.expand("$(RUBY_SO_NAME)") == $so_name
+ $so_name = so_name
+ end
+ v_fast << " CONFIG[\"RUBY_SO_NAME\"] = \"" + $so_name + "\"\n"
+end
+
+print(*v_fast)
+print(*v_others)
+print <<EOS if $unicode_version
+ CONFIG["UNICODE_VERSION"] = #{$unicode_version.dump}
+EOS
+print <<EOS if /darwin/ =~ arch
+ CONFIG["SDKROOT"] = "\#{ENV['SDKROOT']}" # don't run xcrun every time, usually useless.
+EOS
+print <<EOS
+ CONFIG["archdir"] = "$(rubyarchdir)"
+ CONFIG["topdir"] = File.dirname(__FILE__)
+ # Almost same with CONFIG. MAKEFILE_CONFIG has other variable
+ # reference like below.
+ #
+ # MAKEFILE_CONFIG["bindir"] = "$(exec_prefix)/bin"
+ #
+ # The values of this constant is used for creating Makefile.
+ #
+ # require 'rbconfig'
+ #
+ # print <<-END_OF_MAKEFILE
+ # prefix = \#{Config::MAKEFILE_CONFIG['prefix']}
+ # exec_prefix = \#{Config::MAKEFILE_CONFIG['exec_prefix']}
+ # bindir = \#{Config::MAKEFILE_CONFIG['bindir']}
+ # END_OF_MAKEFILE
+ #
+ # => prefix = /usr/local
+ # exec_prefix = $(prefix)
+ # bindir = $(exec_prefix)/bin MAKEFILE_CONFIG = {}
+ #
+ # RbConfig.expand is used for resolving references like above in rbconfig.
+ #
+ # require 'rbconfig'
+ # p Config.expand(Config::MAKEFILE_CONFIG["bindir"])
+ # # => "/usr/local/bin"
+ MAKEFILE_CONFIG = {}
+ CONFIG.each{|k,v| MAKEFILE_CONFIG[k] = v.dup}
+
+ # call-seq:
+ #
+ # RbConfig.expand(val) -> string
+ # RbConfig.expand(val, config) -> string
+ #
+ # expands variable with given +val+ value.
+ #
+ # RbConfig.expand("$(bindir)") # => /home/foobar/all-ruby/ruby19x/bin
+ def RbConfig::expand(val, config = CONFIG)
+ newval = val.gsub(/\\$\\$|\\$\\(([^()]+)\\)|\\$\\{([^{}]+)\\}/) {
+ var = $&
+ if !(v = $1 || $2)
+ '$'
+ elsif key = config[v = v[/\\A[^:]+(?=(?::(.*?)=(.*))?\\z)/]]
+ pat, sub = $1, $2
+ config[v] = false
+ config[v] = RbConfig::expand(key, config)
+ key = key.gsub(/\#{Regexp.quote(pat)}(?=\\s|\\z)/n) {sub} if pat
+ key
+ else
+ var
+ end
+ }
+ val.replace(newval) unless newval == val
+ val
+ end
+ CONFIG.each_value do |val|
+ RbConfig::expand(val)
+ end
+
+ # call-seq:
+ #
+ # RbConfig.ruby -> path
+ #
+ # returns the absolute pathname of the ruby command.
+ def RbConfig.ruby
+ File.join(
+ RbConfig::CONFIG["bindir"],
+ RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]
+ )
+ end
+end
+CROSS_COMPILING = nil unless defined? CROSS_COMPILING
+EOS
+
+# vi:set sw=2:
diff --git a/tool/mkrunnable.rb b/tool/mkrunnable.rb
new file mode 100755
index 0000000000..e43c4329e7
--- /dev/null
+++ b/tool/mkrunnable.rb
@@ -0,0 +1,137 @@
+#!./miniruby
+# -*- coding: us-ascii -*-
+
+# Used by "make runnable" target, to make symbolic links from a build
+# directory.
+
+require './rbconfig'
+require 'fileutils'
+
+case ARGV[0]
+when "-n"
+ ARGV.shift
+ include FileUtils::DryRun
+when "-v"
+ ARGV.shift
+ include FileUtils::Verbose
+else
+ include FileUtils
+end
+
+module Mswin
+ def ln_safe(src, dest, *opt)
+ cmd = ["mklink", dest.tr("/", "\\"), src.tr("/", "\\")]
+ cmd[1, 0] = opt
+ return if system("cmd", "/c", *cmd)
+ # TODO: use RUNAS or something
+ puts cmd.join(" ")
+ end
+
+ def ln_dir_safe(src, dest)
+ ln_safe(src, dest, "/d")
+ end
+end
+
+def clean_link(src, dest)
+ begin
+ link = File.readlink(dest)
+ rescue
+ else
+ return if link == src
+ File.unlink(dest)
+ end
+ yield src, dest
+end
+
+def ln_safe(src, dest)
+ ln_sf(src, dest)
+end
+
+alias ln_dir_safe ln_safe
+
+if !File.respond_to?(:symlink) && /mingw|mswin/ =~ (CROSS_COMPILING || RUBY_PLATFORM)
+ extend Mswin
+end
+
+def clean_path(path)
+ path = "#{path}/".gsub(/(\A|\/)(?:\.\/)+/, '\1').tr_s('/', '/')
+ nil while path.sub!(/[^\/]+\/\.\.\//, '')
+ path
+end
+
+def relative_path_from(path, base)
+ path = clean_path(path)
+ base = clean_path(base)
+ path, base = [path, base].map{|s|s.split("/")}
+ until path.empty? or base.empty? or path[0] != base[0]
+ path.shift
+ base.shift
+ end
+ path, base = [path, base].map{|s|s.join("/")}
+ if /(\A|\/)\.\.\// =~ base
+ File.expand_path(path)
+ else
+ base.gsub!(/[^\/]+/, '..')
+ File.join(base, path)
+ end
+end
+
+def ln_relative(src, dest)
+ return if File.identical?(src, dest)
+ parent = File.dirname(dest)
+ File.directory?(parent) or mkdir_p(parent)
+ clean_link(relative_path_from(src, parent), dest) {|s, d| ln_safe(s, d)}
+end
+
+def ln_dir_relative(src, dest)
+ return if File.identical?(src, dest)
+ parent = File.dirname(dest)
+ File.directory?(parent) or mkdir_p(parent)
+ clean_link(relative_path_from(src, parent), dest) {|s, d| ln_dir_safe(s, d)}
+end
+
+config = RbConfig::MAKEFILE_CONFIG.merge("prefix" => ".", "exec_prefix" => ".")
+config.each_value {|s| RbConfig.expand(s, config)}
+srcdir = config["srcdir"] ||= File.dirname(__FILE__)
+top_srcdir = config["top_srcdir"] ||= File.dirname(srcdir)
+extout = ARGV[0] || config["EXTOUT"]
+version = config["ruby_version"]
+arch = config["arch"]
+bindir = config["bindir"]
+libdirname = config["libdirname"]
+libdir = config[libdirname || "libdir"]
+vendordir = config["vendordir"]
+rubylibdir = config["rubylibdir"]
+rubyarchdir = config["rubyarchdir"]
+archdir = "#{extout}/#{arch}"
+rubylibs = [vendordir, rubylibdir, rubyarchdir]
+[bindir, libdir, archdir].uniq.each do |dir|
+ File.directory?(dir) or mkdir_p(dir)
+end
+
+exeext = config["EXEEXT"]
+ruby_install_name = config["ruby_install_name"]
+rubyw_install_name = config["rubyw_install_name"]
+goruby_install_name = "go" + ruby_install_name
+[ruby_install_name, rubyw_install_name, goruby_install_name].map do |ruby|
+ ruby += exeext
+ if ruby and !ruby.empty? and !File.file?(target = "#{bindir}/#{ruby}")
+ ln_relative(ruby, target)
+ end
+end
+so = config["LIBRUBY_SO"]
+libruby = [config["LIBRUBY_A"]]
+if /\.dll\z/i =~ so
+ ln_relative(so, "#{bindir}/#{so}")
+else
+ libruby << so
+end
+libruby.concat(config["LIBRUBY_ALIASES"].split)
+libruby.each {|lib|ln_relative(lib, "#{libdir}/#{lib}")}
+ln_dir_relative("#{extout}/common", rubylibdir)
+rubyarchdir.sub!(rubylibdir, "#{extout}/common")
+vendordir.sub!(rubylibdir, "#{extout}/common")
+ln_dir_relative(archdir, rubyarchdir)
+vendordir.sub!(rubyarchdir, archdir)
+ln_dir_relative("#{top_srcdir}/lib", vendordir)
+ln_relative("rbconfig.rb", "#{archdir}/rbconfig.rb")
diff --git a/tool/node_name.rb b/tool/node_name.rb
new file mode 100755
index 0000000000..5c67d7ccd5
--- /dev/null
+++ b/tool/node_name.rb
@@ -0,0 +1,10 @@
+#! ./miniruby
+
+# Used when making Ruby to generate node_name.inc.
+# See common.mk for details.
+
+while gets
+ if ~/enum node_type \{/..~/^\};/
+ ~/(NODE_.+),/ and puts(" case #{$1}:\n\treturn \"#{$1}\";")
+ end
+end
diff --git a/tool/parse.rb b/tool/parse.rb
new file mode 100644
index 0000000000..93ae3e43cb
--- /dev/null
+++ b/tool/parse.rb
@@ -0,0 +1,16 @@
+# Used as part of the "make parse" Makefile target.
+# See common.mk for details.
+
+$file = ARGV[0]
+$str = ARGF.read.sub(/^__END__.*\z/m, '')
+puts '# ' + '-' * 70
+puts "# target program: "
+puts '# ' + '-' * 70
+puts $str
+puts '# ' + '-' * 70
+
+$parsed = RubyVM::InstructionSequence.compile_file($file)
+puts "# disasm result: "
+puts '# ' + '-' * 70
+puts $parsed.disasm
+puts '# ' + '-' * 70
diff --git a/tool/prereq.status b/tool/prereq.status
new file mode 100644
index 0000000000..7509b8cbd7
--- /dev/null
+++ b/tool/prereq.status
@@ -0,0 +1,43 @@
+s,@EXTOUT@,tmp,g
+s,@ruby_version@,0.0.0,g
+s,@NULLCMD@,:,g
+s,@ARCH_FLAG@,,g
+s,@BASERUBY@,ruby,g
+s,@BOOTSTRAPRUBY@,$(BASERUBY),g
+s,@CC@,false,g
+s,@CFLAGS@,,g
+s,@CHDIR@,cd,g
+s,@CONFIGURE@,configure,g
+s,@CP@,cp,g
+s,@CPPFLAGS@,,g
+s,@CXXFLAGS@,,g
+s,@DLDFLAGS@,,g
+s,@EXEEXT@,,g
+s,@HAVE_BASERUBY@,yes,g
+s,@IFCHANGE@,tool/ifchange,g
+s,@LDFLAGS@,,g
+s,@LIBEXT@,a,g
+s,@LIBRUBY@,libruby.a,g
+s,@LIBRUBY_A@,libruby.a,g
+s,@MINIRUBY@,$(BASERUBY),g
+s,@MKDIR_P@,mkdir -p,g
+s,@OBJEXT@,o,g
+s,@PATH_SEPARATOR@,:,g
+s,@PWD@,.,g
+s,@RM@,rm -f,g
+s,@RMALL@,rm -fr,g
+s,@RMDIR@,rmdir,g
+s,@RMDIRS@,$(RMDIR) -p,g
+s,@RUBY@,$(BASERUBY),g
+s,@RUNRUBY@,$(MINIRUBY),g
+s,@UNICODE_FILES@,,g
+s,@arch@,noarch,g
+s,@bindir@,,g
+s,@configure_args@,,g
+s,@ruby_install_name@,,g
+s,@rubyarchdir@,,g
+s,@rubylibprefix@,,g
+s,@srcdir@,.,g
+
+s/@[A-Za-z][A-Za-z0-9_]*@//g
+s/{\$([A-Za-z]*)}//g
diff --git a/tool/probes_to_wiki.rb b/tool/probes_to_wiki.rb
new file mode 100644
index 0000000000..ba8204c188
--- /dev/null
+++ b/tool/probes_to_wiki.rb
@@ -0,0 +1,16 @@
+###
+# Converts the probes.d file to redmine wiki format. Usage:
+#
+# ruby tool/probes_to_wiki.rb probes.d
+
+File.read(ARGV[0]).scan(/\/\*.*?\*\//m).grep(/ruby/) do |comment|
+ comment.gsub!(/^(\/\*|[ ]*)|\*\/$/, '').strip!
+ puts
+ comment.each_line.with_index do |line, i|
+ if i == 0
+ puts "=== #{line.chomp}"
+ else
+ puts line.gsub(/`([^`]*)`/, '(({\1}))')
+ end
+ end
+end
diff --git a/tool/pull-latest-mspec-spec b/tool/pull-latest-mspec-spec
new file mode 100755
index 0000000000..dfd80582b3
--- /dev/null
+++ b/tool/pull-latest-mspec-spec
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Assumes all commits have been synchronized to https://github.com/ruby/spec
+# See spec/mspec/tool/sync/sync-rubyspec.rb
+
+rm -rf spec/mspec
+git clone --depth 1 https://github.com/ruby/mspec.git spec/mspec
+commit=$(git -C spec/mspec log -n 1 --format='%h')
+rm -rf spec/mspec/.git
+git add spec/mspec
+git commit -m "Update to ruby/mspec@${commit}"
+
+rm -rf spec/ruby
+git clone --depth 1 https://github.com/ruby/spec.git spec/ruby
+commit=$(git -C spec/ruby log -n 1 --format='%h')
+rm -rf spec/ruby/.git
+git add spec/ruby
+git commit -m "Update to ruby/spec@${commit}"
diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb
new file mode 100755
index 0000000000..d0bb30918e
--- /dev/null
+++ b/tool/rbinstall.rb
@@ -0,0 +1,920 @@
+#!./miniruby
+
+# Used by the "make install" target to install Ruby.
+# See common.mk for more details.
+
+begin
+ load "./rbconfig.rb"
+rescue LoadError
+ CONFIG = Hash.new {""}
+else
+ include RbConfig
+ $".unshift File.expand_path("./rbconfig.rb")
+end
+
+srcdir = File.expand_path('../..', __FILE__)
+unless defined?(CROSS_COMPILING) and CROSS_COMPILING
+ $:.replace([srcdir+"/lib", Dir.pwd])
+end
+require 'fileutils'
+require 'shellwords'
+require 'optparse'
+require 'optparse/shellwords'
+require 'ostruct'
+require 'rubygems'
+begin
+ require "zlib"
+rescue LoadError
+ $" << "zlib.rb"
+end
+
+INDENT = " "*36
+STDOUT.sync = true
+File.umask(0222)
+
+def parse_args(argv = ARGV)
+ $mantype = 'doc'
+ $destdir = nil
+ $extout = nil
+ $make = 'make'
+ $mflags = []
+ $install = []
+ $installed_list = nil
+ $dryrun = false
+ $rdocdir = nil
+ $data_mode = 0644
+ $prog_mode = 0755
+ $dir_mode = nil
+ $script_mode = nil
+ $strip = false
+ $cmdtype = (if File::ALT_SEPARATOR == '\\'
+ File.exist?("rubystub.exe") ? 'exe' : 'cmd'
+ end)
+ mflags = []
+ opt = OptionParser.new
+ opt.on('-n', '--dry-run') {$dryrun = true}
+ opt.on('--dest-dir=DIR') {|dir| $destdir = dir}
+ opt.on('--extout=DIR') {|dir| $extout = (dir unless dir.empty?)}
+ opt.on('--make=COMMAND') {|make| $make = make}
+ opt.on('--mantype=MAN') {|man| $mantype = man}
+ opt.on('--make-flags=FLAGS', '--mflags', Shellwords) do |v|
+ if arg = v.first
+ arg.insert(0, '-') if /\A[^-][^=]*\Z/ =~ arg
+ end
+ $mflags.concat(v)
+ end
+ opt.on('-i', '--install=TYPE', $install_procs.keys) do |ins|
+ $install << ins
+ end
+ opt.on('--data-mode=OCTAL-MODE', OptionParser::OctalInteger) do |mode|
+ $data_mode = mode
+ end
+ opt.on('--prog-mode=OCTAL-MODE', OptionParser::OctalInteger) do |mode|
+ $prog_mode = mode
+ end
+ opt.on('--dir-mode=OCTAL-MODE', OptionParser::OctalInteger) do |mode|
+ $dir_mode = mode
+ end
+ opt.on('--script-mode=OCTAL-MODE', OptionParser::OctalInteger) do |mode|
+ $script_mode = mode
+ end
+ opt.on('--installed-list [FILENAME]') {|name| $installed_list = name}
+ opt.on('--rdoc-output [DIR]') {|dir| $rdocdir = dir}
+ opt.on('--cmd-type=TYPE', %w[cmd plain]) {|cmd| $cmdtype = (cmd unless cmd == 'plain')}
+ opt.on('--[no-]strip') {|strip| $strip = strip}
+
+ opt.order!(argv) do |v|
+ case v
+ when /\AINSTALL[-_]([-\w]+)=(.*)/
+ argv.unshift("--#{$1.tr('_', '-')}=#{$2}")
+ when /\A\w[-\w+]*=\z/
+ mflags << v
+ when /\A\w[-\w+]*\z/
+ $install << v.intern
+ else
+ raise OptionParser::InvalidArgument, v
+ end
+ end rescue abort "#{$!.message}\n#{opt.help}"
+
+ unless defined?(RbConfig)
+ puts opt.help
+ exit
+ end
+
+ $make, *rest = Shellwords.shellwords($make)
+ $mflags.unshift(*rest) unless rest.empty?
+ $mflags.unshift(*mflags)
+
+ def $mflags.set?(flag)
+ grep(/\A-(?!-).*#{flag.chr}/i) { return true }
+ false
+ end
+ def $mflags.defined?(var)
+ grep(/\A#{var}=(.*)/) {return block_given? ? yield($1) : $1}
+ false
+ end
+
+ if $mflags.set?(?n)
+ $dryrun = true
+ else
+ $mflags << '-n' if $dryrun
+ end
+
+ $destdir ||= $mflags.defined?("DESTDIR")
+ if $extout ||= $mflags.defined?("EXTOUT")
+ RbConfig.expand($extout)
+ end
+
+ $continue = $mflags.set?(?k)
+
+ if $installed_list ||= $mflags.defined?('INSTALLED_LIST')
+ RbConfig.expand($installed_list, RbConfig::CONFIG)
+ $installed_list = open($installed_list, "ab")
+ $installed_list.sync = true
+ end
+
+ $rdocdir ||= $mflags.defined?('RDOCOUT')
+
+ $dir_mode ||= $prog_mode | 0700
+ $script_mode ||= $prog_mode
+end
+
+$install_procs = Hash.new {[]}
+def install?(*types, &block)
+ $install_procs[:all] <<= block
+ types.each do |type|
+ $install_procs[type] <<= block
+ end
+end
+
+def strip_file(files)
+ if !defined?($strip_command) and (cmd = CONFIG["STRIP"])
+ case cmd
+ when "", "true", ":" then return
+ else $strip_command = Shellwords.shellwords(cmd)
+ end
+ elsif !$strip_command
+ return
+ end
+ system(*($strip_command + [files].flatten))
+end
+
+def install(src, dest, options = {})
+ options = options.clone
+ strip = options.delete(:strip)
+ options[:preserve] = true
+ d = with_destdir(dest)
+ super(src, d, options)
+ srcs = Array(src)
+ if strip
+ d = srcs.map {|s| File.join(d, File.basename(s))} if $made_dirs[dest]
+ strip_file(d)
+ end
+ if $installed_list
+ dest = srcs.map {|s| File.join(dest, File.basename(s))} if $made_dirs[dest]
+ $installed_list.puts dest
+ end
+end
+
+def ln_sf(src, dest)
+ super(src, with_destdir(dest))
+ $installed_list.puts dest if $installed_list
+end
+
+$made_dirs = {}
+def makedirs(dirs)
+ dirs = fu_list(dirs)
+ dirs.collect! do |dir|
+ realdir = with_destdir(dir)
+ realdir unless $made_dirs.fetch(dir) do
+ $made_dirs[dir] = true
+ $installed_list.puts(File.join(dir, "")) if $installed_list
+ File.directory?(realdir)
+ end
+ end.compact!
+ super(dirs, :mode => $dir_mode) unless dirs.empty?
+end
+
+FalseProc = proc {false}
+def path_matcher(pat)
+ if pat and !pat.empty?
+ proc {|f| pat.any? {|n| File.fnmatch?(n, f)}}
+ else
+ FalseProc
+ end
+end
+
+def install_recursive(srcdir, dest, options = {})
+ opts = options.clone
+ noinst = opts.delete(:no_install)
+ glob = opts.delete(:glob) || "*"
+ maxdepth = opts.delete(:maxdepth)
+ subpath = (srcdir.size+1)..-1
+ prune = []
+ skip = []
+ if noinst
+ if Array === noinst
+ prune = noinst.grep(/#{File::SEPARATOR}/o).map!{|f| f.chomp(File::SEPARATOR)}
+ skip = noinst.grep(/\A[^#{File::SEPARATOR}]*\z/o)
+ else
+ if noinst.index(File::SEPARATOR)
+ prune = [noinst]
+ else
+ skip = [noinst]
+ end
+ end
+ end
+ skip |= %w"#*# *~ *.old *.bak *.orig *.rej *.diff *.patch *.core"
+ prune = path_matcher(prune)
+ skip = path_matcher(skip)
+ File.directory?(srcdir) or return rescue return
+ paths = [[srcdir, dest, 0]]
+ found = []
+ while file = paths.shift
+ found << file
+ file, d, dir = *file
+ if dir
+ depth = dir + 1
+ next if maxdepth and maxdepth < depth
+ files = []
+ Dir.foreach(file) do |f|
+ src = File.join(file, f)
+ d = File.join(dest, dir = src[subpath])
+ stat = File.lstat(src) rescue next
+ if stat.directory?
+ files << [src, d, depth] if maxdepth != depth and /\A\./ !~ f and !prune[dir]
+ elsif stat.symlink?
+ # skip
+ else
+ files << [src, d, false] if File.fnmatch?(glob, f) and !skip[f]
+ end
+ end
+ paths.insert(0, *files)
+ end
+ end
+ for src, d, dir in found
+ if dir
+ makedirs(d)
+ else
+ makedirs(d[/.*(?=\/)/m])
+ if block_given?
+ yield src, d, opts
+ else
+ install src, d, opts
+ end
+ end
+ end
+end
+
+def open_for_install(path, mode)
+ data = open(realpath = with_destdir(path), "rb") {|f| f.read} rescue nil
+ newdata = yield
+ unless $dryrun
+ unless newdata == data
+ open(realpath, "wb", mode) {|f| f.write newdata}
+ end
+ File.chmod(mode, realpath)
+ end
+ $installed_list.puts path if $installed_list
+end
+
+def with_destdir(dir)
+ return dir if !$destdir or $destdir.empty?
+ dir = dir.sub(/\A\w:/, '') if File::PATH_SEPARATOR == ';'
+ $destdir + dir
+end
+
+def without_destdir(dir)
+ return dir if !$destdir or $destdir.empty?
+ dir.start_with?($destdir) ? dir[$destdir.size..-1] : dir
+end
+
+def prepare(mesg, basedir, subdirs=nil)
+ return unless basedir
+ case
+ when !subdirs
+ dirs = basedir
+ when subdirs.size == 0
+ subdirs = nil
+ when subdirs.size == 1
+ dirs = [basedir = File.join(basedir, subdirs)]
+ subdirs = nil
+ else
+ dirs = [basedir, *subdirs.collect {|dir| File.join(basedir, dir)}]
+ end
+ printf("%-*s%s%s\n", INDENT.size, "installing #{mesg}:", basedir,
+ (subdirs ? " (#{subdirs.join(', ')})" : ""))
+ makedirs(dirs)
+end
+
+def CONFIG.[](name, mandatory = false)
+ value = super(name)
+ if mandatory
+ raise "CONFIG['#{name}'] must be set" if !value or value.empty?
+ end
+ value
+end
+
+exeext = CONFIG["EXEEXT"]
+
+ruby_install_name = CONFIG["ruby_install_name", true]
+rubyw_install_name = CONFIG["rubyw_install_name"]
+goruby_install_name = "go" + ruby_install_name
+
+bindir = CONFIG["bindir", true]
+libdir = CONFIG[CONFIG.fetch("libdirname", "libdir"), true]
+rubyhdrdir = CONFIG["rubyhdrdir", true]
+archhdrdir = CONFIG["rubyarchhdrdir"] || (rubyhdrdir + "/" + CONFIG['arch'])
+rubylibdir = CONFIG["rubylibdir", true]
+archlibdir = CONFIG["rubyarchdir", true]
+if CONFIG["sitedir"]
+ sitelibdir = CONFIG["sitelibdir"]
+ sitearchlibdir = CONFIG["sitearchdir"]
+end
+if CONFIG["vendordir"]
+ vendorlibdir = CONFIG["vendorlibdir"]
+ vendorarchlibdir = CONFIG["vendorarchdir"]
+end
+mandir = CONFIG["mandir", true]
+docdir = CONFIG["docdir", true]
+configure_args = Shellwords.shellwords(CONFIG["configure_args"])
+enable_shared = CONFIG["ENABLE_SHARED"] == 'yes'
+dll = CONFIG["LIBRUBY_SO", enable_shared]
+lib = CONFIG["LIBRUBY", true]
+arc = CONFIG["LIBRUBY_A", true]
+load_relative = CONFIG["LIBRUBY_RELATIVE"] == 'yes'
+
+install?(:local, :arch, :bin, :'bin-arch') do
+ prepare "binary commands", bindir
+
+ install ruby_install_name+exeext, bindir, :mode => $prog_mode, :strip => $strip
+ if rubyw_install_name and !rubyw_install_name.empty?
+ install rubyw_install_name+exeext, bindir, :mode => $prog_mode, :strip => $strip
+ end
+ if File.exist? goruby_install_name+exeext
+ install goruby_install_name+exeext, bindir, :mode => $prog_mode, :strip => $strip
+ end
+ if enable_shared and dll != lib
+ install dll, bindir, :mode => $prog_mode, :strip => $strip
+ end
+end
+
+install?(:local, :arch, :lib, :'lib-arch') do
+ prepare "base libraries", libdir
+
+ install lib, libdir, :mode => $prog_mode, :strip => $strip unless lib == arc
+ install arc, libdir, :mode => $data_mode unless CONFIG["INSTALL_STATIC_LIBRARY"] == "no"
+ if dll == lib and dll != arc
+ for link in CONFIG["LIBRUBY_ALIASES"].split
+ ln_sf(dll, File.join(libdir, link))
+ end
+ end
+
+ prepare "arch files", archlibdir
+ install "rbconfig.rb", archlibdir, :mode => $data_mode
+ if CONFIG["ARCHFILE"]
+ for file in CONFIG["ARCHFILE"].split
+ install file, archlibdir, :mode => $data_mode
+ end
+ end
+end
+
+install?(:local, :arch, :data) do
+ pc = CONFIG["ruby_pc"]
+ if pc and File.file?(pc) and File.size?(pc)
+ prepare "pkgconfig data", pkgconfigdir = File.join(libdir, "pkgconfig")
+ install pc, pkgconfigdir, :mode => $data_mode
+ end
+end
+
+install?(:ext, :arch, :'ext-arch') do
+ prepare "extension objects", archlibdir
+ noinst = %w[-* -*/] | (CONFIG["no_install_files"] || "").split
+ install_recursive("#{$extout}/#{CONFIG['arch']}", archlibdir, :no_install => noinst, :mode => $prog_mode, :strip => $strip)
+ prepare "extension objects", sitearchlibdir
+ prepare "extension objects", vendorarchlibdir
+ if extso = File.read("exts.mk")[/^EXTSO[ \t]*=[ \t]*((?:.*\\\n)*.*)/, 1] and
+ !(extso = extso.gsub(/\\\n/, '').split).empty?
+ libpathenv = CONFIG["LIBPATHENV"]
+ dest = CONFIG[!libpathenv || libpathenv == "PATH" ? "bindir" : "libdir"]
+ prepare "external libraries", dest
+ for file in extso
+ install file, dest, :mode => $prog_mode
+ end
+ end
+end
+install?(:ext, :arch, :hdr, :'arch-hdr', :'hdr-arch') do
+ prepare "extension headers", archhdrdir
+ install_recursive("#{$extout}/include/#{CONFIG['arch']}", archhdrdir, :glob => "*.h", :mode => $data_mode)
+end
+install?(:ext, :comm, :'ext-comm') do
+ prepare "extension scripts", rubylibdir
+ install_recursive("#{$extout}/common", rubylibdir, :mode => $data_mode)
+ prepare "extension scripts", sitelibdir
+ prepare "extension scripts", vendorlibdir
+end
+install?(:ext, :comm, :hdr, :'comm-hdr', :'hdr-comm') do
+ hdrdir = rubyhdrdir + "/ruby"
+ prepare "extension headers", hdrdir
+ install_recursive("#{$extout}/include/ruby", hdrdir, :glob => "*.h", :mode => $data_mode)
+end
+
+install?(:doc, :rdoc) do
+ if $rdocdir
+ ridatadir = File.join(CONFIG['ridir'], CONFIG['ruby_version'], "system")
+ prepare "rdoc", ridatadir
+ install_recursive($rdocdir, ridatadir, :mode => $data_mode)
+ end
+end
+install?(:doc, :capi) do
+ prepare "capi-docs", docdir
+ install_recursive "doc/capi", docdir+"/capi", :mode => $data_mode
+end
+
+prolog_script = <<EOS
+bindir="#{load_relative ? '${0%/*}' : bindir.gsub(/\"/, '\\\\"')}"
+EOS
+if CONFIG["LIBRUBY_RELATIVE"] != 'yes' and libpathenv = CONFIG["LIBPATHENV"]
+ pathsep = File::PATH_SEPARATOR
+ prolog_script << <<EOS
+libdir="#{load_relative ? '$\{bindir%/bin\}/lib' : libdir.gsub(/\"/, '\\\\"')}"
+export #{libpathenv}="$libdir${#{libpathenv}:+#{pathsep}$#{libpathenv}}"
+EOS
+end
+prolog_script << %Q[exec "$bindir/#{ruby_install_name}" "-x" "$0" "$@"\n]
+PROLOG_SCRIPT = {}
+PROLOG_SCRIPT["exe"] = "#!#{bindir}/#{ruby_install_name}"
+PROLOG_SCRIPT["cmd"] = <<EOS
+:""||{ ""=> %q<-*- ruby -*-
+@"%~dp0#{ruby_install_name}" -x "%~f0" %*
+@exit /b %ERRORLEVEL%
+};{#\n#{prolog_script.gsub(/(?=\n)/, ' #')}>,\n}
+EOS
+PROLOG_SCRIPT.default = (load_relative || /\s/ =~ bindir) ?
+ <<EOS : PROLOG_SCRIPT["exe"]
+#!/bin/sh
+# -*- ruby -*-
+_=_\\
+=begin
+#{prolog_script}=end
+EOS
+
+$script_installer = Struct.new(:ruby_shebang, :ruby_bin, :ruby_install_name,
+ :stub, :trans) do
+ ruby_shebang = File.join(bindir, ruby_install_name)
+ if File::ALT_SEPARATOR
+ ruby_bin = ruby_shebang.tr(File::SEPARATOR, File::ALT_SEPARATOR)
+ if $cmdtype == 'exe'
+ stub = File.open("rubystub.exe", "rb") {|f| f.read} << "\n" rescue nil
+ end
+ end
+ if trans = CONFIG["program_transform_name"]
+ exp = []
+ trans.gsub!(/\$\$/, '$')
+ trans.scan(%r[\G[\s;]*(/(?:\\.|[^/])*/)?([sy])(\\?\W)((?:(?!\3)(?:\\.|.))*)\3((?:(?!\3)(?:\\.|.))*)\3([gi]*)]) do
+ |addr, cmd, sep, pat, rep, opt|
+ addr &&= Regexp.new(addr[/\A\/(.*)\/\z/, 1])
+ case cmd
+ when 's'
+ next if pat == '^' and rep.empty?
+ exp << [addr, (opt.include?('g') ? :gsub! : :sub!),
+ Regexp.new(pat, opt.include?('i')), rep.gsub(/&/){'\&'}]
+ when 'y'
+ exp << [addr, :tr!, Regexp.quote(pat), rep]
+ end
+ end
+ trans = proc do |base|
+ exp.each {|addr, opt, pat, rep| base.__send__(opt, pat, rep) if !addr or addr =~ base}
+ base
+ end
+ elsif /ruby/ =~ ruby_install_name
+ trans = proc {|base| ruby_install_name.sub(/ruby/, base)}
+ else
+ trans = proc {|base| base}
+ end
+
+ def prolog(shebang)
+ shebang.sub!(/\r$/, '')
+ script = PROLOG_SCRIPT[$cmdtype]
+ shebang.sub!(/\A(\#!.*?ruby\b)?/) do
+ if script.end_with?("\n")
+ script + ($1 || "#!ruby\n")
+ else
+ $1 ? script : "#{script}\n"
+ end
+ end
+ shebang
+ end
+
+ def install(src, cmd)
+ cmd = cmd.sub(/[^\/]*\z/m) {|n| RbConfig.expand(trans[n])}
+
+ shebang, body = open(src, "rb") do |f|
+ next f.gets, f.read
+ end
+ shebang or raise "empty file - #{src}"
+ shebang = prolog(shebang)
+ body.gsub!(/\r$/, '')
+
+ cmd << ".#{$cmdtype}" if $cmdtype
+ open_for_install(cmd, $script_mode) do
+ case $cmdtype
+ when "exe"
+ stub + shebang + body
+ else
+ shebang + body
+ end
+ end
+ end
+
+ break new(ruby_shebang, ruby_bin, ruby_install_name, stub, trans)
+end
+
+install?(:local, :comm, :bin, :'bin-comm') do
+ prepare "command scripts", bindir
+
+ install_recursive(File.join(srcdir, "bin"), bindir, :maxdepth => 1) do |src, cmd|
+ $script_installer.install(src, cmd)
+ end
+end
+
+install?(:local, :comm, :lib) do
+ prepare "library scripts", rubylibdir
+ noinst = %w[*.txt *.rdoc *.gemspec]
+ install_recursive(File.join(srcdir, "lib"), rubylibdir, :no_install => noinst, :mode => $data_mode)
+end
+
+install?(:local, :comm, :hdr, :'comm-hdr') do
+ prepare "common headers", rubyhdrdir
+
+ noinst = []
+ unless RUBY_PLATFORM =~ /mswin|mingw|bccwin/
+ noinst << "win32.h"
+ end
+ noinst = nil if noinst.empty?
+ install_recursive(File.join(srcdir, "include"), rubyhdrdir, :no_install => noinst, :glob => "*.h", :mode => $data_mode)
+end
+
+install?(:local, :comm, :man) do
+ mdocs = Dir["#{srcdir}/man/*.[1-9]"]
+ prepare "manpages", mandir, ([] | mdocs.collect {|mdoc| mdoc[/\d+$/]}).sort.collect {|sec| "man#{sec}"}
+
+ case $mantype
+ when /\.(?:(gz)|bz2)\z/
+ compress = $1 ? "gzip" : "bzip2"
+ suffix = $&
+ end
+ mandir = File.join(mandir, "man")
+ has_goruby = File.exist?(goruby_install_name+exeext)
+ require File.join(srcdir, "tool/mdoc2man.rb") if /\Adoc\b/ !~ $mantype
+ mdocs.each do |mdoc|
+ next unless File.file?(mdoc) and open(mdoc){|fh| fh.read(1) == '.'}
+ base = File.basename(mdoc)
+ if base == "goruby.1"
+ next unless has_goruby
+ end
+
+ destdir = mandir + (section = mdoc[/\d+$/])
+ destname = ruby_install_name.sub(/ruby/, base.chomp(".#{section}"))
+ destfile = File.join(destdir, "#{destname}.#{section}")
+
+ if /\Adoc\b/ =~ $mantype
+ if compress
+ w = open(mdoc) {|f|
+ stdin = STDIN.dup
+ STDIN.reopen(f)
+ begin
+ destfile << suffix
+ IO.popen(compress) {|f| f.read}
+ ensure
+ STDIN.reopen(stdin)
+ stdin.close
+ end
+ }
+ open_for_install(destfile, $data_mode) {w}
+ else
+ install mdoc, destfile, :mode => $data_mode
+ end
+ else
+ class << (w = [])
+ alias print push
+ end
+ open(mdoc) {|r| Mdoc2Man.mdoc2man(r, w)}
+ w = w.join("")
+ if compress
+ require 'tmpdir'
+ Dir.mktmpdir("man") {|d|
+ dest = File.join(d, File.basename(destfile))
+ File.open(dest, "wb") {|f| f.write w}
+ if system(compress, dest)
+ w = File.open(dest+suffix, "rb") {|f| f.read}
+ destfile << suffix
+ end
+ }
+ end
+ open_for_install(destfile, $data_mode) {w}
+ end
+ end
+end
+
+module RbInstall
+ module Specs
+ class FileCollector
+ def initialize(gemspec)
+ @gemspec = gemspec
+ @base_dir = File.dirname(gemspec)
+ end
+
+ def collect
+ (ruby_libraries + built_libraries).sort
+ end
+
+ private
+ def type
+ /\/(ext|lib)?\/.*?\z/ =~ @base_dir
+ $1
+ end
+
+ def ruby_libraries
+ case type
+ when "ext"
+ prefix = "#{$extout}/common/"
+ base = "#{prefix}#{relative_base}"
+ when "lib"
+ base = @base_dir
+ prefix = base.sub(/lib\/.*?\z/, "") + "lib/"
+ end
+
+ if base
+ Dir.glob("#{base}{.rb,/**/*.rb}").collect do |ruby_source|
+ remove_prefix(prefix, ruby_source)
+ end
+ else
+ [remove_prefix(File.dirname(@gemspec) + '/', @gemspec.gsub(/gemspec/, 'rb'))]
+ end
+ end
+
+ def built_libraries
+ case type
+ when "ext"
+ prefix = "#{$extout}/#{CONFIG['arch']}/"
+ base = "#{prefix}#{relative_base}"
+ dlext = CONFIG['DLEXT']
+ Dir.glob("#{base}{.#{dlext},/**/*.#{dlext}}").collect do |built_library|
+ remove_prefix(prefix, built_library)
+ end
+ when "lib"
+ []
+ else
+ []
+ end
+ end
+
+ def relative_base
+ /\/#{Regexp.escape(type)}\/(.*?)\z/ =~ @base_dir
+ $1
+ end
+
+ def remove_prefix(prefix, string)
+ string.sub(/\A#{Regexp.escape(prefix)}/, "")
+ end
+ end
+ end
+
+ class UnpackedInstaller < Gem::Installer
+ module DirPackage
+ def extract_files(destination_dir, pattern = "*")
+ path = File.dirname(@gem.path)
+ return if path == destination_dir
+ File.chmod(0700, destination_dir)
+ mode = pattern == "bin/*" ? $script_mode : $data_mode
+ spec.files.each do |f|
+ src = File.join(path, f)
+ dest = File.join(without_destdir(destination_dir), f)
+ makedirs(dest[/.*(?=\/)/m])
+ install src, dest, :mode => mode
+ end
+ File.chmod($dir_mode, destination_dir)
+ end
+ end
+
+ def initialize(spec, *options)
+ super(spec.loaded_from, *options)
+ @package.extend(DirPackage).spec = spec
+ end
+
+ def write_cache_file
+ end
+
+ def build_extensions
+ end
+
+ def shebang(bin_file_name)
+ path = File.join(gem_dir, spec.bindir, bin_file_name)
+ first_line = File.open(path, "rb") {|file| file.gets}
+ $script_installer.prolog(first_line).chomp
+ end
+
+ def app_script_text(bin_file_name)
+ # move shell script part after comments generated by RubyGems.
+ super.sub(/\A
+ (\#!\/bin\/sh\n\#.*-\*-\s*ruby\s*-\*-.*\n)
+ ((?:.*\n)*?\#!.*ruby.*\n)
+ \#\n
+ ((?:\#.*\n)+)/x, '\1\3\2')
+ end
+
+ def check_executable_overwrite(filename)
+ return if @wrappers and same_bin_script?(filename, @bin_dir)
+ super
+ end
+
+ def generate_bin_script(filename, bindir)
+ return if same_bin_script?(filename, bindir)
+ super
+ end
+
+ def same_bin_script?(filename, bindir)
+ path = File.join(bindir, formatted_program_filename(filename))
+ begin
+ return true if File.binread(path) == app_script_text(filename)
+ rescue
+ end
+ false
+ end
+ end
+end
+
+class Gem::Installer
+ install = instance_method(:install)
+ define_method(:install) do
+ spec.post_install_message = nil
+ begin
+ u = File.umask(0022)
+ install.bind(self).call
+ ensure
+ File.umask(u)
+ end
+ end
+
+ generate_bin_script = instance_method(:generate_bin_script)
+ define_method(:generate_bin_script) do |filename, bindir|
+ generate_bin_script.bind(self).call(filename, bindir)
+ File.chmod($script_mode, File.join(bindir, formatted_program_filename(filename)))
+ end
+end
+
+# :startdoc:
+
+install?(:ext, :comm, :gem, :'default-gems', :'default-gems-comm') do
+ install_default_gem('lib', srcdir)
+end
+install?(:ext, :arch, :gem, :'default-gems', :'default-gems-arch') do
+ install_default_gem('ext', srcdir)
+end
+
+def load_gemspec(file)
+ code = File.read(file, encoding: "utf-8:-")
+ code.gsub!(/`git.*?`/m, '""')
+ begin
+ spec = eval(code, binding, file)
+ rescue SignalException, SystemExit
+ raise
+ rescue SyntaxError, Exception
+ end
+ raise("invalid spec in #{file}") unless spec
+ spec.loaded_from = file
+ spec
+end
+
+def install_default_gem(dir, srcdir)
+ gem_dir = Gem.default_dir
+ directories = Gem.ensure_gem_subdirectories(gem_dir, :mode => $dir_mode)
+ prepare "default gems from #{dir}", gem_dir, directories
+
+ spec_dir = File.join(gem_dir, directories.grep(/^spec/)[0])
+ default_spec_dir = "#{spec_dir}/default"
+ makedirs(default_spec_dir)
+
+ gems = Dir.glob("#{srcdir}/#{dir}/**/*.gemspec").map {|src|
+ spec = load_gemspec(src)
+ file_collector = RbInstall::Specs::FileCollector.new(src)
+ files = file_collector.collect
+ next if files.empty?
+ spec.files = files
+ spec
+ }
+ gems.compact.sort_by(&:name).each do |gemspec|
+ full_name = "#{gemspec.name}-#{gemspec.version}"
+
+ puts "#{INDENT}#{gemspec.name} #{gemspec.version}"
+ gemspec_path = File.join(default_spec_dir, "#{full_name}.gemspec")
+ open_for_install(gemspec_path, $data_mode) do
+ gemspec.to_ruby
+ end
+
+ unless gemspec.executables.empty? then
+ bin_dir = File.join(gem_dir, 'gems', full_name, gemspec.bindir)
+ makedirs(bin_dir)
+
+ gemspec.executables.map {|exec|
+ $script_installer.install(File.join(srcdir, 'bin', exec),
+ File.join(bin_dir, exec))
+ }
+ end
+ end
+end
+
+install?(:ext, :comm, :gem, :'bundled-gems') do
+ gem_dir = Gem.default_dir
+ directories = Gem.ensure_gem_subdirectories(gem_dir, :mode => $dir_mode)
+ prepare "bundled gems", gem_dir, directories
+ install_dir = with_destdir(gem_dir)
+ installed_gems = {}
+ options = {
+ :install_dir => install_dir,
+ :bin_dir => with_destdir(bindir),
+ :domain => :local,
+ :ignore_dependencies => true,
+ :dir_mode => $dir_mode,
+ :data_mode => $data_mode,
+ :prog_mode => $prog_mode,
+ :wrappers => true,
+ :format_executable => true,
+ }
+ gem_ext_dir = "#$extout/gems/#{CONFIG['arch']}"
+ extensions_dir = Gem::StubSpecification.gemspec_stub("", gem_dir, gem_dir).extensions_dir
+ Gem::Specification.each_gemspec([srcdir+'/gems/*']) do |path|
+ spec = load_gemspec(path)
+ next unless spec.platform == Gem::Platform::RUBY
+ next unless spec.full_name == path[srcdir.size..-1][/\A\/gems\/([^\/]+)/, 1]
+ spec.extension_dir = "#{extensions_dir}/#{spec.full_name}"
+ if File.directory?(ext = "#{gem_ext_dir}/#{spec.full_name}")
+ spec.extensions[0] ||= "-"
+ end
+ ins = RbInstall::UnpackedInstaller.new(spec, options)
+ puts "#{INDENT}#{spec.name} #{spec.version}"
+ ins.install
+ File.chmod($data_mode, File.join(install_dir, "specifications", "#{spec.full_name}.gemspec"))
+ unless spec.extensions.empty?
+ install_recursive(ext, spec.extension_dir)
+ end
+ installed_gems[spec.full_name] = true
+ end
+ installed_gems, gems = Dir.glob(srcdir+'/gems/*.gem').partition {|gem| installed_gems.key?(File.basename(gem, '.gem'))}
+ unless installed_gems.empty?
+ install installed_gems, gem_dir+"/cache"
+ end
+ next if gems.empty?
+ if defined?(Zlib)
+ Gem.instance_variable_set(:@ruby, with_destdir(File.join(bindir, ruby_install_name)))
+ silent = Gem::SilentUI.new
+ gems.each do |gem|
+ inst = Gem::Installer.new(gem, options)
+ inst.spec.extension_dir = with_destdir(inst.spec.extension_dir)
+ begin
+ Gem::DefaultUserInteraction.use_ui(silent) {inst.install}
+ rescue Gem::InstallError => e
+ next
+ end
+ gemname = File.basename(gem)
+ puts "#{INDENT}#{gemname}"
+ end
+ # fix directory permissions
+ # TODO: Gem.install should accept :dir_mode option or something
+ File.chmod($dir_mode, *Dir.glob(install_dir+"/**/"))
+ # fix .gemspec permissions
+ File.chmod($data_mode, *Dir.glob(install_dir+"/specifications/*.gemspec"))
+ else
+ puts "skip installing bundled gems because of lacking zlib"
+ end
+end
+
+parse_args()
+
+include FileUtils
+include FileUtils::NoWrite if $dryrun
+@fileutils_output = STDOUT
+@fileutils_label = ''
+
+all = $install.delete(:all)
+$install << :local << :ext if $install.empty?
+installs = $install.map do |inst|
+ if !(procs = $install_procs[inst]) || procs.empty?
+ next warn("unknown install target - #{inst}")
+ end
+ procs
+end
+installs.flatten!
+installs.uniq!
+installs |= $install_procs[:all] if all
+installs.each do |block|
+ dir = Dir.pwd
+ begin
+ block.call
+ ensure
+ Dir.chdir(dir)
+ end
+end
+
+# vi:set sw=2:
diff --git a/tool/rbuninstall.rb b/tool/rbuninstall.rb
new file mode 100755
index 0000000000..eb324c966e
--- /dev/null
+++ b/tool/rbuninstall.rb
@@ -0,0 +1,71 @@
+#! /usr/bin/ruby -nl
+
+# Used by the "make uninstall" target to uninstall Ruby.
+# See common.mk for more details.
+
+BEGIN {
+ $dryrun = false
+ $tty = STDOUT.tty?
+ until ARGV.empty?
+ case ARGV[0]
+ when /\A--destdir=(.*)/
+ $destdir = $1
+ when /\A-n\z/
+ $dryrun = true
+ when /\A--(?:no-)?tty\z/
+ $tty = !$1
+ else
+ break
+ end
+ ARGV.shift
+ end
+ $dirs = []
+ $files = []
+}
+list = ($_.chomp!('/') ? $dirs : $files)
+$_ = File.join($destdir, $_) if $destdir
+list << $_
+END {
+ status = true
+ $\ = ors = (!$dryrun and $tty) ? "\e[K\r" : "\n"
+ $files.each do |file|
+ print "rm #{file}"
+ unless $dryrun
+ begin
+ File.unlink(file)
+ rescue Errno::ENOENT
+ rescue
+ status = false
+ puts $!
+ end
+ end
+ end
+ unlink = {}
+ $dirs.each do |dir|
+ unlink[dir] = true
+ end
+ while dir = $dirs.pop
+ print "rmdir #{dir}"
+ unless $dryrun
+ begin
+ begin
+ unlink.delete(dir)
+ Dir.rmdir(dir)
+ rescue Errno::ENOTDIR
+ raise unless File.symlink?(dir)
+ File.unlink(dir)
+ end
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
+ rescue
+ status = false
+ puts $!
+ else
+ parent = File.dirname(dir)
+ $dirs.push(parent) unless parent == dir or unlink[parent]
+ end
+ end
+ end
+ $\ = nil
+ print ors.chomp
+ exit(status)
+}
diff --git a/tool/redmine-backporter.rb b/tool/redmine-backporter.rb
new file mode 100755
index 0000000000..38ebf55fed
--- /dev/null
+++ b/tool/redmine-backporter.rb
@@ -0,0 +1,608 @@
+#!/usr/bin/env ruby
+require 'open-uri'
+require 'openssl'
+require 'net/http'
+require 'json'
+require 'io/console'
+require 'stringio'
+require 'strscan'
+require 'optparse'
+require 'abbrev'
+require 'pp'
+begin
+ require 'readline'
+rescue LoadError
+ module Readline; end
+end
+
+VERSION = '0.0.1'
+
+opts = OptionParser.new
+target_version = nil
+repo_path = nil
+api_key = nil
+ssl_verify = true
+opts.on('-k REDMINE_API_KEY', '--key=REDMINE_API_KEY', 'specify your REDMINE_API_KEY') {|v| api_key = v}
+opts.on('-t TARGET_VERSION', '--target=TARGET_VARSION', /\A\d(?:\.\d)+\z/, 'specify target version (ex: 2.1)') {|v| target_version = v}
+opts.on('-r RUBY_REPO_PATH', '--repository=RUBY_REPO_PATH', 'specify repository path') {|v| repo_path = v}
+opts.on('--[no-]ssl-verify', TrueClass, 'use / not use SSL verify') {|v| ssl_verify = v}
+opts.version = VERSION
+opts.parse!(ARGV)
+
+http_options = {use_ssl: true}
+http_options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
+$openuri_options = {}
+$openuri_options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
+
+TARGET_VERSION = target_version || ENV['TARGET_VERSION'] || (raise 'need to specify TARGET_VERSION')
+RUBY_REPO_PATH = repo_path || ENV['RUBY_REPO_PATH']
+BACKPORT_CF_KEY = 'cf_5'
+STATUS_CLOSE = 5
+REDMINE_API_KEY = api_key || ENV['REDMINE_API_KEY'] || (raise 'need to specify REDMINE_API_KEY')
+REDMINE_BASE = 'https://bugs.ruby-lang.org'
+
+@query = {
+ 'f[]' => BACKPORT_CF_KEY,
+ "op[#{BACKPORT_CF_KEY}]" => '~',
+ "v[#{BACKPORT_CF_KEY}][]" => "#{TARGET_VERSION}: REQUIRED",
+ 'limit' => 40,
+ 'status_id' => STATUS_CLOSE,
+ 'sort' => 'updated_on'
+}
+
+PRIORITIES = {
+ 'Low' => [:white, :blue],
+ 'Normal' => [],
+ 'High' => [:red],
+ 'Urgent' => [:red, :white],
+ 'Immediate' => [:red, :white, {underscore: true}],
+}
+COLORS = {
+ black: 30,
+ red: 31,
+ green: 32,
+ yellow: 33,
+ blue: 34,
+ magenta: 35,
+ cyan: 36,
+ white: 37,
+}
+
+class String
+ def color(fore=nil, back=nil, bold: false, underscore: false)
+ seq = ""
+ if bold
+ seq << "\e[1m"
+ end
+ if underscore
+ seq << "\e[2m"
+ end
+ if fore
+ c = COLORS[fore]
+ raise "unknown foreground color #{fore}" unless c
+ seq << "\e[#{c}m"
+ end
+ if back
+ c = COLORS[back]
+ raise "unknown background color #{back}" unless c
+ seq << "\e[#{c + 10}m"
+ end
+ if seq.empty?
+ self
+ else
+ seq << self << "\e[0m"
+ end
+ end
+end
+
+def wcwidth(wc)
+ return 8 if wc == "\t"
+ n = wc.ord
+ if n < 0x20
+ 0
+ elsif n < 0x80
+ 1
+ else
+ 2
+ end
+end
+
+def fold(str, col)
+ i = 0
+ size = str.size
+ len = 0
+ while i < size
+ case c = str[i]
+ when "\r", "\n"
+ len = 0
+ else
+ d = wcwidth(c)
+ len += d
+ if len == col
+ str.insert(i+1, "\n")
+ len = 0
+ i += 2
+ next
+ elsif len > col
+ str.insert(i, "\n")
+ len = d
+ i += 2
+ next
+ end
+ end
+ i += 1
+ end
+ str
+end
+
+class StringScanner
+ # lx: limit of x (colmns of screen)
+ # ly: limit of y (rows of screen)
+ def getrows(lx, ly)
+ cp1 = charpos
+ x = 0
+ y = 0
+ until eos?
+ case c = getch
+ when "\r"
+ x = 0
+ when "\n"
+ x = 0
+ y += 1
+ when "\t"
+ x += 8
+ when /[\x00-\x7f]/
+ # halfwidth
+ x += 1
+ else
+ # fullwidth
+ x += 2
+ end
+
+ if x > lx
+ x = 0
+ y += 1
+ unscan
+ end
+ if y >= ly
+ return string[cp1...charpos]
+ end
+ end
+ string[cp1..-1]
+ end
+end
+
+def more(sio)
+ console = IO.console
+ ly, lx = console.winsize
+ ly -= 1
+ str = sio.string
+ cls = "\r" + (" " * lx) + "\r"
+
+ ss = StringScanner.new(str)
+
+ rows = ss.getrows(lx, ly)
+ puts rows
+ until ss.eos?
+ print ":"
+ case c = console.getch
+ when ' '
+ rows = ss.getrows(lx, ly)
+ puts cls + rows
+ when 'j', "\r"
+ rows = ss.getrows(lx, 1)
+ puts cls + rows
+ when "q"
+ print cls
+ break
+ else
+ print "\b"
+ end
+ end
+end
+
+class << Readline
+ def readline(prompt = '')
+ console = IO.console
+ console.binmode
+ ly, lx = console.winsize
+ if /mswin|mingw/ =~ RUBY_PLATFORM or /^(?:vt\d\d\d|xterm)/i =~ ENV["TERM"]
+ cls = "\r\e[2K"
+ else
+ cls = "\r" << (" " * lx)
+ end
+ cls << "\r" << prompt
+ console.print prompt
+ console.flush
+ line = ''
+ while 1
+ case c = console.getch
+ when "\r", "\n"
+ puts
+ HISTORY << line
+ return line
+ when "\C-?", "\b" # DEL/BS
+ print "\b \b" if line.chop!
+ when "\C-u"
+ print cls
+ line.clear
+ when "\C-d"
+ return nil if line.empty?
+ line << c
+ when "\C-p"
+ HISTORY.pos -= 1
+ line = HISTORY.current
+ print cls
+ print line
+ when "\C-n"
+ HISTORY.pos += 1
+ line = HISTORY.current
+ print cls
+ print line
+ else
+ if c >= " "
+ print c
+ line << c
+ end
+ end
+ end
+ end
+
+ HISTORY = []
+ def HISTORY.<<(val)
+ HISTORY.push(val)
+ @pos = self.size
+ self
+ end
+ def HISTORY.pos
+ @pos ||= 0
+ end
+ def HISTORY.pos=(val)
+ @pos = val
+ if @pos < 0
+ @pos = -1
+ elsif @pos >= self.size
+ @pos = self.size
+ end
+ end
+ def HISTORY.current
+ @pos ||= 0
+ if @pos < 0 || @pos >= self.size
+ ''
+ else
+ self[@pos]
+ end
+ end
+end unless defined?(Readline.readline)
+
+def mergeinfo
+ `svn propget svn:mergeinfo #{RUBY_REPO_PATH}`
+end
+
+def find_svn_log(pattern)
+ `svn log --xml --stop-on-copy --search="#{pattern}" #{RUBY_REPO_PATH}`
+end
+
+def show_last_journal(http, uri)
+ res = http.get("#{uri.path}?include=journals")
+ res.value
+ h = JSON(res.body)
+ x = h["issue"]
+ raise "no issue" unless x
+ x = x["journals"]
+ raise "no journals" unless x
+ x = x.last
+ puts "== #{x["user"]["name"]} (#{x["created_on"]})"
+ x["details"].each do |y|
+ puts JSON(y)
+ end
+ puts x["notes"]
+end
+
+def merger_path
+ RUBY_PLATFORM =~ /mswin|mingw/ ? 'merger' : File.expand_path('../merger.rb', __FILE__)
+end
+
+def backport_command_string
+ unless @changesets.respond_to?(:validated)
+ @changesets = @changesets.select do |c|
+ begin
+ uri = URI("#{REDMINE_BASE}/projects/ruby-trunk/repository/revisions/#{c}")
+ uri.read($openuri_options)
+ true
+ rescue
+ false
+ end
+ end
+ @changesets.define_singleton_method(:validated){true}
+ end
+ " #{merger_path} --ticket=#{@issue} #{@changesets.sort.join(',')}"
+end
+
+def status_char(obj)
+ case obj["name"]
+ when "Closed"
+ "C".color(bold: true)
+ else
+ obj["name"][0]
+ end
+end
+
+console = IO.console
+row, col = console.winsize
+@query['limit'] = row - 2
+puts "Backporter #{VERSION}".color(bold: true) + " for #{TARGET_VERSION}"
+
+class CommandSyntaxError < RuntimeError; end
+commands = {
+ "ls" => proc{|args|
+ raise CommandSyntaxError unless /\A(\d+)?\z/ =~ args
+ uri = URI(REDMINE_BASE+'/projects/ruby-trunk/issues.json?'+URI.encode_www_form(@query.dup.merge('page' => ($1 ? $1.to_i : 1))))
+ # puts uri
+ res = JSON(uri.read($openuri_options))
+ @issues = issues = res["issues"]
+ from = res["offset"] + 1
+ total = res["total_count"]
+ to = from + issues.size - 1
+ puts "#{from}-#{to} / #{total}"
+ issues.each_with_index do |x, i|
+ id = "##{x["id"]}".color(*PRIORITIES[x["priority"]["name"]])
+ puts "#{'%2d' % i} #{id} #{x["priority"]["name"][0]} #{status_char(x["status"])} #{x["subject"][0,80]}"
+ end
+ },
+
+ "show" => proc{|args|
+ if /\A(\d+)\z/ =~ args
+ id = $1.to_i
+ id = @issues[id]["id"] if @issues && id < @issues.size
+ @issue = id
+ elsif @issue
+ id = @issue
+ else
+ raise CommandSyntaxError
+ end
+ uri = "#{REDMINE_BASE}/issues/#{id}"
+ uri = URI(uri+".json?include=children,attachments,relations,changesets,journals")
+ res = JSON(uri.read($openuri_options))
+ i = res["issue"]
+ unless i["changesets"]
+ abort "You don't have view_changesets permission"
+ end
+ unless i["custom_fields"]
+ puts "The specified ticket \##{@issue} seems to be a feature ticket"
+ @issue = nil
+ next
+ end
+ id = "##{i["id"]}".color(*PRIORITIES[i["priority"]["name"]])
+ sio = StringIO.new
+ sio.puts <<eom
+#{i["subject"].color(bold: true, underscore: true)}
+#{i["project"]["name"]} [#{i["tracker"]["name"]} #{id}] #{i["status"]["name"]} (#{i["created_on"]})
+author: #{i["author"]["name"]}
+assigned: #{i["assigned_to"].to_h["name"]}
+eom
+ i["custom_fields"].each do |x|
+ sio.puts "%-10s: %s" % [x["name"], x["value"]]
+ end
+ #res["attachements"].each do |x|
+ #end
+ sio.puts i["description"]
+ sio.puts
+ sio.puts "= changesets".color(bold: true, underscore: true)
+ @changesets = []
+ i["changesets"].each do |x|
+ @changesets << x["revision"]
+ sio.puts "== #{x["revision"]} #{x["committed_on"]} #{x["user"]["name"] rescue nil}".color(bold: true, underscore: true)
+ sio.puts x["comments"]
+ end
+ @changesets = @changesets.sort.uniq
+ if i["journals"] && !i["journals"].empty?
+ sio.puts "= journals".color(bold: true, underscore: true)
+ i["journals"].each do |x|
+ sio.puts "== #{x["user"]["name"]} (#{x["created_on"]})".color(bold: true, underscore: true)
+ x["details"].each do |y|
+ sio.puts JSON(y)
+ end
+ sio.puts x["notes"]
+ end
+ end
+ more(sio)
+ },
+
+ "rel" => proc{|args|
+ # this feature requires custom redmine which allows add_related_issue API
+ raise CommandSyntaxError unless /\Ar?(\d+)\z/ =~ args
+ unless @issue
+ puts "ticket not selected"
+ next
+ end
+ rev = $1
+ uri = URI("#{REDMINE_BASE}/projects/ruby-trunk/repository/revisions/#{rev}/issues.json")
+ Net::HTTP.start(uri.host, uri.port, http_options) do |http|
+ res = http.post(uri.path, "issue_id=#@issue",
+ 'X-Redmine-API-Key' => REDMINE_API_KEY)
+ begin
+ res.value
+ rescue
+ if $!.respond_to?(:response) && $!.response.is_a?(Net::HTTPConflict)
+ $stderr.puts "the revision has already related to the ticket"
+ else
+ $stderr.puts "deployed redmine doesn't have https://github.com/ruby/bugs.ruby-lang.org/commit/01fbba60d68cb916ddbccc8a8710e68c5217171d\nask naruse or hsbt"
+ end
+ next
+ end
+ puts res.body
+ @changesets << rev
+ class << @changesets
+ remove_method(:validated) rescue nil
+ end
+ end
+ },
+
+ "backport" => proc{|args|
+ # this feature implies backport command which wraps tool/merger.rb
+ raise CommandSyntaxError unless args.empty?
+ unless @issue
+ puts "ticket not selected"
+ next
+ end
+ puts backport_command_string
+ },
+
+ "done" => proc{|args|
+ raise CommandSyntaxError unless /\A(\d+)?(?:\s*-- +(.*))?\z/ =~ args
+ notes = $2
+ notes.strip! if notes
+ if $1
+ i = $1.to_i
+ i = @issues[i]["id"] if @issues && i < @issues.size
+ @issue = i
+ end
+ unless @issue
+ puts "ticket not selected"
+ next
+ end
+
+ log = find_svn_log("##@issue]")
+ if log && /revision="(?<rev>\d+)/ =~ log
+ str = log[/merge revision\(s\) ([^:]+)(?=:)/]
+ str.insert(5, "d")
+ str = "ruby_#{TARGET_VERSION.tr('.','_')} r#{rev} #{str}."
+ if notes
+ str << "\n"
+ str << notes
+ end
+ notes = str
+ else
+ puts "no commit is found whose log include ##@issue"
+ next
+ end
+ puts notes
+
+ uri = URI("#{REDMINE_BASE}/issues/#{@issue}.json")
+ Net::HTTP.start(uri.host, uri.port, http_options) do |http|
+ res = http.get(uri.path)
+ data = JSON(res.body)
+ h = data["issue"]["custom_fields"].find{|x|x["id"]==5}
+ if h and val = h["value"] and val != ""
+ case val[/(?:\A|, )#{Regexp.quote TARGET_VERSION}: ([^,]+)/, 1]
+ when 'REQUIRED', 'UNKNOWN', 'DONTNEED', 'WONTFIX'
+ val[$~.offset(1)[0]...$~.offset(1)[1]] = 'DONE'
+ when 'DONE' # , /\A\d+\z/
+ puts 'already backport is done'
+ next # already done
+ when nil
+ val << ", #{TARGET_VERSION}: DONE"
+ else
+ raise "unknown status '#$1'"
+ end
+ else
+ val = "#{TARGET_VERSION}: DONE"
+ end
+
+ data = { "issue" => { "custom_fields" => [ {"id"=>5, "value" => val} ] } }
+ data['issue']['notes'] = notes if notes
+ res = http.put(uri.path, JSON(data),
+ 'X-Redmine-API-Key' => REDMINE_API_KEY,
+ 'Content-Type' => 'application/json')
+ res.value
+
+ show_last_journal(http, uri)
+ end
+ },
+
+ "close" => proc{|args|
+ raise CommandSyntaxError unless /\A(\d+)?\z/ =~ args
+ if $1
+ i = $1.to_i
+ i = @issues[i]["id"] if @issues && i < @issues.size
+ @issue = i
+ end
+ unless @issue
+ puts "ticket not selected"
+ next
+ end
+
+ uri = URI("#{REDMINE_BASE}/issues/#{@issue}.json")
+ Net::HTTP.start(uri.host, uri.port, http_options) do |http|
+ data = { "issue" => { "status_id" => STATUS_CLOSE } }
+ res = http.put(uri.path, JSON(data),
+ 'X-Redmine-API-Key' => REDMINE_API_KEY,
+ 'Content-Type' => 'application/json')
+ res.value
+
+ show_last_journal(http, uri)
+ end
+ },
+
+ "last" => proc{|args|
+ raise CommandSyntaxError unless /\A(\d+)?\z/ =~ args
+ if $1
+ i = $1.to_i
+ i = @issues[i]["id"] if @issues && i < @issues.size
+ @issue = i
+ end
+ unless @issue
+ puts "ticket not selected"
+ next
+ end
+
+ uri = URI("#{REDMINE_BASE}/issues/#{@issue}.json")
+ Net::HTTP.start(uri.host, uri.port, http_options) do |http|
+ show_last_journal(http, uri)
+ end
+ },
+
+ "!" => proc{|args|
+ system(args.strip)
+ },
+
+ "quit" => proc{|args|
+ raise CommandSyntaxError unless args.empty?
+ exit
+ },
+ "exit" => "quit",
+
+ "help" => proc{|args|
+ puts 'ls [PAGE] '.color(bold: true) + ' show all required tickets'
+ puts '[show] TICKET '.color(bold: true) + ' show the detail of the TICKET, and select it'
+ puts 'backport '.color(bold: true) + ' show the option of selected ticket for merger.rb'
+ puts 'rel REVISION '.color(bold: true) + ' add the selected ticket as related to the REVISION'
+ puts 'done [TICKET] [-- NOTE]'.color(bold: true) + ' set Backport field of the TICKET to DONE'
+ puts 'close [TICKET] '.color(bold: true) + ' close the TICKET'
+ puts 'last [TICKET] '.color(bold: true) + ' show the last journal of the TICKET'
+ puts '! COMMAND '.color(bold: true) + ' execute COMMAND'
+ }
+}
+list = Abbrev.abbrev(commands.keys)
+
+@issues = nil
+@issue = nil
+@changesets = nil
+while true
+ begin
+ l = Readline.readline "#{('#' + @issue.to_s).color(bold: true) if @issue}> "
+ rescue Interrupt
+ break
+ end
+ break unless l
+ cmd, args = l.strip.split(/\s+|\b/, 2)
+ next unless cmd
+ if (!args || args.empty?) && /\A\d+\z/ =~ cmd
+ args = cmd
+ cmd = "show"
+ end
+ cmd = list[cmd]
+ if commands[cmd].is_a? String
+ cmd = list[commands[cmd]]
+ end
+ begin
+ if cmd
+ commands[cmd].call(args)
+ else
+ raise CommandSyntaxError
+ end
+ rescue CommandSyntaxError
+ puts "error #{l.inspect}"
+ end
+end
diff --git a/tool/release.sh b/tool/release.sh
new file mode 100755
index 0000000000..04c361a48a
--- /dev/null
+++ b/tool/release.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+RUBYDIR=/home/ftp/pub/ruby
+EXTS='.tar.gz .tar.bz2 .tar.xz .zip'
+
+releases=`ls ruby-*|grep -o 'ruby-[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\(-\(preview\|rc\|p\)[0-9]\{1,4\}\)\?'|uniq`
+
+# check files
+for r in $releases
+do
+ echo "checking files for $r..."
+ for ext in $EXTS
+ do
+ if ! [ -f $r$ext ];then
+ echo "ERROR: $r$ext not found"
+ exit 1
+ fi
+ done
+ echo "files are ok"
+done
+
+# version directory
+for r in $releases
+do
+ xy=`echo $r|grep -o '[0-9]\.[0-9]'`
+ preview=`echo $r|grep -o -- '-\(preview\|rc\)'`
+ dir="${RUBYDIR}/$xy"
+ echo "$dir"
+ mkdir -p $dir
+ for ext in $EXTS
+ do
+ cp $r$ext $dir/$r$ext
+ ln -sf $xy/$r$ext ${RUBYDIR}/$r$ext
+ if [ x$preview = x ];then
+ ln -sf $xy/$r$ext ${RUBYDIR}/ruby-$xy-stable$ext
+ fi
+ done
+done
diff --git a/tool/rmdirs b/tool/rmdirs
new file mode 100755
index 0000000000..76c4a39cb1
--- /dev/null
+++ b/tool/rmdirs
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Script used by configure to delete directories recursively.
+
+for dir do
+ while rmdir "$dir" >/dev/null 2>&1 &&
+ parent=`expr "$dir" : '\(.*\)/[^/][^/]*'`; do
+ case "$parent" in
+ . | .. | "$dir") break;;
+ *) dir="$parent";;
+ esac
+ done
+done
+true
diff --git a/tool/run-gcov.rb b/tool/run-gcov.rb
new file mode 100644
index 0000000000..5df7622aa3
--- /dev/null
+++ b/tool/run-gcov.rb
@@ -0,0 +1,54 @@
+#!ruby
+require "pathname"
+require "open3"
+
+Pathname.glob("**/*.gcda").sort.each do |gcda|
+ if gcda.fnmatch("ext/*")
+ cwd, gcda = gcda.split.map {|s| s.to_s }
+ objdir = "."
+ elsif gcda.fnmatch("rubyspec_temp/*")
+ next
+ else
+ cwd, objdir, gcda = ".", gcda.dirname.to_s, gcda.to_s
+ end
+ puts "$ gcov -lpbc -o #{ objdir } #{ gcda }"
+ out, err, _status = Open3.capture3("gcov", "-lpbc", "-o", objdir, gcda, chdir: cwd)
+ puts out
+ puts err
+
+ # a black list of source files that contains wrong #line directives
+ if err !~ %r(
+ \A(
+ Cannot\ open\ source\ file\ (
+ defs/keywords
+ |zonetab\.list
+ |enc/jis/props\.kwd
+ |parser\.c
+ |parser\.rl
+ )\n
+ )*\z
+ )x
+ raise "Unexpected gcov output"
+ end
+
+ if out !~ %r(
+ \A(
+ File\ .*\nLines\ executed:.*\n
+ (
+ Branches\ executed:.*\n
+ Taken\ at\ least\ once:.*\n
+ |
+ No\ branches\n
+ )?
+ (
+ Calls\ executed:.*\n
+ |
+ No\ calls\n
+ )?
+ Creating\ .*\n
+ \n
+ )+\z
+ )x
+ raise "Unexpected gcov output"
+ end
+end
diff --git a/tool/run-lcov.rb b/tool/run-lcov.rb
new file mode 100644
index 0000000000..f27578200a
--- /dev/null
+++ b/tool/run-lcov.rb
@@ -0,0 +1,164 @@
+#!ruby
+require "pathname"
+require "open3"
+require "tmpdir"
+
+def backup_gcda_files(gcda_files)
+ gcda_files = gcda_files.map do |gcda|
+ [gcda, gcda.sub_ext(".bak")]
+ end
+ begin
+ gcda_files.each do |before, after|
+ before.rename(after)
+ end
+ yield
+ ensure
+ gcda_files.each do |before, after|
+ after.rename(before)
+ end
+ end
+end
+
+def run_lcov(*args)
+ system("lcov", "--rc", "lcov_branch_coverage=1", *args)
+end
+
+$info_files = []
+def run_lcov_capture(dir, info)
+ $info_files << info
+ run_lcov("--capture", "-d", dir, "-o", info)
+end
+
+def run_lcov_merge(files, info)
+ run_lcov(*files.flat_map {|f| ["--add-tracefile", f] }, "-o", info)
+end
+
+def run_lcov_remove(info_src, info_out)
+ dirs = %w(/usr/*)
+ dirs << File.join(Dir.tmpdir, "*")
+ %w(
+ test/*
+ ext/-test-/*
+ ext/nkf/nkf-utf8/nkf.c
+ ).each {|f| dirs << File.join(File.dirname(__dir__), f) }
+ run_lcov("--remove", info_src, *dirs, "-o", info_out)
+end
+
+def run_genhtml(info, out)
+ system("genhtml", "--branch-coverage", "--ignore-errors", "source", info, "-o", out)
+end
+
+def gen_rb_lcov(file)
+ res = Marshal.load(File.binread(file))
+
+ open("lcov-rb-all.info", "w") do |f|
+ f.puts "TN:" # no test name
+ base_dir = File.dirname(__dir__)
+ res.each do |path, cov|
+ next unless path.start_with?(base_dir)
+ next if path.start_with?(File.join(base_dir, "test"))
+ f.puts "SF:#{ path }"
+
+ total = covered = 0
+ cov.each_with_index do |count, lineno|
+ next unless count
+ f.puts "DA:#{ lineno + 1 },#{ count }"
+ total += 1
+ covered += 1 if count > 0
+ end
+ f.puts "LF:#{ total }"
+ f.puts "LH:#{ covered }"
+
+ f.puts "end_of_record"
+ end
+ end
+end
+
+def gen_rb_lcov(file)
+ res = Marshal.load(File.binread(file))
+
+ open("lcov-rb-all.info", "w") do |f|
+ f.puts "TN:" # no test name
+ base_dir = File.dirname(File.dirname(__dir__))
+ res.each do |path, cov|
+ next unless path.start_with?(base_dir)
+ next if path.start_with?(File.join(base_dir, "test"))
+ f.puts "SF:#{ path }"
+
+ # function coverage
+ total = covered = 0
+ cov[:methods].each do |(klass, name, lineno), count|
+ f.puts "FN:#{ lineno },#{ klass }##{ name }"
+ total += 1
+ covered += 1 if count > 0
+ end
+ f.puts "FNF:#{ total }"
+ f.puts "FNF:#{ covered }"
+ cov[:methods].each do |(klass, name, _), count|
+ f.puts "FNDA:#{ count },#{ klass }##{ name }"
+ end
+
+ # line coverage
+ total = covered = 0
+ cov[:lines].each_with_index do |count, lineno|
+ next unless count
+ f.puts "DA:#{ lineno + 1 },#{ count }"
+ total += 1
+ covered += 1 if count > 0
+ end
+ f.puts "LF:#{ total }"
+ f.puts "LH:#{ covered }"
+
+ # branch coverage
+ total = covered = 0
+ id = 0
+ cov[:branches].each do |(_base_type, _, base_lineno), targets|
+ i = 0
+ targets.each do |(_target_type, _target_lineno), count|
+ f.puts "BRDA:#{ base_lineno },#{ id },#{ i },#{ count }"
+ total += 1
+ covered += 1 if count > 0
+ i += 1
+ end
+ id += 1
+ end
+ f.puts "BRF:#{ total }"
+ f.puts "BRH:#{ covered }"
+ f.puts "end_of_record"
+ end
+ end
+end
+
+gcda_files = Pathname.glob("**/*.gcda")
+ext_gcda_files = gcda_files.select {|f| f.fnmatch("ext/*") }
+rubyspec_temp_gcda_files = gcda_files.select {|f| f.fnmatch("rubyspec_temp/*") }
+
+backup_gcda_files(rubyspec_temp_gcda_files) do
+ if ext_gcda_files != []
+ backup_gcda_files(ext_gcda_files) do
+ info = "lcov-root.info"
+ run_lcov_capture(".", info)
+ end
+ end
+ ext_gcda_files.group_by {|f| f.descend.to_a[1] }.each do |key, files|
+ info = "lcov-#{ key.to_s.gsub(File::Separator, "-") }.info"
+ run_lcov_capture(key.to_s, info)
+ end
+end
+if $info_files != []
+ run_lcov_merge($info_files, "lcov-c-all.info")
+ run_lcov_remove("lcov-c-all.info", "lcov-c-all-filtered.info")
+ run_genhtml("lcov-c-all-filtered.info", "lcov-c-out")
+end
+
+if File.readable?("test-coverage.dat")
+ gen_rb_lcov("test-coverage.dat")
+ run_lcov_remove("lcov-rb-all.info", "lcov-rb-all-filtered.info")
+ run_genhtml("lcov-rb-all-filtered.info", "lcov-rb-out")
+end
+
+if File.readable?("lcov-c-all.info") && File.readable?("lcov-rb-all.info")
+ run_lcov_merge(%w(lcov-c-all.info lcov-rb-all.info), "lcov-all.info")
+ run_lcov_remove("lcov-all.info", "lcov-all-filtered.info")
+ run_genhtml("lcov-all-filtered.info", "lcov-out")
+end
diff --git a/tool/runruby.rb b/tool/runruby.rb
new file mode 100755
index 0000000000..a5aa3e7d8d
--- /dev/null
+++ b/tool/runruby.rb
@@ -0,0 +1,159 @@
+#!./miniruby
+
+# Used by "make runruby", configure, and by hand to run a locally-built Ruby
+# with correct environment variables and arguments.
+
+show = false
+precommand = []
+srcdir = File.realpath('..', File.dirname(__FILE__))
+while arg = ARGV[0]
+ break ARGV.shift if arg == '--'
+ case arg
+ when '-C', /\A-C(.+)/m
+ ARGV.shift
+ Dir.chdir($1 || ARGV.shift)
+ next
+ end
+ /\A--([-\w]+)(?:=(.*))?\z/ =~ arg or break
+ arg, value = $1, $2
+ re = Regexp.new('\A'+arg.gsub(/\w+\b/, '\&\\w*')+'\z', "i")
+ case
+ when re =~ "srcdir"
+ srcdir = value
+ when re =~ "archdir"
+ archdir = value
+ when re =~ "cpu"
+ precommand << "arch" << "-arch" << value
+ when re =~ "extout"
+ extout = value
+ when re =~ "pure"
+ # obsolete switch do nothing
+ when re =~ "debugger"
+ require 'shellwords'
+ case value
+ when nil
+ debugger = :gdb
+ when "no"
+ else
+ debugger = Shellwords.shellwords(value)
+ end and precommand |= [:debugger]
+ when re =~ "precommand"
+ require 'shellwords'
+ precommand.concat(Shellwords.shellwords(value))
+ when re =~ "show"
+ show = true
+ when re =~ "chdir"
+ Dir.chdir(value)
+ else
+ break
+ end
+ ARGV.shift
+end
+
+unless defined?(File.realpath)
+ def File.realpath(*args)
+ path = expand_path(*args)
+ if File.stat(path).directory?
+ Dir.chdir(path) {Dir.pwd}
+ else
+ dir, base = File.split(path)
+ File.join(Dir.chdir(dir) {Dir.pwd}, base)
+ end
+ end
+end
+
+begin
+ conffile = File.realpath('rbconfig.rb', archdir)
+rescue Errno::ENOENT => e
+ # retry if !archdir and ARGV[0] and File.directory?(archdir = ARGV.shift)
+ abort "#$0: rbconfig.rb not found, use --archdir option"
+end
+
+abs_archdir = File.dirname(conffile)
+archdir ||= abs_archdir
+$:.unshift(abs_archdir)
+
+config = File.read(conffile)
+config.sub!(/^(\s*)RUBY_VERSION\b.*(\sor\s*)\n.*\n/, '')
+config = Module.new {module_eval(config, conffile)}::RbConfig::CONFIG
+
+ruby = File.join(archdir, config["RUBY_INSTALL_NAME"]+config['EXEEXT'])
+unless File.exist?(ruby)
+ abort "#{ruby} is not found.\nTry `make' first, then `make test', please.\n"
+end
+
+libs = [abs_archdir]
+extout ||= config["EXTOUT"]
+if extout
+ abs_extout = File.expand_path(extout, abs_archdir)
+ libs << File.expand_path("common", abs_extout) << File.expand_path(config['arch'], abs_extout)
+end
+libs << File.expand_path("lib", srcdir)
+config["bindir"] = abs_archdir
+
+env = {
+ # Test with the smallest possible machine stack sizes.
+ # These values are clamped to machine-dependent minimum values in vm_core.h
+ 'RUBY_THREAD_MACHINE_STACK_SIZE' => '1',
+ 'RUBY_FIBER_MACHINE_STACK_SIZE' => '1',
+}
+
+runner = File.join(abs_archdir, "exe/ruby#{config['EXEEXT']}")
+runner = nil unless File.exist?(runner)
+abs_ruby = runner || File.expand_path(ruby)
+env["RUBY"] = abs_ruby
+env["GEM_PATH"] = env["GEM_HOME"] = File.expand_path(".bundle", srcdir)
+env["BUNDLE_RUBY"] = abs_ruby
+env["BUNDLE_GEM"] = "#{abs_ruby} -rrubygems #{srcdir}/bin/gem --backtrace"
+env["PATH"] = [File.dirname(abs_ruby), abs_archdir, ENV["PATH"]].compact.join(File::PATH_SEPARATOR)
+
+if e = ENV["RUBYLIB"]
+ libs |= e.split(File::PATH_SEPARATOR)
+end
+env["RUBYLIB"] = $:.replace(libs).join(File::PATH_SEPARATOR)
+
+libruby_so = File.join(abs_archdir, config['LIBRUBY_SO'])
+if File.file?(libruby_so)
+ if e = config['LIBPATHENV'] and !e.empty?
+ env[e] = [abs_archdir, ENV[e]].compact.join(File::PATH_SEPARATOR)
+ end
+ unless runner
+ if e = config['PRELOADENV']
+ e = nil if e.empty?
+ e ||= "LD_PRELOAD" if /linux/ =~ RUBY_PLATFORM
+ end
+ if e
+ env[e] = [libruby_so, ENV[e]].compact.join(File::PATH_SEPARATOR)
+ end
+ end
+end
+
+ENV.update env
+
+if debugger or ENV['RUNRUBY_USE_GDB'] == 'true'
+ if debugger == :gdb or !debugger
+ debugger = %w'gdb'
+ if File.exist?(gdb = 'run.gdb') or
+ File.exist?(gdb = File.join(abs_archdir, 'run.gdb'))
+ debugger.push('-x', gdb)
+ end
+ debugger << '--args'
+ end
+ if idx = precommand.index(:debugger)
+ precommand[idx, 1] = debugger
+ else
+ precommand.concat(debugger)
+ end
+end
+
+cmd = [runner || ruby]
+cmd.concat(ARGV)
+cmd.unshift(*precommand) unless precommand.empty?
+
+if show
+ require 'shellwords'
+ env.each {|k,v| puts "#{k}=#{v}"}
+ puts Shellwords.join(cmd)
+end
+
+exec(*cmd, close_others: false)
diff --git a/tool/strip-rdoc.rb b/tool/strip-rdoc.rb
new file mode 100755
index 0000000000..0ac9c39323
--- /dev/null
+++ b/tool/strip-rdoc.rb
@@ -0,0 +1,26 @@
+#!ruby
+
+# Filter for preventing Doxygen from processing RDoc comments.
+# Used by the Doxygen template.
+
+ARGF.binmode
+source = ARGF.read
+source = source.gsub(%r{/\*([!*])((?!\*/).+?)\*/}m) do |comment|
+ marker, comment = $1, $2
+ next "/**#{comment}*/" unless /^\s*\*\s?\-\-\s*$/ =~ comment
+ doxybody = nil
+ comment.each_line do |line|
+ if doxybody
+ if /^\s*\*\s?\+\+\s*$/ =~ line
+ break
+ end
+ doxybody << line
+ else
+ if /^\s*\*\s?--\s*$/ =~ line
+ doxybody = "\n"
+ end
+ end
+ end
+ "/*#{marker}#{doxybody}*/"
+end
+print source
diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb
new file mode 100644
index 0000000000..b12b7a792a
--- /dev/null
+++ b/tool/sync_default_gems.rb
@@ -0,0 +1,198 @@
+# sync following repositories to ruby repository
+#
+# * https://github.com/rubygems/rubygems
+# * https://github.com/ruby/rdoc
+# * https://github.com/flori/json
+# * https://github.com/ruby/psych
+# * https://github.com/ruby/fileutils
+# * https://github.com/ruby/fiddle
+# * https://github.com/ruby/stringio
+# * https://github.com/ruby/io-console
+# * https://github.com/ruby/csv
+# * https://github.com/ruby/webrick
+# * https://github.com/ruby/dbm
+# * https://github.com/ruby/gdbm
+# * https://github.com/ruby/sdbm
+# * https://github.com/ruby/etc
+# * https://github.com/ruby/date
+# * https://github.com/ruby/zlib
+# * https://github.com/ruby/fcntl
+# * https://github.com/ruby/scanf
+# * https://github.com/ruby/cmath
+# * https://github.com/ruby/strscan
+# * https://github.com/ruby/ipaddr
+#
+
+$repositories = {
+ rubygems: 'rubygems/rubygems',
+ rdoc: 'ruby/rdoc',
+ json: 'flori/json',
+ psych: 'ruby/psych',
+ fileutils: 'ruby/fileutils',
+ fiddle: 'ruby/fiddle',
+ stringio: 'ruby/stringio',
+ ioconsole: 'ruby/io-console',
+ csv: 'ruby/csv',
+ webrick: 'ruby/webrick',
+ dbm: 'ruby/dbm',
+ gdbm: 'ruby/gdbm',
+ sdbm: 'ruby/sdbm',
+ etc: 'ruby/etc',
+ date: 'ruby/date',
+ zlib: 'ruby/zlib',
+ fcntl: 'ruby/fcntl',
+ scanf: 'ruby/scanf',
+ cmath: 'ruby/cmath',
+ strscan: 'ruby/strscan',
+ ipaddr: 'ruby/ipaddr',
+}
+
+def sync_default_gems(gem)
+ author, repository = $repositories[gem.to_sym].split('/')
+ unless File.exist?("../../#{author}/#{repository}")
+ `mkdir -p ../../#{author}`
+ `git clone git@github.com:#{author}/#{repository}.git ../../#{author}/#{repository}`
+ end
+
+ puts "Sync #{$repositories[gem.to_sym]}"
+
+ case gem
+ when "rubygems"
+ `rm -rf lib/rubygems* test/rubygems`
+ `cp -r ../../rubygems/rubygems/lib/rubygems* ./lib`
+ `cp -r ../../rubygems/rubygems/test/rubygems ./test`
+ when "rdoc"
+ `rm -rf lib/rdoc* test/rdoc`
+ `cp -rf ../rdoc/lib/rdoc* ./lib`
+ `cp -rf ../rdoc/test test/rdoc`
+ `cp ../rdoc/rdoc.gemspec ./lib/rdoc`
+ `rm -f lib/rdoc/markdown.kpeg lib/rdoc/markdown/literals.kpeg lib/rdoc/rd/block_parser.ry lib/rdoc/rd/inline_parser.ry`
+ `git checkout lib/rdoc/.document`
+ when "json"
+ `rm -rf ext/json test/json`
+ `cp -rf ../../flori/json/ext/json/ext ext/json`
+ `cp -rf ../../flori/json/tests test/json`
+ `cp -rf ../../flori/json/lib ext/json`
+ `rm -rf ext/json/lib/json/pure*`
+ `cp ../../flori/json/json.gemspec ext/json`
+ `rm -r ext/json/lib/json/ext`
+ `git checkout ext/json/extconf.rb ext/json/parser/prereq.mk ext/json/generator/depend ext/json/parser/depend`
+ when "psych"
+ `rm -rf ext/psych test/psych`
+ `cp -rf ../psych/ext/psych ./ext`
+ `cp -rf ../psych/lib ./ext/psych`
+ `cp -rf ../psych/test/psych ./test`
+ `rm -rf ext/psych/lib/org ext/psych/lib/psych.jar ext/psych/lib/psych_jars.rb`
+ `rm -rf ext/psych/lib/psych.{bundle,so} ext/psych/lib/{2.0,2.1,2.2,2.3,2.4}`
+ `rm -f ext/psych/yaml/LICENSE`
+ `cp ../psych/psych.gemspec ext/psych/`
+ `git checkout ext/psych/depend`
+ when "fileutils"
+ `rm -rf lib/fileutils.rb test/fileutils lib/fileutils.gemspec`
+ `cp -rf ../fileutils/lib/* lib`
+ `cp -rf ../fileutils/test/fileutils test`
+ `cp -f ../fileutils/fileutils.gemspec lib`
+ when "fiddle"
+ `rm -rf ext/fiddle test/fiddle`
+ `cp -rf ../fiddle/ext/fiddle ext`
+ `cp -rf ../fiddle/lib ext/fiddle`
+ `cp -rf ../fiddle/test/fiddle test`
+ `cp -f ../fiddle/fiddle.gemspec ext/fiddle`
+ `git checkout ext/fiddle/depend`
+ when "stringio"
+ `rm -rf ext/stringio test/stringio`
+ `cp -rf ../stringio/ext/stringio ext`
+ `cp -rf ../stringio/test/stringio test`
+ `cp -f ../stringio/stringio.gemspec ext/stringio`
+ `git checkout ext/stringio/depend ext/stringio/README.md`
+ when "ioconsole"
+ `rm -rf ext/io/console test/io/console`
+ `cp -rf ../io-console/ext/io/console ext/io`
+ `cp -rf ../io-console/test/io/console test/io`
+ `mkdir -p ext/io/console/lib`
+ `cp -rf ../io-console/lib/console ext/io/console/lib`
+ `cp -f ../io-console/io-console.gemspec ext/io/console`
+ `git checkout ext/io/console/depend`
+ when "csv"
+ `rm -rf lib/csv.rb test/csv lib/csv.gemspec`
+ `cp -rf ../csv/lib/* lib`
+ `cp -rf ../csv/test/csv test`
+ `cp -f ../csv/csv.gemspec lib`
+ when "webrick"
+ `rm -rf lib/webrick test/webrick`
+ `cp -rf ../webrick/lib/webrick lib`
+ `cp -rf ../webrick/test/webrick test`
+ `cp -f ../webrick/webrick.gemspec lib/webrick`
+ when "dbm"
+ `rm -rf ext/dbm test/dbm`
+ `cp -rf ../dbm/ext/dbm ext`
+ `cp -rf ../dbm/test/dbm test`
+ `cp -f ../dbm/dbm.gemspec ext/dbm`
+ `git checkout ext/dbm/depend`
+ when "gdbm"
+ `rm -rf ext/gdbm test/gdbm`
+ `cp -rf ../gdbm/ext/gdbm ext`
+ `cp -rf ../gdbm/test/gdbm test`
+ `cp -f ../gdbm/gdbm.gemspec ext/gdbm`
+ `git checkout ext/gdbm/depend ext/gdbm/README`
+ when "sdbm"
+ `rm -rf ext/sdbm test/sdbm`
+ `cp -rf ../sdbm/ext/sdbm ext`
+ `cp -rf ../sdbm/test/sdbm test`
+ `cp -f ../sdbm/sdbm.gemspec ext/sdbm`
+ `git checkout ext/sdbm/depend`
+ when "etc"
+ `rm -rf ext/etc test/etc`
+ `cp -rf ../etc/ext/etc ext`
+ `cp -rf ../etc/test/etc test`
+ `cp -f ../etc/etc.gemspec ext/etc`
+ `git checkout ext/etc/depend`
+ when "date"
+ `rm -rf ext/date test/date`
+ `cp -rf ../date/ext/date ext`
+ `cp -rf ../date/lib ext/date`
+ `cp -rf ../date/test/date test`
+ `cp -f ../date/date.gemspec ext/date`
+ `git checkout ext/date/depend`
+ when "zlib"
+ `rm -rf ext/zlib test/zlib`
+ `cp -rf ../zlib/ext/zlib ext`
+ `cp -rf ../zlib/test/zlib test`
+ `cp -f ../zlib/zlib.gemspec ext/zlib`
+ `git checkout ext/zlib/depend`
+ when "fcntl"
+ `rm -rf ext/fcntl`
+ `cp -rf ../fcntl/ext/fcntl ext`
+ `cp -f ../fcntl/fcntl.gemspec ext/fcntl`
+ `git checkout ext/fcntl/depend`
+ when "scanf"
+ `rm -rf lib/scanf.rb test/scanf`
+ `cp -rf ../scanf/lib/* lib`
+ `cp -rf ../scanf/test/scanf test`
+ `cp -f ../scanf/scanf.gemspec lib`
+ when "cmath"
+ `rm -rf lib/cmath.rb test/test_cmath.rb`
+ `cp -rf ../cmath/lib/* lib`
+ `cp -rf ../cmath/test/test_cmath.rb test`
+ `cp -f ../cmath/cmath.gemspec lib`
+ when "strscan"
+ `rm -rf ext/strscan test/strscan`
+ `cp -rf ../strscan/ext/strscan ext`
+ `cp -rf ../strscan/test/strscan test`
+ `cp -f ../strscan/strscan.gemspec ext/strscan`
+ `rm -f ext/strscan/regenc.h ext/strscan/regint.h`
+ `git checkout ext/strscan/depend`
+ when "ipaddr"
+ `rm -rf lib/ipaddr.rb test/test_ipaddr.rb`
+ `cp -rf ../ipaddr/lib/* lib`
+ `cp -rf ../ipaddr/test/test_ipaddr.rb test`
+ `cp -f ../ipaddr/ipaddr.gemspec lib`
+ else
+ end
+end
+
+if ARGV[0]
+ sync_default_gems(ARGV[0])
+else
+ $repositories.keys.each{|gem| sync_default_gems(gem.to_s)}
+end
diff --git a/tool/test-coverage.rb b/tool/test-coverage.rb
new file mode 100644
index 0000000000..7a27c5cfb4
--- /dev/null
+++ b/tool/test-coverage.rb
@@ -0,0 +1,103 @@
+require "coverage"
+
+Coverage.start(lines: true, branches: true, methods: true)
+
+TEST_COVERAGE_DATA_FILE = "test-coverage.dat"
+
+def merge_coverage_data(res1, res2)
+ res1.each do |path, cov1|
+ cov2 = res2[path]
+ if cov2
+ cov1[:lines].each_with_index do |count1, i|
+ next unless count1
+ add_count(cov2[:lines], i, count1)
+ end
+ cov1[:branches].each do |base_key, targets1|
+ if cov2[:branches][base_key]
+ targets1.each do |target_key, count1|
+ add_count(cov2[:branches][base_key], target_key, count1)
+ end
+ else
+ cov2[:branches][base_key] = targets1
+ end
+ end
+ cov1[:methods].each do |key, count1|
+ add_count(cov2[:methods], key, count1)
+ end
+ else
+ res2[path] = cov1
+ end
+ end
+ res2
+end
+
+def add_count(h, key, count)
+ if h[key]
+ h[key] += count
+ else
+ h[key] = count
+ end
+end
+
+def save_coverage_data(res1)
+ res1.each do |_path, cov|
+ if cov[:methods]
+ h = {}
+ cov[:methods].each do |(klass, *key), count|
+ h[[klass.inspect, *key]] = count
+ end
+ cov[:methods].replace h
+ end
+ end
+ File.open(TEST_COVERAGE_DATA_FILE, File::RDWR | File::CREAT | File::BINARY) do |f|
+ f.flock(File::LOCK_EX)
+ s = f.read
+ res2 = s.size > 0 ? Marshal.load(s) : {}
+ res1 = merge_coverage_data(res1, res2)
+ f.rewind
+ f << Marshal.dump(res2)
+ f.flush
+ f.truncate(f.pos)
+ end
+end
+
+def invoke_simplecov_formatter
+ %w[doclie simplecov-html simplecov].each do |f|
+ $LOAD_PATH.unshift "#{__dir__}/../coverage/#{f}/lib"
+ end
+
+ require "simplecov"
+ res = Marshal.load(File.binread(TEST_COVERAGE_DATA_FILE))
+ simplecov_result = {}
+ base_dir = File.dirname(__dir__)
+
+ res.each do |path, cov|
+ next unless path.start_with?(base_dir)
+ next if path.start_with?(File.join(base_dir, "test"))
+ simplecov_result[path] = cov[:lines]
+ end
+
+ res = SimpleCov::Result.new(simplecov_result)
+ res.command_name = "Ruby's `make test-all`"
+ SimpleCov::Formatter::HTMLFormatter.new.format(res)
+end
+
+pid = $$
+pwd = Dir.pwd
+
+at_exit do
+ exit_exc = $!
+
+ Dir.chdir(pwd) do
+ save_coverage_data(Coverage.result)
+ if pid == $$
+ begin
+ nil while Process.waitpid(-1)
+ rescue Errno::ECHILD
+ invoke_simplecov_formatter
+ end
+ end
+ end
+
+ raise exit_exc if exit_exc
+end
diff --git a/tool/test/test_jisx0208.rb b/tool/test/test_jisx0208.rb
new file mode 100644
index 0000000000..d323c84745
--- /dev/null
+++ b/tool/test/test_jisx0208.rb
@@ -0,0 +1,40 @@
+require 'test/unit'
+
+require '../tool/jisx0208'
+
+class Test_JISX0208_Char < Test::Unit::TestCase
+ def test_create_with_row_cell
+ assert_equal JISX0208::Char.new(0x2121), JISX0208::Char.new(1, 1)
+ end
+
+ def test_succ
+ assert_equal JISX0208::Char.new(0x2221), JISX0208::Char.new(0x217e).succ
+ assert_equal JISX0208::Char.new(2, 1), JISX0208::Char.new(1, 94).succ
+ assert_equal JISX0208::Char.new(0x7f21), JISX0208::Char.new(0x7e7e).succ
+ end
+
+ def test_to_shift_jis
+ assert_equal 0x895C, JISX0208::Char.new(0x313D).to_sjis
+ assert_equal 0x895C, JISX0208::Char.from_sjis(0x895C).to_sjis
+ assert_equal 0xF3DE, JISX0208::Char.from_sjis(0xF3DE).to_sjis
+ assert_equal 0xFC40, JISX0208::Char.from_sjis(0xFC40).to_sjis
+ end
+
+ def test_from_sjis
+ assert_raise(ArgumentError) { JISX0208::Char.from_sjis(-1) }
+ assert_raise(ArgumentError) { JISX0208::Char.from_sjis(0x10000) }
+ assert_nothing_raised { JISX0208::Char.from_sjis(0x8140) }
+ assert_nothing_raised { JISX0208::Char.from_sjis(0xFCFC) }
+ assert_equal JISX0208::Char.new(0x313D), JISX0208::Char.from_sjis(0x895C)
+ end
+
+ def test_row
+ assert_equal 1, JISX0208::Char.new(0x2121).row
+ assert_equal 94, JISX0208::Char.new(0x7E7E).row
+ end
+
+ def test_cell
+ assert_equal 1, JISX0208::Char.new(0x2121).cell
+ assert_equal 94, JISX0208::Char.new(0x7E7E).cell
+ end
+end
diff --git a/tool/transcode-tblgen.rb b/tool/transcode-tblgen.rb
new file mode 100644
index 0000000000..f43627b781
--- /dev/null
+++ b/tool/transcode-tblgen.rb
@@ -0,0 +1,1114 @@
+# frozen_string_literal: true
+
+require 'optparse'
+require 'erb'
+require 'fileutils'
+require 'pp'
+
+class Array
+ unless [].respond_to? :product
+ def product(*args)
+ if args.empty?
+ self.map {|e| [e] }
+ else
+ result = []
+ self.each {|e0|
+ result.concat args.first.product(*args[1..-1]).map {|es| [e0, *es] }
+ }
+ result
+ end
+ end
+ end
+end
+
+class String
+ unless "".respond_to? :start_with?
+ def start_with?(*prefixes)
+ prefixes.each {|prefix|
+ return true if prefix.length <= self.length && prefix == self[0, prefix.length]
+ }
+ false
+ end
+ end
+end
+
+NUM_ELEM_BYTELOOKUP = 2
+
+C_ESC = {
+ "\\" => "\\\\",
+ '"' => '\"',
+ "\n" => '\n',
+}
+
+0x00.upto(0x1f) {|ch| C_ESC[[ch].pack("C")] ||= "\\%03o" % ch }
+0x7f.upto(0xff) {|ch| C_ESC[[ch].pack("C")] = "\\%03o" % ch }
+C_ESC_PAT = Regexp.union(*C_ESC.keys)
+
+def c_esc(str)
+ '"' + str.gsub(C_ESC_PAT) { C_ESC[$&] } + '"'
+end
+
+HEX2 = /(?:[0-9A-Fa-f]{2})/
+
+class ArrayCode
+ def initialize(type, name)
+ @type = type
+ @name = name
+ @len = 0;
+ @content = ''.dup
+ end
+
+ def length
+ @len
+ end
+
+ def insert_at_last(num, str)
+ newnum = self.length + num
+ @content << str
+ @len += num
+ end
+
+ def to_s
+ <<"End"
+static const #{@type}
+#{@name}[#{@len}] = {
+#{@content}};
+End
+ end
+end
+
+class Action
+ def initialize(value)
+ @value = value
+ end
+ attr_reader :value
+
+ def hash
+ @value.hash
+ end
+
+ def eql?(other)
+ self.class == other.class &&
+ @value == other.value
+ end
+ alias == eql?
+end
+
+class Branch
+ def initialize(byte_min, byte_max, child_tree)
+ @byte_min = byte_min
+ @byte_max = byte_max
+ @child_tree = child_tree
+ @hash = byte_min.hash ^ byte_max.hash ^ child_tree.hash
+ end
+ attr_reader :byte_min, :byte_max, :child_tree, :hash
+
+ def eql?(other)
+ self.class == other.class &&
+ @hash == other.hash &&
+ @byte_min == other.byte_min &&
+ @byte_max == other.byte_max &&
+ @child_tree == other.child_tree
+ end
+ alias == eql?
+end
+
+class ActionMap
+ def self.parse_to_rects(mapping)
+ rects = []
+ n = 0
+ mapping.each {|pat, action|
+ pat = pat.to_s
+ if /\A\s*\(empset\)\s*\z/ =~ pat
+ next
+ elsif /\A\s*\(empstr\)\s*\z/ =~ pat
+ rects << ['', '', action]
+ n += 1
+ elsif /\A\s*(#{HEX2}+)\s*\z/o =~ pat
+ hex = $1.upcase
+ rects << [hex, hex, action]
+ elsif /\A\s*((#{HEX2}|\{#{HEX2}(?:-#{HEX2})?(,#{HEX2}(?:-#{HEX2})?)*\})+(\s+|\z))*\z/o =~ pat
+ pat = pat.upcase
+ pat.scan(/\S+/) {
+ pat1 = $&
+ ranges_list = []
+ pat1.scan(/#{HEX2}|\{([^\}]*)\}/o) {
+ ranges_list << []
+ if !$1
+ ranges_list.last << [$&,$&]
+ else
+ set = {}
+ $1.scan(/(#{HEX2})(?:-(#{HEX2}))?/o) {
+ if !$2
+ c = $1.to_i(16)
+ set[c] = true
+ else
+ b = $1.to_i(16)
+ e = $2.to_i(16)
+ b.upto(e) {|c| set[c] = true }
+ end
+ }
+ i = nil
+ 0.upto(256) {|j|
+ if set[j]
+ if !i
+ i = j
+ end
+ if !set[j+1]
+ ranges_list.last << ["%02X" % i, "%02X" % j]
+ i = nil
+ end
+ end
+ }
+ end
+ }
+ first_ranges = ranges_list.shift
+ first_ranges.product(*ranges_list).each {|range_list|
+ min = range_list.map {|x, y| x }.join
+ max = range_list.map {|x, y| y }.join
+ rects << [min, max, action]
+ }
+ }
+ else
+ raise ArgumentError, "invalid pattern: #{pat.inspect}"
+ end
+ }
+ rects
+ end
+
+ def self.unambiguous_action(actions0)
+ actions = actions0.uniq
+ if actions.length == 1
+ actions[0]
+ else
+ actions.delete(:nomap0)
+ if actions.length == 1
+ actions[0]
+ else
+ raise ArgumentError, "ambiguous actions: #{actions0.inspect}"
+ end
+ end
+ end
+
+ def self.build_tree(rects)
+ expand(rects) {|prefix, actions|
+ unambiguous_action(actions)
+ }
+ end
+
+ def self.parse(mapping)
+ rects = parse_to_rects(mapping)
+ tree = build_tree(rects)
+ self.new(tree)
+ end
+
+ def self.merge_rects(*rects_list)
+ if rects_list.length < 2
+ raise ArgumentError, "not enough arguments"
+ end
+
+ all_rects = []
+ rects_list.each_with_index {|rects, i|
+ all_rects.concat rects.map {|min, max, action| [min, max, [i, action]] }
+ }
+
+ tree = expand(all_rects) {|prefix, actions|
+ args = Array.new(rects_list.length) { [] }
+ actions.each {|i, action|
+ args[i] << action
+ }
+ yield(prefix, *args)
+ }
+
+ self.new(tree)
+ end
+
+ def self.merge(*mappings, &block)
+ merge_rects(*mappings.map {|m| parse_to_rects(m) }, &block)
+ end
+
+ def self.merge2(map1, map2, &block)
+ rects1 = parse_to_rects(map1)
+ rects2 = parse_to_rects(map2)
+
+ actions = []
+ all_rects = []
+
+ rects1.each {|rect|
+ min, max, action = rect
+ rect[2] = actions.length
+ actions << action
+ all_rects << rect
+ }
+
+ boundary = actions.length
+
+ rects2.each {|rect|
+ min, max, action = rect
+ rect[2] = actions.length
+ actions << action
+ all_rects << rect
+ }
+
+ tree = expand(all_rects) {|prefix, as0|
+ as1 = []
+ as2 = []
+ as0.each {|i|
+ if i < boundary
+ as1 << actions[i]
+ else
+ as2 << actions[i]
+ end
+ }
+ yield(prefix, as1, as2)
+ }
+
+ self.new(tree)
+ end
+
+ def self.expand(rects, &block)
+ #numsing = numreg = 0
+ #rects.each {|min, max, action| if min == max then numsing += 1 else numreg += 1 end }
+ #puts "#{numsing} singleton mappings and #{numreg} region mappings."
+ singleton_rects = []
+ region_rects = []
+ rects.each {|rect|
+ min, max, action = rect
+ if min == max
+ singleton_rects << rect
+ else
+ region_rects << rect
+ end
+ }
+ @singleton_rects = singleton_rects.sort_by {|min, max, action| min }
+ @singleton_rects.reverse!
+ ret = expand_rec("", region_rects, &block)
+ @singleton_rects = nil
+ ret
+ end
+
+ TMPHASH = {}
+ def self.expand_rec(prefix, region_rects, &block)
+ return region_rects if region_rects.empty? && !((s_rect = @singleton_rects.last) && s_rect[0].start_with?(prefix))
+ if region_rects.empty? ? s_rect[0].length == prefix.length : region_rects[0][0].empty?
+ h = TMPHASH
+ while (s_rect = @singleton_rects.last) && s_rect[0].start_with?(prefix)
+ min, max, action = @singleton_rects.pop
+ raise ArgumentError, "ambiguous pattern: #{prefix}" if min.length != prefix.length
+ h[action] = true
+ end
+ region_rects.each {|min, max, action|
+ raise ArgumentError, "ambiguous pattern: #{prefix}" if !min.empty?
+ h[action] = true
+ }
+ tree = Action.new(block.call(prefix, h.keys))
+ h.clear
+ else
+ tree = []
+ each_firstbyte_range(prefix, region_rects) {|byte_min, byte_max, r_rects2|
+ if byte_min == byte_max
+ prefix2 = prefix + "%02X" % byte_min
+ else
+ prefix2 = prefix + "{%02X-%02X}" % [byte_min, byte_max]
+ end
+ child_tree = expand_rec(prefix2, r_rects2, &block)
+ tree << Branch.new(byte_min, byte_max, child_tree)
+ }
+ end
+ return tree
+ end
+
+ def self.each_firstbyte_range(prefix, region_rects)
+ index_from = TMPHASH
+
+ region_ary = []
+ region_rects.each {|min, max, action|
+ raise ArgumentError, "ambiguous pattern: #{prefix}" if min.empty?
+ min_firstbyte = min[0,2].to_i(16)
+ min_rest = min[2..-1]
+ max_firstbyte = max[0,2].to_i(16)
+ max_rest = max[2..-1]
+ region_ary << [min_firstbyte, max_firstbyte, [min_rest, max_rest, action]]
+ index_from[min_firstbyte] = true
+ index_from[max_firstbyte+1] = true
+ }
+
+ byte_from = Array.new(index_from.size)
+ bytes = index_from.keys
+ bytes.sort!
+ bytes.reverse!
+ bytes.each_with_index {|byte, i|
+ index_from[byte] = i
+ byte_from[i] = byte
+ }
+
+ region_rects_ary = Array.new(index_from.size) { [] }
+ region_ary.each {|min_firstbyte, max_firstbyte, rest_elt|
+ index_from[min_firstbyte].downto(index_from[max_firstbyte+1]+1) {|i|
+ region_rects_ary[i] << rest_elt
+ }
+ }
+
+ index_from.clear
+
+ r_rects = region_rects_ary.pop
+ region_byte = byte_from.pop
+ prev_r_start = region_byte
+ prev_r_rects = []
+ while r_rects && (s_rect = @singleton_rects.last) && (seq = s_rect[0]).start_with?(prefix)
+ singleton_byte = seq[prefix.length, 2].to_i(16)
+ min_byte = singleton_byte < region_byte ? singleton_byte : region_byte
+ if prev_r_start < min_byte && !prev_r_rects.empty?
+ yield prev_r_start, min_byte-1, prev_r_rects
+ end
+ if region_byte < singleton_byte
+ prev_r_start = region_byte
+ prev_r_rects = r_rects
+ r_rects = region_rects_ary.pop
+ region_byte = byte_from.pop
+ elsif region_byte > singleton_byte
+ yield singleton_byte, singleton_byte, prev_r_rects
+ prev_r_start = singleton_byte+1
+ else # region_byte == singleton_byte
+ prev_r_start = region_byte+1
+ prev_r_rects = r_rects
+ r_rects = region_rects_ary.pop
+ region_byte = byte_from.pop
+ yield singleton_byte, singleton_byte, prev_r_rects
+ end
+ end
+
+ while r_rects
+ if prev_r_start < region_byte && !prev_r_rects.empty?
+ yield prev_r_start, region_byte-1, prev_r_rects
+ end
+ prev_r_start = region_byte
+ prev_r_rects = r_rects
+ r_rects = region_rects_ary.pop
+ region_byte = byte_from.pop
+ end
+
+ while (s_rect = @singleton_rects.last) && (seq = s_rect[0]).start_with?(prefix)
+ singleton_byte = seq[prefix.length, 2].to_i(16)
+ yield singleton_byte, singleton_byte, []
+ end
+ end
+
+ def initialize(tree)
+ @tree = tree
+ end
+
+ def inspect
+ "\#<#{self.class}:" +
+ @tree.inspect +
+ ">"
+ end
+
+ def max_input_length_rec(tree)
+ case tree
+ when Action
+ 0
+ else
+ tree.map {|branch|
+ max_input_length_rec(branch.child_tree)
+ }.max + 1
+ end
+ end
+
+ def max_input_length
+ max_input_length_rec(@tree)
+ end
+
+ def empty_action
+ if @tree.kind_of? Action
+ @tree.value
+ else
+ nil
+ end
+ end
+
+ OffsetsMemo = {}
+ InfosMemo = {}
+
+ def format_offsets(min, max, offsets)
+ offsets = offsets[min..max]
+ code = "%d, %d,\n" % [min, max]
+ 0.step(offsets.length-1,16) {|i|
+ code << " "
+ code << offsets[i,8].map {|off| "%3d," % off.to_s }.join('')
+ if i+8 < offsets.length
+ code << " "
+ code << offsets[i+8,8].map {|off| "%3d," % off.to_s }.join('')
+ end
+ code << "\n"
+ }
+ code
+ end
+
+ UsedName = {}
+
+ StrMemo = {}
+
+ def str_name(bytes)
+ size = @bytes_code.length
+ rawbytes = [bytes].pack("H*")
+
+ n = nil
+ if !n && !(suf = rawbytes.gsub(/[^A-Za-z0-9_]/, '')).empty? && !UsedName[nn = "str1_" + suf] then n = nn end
+ if !n && !UsedName[nn = "str1_" + bytes] then n = nn end
+ n ||= "str1s_#{size}"
+
+ StrMemo[bytes] = n
+ UsedName[n] = true
+ n
+ end
+
+ def gen_str(bytes)
+ if n = StrMemo[bytes]
+ n
+ else
+ len = bytes.length/2
+ size = @bytes_code.length
+ n = str_name(bytes)
+ @bytes_code.insert_at_last(1 + len,
+ "\#define #{n} makeSTR1(#{size})\n" +
+ " makeSTR1LEN(#{len})," + bytes.gsub(/../, ' 0x\&,') + "\n\n")
+ n
+ end
+ end
+
+ def generate_info(info)
+ case info
+ when :nomap, :nomap0
+ # :nomap0 is low priority. it never collides.
+ "NOMAP"
+ when :undef
+ "UNDEF"
+ when :invalid
+ "INVALID"
+ when :func_ii
+ "FUNii"
+ when :func_si
+ "FUNsi"
+ when :func_io
+ "FUNio"
+ when :func_so
+ "FUNso"
+ when /\A(#{HEX2})\z/o
+ "o1(0x#$1)"
+ when /\A(#{HEX2})(#{HEX2})\z/o
+ "o2(0x#$1,0x#$2)"
+ when /\A(#{HEX2})(#{HEX2})(#{HEX2})\z/o
+ "o3(0x#$1,0x#$2,0x#$3)"
+ when /funsio\((\d+)\)/
+ "funsio(#{$1})"
+ when /\A(#{HEX2})(3[0-9])(#{HEX2})(3[0-9])\z/o
+ "g4(0x#$1,0x#$2,0x#$3,0x#$4)"
+ when /\A(f[0-7])(#{HEX2})(#{HEX2})(#{HEX2})\z/o
+ "o4(0x#$1,0x#$2,0x#$3,0x#$4)"
+ when /\A(#{HEX2}){4,259}\z/o
+ gen_str(info.upcase)
+ when /\A\/\*BYTE_LOOKUP\*\// # pointer to BYTE_LOOKUP structure
+ $'.to_s
+ else
+ raise "unexpected action: #{info.inspect}"
+ end
+ end
+
+ def format_infos(infos)
+ infos = infos.map {|info| generate_info(info) }
+ maxlen = infos.map {|info| info.length }.max
+ columns = maxlen <= 16 ? 4 : 2
+ code = "".dup
+ 0.step(infos.length-1, columns) {|i|
+ code << " "
+ is = infos[i,columns]
+ is.each {|info|
+ code << sprintf(" %#{maxlen}s,", info)
+ }
+ code << "\n"
+ }
+ code
+ end
+
+ def generate_lookup_node(name, table)
+ bytes_code = @bytes_code
+ words_code = @words_code
+ offsets = []
+ infos = []
+ infomap = {}
+ min = max = nil
+ table.each_with_index {|action, byte|
+ action ||= :invalid
+ if action != :invalid
+ min = byte if !min
+ max = byte
+ end
+ unless o = infomap[action]
+ infomap[action] = o = infos.length
+ infos[o] = action
+ end
+ offsets[byte] = o
+ }
+ infomap.clear
+ if !min
+ min = max = 0
+ end
+
+ offsets_key = [min, max, offsets[min..max]]
+ if n = OffsetsMemo[offsets_key]
+ offsets_name = n
+ else
+ offsets_name = "#{name}_offsets"
+ OffsetsMemo[offsets_key] = offsets_name
+ size = bytes_code.length
+ bytes_code.insert_at_last(2+max-min+1,
+ "\#define #{offsets_name} #{size}\n" +
+ format_offsets(min,max,offsets) + "\n")
+ end
+
+ if n = InfosMemo[infos]
+ infos_name = n
+ else
+ infos_name = "#{name}_infos"
+ InfosMemo[infos] = infos_name
+
+ size = words_code.length
+ words_code.insert_at_last(infos.length,
+ "\#define #{infos_name} WORDINDEX2INFO(#{size})\n" +
+ format_infos(infos) + "\n")
+ end
+
+ size = words_code.length
+ words_code.insert_at_last(NUM_ELEM_BYTELOOKUP,
+ "\#define #{name} WORDINDEX2INFO(#{size})\n" +
+ <<"End" + "\n")
+ #{offsets_name},
+ #{infos_name},
+End
+ end
+
+ PreMemo = {}
+ NextName = "a"
+
+ def generate_node(name_hint=nil)
+ if n = PreMemo[@tree]
+ return n
+ end
+
+ table = Array.new(0x100, :invalid)
+ @tree.each {|branch|
+ byte_min, byte_max, child_tree = branch.byte_min, branch.byte_max, branch.child_tree
+ rest = ActionMap.new(child_tree)
+ if a = rest.empty_action
+ table.fill(a, byte_min..byte_max)
+ else
+ name_hint2 = nil
+ if name_hint
+ name_hint2 = "#{name_hint}_#{byte_min == byte_max ? '%02X' % byte_min : '%02Xto%02X' % [byte_min, byte_max]}"
+ end
+ v = "/*BYTE_LOOKUP*/" + rest.gennode(@bytes_code, @words_code, name_hint2)
+ table.fill(v, byte_min..byte_max)
+ end
+ }
+
+ if !name_hint
+ name_hint = "fun_" + NextName
+ NextName.succ!
+ end
+
+ PreMemo[@tree] = name_hint
+
+ generate_lookup_node(name_hint, table)
+ name_hint
+ end
+
+ def gennode(bytes_code, words_code, name_hint=nil)
+ @bytes_code = bytes_code
+ @words_code = words_code
+ name = generate_node(name_hint)
+ @bytes_code = nil
+ @words_code = nil
+ return name
+ end
+end
+
+def citrus_mskanji_cstomb(csid, index)
+ case csid
+ when 0
+ index
+ when 1
+ index + 0x80
+ when 2, 3
+ row = index >> 8
+ raise "invalid byte sequence" if row < 0x21
+ if csid == 3
+ if row <= 0x2F
+ offset = (row == 0x22 || row >= 0x26) ? 0xED : 0xF0
+ elsif row >= 0x4D && row <= 0x7E
+ offset = 0xCE
+ else
+ raise "invalid byte sequence"
+ end
+ else
+ raise "invalid byte sequence" if row > 0x97
+ offset = (row < 0x5F) ? 0x81 : 0xC1
+ end
+ col = index & 0xFF
+ raise "invalid byte sequence" if (col < 0x21 || col > 0x7E)
+
+ row -= 0x21
+ col -= 0x21
+ if (row & 1) == 0
+ col += 0x40
+ col += 1 if (col >= 0x7F)
+ else
+ col += 0x9F;
+ end
+ row = row / 2 + offset
+ (row << 8) | col
+ end.to_s(16)
+end
+
+def citrus_euc_cstomb(csid, index)
+ case csid
+ when 0x0000
+ index
+ when 0x8080
+ index | 0x8080
+ when 0x0080
+ index | 0x8E80
+ when 0x8000
+ index | 0x8F8080
+ end.to_s(16)
+end
+
+def citrus_stateless_iso_cstomb(csid, index)
+ (index | 0x8080 | (csid << 16)).to_s(16)
+end
+
+def citrus_cstomb(ces, csid, index)
+ case ces
+ when 'mskanji'
+ citrus_mskanji_cstomb(csid, index)
+ when 'euc'
+ citrus_euc_cstomb(csid, index)
+ when 'stateless_iso'
+ citrus_stateless_iso_cstomb(csid, index)
+ end
+end
+
+SUBDIR = %w/APPLE AST BIG5 CNS CP EBCDIC EMOJI GB GEORGIAN ISO646 ISO-8859 JIS KAZAKH KOI KS MISC TCVN/
+
+
+def citrus_decode_mapsrc(ces, csid, mapsrcs)
+ table = []
+ mapsrcs.split(',').each do |mapsrc|
+ path = [$srcdir]
+ mode = nil
+ if mapsrc.rindex(/UCS(?:@[A-Z]+)?/, 0)
+ mode = :from_ucs
+ from = mapsrc[$&.size+1..-1]
+ path << SUBDIR.find{|x| from.rindex(x, 0) }
+ else
+ mode = :to_ucs
+ path << SUBDIR.find{|x| mapsrc.rindex(x, 0) }
+ end
+ if /\bUCS@(BMP|SMP|SIP|TIP|SSP)\b/ =~ mapsrc
+ plane = {"BMP"=>0, "SMP"=>1, "SIP"=>2, "TIP"=>3, "SSP"=>14}[$1]
+ else
+ plane = 0
+ end
+ plane <<= 16
+ path << mapsrc.gsub(':', '@')
+ path = File.join(*path)
+ path << ".src"
+ path[path.rindex('/')] = '%'
+ STDERR.puts 'load mapsrc %s' % path if VERBOSE_MODE
+ open(path, 'rb') do |f|
+ f.each_line do |l|
+ break if /^BEGIN_MAP/ =~ l
+ end
+ f.each_line do |l|
+ next if /^\s*(?:#|$)/ =~ l
+ break if /^END_MAP/ =~ l
+ case mode
+ when :from_ucs
+ case l
+ when /0x(\w+)\s*-\s*0x(\w+)\s*=\s*INVALID/
+ # Citrus OOB_MODE
+ when /(0x\w+)\s*=\s*(0x\w+)/
+ table.push << [plane | $1.hex, citrus_cstomb(ces, csid, $2.hex)]
+ else
+ raise "unknown notation '%s'"% l.chomp
+ end
+ when :to_ucs
+ case l
+ when /(0x\w+)\s*=\s*(0x\w+)/
+ table.push << [citrus_cstomb(ces, csid, $1.hex), plane | $2.hex]
+ else
+ raise "unknown notation '%s'"% l.chomp
+ end
+ end
+ end
+ end
+ end
+ return table
+end
+
+def import_ucm(path)
+ to_ucs = []
+ from_ucs = []
+ File.foreach(File.join($srcdir, "ucm", path)) do |line|
+ uc, bs, fb = nil
+ if /^<U([0-9a-fA-F]+)>\s*([\+0-9a-fA-Fx\\]+)\s*\|(\d)/ =~ line
+ uc = $1.hex
+ bs = $2.delete('x\\')
+ fb = $3.to_i
+ next if uc < 128 && uc == bs.hex
+ elsif /^([<U0-9a-fA-F>+]+)\s*([\+0-9a-fA-Fx\\]+)\s*\|(\d)/ =~ line
+ uc = $1.scan(/[0-9a-fA-F]+>/).map(&:hex).pack("U*").unpack("H*")[0]
+ bs = $2.delete('x\\')
+ fb = $3.to_i
+ end
+ to_ucs << [bs, uc] if fb == 0 || fb == 3
+ from_ucs << [uc, bs] if fb == 0 || fb == 1
+ end
+ [to_ucs, from_ucs]
+end
+
+def encode_utf8(map)
+ r = []
+ map.each {|k, v|
+ # integer means UTF-8 encoded sequence.
+ k = [k].pack("U").unpack("H*")[0].upcase if Integer === k
+ v = [v].pack("U").unpack("H*")[0].upcase if Integer === v
+ r << [k,v]
+ }
+ r
+end
+
+UnspecifiedValidEncoding = Object.new
+
+def transcode_compile_tree(name, from, map, valid_encoding)
+ map = encode_utf8(map)
+ h = {}
+ map.each {|k, v|
+ h[k] = v unless h[k] # use first mapping
+ }
+ if valid_encoding.equal? UnspecifiedValidEncoding
+ valid_encoding = ValidEncoding.fetch(from)
+ end
+ if valid_encoding
+ am = ActionMap.merge2(h, {valid_encoding => :undef}) {|prefix, as1, as2|
+ a1 = as1.empty? ? nil : ActionMap.unambiguous_action(as1)
+ a2 = as2.empty? ? nil : ActionMap.unambiguous_action(as2)
+ if !a2
+ raise "invalid mapping: #{prefix}"
+ end
+ a1 || a2
+ }
+ else
+ am = ActionMap.parse(h)
+ end
+ h.clear
+
+ max_input = am.max_input_length
+ defined_name = am.gennode(TRANSCODE_GENERATED_BYTES_CODE, TRANSCODE_GENERATED_WORDS_CODE, name)
+ return defined_name, max_input
+end
+
+TRANSCODERS = []
+TRANSCODE_GENERATED_TRANSCODER_CODE = ''.dup
+
+def transcode_tbl_only(from, to, map, valid_encoding=UnspecifiedValidEncoding)
+ if VERBOSE_MODE
+ if from.empty? || to.empty?
+ STDERR.puts "converter for #{from.empty? ? to : from}"
+ else
+ STDERR.puts "converter from #{from} to #{to}"
+ end
+ end
+ id_from = from.tr('^0-9A-Za-z', '_')
+ id_to = to.tr('^0-9A-Za-z', '_')
+ if from == "UTF-8"
+ tree_name = "to_#{id_to}"
+ elsif to == "UTF-8"
+ tree_name = "from_#{id_from}"
+ else
+ tree_name = "from_#{id_from}_to_#{id_to}"
+ end
+ real_tree_name, max_input = transcode_compile_tree(tree_name, from, map, valid_encoding)
+ return map, tree_name, real_tree_name, max_input
+end
+
+#
+# call-seq:
+# transcode_tblgen(from_name, to_name, map [, valid_encoding_check [, ascii_compatibility]]) -> ''
+#
+# Returns an empty string just in case the result is used somewhere.
+# Stores the actual product for later output with transcode_generated_code and
+# transcode_register_code.
+#
+# The first argument is a string that will be used for the source (from) encoding.
+# The second argument is a string that will be used for the target (to) encoding.
+#
+# The third argument is the actual data, a map represented as an array of two-element
+# arrays. Each element of the array stands for one character being converted. The
+# first element of each subarray is the code of the character in the source encoding,
+# the second element of each subarray is the code of the character in the target encoding.
+#
+# Each code (i.e. byte sequence) is represented as a string of hexadecimal characters
+# of even length. Codes can also be represented as integers (usually in the form Ox...),
+# in which case they are interpreted as Unicode codepoints encoded in UTF-8. So as
+# an example, 0x677E is the same as "E69DBE" (but somewhat easier to produce and check).
+#
+# In addition, the following symbols can also be used instead of actual codes in the
+# second element of a subarray:
+# :nomap (no mapping, just copy input to output), :nomap0 (same as :nomap, but low priority),
+# :undef (input code undefined in the destination encoding),
+# :invalid (input code is an invalid byte sequence in the source encoding),
+# :func_ii, :func_si, :func_io, :func_so (conversion by function with specific call
+# convention).
+#
+# The forth argument specifies the overall structure of the encoding. For examples,
+# see ValidEncoding below. This is used to cross-check the data in the third argument
+# and to automatically add :undef and :invalid mappings where necessary.
+#
+# The fifth argument gives the ascii-compatibility of the transcoding. See
+# rb_transcoder_asciicompat_type_t in transcode_data.h for details. In most
+# cases, this argument can be left out.
+#
+def transcode_tblgen(from, to, map, valid_encoding=UnspecifiedValidEncoding,
+ ascii_compatibility='asciicompat_converter')
+ map, tree_name, real_tree_name, max_input = transcode_tbl_only(from, to, map, valid_encoding)
+ transcoder_name = "rb_#{tree_name}"
+ TRANSCODERS << transcoder_name
+ input_unit_length = UnitLength[from]
+ max_output = map.map {|k,v| String === v ? v.length/2 : 1 }.max
+ transcoder_code = <<"End"
+static const rb_transcoder
+#{transcoder_name} = {
+ #{c_esc from}, #{c_esc to}, #{real_tree_name},
+ TRANSCODE_TABLE_INFO,
+ #{input_unit_length}, /* input_unit_length */
+ #{max_input}, /* max_input */
+ #{max_output}, /* max_output */
+ #{ascii_compatibility}, /* asciicompat_type */
+ 0, NULL, NULL, /* state_size, state_init, state_fini */
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL
+};
+End
+ TRANSCODE_GENERATED_TRANSCODER_CODE << transcoder_code
+ ''
+end
+
+def transcode_generate_node(am, name_hint=nil)
+ STDERR.puts "converter for #{name_hint}" if VERBOSE_MODE
+ name = am.gennode(TRANSCODE_GENERATED_BYTES_CODE, TRANSCODE_GENERATED_WORDS_CODE, name_hint)
+ ''
+end
+
+def transcode_generated_code
+ TRANSCODE_GENERATED_BYTES_CODE.to_s +
+ TRANSCODE_GENERATED_WORDS_CODE.to_s +
+ "\#define TRANSCODE_TABLE_INFO " +
+ "#{OUTPUT_PREFIX}byte_array, #{TRANSCODE_GENERATED_BYTES_CODE.length}, " +
+ "#{OUTPUT_PREFIX}word_array, #{TRANSCODE_GENERATED_WORDS_CODE.length}, " +
+ "((int)sizeof(unsigned int))\n" +
+ TRANSCODE_GENERATED_TRANSCODER_CODE
+end
+
+def transcode_register_code
+ code = ''.dup
+ TRANSCODERS.each {|transcoder_name|
+ code << " rb_register_transcoder(&#{transcoder_name});\n"
+ }
+ code
+end
+
+UnitLength = {
+ 'UTF-16BE' => 2,
+ 'UTF-16LE' => 2,
+ 'UTF-32BE' => 4,
+ 'UTF-32LE' => 4,
+}
+UnitLength.default = 1
+
+ValidEncoding = {
+ '1byte' => '{00-ff}',
+ '2byte' => '{00-ff}{00-ff}',
+ '4byte' => '{00-ff}{00-ff}{00-ff}{00-ff}',
+ 'US-ASCII' => '{00-7f}',
+ 'UTF-8' => '{00-7f}
+ {c2-df}{80-bf}
+ e0{a0-bf}{80-bf}
+ {e1-ec}{80-bf}{80-bf}
+ ed{80-9f}{80-bf}
+ {ee-ef}{80-bf}{80-bf}
+ f0{90-bf}{80-bf}{80-bf}
+ {f1-f3}{80-bf}{80-bf}{80-bf}
+ f4{80-8f}{80-bf}{80-bf}',
+ 'UTF-16BE' => '{00-d7,e0-ff}{00-ff}
+ {d8-db}{00-ff}{dc-df}{00-ff}',
+ 'UTF-16LE' => '{00-ff}{00-d7,e0-ff}
+ {00-ff}{d8-db}{00-ff}{dc-df}',
+ 'UTF-32BE' => '0000{00-d7,e0-ff}{00-ff}
+ 00{01-10}{00-ff}{00-ff}',
+ 'UTF-32LE' => '{00-ff}{00-d7,e0-ff}0000
+ {00-ff}{00-ff}{01-10}00',
+ 'EUC-JP' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-fe}
+ 8f{a1-fe}{a1-fe}',
+ 'CP51932' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-fe}',
+ 'EUC-JIS-2004' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-fe}
+ 8f{a1-fe}{a1-fe}',
+ 'Shift_JIS' => '{00-7f}
+ {81-9f,e0-fc}{40-7e,80-fc}
+ {a1-df}',
+ 'EUC-KR' => '{00-7f}
+ {a1-fe}{a1-fe}',
+ 'CP949' => '{00-7f}
+ {81-fe}{41-5a,61-7a,81-fe}',
+ 'Big5' => '{00-7f}
+ {81-fe}{40-7e,a1-fe}',
+ 'EUC-TW' => '{00-7f}
+ {a1-fe}{a1-fe}
+ 8e{a1-b0}{a1-fe}{a1-fe}',
+ 'GBK' => '{00-80}
+ {81-fe}{40-7e,80-fe}',
+ 'GB18030' => '{00-7f}
+ {81-fe}{40-7e,80-fe}
+ {81-fe}{30-39}{81-fe}{30-39}',
+}
+
+def ValidEncoding(enc)
+ ValidEncoding.fetch(enc)
+end
+
+def set_valid_byte_pattern(encoding, pattern_or_label)
+ pattern =
+ if ValidEncoding[pattern_or_label]
+ ValidEncoding[pattern_or_label]
+ else
+ pattern_or_label
+ end
+ if ValidEncoding[encoding] and ValidEncoding[encoding]!=pattern
+ raise ArgumentError, "trying to change valid byte pattern for encoding #{encoding} from #{ValidEncoding[encoding]} to #{pattern}"
+ end
+ ValidEncoding[encoding] = pattern
+end
+
+# the following may be used in different places, so keep them here for the moment
+set_valid_byte_pattern 'ASCII-8BIT', '1byte'
+set_valid_byte_pattern 'Windows-31J', 'Shift_JIS'
+set_valid_byte_pattern 'eucJP-ms', 'EUC-JP'
+
+def make_signature(filename, src)
+ "src=#{filename.dump}, len=#{src.length}, checksum=#{src.sum}"
+end
+
+if __FILE__ == $0
+ start_time = Time.now
+
+ output_filename = nil
+ verbose_mode = false
+ force_mode = false
+
+ op = OptionParser.new
+ op.def_option("--help", "show help message") { puts op; exit 0 }
+ op.def_option("--verbose", "verbose mode") { verbose_mode = true }
+ op.def_option("--force", "force table generation") { force_mode = true }
+ op.def_option("--output=FILE", "specify output file") {|arg| output_filename = arg }
+ op.parse!
+
+ VERBOSE_MODE = verbose_mode
+
+ OUTPUT_FILENAME = output_filename
+ OUTPUT_PREFIX = output_filename ? File.basename(output_filename)[/\A[A-Za-z0-9_]*/] : "".dup
+ OUTPUT_PREFIX.sub!(/\A_+/, '')
+ OUTPUT_PREFIX.sub!(/_*\z/, '_')
+
+ TRANSCODE_GENERATED_BYTES_CODE = ArrayCode.new("unsigned char", "#{OUTPUT_PREFIX}byte_array")
+ TRANSCODE_GENERATED_WORDS_CODE = ArrayCode.new("unsigned int", "#{OUTPUT_PREFIX}word_array")
+
+ arg = ARGV.shift
+ $srcdir = File.dirname(arg)
+ $:.unshift $srcdir unless $:.include? $srcdir
+ src = File.read(arg)
+ src.force_encoding("ascii-8bit") if src.respond_to? :force_encoding
+ this_script = File.read(__FILE__)
+ this_script.force_encoding("ascii-8bit") if this_script.respond_to? :force_encoding
+
+ base_signature = "/* autogenerated. */\n".dup
+ base_signature << "/* #{make_signature(File.basename(__FILE__), this_script)} */\n"
+ base_signature << "/* #{make_signature(File.basename(arg), src)} */\n"
+
+ if !force_mode && output_filename && File.readable?(output_filename)
+ old_signature = File.open(output_filename) {|f| f.gets("").chomp }
+ chk_signature = base_signature.dup
+ old_signature.each_line {|line|
+ if %r{/\* src="([0-9a-z_.-]+)",} =~ line
+ name = $1
+ next if name == File.basename(arg) || name == File.basename(__FILE__)
+ path = File.join($srcdir, name)
+ if File.readable? path
+ chk_signature << "/* #{make_signature(name, File.read(path))} */\n"
+ end
+ end
+ }
+ if old_signature == chk_signature
+ now = Time.now
+ File.utime(now, now, output_filename)
+ STDERR.puts "already up-to-date: #{output_filename}" if VERBOSE_MODE
+ exit
+ end
+ end
+
+ if VERBOSE_MODE
+ if output_filename
+ STDERR.puts "generating #{output_filename} ..."
+ end
+ end
+
+ libs1 = $".dup
+ erb = ERB.new(src, nil, '%')
+ erb.filename = arg
+ erb_result = erb.result(binding)
+ libs2 = $".dup
+
+ libs = libs2 - libs1
+ lib_sigs = ''.dup
+ libs.each {|lib|
+ lib = File.basename(lib)
+ path = File.join($srcdir, lib)
+ if File.readable? path
+ lib_sigs << "/* #{make_signature(lib, File.read(path))} */\n"
+ end
+ }
+
+ result = ''.dup
+ result << base_signature
+ result << lib_sigs
+ result << "\n"
+ result << erb_result
+ result << "\n"
+
+ if output_filename
+ new_filename = output_filename + ".new"
+ FileUtils.mkdir_p(File.dirname(output_filename))
+ File.open(new_filename, "wb") {|f| f << result }
+ File.rename(new_filename, output_filename)
+ tms = Process.times
+ elapsed = Time.now - start_time
+ STDERR.puts "done. (#{'%.2f' % tms.utime}user #{'%.2f' % tms.stime}system #{'%.2f' % elapsed}elapsed)" if VERBOSE_MODE
+ else
+ print result
+ end
+end
diff --git a/tool/update-deps b/tool/update-deps
new file mode 100755
index 0000000000..c308f35195
--- /dev/null
+++ b/tool/update-deps
@@ -0,0 +1,625 @@
+#!/usr/bin/ruby
+
+# tool/update-deps verify makefile dependencies.
+
+# Requirements:
+# gcc 4.5 (for -save-temps=obj option)
+# GNU make (for -p option)
+#
+# Warning: ccache (and similar tools) must be disabled for
+# -save-temps=obj to work properly.
+#
+# Usage:
+# 1. Compile ruby with -save-temps=obj option.
+# Ex. ./configure debugflags='-save-temps=obj -g' && make all golf
+# 2. Run tool/update-deps to show dependency problems.
+# Ex. ruby tool/update-deps
+# 3. Use --fix to fix makefiles.
+# Ex. ruby tool/update-deps --fix
+#
+# Other usages:
+# * Fix makefiles using previously detected dependency problems
+# Ex. ruby tool/update-deps --actual-fix [file]
+# "ruby tool/update-deps --fix" is same as "ruby tool/update-deps | ruby tool/update-deps --actual-fix".
+
+require 'optparse'
+require 'stringio'
+require 'pathname'
+require 'open3'
+require 'pp'
+
+# When out-of-place build, files may be built in source directory or
+# build directory.
+# Some files are always built in the source directory.
+# Some files are always built in the build directory.
+# Some files are built in the source directory for tarball but build directory for repository (svn).
+
+=begin
+How to build test directories.
+
+VER=2.2.0
+REV=48577
+tar xf ruby-$VER-r$REV.tar.xz
+cp -a ruby-$VER-r$REV tarball_source_dir_original
+mv ruby-$VER-r$REV tarball_source_dir_after_build
+svn co -q -r$REV http://svn.ruby-lang.org/repos/ruby/trunk ruby
+(cd ruby; autoconf)
+cp -a ruby repo_source_dir_original
+mv ruby repo_source_dir_after_build
+mkdir tarball_build_dir repo_build_dir tarball_install_dir repo_install_dir
+(cd tarball_build_dir; ../tarball_source_dir_after_build/configure --prefix=$(cd ../tarball_install_dir; pwd) && make all golf install) > tarball.log 2>&1
+(cd repo_build_dir; ../repo_source_dir_after_build/configure --prefix=$(cd ../repo_install_dir; pwd) && make all golf install) > repo.log 2>&1
+ruby -rpp -rfind -e '
+ds = %w[
+ repo_source_dir_original
+ repo_source_dir_after_build
+ repo_build_dir
+ tarball_source_dir_original
+ tarball_source_dir_after_build
+ tarball_build_dir
+]
+files = {}
+ds.each {|d|
+ files[d] = {}
+ Dir.chdir(d) { Find.find(".") {|f| files[d][f] = true if %r{\.(c|h|inc|dmyh)\z} =~ f } }
+}
+result = {}
+files_union = files.values.map {|h| h.keys }.flatten.uniq.sort
+files_union.each {|f|
+ k = files.map {|d,h| h[f] ? d : nil }.compact.sort
+ next if k == %w[repo_source_dir_after_build repo_source_dir_original tarball_source_dir_after_build tarball_source_dir_original]
+ next if k == %w[repo_build_dir tarball_build_dir] && File.basename(f) == "extconf.h"
+ result[k] ||= []
+ result[k] << f
+}
+result.each {|k,v|
+ k.each {|d|
+ puts d
+ }
+ v.each {|f|
+ puts " " + f.sub(%r{\A\./}, "")
+ }
+ puts
+}
+' | tee compare.log
+=end
+
+# Files built in the source directory.
+# They can be referenced as $(top_srcdir)/filename.
+# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts((g("repo_source_dir_after_build") - g("repo_source_dir_original")).sort)'
+FILES_IN_SOURCE_DIRECTORY = %w[
+ revision.h
+]
+
+# Files built in the build directory (except extconf.h).
+# They can be referenced as $(topdir)/filename.
+# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts(g("tarball_build_dir").reject {|f| %r{/extconf.h\z} =~ f }.sort)'
+FILES_IN_BUILD_DIRECTORY = %w[
+ encdb.h
+ ext/etc/constdefs.h
+ ext/socket/constdefs.c
+ ext/socket/constdefs.h
+ probes.h
+ transdb.h
+ verconf.h
+]
+
+# They are built in the build directory if the source is obtained from the repository.
+# However they are pre-built for tarball and they exist in the source directory extracted from the tarball.
+# % ruby -e 'def g(d) Dir.chdir(d) { Dir["**/*.{c,h,inc,dmyh}"] } end; puts((g("repo_build_dir") & g("tarball_source_dir_original")).sort)'
+FILES_NEED_VPATH = %w[
+ ext/rbconfig/sizeof/sizes.c
+ ext/ripper/eventids1.c
+ ext/ripper/eventids2table.c
+ ext/ripper/ripper.c
+ golf_prelude.c
+ id.c
+ id.h
+ insns.inc
+ insns_info.inc
+ known_errors.inc
+ lex.c
+ miniprelude.c
+ newline.c
+ node_name.inc
+ opt_sc.inc
+ optinsn.inc
+ optunifs.inc
+ parse.c
+ parse.h
+ prelude.c
+ probes.dmyh
+ vm.inc
+ vmtc.inc
+
+ enc/trans/big5.c
+ enc/trans/chinese.c
+ enc/trans/emoji.c
+ enc/trans/emoji_iso2022_kddi.c
+ enc/trans/emoji_sjis_docomo.c
+ enc/trans/emoji_sjis_kddi.c
+ enc/trans/emoji_sjis_softbank.c
+ enc/trans/escape.c
+ enc/trans/gb18030.c
+ enc/trans/gbk.c
+ enc/trans/iso2022.c
+ enc/trans/japanese.c
+ enc/trans/japanese_euc.c
+ enc/trans/japanese_sjis.c
+ enc/trans/korean.c
+ enc/trans/single_byte.c
+ enc/trans/utf8_mac.c
+ enc/trans/utf_16_32.c
+]
+
+# Multiple files with same filename.
+# It is not good idea to refer them using VPATH.
+# Files in FILES_SAME_NAME_INC is referenced using $(hdrdir).
+# Files in FILES_SAME_NAME_TOP is referenced using $(top_srcdir).
+# include/ruby.h is referenced using $(top_srcdir) because mkmf.rb replaces
+# $(hdrdir)/ruby.h to $(hdrdir)/ruby/ruby.h
+
+FILES_SAME_NAME_INC = %w[
+ include/ruby/ruby.h
+ include/ruby/version.h
+]
+
+FILES_SAME_NAME_TOP = %w[
+ include/ruby.h
+ version.h
+]
+
+# Other source files exist in the source directory.
+
+def in_makefile(target, source)
+ target = target.to_s
+ source = source.to_s
+ case target
+ when %r{\A[^/]*\z}
+ target2 = "#{target.sub(/\.o\z/, '.$(OBJEXT)')}"
+ case source
+ when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
+ when *FILES_IN_BUILD_DIRECTORY then source2 = "{$(VPATH)}#{source}" # VPATH is not used now but it may changed in future.
+ when *FILES_NEED_VPATH then source2 = "{$(VPATH)}#{source}"
+ when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
+ when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
+ when 'thread_pthread.c' then source2 = '{$(VPATH)}thread_$(THREAD_MODEL).c'
+ when 'thread_pthread.h' then source2 = '{$(VPATH)}thread_$(THREAD_MODEL).h'
+ when %r{\A[^/]*\z} then source2 = "{$(VPATH)}#{File.basename source}"
+ when %r{\A\.ext/include/[^/]+/ruby/} then source2 = "{$(VPATH)}#{$'}"
+ when %r{\Ainclude/ruby/} then source2 = "{$(VPATH)}#{$'}"
+ when %r{\Aenc/} then source2 = "{$(VPATH)}#{$'}"
+ when %r{\Amissing/} then source2 = "{$(VPATH)}#{$'}"
+ when %r{\Accan/} then source2 = "$(CCAN_DIR)/#{$'}"
+ when %r{\Adefs/} then source2 = "{$(VPATH)}#{source}"
+ else source2 = "$(top_srcdir)/#{source}"
+ end
+ ["common.mk", target2, source2]
+ when %r{\Aenc/}
+ target2 = "#{target.sub(/\.o\z/, '.$(OBJEXT)')}"
+ case source
+ when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
+ when *FILES_IN_BUILD_DIRECTORY then source2 = source
+ when *FILES_NEED_VPATH then source2 = source
+ when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
+ when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
+ when %r{\A\.ext/include/[^/]+/ruby/} then source2 = $'
+ when %r{\Ainclude/ruby/} then source2 = $'
+ when %r{\Aenc/} then source2 = source
+ else source2 = "$(top_srcdir)/#{source}"
+ end
+ ["enc/depend", target2, source2]
+ when %r{\Aext/}
+ unless File.exist?("#{File.dirname(target)}/extconf.rb")
+ warn "warning: not found: #{File.dirname(target)}/extconf.rb"
+ end
+ target2 = File.basename(target)
+ relpath = Pathname(source).relative_path_from(Pathname(target).dirname).to_s
+ case source
+ when *FILES_IN_SOURCE_DIRECTORY then source2 = "$(top_srcdir)/#{source}"
+ when *FILES_IN_BUILD_DIRECTORY then source2 = relpath
+ when *FILES_NEED_VPATH then source2 = "{$(VPATH)}#{File.basename source}"
+ when *FILES_SAME_NAME_INC then source2 = "$(hdrdir)/#{source.sub(%r{\Ainclude/},'')}"
+ when *FILES_SAME_NAME_TOP then source2 = "$(top_srcdir)/#{source}"
+ when %r{\A\.ext/include/[^/]+/ruby/} then source2 = "$(arch_hdrdir)/ruby/#{$'}"
+ when %r{\Ainclude/} then source2 = "$(hdrdir)/#{$'}"
+ when %r{\A#{Regexp.escape File.dirname(target)}/extconf\.h\z} then source2 = "$(RUBY_EXTCONF_H)"
+ when %r{\A#{Regexp.escape File.dirname(target)}/} then source2 = $'
+ else source2 = "$(top_srcdir)/#{source}"
+ end
+ ["#{File.dirname(target)}/depend", target2, source2]
+ else
+ raise "unexpected target: #{target}"
+ end
+end
+
+DEPENDENCIES_SECTION_START_MARK = "\# AUTOGENERATED DEPENDENCIES START\n"
+DEPENDENCIES_SECTION_END_MARK = "\# AUTOGENERATED DEPENDENCIES END\n"
+
+def init_global
+ ENV['LC_ALL'] = 'C'
+
+ $opt_fix = false
+ $opt_a = false
+ $opt_actual_fix = false
+ $i_not_found = false
+end
+
+def optionparser
+ op = OptionParser.new
+ op.banner = 'Usage: ruby tool/update-deps'
+ op.def_option('-a', 'show valid dependencies') { $opt_a = true }
+ op.def_option('--fix') { $opt_fix = true }
+ op.def_option('--actual-fix') { $opt_actual_fix = true }
+ op
+end
+
+def read_make_deps(cwd)
+ dependencies = {}
+ make_p, make_p_stderr, make_p_status = Open3.capture3("make -p all miniruby ruby golf")
+ File.open('update-deps.make.out.log', 'w') {|f| f.print make_p }
+ File.open('update-deps.make.err.log', 'w') {|f| f.print make_p_stderr }
+ if !make_p_status.success?
+ puts make_p_stderr
+ raise "make failed"
+ end
+ dirstack = [cwd]
+ curdir = nil
+ make_p.scan(%r{Entering\ directory\ ['`](.*)'|
+ ^\#\ (GNU\ Make)\ |
+ ^CURDIR\ :=\ (.*)|
+ ^([/0-9a-zA-Z._-]+):(.*)\n((?:\#.*\n)*)|
+ ^\#\ (Finished\ Make\ data\ base\ on)\ |
+ Leaving\ directory\ ['`](.*)'}x) {
+ directory_enter = $1
+ data_base_start = $2
+ data_base_curdir = $3
+ rule_target = $4
+ rule_sources = $5
+ rule_desc = $6
+ data_base_end = $7
+ directory_leave = $8
+ #p $~
+ if directory_enter
+ enter_dir = Pathname(directory_enter)
+ #p [:enter, enter_dir]
+ dirstack.push enter_dir
+ elsif data_base_start
+ curdir = nil
+ elsif data_base_curdir
+ curdir = Pathname(data_base_curdir)
+ elsif rule_target && rule_sources && rule_desc &&
+ /Modification time never checked/ !~ rule_desc # This pattern match eliminates rules which VPATH is not expanded.
+ target = rule_target
+ deps = rule_sources
+ deps = deps.scan(%r{[/0-9a-zA-Z._-]+})
+ next if /\.o\z/ !~ target.to_s
+ next if /\A\./ =~ target.to_s # skip rules such as ".c.o"
+ #p [curdir, target, deps]
+ dir = curdir || dirstack.last
+ dependencies[dir + target] ||= []
+ dependencies[dir + target] |= deps.map {|dep| dir + dep }
+ elsif data_base_end
+ curdir = nil
+ elsif directory_leave
+ leave_dir = Pathname(directory_leave)
+ #p [:leave, leave_dir]
+ if leave_dir != dirstack.last
+ warn "unexpected leave_dir : #{dirstack.last.inspect} != #{leave_dir.inspect}"
+ end
+ dirstack.pop
+ end
+ }
+ dependencies
+end
+
+#def guess_compiler_wd(filename, hint0)
+# hint = hint0
+# begin
+# guess = hint + filename
+# if guess.file?
+# return hint
+# end
+# hint = hint.parent
+# end while hint.to_s != '.'
+# raise ArgumentError, "can not find #{filename} (hint: #{hint0})"
+#end
+
+def read_single_cc_deps(path_i, cwd)
+ files = {}
+ compiler_wd = nil
+ path_i.each_line {|line|
+ next if /\A\# \d+ "(.*)"/ !~ line
+ dep = $1
+ next if %r{\A<.*>\z} =~ dep # omit <command-line>, etc.
+ compiler_wd ||= dep
+ files[dep] = true
+ }
+ # gcc emits {# 1 "/absolute/directory/of/the/source/file//"} at 2nd line.
+ if %r{\A/.*//\z} =~ compiler_wd
+ files.delete compiler_wd
+ compiler_wd = Pathname(compiler_wd.sub(%r{//\z}, ''))
+ elsif !(compiler_wd = yield)
+ raise "compiler working directory not found: #{path_i}"
+ end
+ deps = []
+ files.each_key {|dep|
+ dep = Pathname(dep)
+ if dep.relative?
+ dep = compiler_wd + dep
+ end
+ if !dep.file?
+ warn "warning: file not found: #{dep}"
+ next
+ end
+ next if !dep.to_s.start_with?(cwd.to_s) # omit system headers.
+ deps << dep
+ }
+ deps
+end
+
+def read_cc_deps(cwd)
+ deps = {}
+ Pathname.glob('**/*.o').sort.each {|fn_o|
+ fn_i = fn_o.sub_ext('.i')
+ if !fn_i.exist?
+ warn "warning: not found: #{fn_i}"
+ $i_not_found = true
+ next
+ end
+ path_o = cwd + fn_o
+ path_i = cwd + fn_i
+ deps[path_o] = read_single_cc_deps(path_i, cwd) do
+ if fn_o.to_s.start_with?("enc/")
+ cwd
+ else
+ path_i.parent
+ end
+ end
+ }
+ deps
+end
+
+def concentrate(dependencies, cwd)
+ deps = {}
+ dependencies.keys.sort.each {|target|
+ sources = dependencies[target]
+ target = target.relative_path_from(cwd)
+ sources = sources.map {|s|
+ rel = s.relative_path_from(cwd)
+ rel
+ }
+ if %r{\A\.\.(/|\z)} =~ target.to_s
+ warn "warning: out of tree target: #{target}"
+ next
+ end
+ sources = sources.reject {|s|
+ if %r{\A\.\.(/|\z)} =~ s.to_s
+ warn "warning: out of tree source: #{s}"
+ true
+ elsif %r{/\.time\z} =~ s.to_s
+ true
+ else
+ false
+ end
+ }
+ deps[target] = sources
+ }
+ deps
+end
+
+def sort_paths(paths)
+ paths.sort_by {|t|
+ ary = t.to_s.split(%r{/})
+ ary.map.with_index {|e, i| i == ary.length-1 ? [0, e] : [1, e] } # regular file first, directories last.
+ }
+end
+
+def show_deps(tag, deps)
+ targets = sort_paths(deps.keys)
+ targets.each {|t|
+ sources = sort_paths(deps[t])
+ sources.each {|s|
+ puts "#{tag} #{t}: #{s}"
+ }
+ }
+end
+
+def detect_dependencies(out=$stdout)
+ cwd = Pathname.pwd
+ make_deps = read_make_deps(cwd)
+ #pp make_deps
+ make_deps = concentrate(make_deps, cwd)
+ #pp make_deps
+ cc_deps = read_cc_deps(cwd)
+ #pp cc_deps
+ cc_deps = concentrate(cc_deps, cwd)
+ #pp cc_deps
+ return make_deps, cc_deps
+end
+
+def compare_deps(make_deps, cc_deps, out=$stdout)
+ targets = make_deps.keys | cc_deps.keys
+
+ makefiles = {}
+
+ make_lines_hash = {}
+ make_deps.each {|t, sources|
+ sources.each {|s|
+ makefile, t2, s2 = in_makefile(t, s)
+ makefiles[makefile] = true
+ make_lines_hash[makefile] ||= Hash.new(false)
+ make_lines_hash[makefile]["#{t2}: #{s2}"] = true
+ }
+ }
+
+ cc_lines_hash = {}
+ cc_deps.each {|t, sources|
+ sources.each {|s|
+ makefile, t2, s2 = in_makefile(t, s)
+ makefiles[makefile] = true
+ cc_lines_hash[makefile] ||= Hash.new(false)
+ cc_lines_hash[makefile]["#{t2}: #{s2}"] = true
+ }
+ }
+
+ makefiles.keys.sort.each {|makefile|
+ cc_lines = cc_lines_hash[makefile] || Hash.new(false)
+ make_lines = make_lines_hash[makefile] || Hash.new(false)
+ content = begin
+ File.read(makefile)
+ rescue Errno::ENOENT
+ ''
+ end
+ if /^#{Regexp.escape DEPENDENCIES_SECTION_START_MARK}
+ ((?:.*\n)*)
+ #{Regexp.escape DEPENDENCIES_SECTION_END_MARK}/x =~ content
+ pre_post_part = [$`, $']
+ current_lines = Hash.new(false)
+ $1.each_line {|line| current_lines[line.chomp] = true }
+ (cc_lines.keys | current_lines.keys | make_lines.keys).sort.each {|line|
+ status = [cc_lines[line], current_lines[line], make_lines[line]]
+ case status
+ when [true, true, true]
+ # no problem
+ when [true, true, false]
+ out.puts "warning #{makefile} : #{line} (make doesn't detect written dependency)"
+ when [true, false, true]
+ out.puts "add_auto #{makefile} : #{line} (harmless)" # This is automatically updatable.
+ when [true, false, false]
+ out.puts "add_auto #{makefile} : #{line} (harmful)" # This is automatically updatable.
+ when [false, true, true]
+ out.puts "del_cc #{makefile} : #{line}" # Not automatically updatable because build on other OS may need the dependency.
+ when [false, true, false]
+ out.puts "del_cc #{makefile} : #{line} (Curious. make doesn't detect this dependency.)" # Not automatically updatable because build on other OS may need the dependency.
+ when [false, false, true]
+ out.puts "del_make #{makefile} : #{line}" # Not automatically updatable because the dependency is written manually.
+ else
+ raise "unexpected status: #{status.inspect}"
+ end
+ }
+ else
+ (cc_lines.keys | make_lines.keys).sort.each {|line|
+ status = [cc_lines[line], make_lines[line]]
+ case status
+ when [true, true]
+ # no problem
+ when [true, false]
+ out.puts "add_manual #{makefile} : #{line}" # Not automatically updatable because makefile has no section to update automatically.
+ when [false, true]
+ out.puts "del_manual #{makefile} : #{line}" # Not automatically updatable because makefile has no section to update automatically.
+ else
+ raise "unexpected status: #{status.inspect}"
+ end
+ }
+ end
+ }
+end
+
+def main_show(out=$stdout)
+ make_deps, cc_deps = detect_dependencies(out)
+ compare_deps(make_deps, cc_deps, out)
+end
+
+def extract_deplines(problems)
+ adds = {}
+ others = {}
+ problems.each_line {|line|
+ case line
+ when /\Aadd_auto (\S+) : ((\S+): (\S+))/
+ (adds[$1] ||= []) << [line, "#{$2}\n"]
+ when /\A(?:del_cc|del_make|add_manual|del_manual|warning) (\S+) : /
+ (others[$1] ||= []) << line
+ else
+ raise "unexpected line: #{line.inspect}"
+ end
+ }
+ return adds, others
+end
+
+def main_actual_fix(problems)
+ adds, others = extract_deplines(problems)
+ (adds.keys | others.keys).sort.each {|makefile|
+ content = begin
+ File.read(makefile)
+ rescue Errno::ENOENT
+ nil
+ end
+
+ if content &&
+ /^#{Regexp.escape DEPENDENCIES_SECTION_START_MARK}
+ ((?:.*\n)*)
+ #{Regexp.escape DEPENDENCIES_SECTION_END_MARK}/x =~ content
+ pre_dep_post = [$`, $1, $']
+ else
+ pre_dep_post = nil
+ end
+
+ if pre_dep_post && adds[makefile]
+ pre_lines, dep_lines, post_lines = pre_dep_post
+ dep_lines = dep_lines.lines.to_a
+ add_lines = adds[makefile].map(&:last)
+ new_lines = (dep_lines | add_lines).sort.uniq
+ new_content = [
+ pre_lines,
+ DEPENDENCIES_SECTION_START_MARK,
+ *new_lines,
+ DEPENDENCIES_SECTION_END_MARK,
+ post_lines
+ ].join
+ if content != new_content
+ puts "modified: #{makefile}"
+ tmp_makefile = "#{makefile}.new.#{$$}"
+ File.write(tmp_makefile, new_content)
+ File.rename tmp_makefile, makefile
+ (add_lines - dep_lines).each {|line| puts " added #{line}" }
+ else
+ puts "not modified: #{makefile}"
+ end
+ if others[makefile]
+ others[makefile].each {|line| puts " #{line}" }
+ end
+ else
+ if pre_dep_post
+ puts "no additional lines: #{makefile}"
+ elsif content
+ puts "no dependencies section: #{makefile}"
+ else
+ puts "no makefile: #{makefile}"
+ end
+ if adds[makefile]
+ puts " warning: dependencies section was exist at previous phase."
+ end
+ if adds[makefile]
+ adds[makefile].map(&:first).each {|line| puts " #{line}" }
+ end
+ if others[makefile]
+ others[makefile].each {|line| puts " #{line}" }
+ end
+ end
+ }
+end
+
+def main_fix
+ problems = StringIO.new
+ main_show(problems)
+ main_actual_fix(problems.string)
+end
+
+def run
+ op = optionparser
+ op.parse!(ARGV)
+ if $opt_actual_fix
+ main_actual_fix(ARGF.read)
+ elsif $opt_fix
+ main_fix
+ else
+ main_show
+ end
+end
+
+init_global
+run
+if $i_not_found
+ warn "warning: missing *.i files, see help in #$0 and ensure ccache is disabled"
+end
diff --git a/tool/vcs.rb b/tool/vcs.rb
new file mode 100644
index 0000000000..c3a5b3a0f8
--- /dev/null
+++ b/tool/vcs.rb
@@ -0,0 +1,495 @@
+# vcs
+require 'fileutils'
+
+# This library is used by several other tools/ scripts to detect the current
+# VCS in use (e.g. SVN, Git) or to interact with that VCS.
+
+ENV.delete('PWD')
+
+unless File.respond_to? :realpath
+ require 'pathname'
+ def File.realpath(arg)
+ Pathname(arg).realpath.to_s
+ end
+end
+
+def IO.pread(*args)
+ STDERR.puts(*args.inspect) if $DEBUG
+ popen(*args) {|f|f.read}
+end
+
+if RUBY_VERSION < "2.0"
+ class IO
+ @orig_popen = method(:popen)
+
+ if defined?(fork)
+ def self.popen(command, *rest, &block)
+ if command.kind_of?(Hash)
+ env = command
+ command = rest.shift
+ end
+ opts = rest.last
+ if opts.kind_of?(Hash)
+ dir = opts.delete(:chdir)
+ rest.pop if opts.empty?
+ end
+
+ if block
+ @orig_popen.call("-", *rest) do |f|
+ if f
+ yield(f)
+ else
+ Dir.chdir(dir) if dir
+ ENV.replace(env) if env
+ exec(*command)
+ end
+ end
+ else
+ f = @orig_popen.call("-", *rest)
+ unless f
+ Dir.chdir(dir) if dir
+ ENV.replace(env) if env
+ exec(*command)
+ end
+ f
+ end
+ end
+ else
+ require 'shellwords'
+ def self.popen(command, *rest, &block)
+ if command.kind_of?(Hash)
+ env = command
+ oldenv = ENV.to_hash
+ command = rest.shift
+ end
+ opts = rest.last
+ if opts.kind_of?(Hash)
+ dir = opts.delete(:chdir)
+ rest.pop if opts.empty?
+ end
+
+ command = command.shelljoin if Array === command
+ Dir.chdir(dir || ".") do
+ ENV.replace(env) if env
+ @orig_popen.call(command, *rest, &block)
+ ENV.replace(oldenv) if oldenv
+ end
+ end
+ end
+ end
+else
+ module DebugPOpen
+ verbose, $VERBOSE = $VERBOSE, nil if RUBY_VERSION < "2.1"
+ refine IO.singleton_class do
+ def popen(*args)
+ STDERR.puts args.inspect if $DEBUG
+ super
+ end
+ end
+ ensure
+ $VERBOSE = verbose unless verbose.nil?
+ end
+ using DebugPOpen
+end
+
+class VCS
+ class NotFoundError < RuntimeError; end
+
+ @@dirs = []
+ def self.register(dir, &pred)
+ @@dirs << [dir, self, pred]
+ end
+
+ def self.detect(path)
+ @@dirs.each do |dir, klass, pred|
+ curr = path
+ loop {
+ return klass.new(curr) if pred ? pred[curr, dir] : File.directory?(File.join(curr, dir))
+ prev, curr = curr, File.realpath(File.join(curr, '..'))
+ break if curr == prev # stop at the root directory
+ }
+ end
+ raise VCS::NotFoundError, "does not seem to be under a vcs: #{path}"
+ end
+
+ def self.local_path?(path)
+ String === path or path.respond_to?(:to_path)
+ end
+
+ attr_reader :srcdir
+
+ def initialize(path)
+ @srcdir = path
+ super()
+ end
+
+ NullDevice = defined?(IO::NULL) ? IO::NULL :
+ %w[/dev/null NUL NIL: NL:].find {|dev| File.exist?(dev)}
+
+ # return a pair of strings, the last revision and the last revision in which
+ # +path+ was modified.
+ def get_revisions(path)
+ if self.class.local_path?(path)
+ path = relative_to(path)
+ end
+ last, changed, modified, *rest = (
+ begin
+ if NullDevice
+ save_stderr = STDERR.dup
+ STDERR.reopen NullDevice, 'w'
+ end
+ self.class.get_revisions(path, @srcdir)
+ rescue Errno::ENOENT => e
+ raise VCS::NotFoundError, e.message
+ ensure
+ if save_stderr
+ STDERR.reopen save_stderr
+ save_stderr.close
+ end
+ end
+ )
+ last or raise VCS::NotFoundError, "last revision not found"
+ changed or raise VCS::NotFoundError, "changed revision not found"
+ if modified
+ /\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ modified or
+ raise "unknown time format - #{modified}"
+ match = $~[1..6].map { |x| x.to_i }
+ off = $7 ? "#{$7}:#{$8}" : "+00:00"
+ match << off
+ begin
+ modified = Time.new(*match)
+ rescue ArgumentError
+ modified = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60
+ end
+ end
+ return last, changed, modified, *rest
+ end
+
+ def modified(path)
+ _, _, modified, * = get_revisions(path)
+ modified
+ end
+
+ def relative_to(path)
+ if path
+ srcdir = File.realpath(@srcdir)
+ path = File.realdirpath(path)
+ list1 = srcdir.split(%r{/})
+ list2 = path.split(%r{/})
+ while !list1.empty? && !list2.empty? && list1.first == list2.first
+ list1.shift
+ list2.shift
+ end
+ if list1.empty? && list2.empty?
+ "."
+ else
+ ([".."] * list1.length + list2).join("/")
+ end
+ else
+ '.'
+ end
+ end
+
+ def after_export(dir)
+ end
+
+ class SVN < self
+ register(".svn")
+ COMMAND = ENV['SVN'] || 'svn'
+
+ def self.get_revisions(path, srcdir = nil)
+ if srcdir and local_path?(path)
+ path = File.join(srcdir, path)
+ end
+ if srcdir
+ info_xml = IO.pread(%W"#{COMMAND} info --xml #{srcdir}")
+ info_xml = nil unless info_xml[/<url>(.*)<\/url>/, 1] == path.to_s
+ end
+ info_xml ||= IO.pread(%W"#{COMMAND} info --xml #{path}")
+ _, last, _, changed, _ = info_xml.split(/revision="(\d+)"/)
+ modified = info_xml[/<date>([^<>]*)/, 1]
+ branch = info_xml[%r'<relative-url>\^/(?:branches/|tags/)?([^<>]+)', 1]
+ [last, changed, modified, branch]
+ end
+
+ def self.search_root(path)
+ return unless local_path?(path)
+ parent = File.realpath(path)
+ begin
+ parent = File.dirname(wkdir = parent)
+ return wkdir if File.directory?(wkdir + "/.svn")
+ end until parent == wkdir
+ end
+
+ def get_info
+ @info ||= IO.pread(%W"#{COMMAND} info --xml #{@srcdir}")
+ end
+
+ def url
+ unless @url
+ url = get_info[/<root>(.*)<\/root>/, 1]
+ @url = URI.parse(url+"/") if url
+ end
+ @url
+ end
+
+ def wcroot
+ unless @wcroot
+ info = get_info
+ @wcroot = info[/<wcroot-abspath>(.*)<\/wcroot-abspath>/, 1]
+ @wcroot ||= self.class.search_root(@srcdir)
+ end
+ @wcroot
+ end
+
+ def branch(name)
+ url + "branches/#{name}"
+ end
+
+ def tag(name)
+ url + "tags/#{name}"
+ end
+
+ def trunk
+ url + "trunk"
+ end
+
+ def branch_list(pat)
+ IO.popen(%W"#{COMMAND} ls #{branch('')}") do |f|
+ f.each do |line|
+ line.chomp!
+ line.chomp!('/')
+ yield(line) if File.fnmatch?(pat, line)
+ end
+ end
+ end
+
+ def grep(pat, tag, *files, &block)
+ cmd = %W"#{COMMAND} cat"
+ files.map! {|n| File.join(tag, n)} if tag
+ set = block.binding.eval("proc {|match| $~ = match}")
+ IO.popen([cmd, *files]) do |f|
+ f.grep(pat) do |s|
+ set[$~]
+ yield s
+ end
+ end
+ end
+
+ def export(revision, url, dir, keep_temp = false)
+ if @srcdir and (rootdir = wcroot)
+ srcdir = File.realpath(@srcdir)
+ rootdir << "/"
+ if srcdir.start_with?(rootdir)
+ subdir = srcdir[rootdir.size..-1]
+ subdir = nil if subdir.empty?
+ FileUtils.mkdir_p(svndir = dir+"/.svn")
+ FileUtils.ln_s(Dir.glob(rootdir+"/.svn/*"), svndir)
+ system(COMMAND, "-q", "revert", "-R", subdir || ".", :chdir => dir) or return false
+ FileUtils.rm_rf(svndir) unless keep_temp
+ if subdir
+ tmpdir = Dir.mktmpdir("tmp-co.", "#{dir}/#{subdir}")
+ File.rename(tmpdir, tmpdir = "#{dir}/#{File.basename(tmpdir)}")
+ FileUtils.mv(Dir.glob("#{dir}/#{subdir}/{.[^.]*,..?*,*}"), tmpdir)
+ begin
+ Dir.rmdir("#{dir}/#{subdir}")
+ end until (subdir = File.dirname(subdir)) == '.'
+ FileUtils.mv(Dir.glob("#{tmpdir}/#{subdir}/{.[^.]*,..?*,*}"), dir)
+ Dir.rmdir(tmpdir)
+ end
+ return true
+ end
+ end
+ IO.popen(%W"#{COMMAND} export -r #{revision} #{url} #{dir}") do |pipe|
+ pipe.each {|line| /^A/ =~ line or yield line}
+ end
+ $?.success?
+ end
+
+ def after_export(dir)
+ FileUtils.rm_rf(dir+"/.svn")
+ end
+
+ def export_changelog(url, from, to, path)
+ range = [to, (from+1 if from)].compact.join(':')
+ IO.popen({'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'},
+ %W"#{COMMAND} log -r#{range} #{url}") do |r|
+ open(path, 'w') do |w|
+ IO.copy_stream(r, w)
+ end
+ end
+ end
+
+ def commit
+ system(*%W"#{COMMAND} commit")
+ end
+ end
+
+ class GIT < self
+ register(".git") {|path, dir| File.exist?(File.join(path, dir))}
+ COMMAND = ENV["GIT"] || 'git'
+
+ def self.cmd_args(cmds, srcdir = nil)
+ if srcdir and local_path?(srcdir)
+ (opts = cmds.last).kind_of?(Hash) or cmds << (opts = {})
+ opts[:chdir] ||= srcdir
+ end
+ cmds
+ end
+
+ def self.cmd_pipe_at(srcdir, cmds, &block)
+ IO.popen(*cmd_args(cmds, srcdir), &block)
+ end
+
+ def self.cmd_read_at(srcdir, cmds)
+ IO.pread(*cmd_args(cmds, srcdir))
+ end
+
+ def self.get_revisions(path, srcdir = nil)
+ gitcmd = [COMMAND]
+ desc = cmd_read_at(srcdir, [gitcmd + %w[describe --tags --match REV_*]])
+ if /\AREV_(\d+)(?:-(\d+)-g\h+)?\Z/ =~ desc
+ last = ($1.to_i + $2.to_i).to_s
+ end
+ logcmd = gitcmd + %W[log -n1 --date=iso]
+ logcmd << "--grep=^ *git-svn-id: .*@[0-9][0-9]*" unless last
+ idpat = /git-svn-id: .*?@(\d+) \S+\Z/
+ log = cmd_read_at(srcdir, [logcmd])
+ commit = log[/\Acommit (\w+)/, 1]
+ last ||= log[idpat, 1]
+ if path
+ cmd = logcmd
+ cmd += [path] unless path == '.'
+ log = cmd_read_at(srcdir, [cmd])
+ changed = log[idpat, 1] || last
+ else
+ changed = last
+ end
+ modified = log[/^Date:\s+(.*)/, 1]
+ branch = cmd_read_at(srcdir, [gitcmd + %W[symbolic-ref HEAD]])[%r'\A(?:refs/heads/)?(.+)', 1]
+ title = cmd_read_at(srcdir, [gitcmd + %W[log --format=%s -n1 #{commit}..HEAD]])
+ title = nil if title.empty?
+ [last, changed, modified, branch, title]
+ end
+
+ def initialize(*)
+ super
+ if srcdir = @srcdir and self.class.local_path?(srcdir)
+ @srcdir = File.realpath(srcdir)
+ end
+ self
+ end
+
+ def cmd_pipe(*cmds, &block)
+ self.class.cmd_pipe_at(@srcdir, cmds, &block)
+ end
+
+ def cmd_read(*cmds)
+ self.class.cmd_read_at(@srcdir, cmds)
+ end
+
+ Branch = Struct.new(:to_str)
+
+ def branch(name)
+ Branch.new(name)
+ end
+
+ alias tag branch
+
+ def trunk
+ branch("trunk")
+ end
+
+ def stable
+ cmd = %W"#{COMMAND} for-each-ref --format=\%(refname:short) refs/heads/ruby_[0-9]*"
+ branch(cmd_read(cmd)[/.*^(ruby_\d+_\d+)$/m, 1])
+ end
+
+ def branch_list(pat)
+ cmd = %W"#{COMMAND} for-each-ref --format=\%(refname:short) refs/heads/#{pat}"
+ cmd_pipe(cmd) {|f|
+ f.each {|line|
+ line.chomp!
+ yield line
+ }
+ }
+ end
+
+ def grep(pat, tag, *files, &block)
+ cmd = %W[#{COMMAND} grep -h --perl-regexp #{tag} --]
+ set = block.binding.eval("proc {|match| $~ = match}")
+ cmd_pipe(cmd+files) do |f|
+ f.grep(pat) do |s|
+ set[$~]
+ yield s
+ end
+ end
+ end
+
+ def export(revision, url, dir, keep_temp = false)
+ ret = system(COMMAND, "clone", "-s", (@srcdir || '.').to_s, "-b", url, dir)
+ FileUtils.rm_rf("#{dir}/.git") if ret and !keep_temp
+ ret
+ end
+
+ def after_export(dir)
+ FileUtils.rm_rf(Dir.glob("#{dir}/.git*"))
+ end
+
+ def export_changelog(url, from, to, path)
+ range = [from, to].map do |rev|
+ rev or next
+ rev = cmd_read({'LANG' => 'C', 'LC_ALL' => 'C'},
+ %W"#{COMMAND} log -n1 --format=format:%H" <<
+ "--grep=^ *git-svn-id: .*@#{rev} ")
+ rev unless rev.empty?
+ end.join('..')
+ cmd_pipe({'TZ' => 'JST-9', 'LANG' => 'C', 'LC_ALL' => 'C'},
+ %W"#{COMMAND} log --date=iso-local --topo-order #{range}", "rb") do |r|
+ open(path, 'w') do |w|
+ sep = "-"*72
+ w.puts sep
+ while s = r.gets('')
+ author = s[/^Author:\s*(\S+)/, 1]
+ time = s[/^Date:\s*(.+)/, 1]
+ s = r.gets('')
+ s.gsub!(/^ {4}/, '')
+ s.sub!(/^git-svn-id: .*@(\d+) .*\n+\z/, '')
+ rev = $1
+ s.gsub!(/^ {8}/, '') if /^(?! {8}|$)/ !~ s
+ if /\A(\d+)-(\d+)-(\d+)/ =~ time
+ date = Time.new($1.to_i, $2.to_i, $3.to_i).strftime("%a, %d %b %Y")
+ end
+ w.puts "r#{rev} | #{author} | #{time} (#{date}) | #{s.count("\n")} lines\n\n"
+ w.puts s, sep
+ end
+ end
+ end
+ end
+
+ def commit
+ rev = cmd_read(%W"#{COMMAND} svn info"+[STDERR=>[:child, :out]])[/^Last Changed Rev: (\d+)/, 1]
+ com = cmd_read(%W"#{COMMAND} svn find-rev r#{rev}").chomp
+
+ # TODO: dcommit necessary commits only with --add-author-from
+ same = true
+ cmd_pipe([COMMAND, "log", "--format=%ae %ce", "#{com}..@"], "rb") do |r|
+ r.each do |l|
+ same &&= /^(\S+) +\1$/ =~ l
+ end
+ end
+ ret = system(COMMAND, "svn", "dcommit", *(["--add-author-from"] unless same))
+
+ if ret and rev
+ old = [cmd_read(%W"#{COMMAND} log -1 --format=%H").chomp]
+ old << cmd_read(%W"#{COMMAND} svn reset -r#{rev}")[/^r#{rev} = (\h+)/, 1]
+ 3.times do
+ sleep 2
+ system(*%W"#{COMMAND} pull --no-edit --rebase")
+ break unless old.include?(cmd_read(%W"#{COMMAND} log -1 --format=%H").chomp)
+ end
+ end
+ ret
+ end
+ end
+end
diff --git a/tool/vpath.rb b/tool/vpath.rb
new file mode 100644
index 0000000000..48ab148405
--- /dev/null
+++ b/tool/vpath.rb
@@ -0,0 +1,87 @@
+# -*- coding: us-ascii -*-
+
+class VPath
+ attr_accessor :separator
+
+ def initialize(*list)
+ @list = list
+ @additional = []
+ @separator = nil
+ end
+
+ def inspect
+ list.inspect
+ end
+
+ def search(meth, base, *rest)
+ begin
+ meth.call(base, *rest)
+ rescue Errno::ENOENT => error
+ list.each do |dir|
+ return meth.call(File.join(dir, base), *rest) rescue nil
+ end
+ raise error
+ end
+ end
+
+ def process(*args, &block)
+ search(File.method(__callee__), *args, &block)
+ end
+
+ alias stat process
+ alias lstat process
+
+ def open(*args)
+ f = search(File.method(:open), *args)
+ if block_given?
+ begin
+ yield f
+ ensure
+ f.close unless f.closed?
+ end
+ else
+ f
+ end
+ end
+
+ def read(*args)
+ open(*args) {|f| f.read}
+ end
+
+ def foreach(file, *args, &block)
+ open(file) {|f| f.each(*args, &block)}
+ end
+
+ def def_options(opt)
+ opt.on("-I", "--srcdir=DIR", "add a directory to search path") {|dir|
+ @additional << dir
+ }
+ opt.on("-L", "--vpath=PATH LIST", "add directories to search path") {|dirs|
+ @additional << [dirs]
+ }
+ opt.on("--path-separator=SEP", /\A(?:\W\z|\.(\W).+)/, "separator for vpath") {|sep, vsep|
+ # hack for msys make.
+ @separator = vsep || sep
+ }
+ end
+
+ def list
+ @additional.reject! do |dirs|
+ case dirs
+ when String
+ @list << dirs
+ when Array
+ raise "--path-separator option is needed for vpath list" unless @separator
+ # @separator ||= (require 'rbconfig'; RbConfig::CONFIG["PATH_SEPARATOR"])
+ @list.concat(dirs[0].split(@separator))
+ end
+ true
+ end
+ @list
+ end
+
+ def strip(path)
+ prefix = list.map {|dir| Regexp.quote(dir)}
+ path.sub(/\A#{prefix.join('|')}(?:\/|\z)/, '')
+ end
+end
diff --git a/tool/vtlh.rb b/tool/vtlh.rb
new file mode 100644
index 0000000000..2e1faf2ce8
--- /dev/null
+++ b/tool/vtlh.rb
@@ -0,0 +1,17 @@
+# Convert addresses to line numbers for MiniRuby.
+
+# ARGF = open('ha')
+cd = `pwd`.chomp + '/'
+ARGF.each{|line|
+ if /^0x([a-z0-9]+),/ =~ line
+ stat = line.split(',')
+ addr = stat[0].hex + 0x00400000
+ retired = stat[2].to_i
+ ticks = stat[3].to_i
+
+ src = `addr2line -e miniruby.exe #{addr.to_s(16)}`.chomp
+ src.sub!(cd, '')
+ puts '%-40s 0x%08x %8d %8d' % [src, addr, retired, ticks]
+ end
+}
+
diff --git a/tool/ytab.sed b/tool/ytab.sed
new file mode 100755
index 0000000000..a3b411aa8f
--- /dev/null
+++ b/tool/ytab.sed
@@ -0,0 +1,61 @@
+#!/bin/sed -f
+# This file is used when generating code for the Ruby parser.
+/^int yydebug;/{
+i\
+#ifndef yydebug
+a\
+#endif
+}
+/^extern int yydebug;/{
+i\
+#ifndef yydebug
+a\
+#endif
+}
+/^yydestruct.*yymsg/,/#endif/{
+ /^yydestruct/{
+ /parser/!{
+ H
+ s/^/ruby_parser_&/
+ s/)$/, parser)/
+ /\*/s/parser)$/struct parser_params *&/
+ }
+ }
+ /^#endif/{
+ x
+ /yydestruct/{
+ i\
+ struct parser_params *parser;
+ a\
+#define yydestruct(m, t, v) ruby_parser_yydestruct(m, t, v, parser)
+ }
+ x
+ }
+}
+/^yy_stack_print/{
+ /parser/!{
+ H
+ s/)$/, parser)/
+ /\*/s/parser)$/struct parser_params *&/
+ }
+}
+/yy_stack_print.*;/{
+ x
+ /yy_stack_print/{
+ x
+ s/\(yy_stack_print *\)(\(.*\));/\1(\2, parser);/
+ x
+ }
+ x
+}
+/^yy_reduce_print/,/^}/{
+ s/fprintf *(stderr,/YYFPRINTF (parser,/g
+}
+s/\( YYFPRINTF *(\)yyoutput,/\1parser,/
+s/\( YYFPRINTF *(\)yyo,/\1parser,/
+s/\( YYFPRINTF *(\)stderr,/\1parser,/
+s/\( YYDPRINTF *((\)stderr,/\1parser,/
+s/^\([ ]*\)\(yyerror[ ]*([ ]*parser,\)/\1parser_\2/
+s!^ *extern char \*getenv();!/* & */!
+s/^\(#.*\)".*\.tab\.c"/\1"parse.c"/
+/^\(#.*\)".*\.y"/s:\\\\:/:g