summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rw-r--r--misc/.vscode/launch.json13
-rw-r--r--misc/.vscode/settings.json10
-rw-r--r--misc/.vscode/tasks.json14
-rw-r--r--misc/README19
-rw-r--r--misc/call_fuzzer.rb372
-rwxr-xr-xmisc/call_fuzzer.sh13
-rwxr-xr-xmisc/expand_tabs.rb178
-rw-r--r--misc/gdb.py181
-rw-r--r--misc/inf-ruby.el416
-rw-r--r--misc/lldb_cruby.py747
-rw-r--r--misc/lldb_disasm.py250
-rw-r--r--misc/lldb_rb/commands/command_template.py30
-rw-r--r--misc/lldb_rb/commands/heap_page_command.py27
-rw-r--r--misc/lldb_rb/commands/print_flags_command.py31
-rw-r--r--misc/lldb_rb/commands/rb_id2str_command.py49
-rw-r--r--misc/lldb_rb/commands/rclass_ext_command.py14
-rw-r--r--misc/lldb_rb/commands/rp_command.py15
-rw-r--r--misc/lldb_rb/constants.py6
-rw-r--r--misc/lldb_rb/lldb_interface.py18
-rw-r--r--misc/lldb_rb/rb_base_command.py57
-rw-r--r--misc/lldb_rb/rb_heap_structs.py152
-rw-r--r--misc/lldb_rb/utils.py515
-rw-r--r--misc/rb_optparse.bash5
-rw-r--r--[-rwxr-xr-x]misc/rb_optparse.zsh15
-rw-r--r--misc/rdoc-mode.el130
-rw-r--r--misc/ruby-additional.el100
-rw-r--r--misc/ruby-electric.el205
-rw-r--r--misc/ruby-mode.el1514
-rw-r--r--misc/ruby-style.el35
-rw-r--r--misc/rubydb2x.el104
-rw-r--r--misc/rubydb3x.el115
-rw-r--r--misc/test_lldb_cruby.rb40
-rw-r--r--misc/tsan_suppressions.txt109
-rwxr-xr-xmisc/yjit_perf.py116
34 files changed, 3007 insertions, 2608 deletions
diff --git a/misc/.vscode/launch.json b/misc/.vscode/launch.json
new file mode 100644
index 0000000000..51bfef09d7
--- /dev/null
+++ b/misc/.vscode/launch.json
@@ -0,0 +1,13 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "name": "Run ruby",
+ "request": "launch",
+ "program": "${workspaceFolder}/ruby",
+ "args": ["test.rb"],
+ "preLaunchTask": "${defaultBuildTask}"
+ }
+ ]
+}
diff --git a/misc/.vscode/settings.json b/misc/.vscode/settings.json
new file mode 100644
index 0000000000..a2e4e1ec69
--- /dev/null
+++ b/misc/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "rust-analyzer.cargo.features": [
+ "disasm",
+ ],
+ "rust-analyzer.cfg.setTest": false,
+ // rust-analyzer bundled in the VSCode extension may only support Rust newer than 1.85.0.
+ // To avoid warnings, install rust-analyzer with `rustup component add rust-analyzer` and
+ // use `~/.cargo/bin/rust-analyzer` with the following config.
+ "rust-analyzer.server.path": "rust-analyzer",
+}
diff --git a/misc/.vscode/tasks.json b/misc/.vscode/tasks.json
new file mode 100644
index 0000000000..045fe7e5c0
--- /dev/null
+++ b/misc/.vscode/tasks.json
@@ -0,0 +1,14 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "type": "shell",
+ "command": "make -j",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+}
diff --git a/misc/README b/misc/README
index 93d1de90b2..86b680e724 100644
--- a/misc/README
+++ b/misc/README
@@ -1,12 +1,7 @@
-README this file
-inf-ruby.el program to run ruby under emacs
-rb_optparse.bash bash completion script
-rb_optparse.zsh zsh completion script
-rdoc-mode.el RDoc mode for emacs
-ruby-mode.el ruby mode for emacs
-ruby-style.el Ruby's C/C++ mode style for emacs
-rubydb2x.el ruby debugger support for emacs 19.2x or before
-rubydb3x.el ruby debugger support for emacs 19.3x or later
-ruby-electric.el emacs minor mode providing electric commands
-
-Check out http://rubyforge.org/projects/ruby-debug/ also.
+README this file
+rb_optparse.bash bash completion script
+rb_optparse.zsh zsh completion script
+ruby-style.el Ruby's C/C++ mode style for emacs
+lldb_cruby.py LLDB port of debug utility
+test_lldb_cruby.rb test file for LLDB port
+.vscode example VSCode config to debug Ruby
diff --git a/misc/call_fuzzer.rb b/misc/call_fuzzer.rb
new file mode 100644
index 0000000000..c3f9f90490
--- /dev/null
+++ b/misc/call_fuzzer.rb
@@ -0,0 +1,372 @@
+require 'optparse'
+require 'set'
+
+# Number of iterations to test
+num_iters = 10_000
+
+# Parse the command-line options
+OptionParser.new do |opts|
+ opts.on("--num-iters=N") do |n|
+ num_iters = n.to_i
+ end
+end.parse!
+
+# Format large numbers with comma separators for readability
+def format_number(pad, number)
+ s = number.to_s
+ i = s.index('.') || s.size
+ s.insert(i -= 3, ',') while i > 3
+ s.rjust(pad, ' ')
+end
+
+# Wrap an integer to pass as argument
+# We use this so we can have some object arguments
+class IntWrapper
+ def initialize(v)
+ # Force the object to have a random shape
+ if rand() < 50
+ @v0 = 1
+ end
+ if rand() < 50
+ @v1 = 1
+ end
+ if rand() < 50
+ @v2 = 1
+ end
+ if rand() < 50
+ @v3 = 1
+ end
+ if rand() < 50
+ @v4 = 1
+ end
+ if rand() < 50
+ @v5 = 1
+ end
+ if rand() < 50
+ @v6 = 1
+ end
+
+ @value = v
+ end
+
+ attr_reader :value
+end
+
+# Generate a random argument value, integer or string or object
+def sample_arg()
+ c = ['int', 'string', 'object'].sample()
+
+ if c == 'int'
+ return rand(0...100)
+ end
+
+ if c == 'string'
+ return 'f' * rand(0...100)
+ end
+
+ if c == 'object'
+ return IntWrapper.new(rand(0...100))
+ end
+
+ raise "should not get here"
+end
+
+# Evaluate the value of an argument with respect to the checksum
+def arg_val(arg)
+ if arg.kind_of? Integer
+ return arg
+ end
+
+ if arg.kind_of? String
+ return arg.length
+ end
+
+ if arg.kind_of? Object
+ return arg.value
+ end
+
+ raise "unknown arg type"
+end
+
+# List of parameters/arguments for a method
+class ParamList
+ def initialize()
+ self.sample_params()
+ self.sample_args()
+ end
+
+ # Sample/generate a random set of parameters for a method
+ def sample_params()
+ # Choose how many positional arguments to use, and how many are optional
+ num_pargs = rand(10)
+ @opt_parg_idx = rand(num_pargs)
+ @num_opt_pargs = rand(num_pargs + 1 - @opt_parg_idx)
+ @num_pargs_req = num_pargs - @num_opt_pargs
+ @pargs = (0...num_pargs).map do |i|
+ {
+ :name => "p#{i}",
+ :optional => (i >= @opt_parg_idx && i < @opt_parg_idx + @num_opt_pargs)
+ }
+ end
+
+ # Choose how many kwargs to use, and how many are optional
+ num_kwargs = rand(10)
+ @kwargs = (0...num_kwargs).map do |i|
+ {
+ :name => "k#{i}",
+ :optional => rand() < 0.5
+ }
+ end
+
+ # Choose whether to have rest parameters or not
+ @has_rest = @num_opt_pargs == 0 && rand() < 0.5
+ @has_kwrest = rand() < 0.25
+
+ # Choose whether to have a named block parameter or not
+ @has_block_param = rand() < 0.25
+ end
+
+ # Sample/generate a random set of arguments corresponding to the parameters
+ def sample_args()
+ # Choose how many positional args to pass
+ num_pargs_passed = rand(@num_pargs_req..@pargs.size)
+
+ # How many optional arguments will be filled
+ opt_pargs_filled = num_pargs_passed - @num_pargs_req
+
+ @pargs.each_with_index do |parg, i|
+ if parg[:optional]
+ parg[:default] = rand(100)
+ end
+
+ if !parg[:optional] || i < @opt_parg_idx + opt_pargs_filled
+ parg[:argval] = rand(100)
+ end
+ end
+
+ @kwargs.each_with_index do |kwarg, i|
+ if kwarg[:optional]
+ kwarg[:default] = rand(100)
+ end
+
+ if !kwarg[:optional] || rand() < 0.5
+ kwarg[:argval] = rand(100)
+ end
+ end
+
+ # Randomly pass a block or not
+ @block_arg = nil
+ if rand() < 0.5
+ @block_arg = rand(100)
+ end
+ end
+
+ # Compute the expected checksum of arguments ahead of time
+ def compute_checksum()
+ checksum = 0
+
+ @pargs.each_with_index do |arg, i|
+ value = (arg.key? :argval)? arg[:argval]:arg[:default]
+ checksum += (i+1) * arg_val(value)
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ value = (arg.key? :argval)? arg[:argval]:arg[:default]
+ checksum += (i+1) * arg_val(value)
+ end
+
+ if @block_arg
+ if @has_block_param
+ checksum += arg_val(@block_arg)
+ end
+
+ checksum += arg_val(@block_arg)
+ end
+
+ checksum
+ end
+
+ # Generate code for the method signature and method body
+ def gen_method_str()
+ m_str = "def m("
+
+ @pargs.each do |arg|
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += arg[:name]
+
+ # If this has a default value
+ if arg[:optional]
+ m_str += " = #{arg[:default]}"
+ end
+ end
+
+ if @has_rest
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+ m_str += "*rest"
+ end
+
+ @kwargs.each do |arg|
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += "#{arg[:name]}:"
+
+ # If this has a default value
+ if arg[:optional]
+ m_str += " #{arg[:default]}"
+ end
+ end
+
+ if @has_kwrest
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+ m_str += "**kwrest"
+ end
+
+ if @has_block_param
+ if !m_str.end_with?("(")
+ m_str += ", "
+ end
+
+ m_str += "&block"
+ end
+
+ m_str += ")\n"
+
+ # Add some useless locals
+ rand(0...16).times do |i|
+ m_str += "local#{i} = #{i}\n"
+ end
+
+ # Add some useless if statements
+ @pargs.each_with_index do |arg, i|
+ if rand() < 50
+ m_str += "if #{arg[:name]} > 4; end\n"
+ end
+ end
+
+ m_str += "checksum = 0\n"
+
+ @pargs.each_with_index do |arg, i|
+ m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ m_str += "checksum += #{i+1} * arg_val(#{arg[:name]})\n"
+ end
+
+ if @has_block_param
+ m_str += "if block; r = block.call; checksum += arg_val(r); end\n"
+ end
+
+ m_str += "if block_given?; r = yield; checksum += arg_val(r); end\n"
+
+ if @has_rest
+ m_str += "raise 'rest is not array' unless rest.kind_of?(Array)\n"
+ m_str += "raise 'rest size not integer' unless rest.size.kind_of?(Integer)\n"
+ end
+
+ if @has_kwrest
+ m_str += "raise 'kwrest is not a hash' unless kwrest.kind_of?(Hash)\n"
+ m_str += "raise 'kwrest size not integer' unless kwrest.size.kind_of?(Integer)\n"
+ end
+
+ m_str += "checksum\n"
+ m_str += "end"
+
+ m_str
+ end
+
+ # Generate code to call into the method and pass the arguments
+ def gen_call_str()
+ c_str = "m("
+
+ @pargs.each_with_index do |arg, i|
+ if !arg.key? :argval
+ next
+ end
+
+ if !c_str.end_with?("(")
+ c_str += ", "
+ end
+
+ c_str += "#{arg[:argval]}"
+ end
+
+ @kwargs.each_with_index do |arg, i|
+ if !arg.key? :argval
+ next
+ end
+
+ if !c_str.end_with?("(")
+ c_str += ", "
+ end
+
+ c_str += "#{arg[:name]}: #{arg[:argval]}"
+ end
+
+ c_str += ")"
+
+ # Randomly pass a block or not
+ if @block_arg
+ c_str += " { #{@block_arg} }"
+ end
+
+ c_str
+ end
+end
+
+iseqs_compiled_start = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
+start_time = Time.now.to_f
+
+num_iters.times do |i|
+ puts "Iteration #{i}"
+
+ lst = ParamList.new()
+ m_str = lst.gen_method_str()
+ c_str = lst.gen_call_str()
+ checksum = lst.compute_checksum()
+
+ f = Object.new
+
+ # Define the method on f
+ puts "Defining"
+ p m_str
+ f.instance_eval(m_str)
+ #puts RubyVM::InstructionSequence.disasm(f.method(:m))
+ #exit 0
+
+ puts "Calling"
+ c_str = "f.#{c_str}"
+ p c_str
+ r = eval(c_str)
+ puts "checksum=#{r}"
+
+ if r != checksum
+ raise "return value #{r} doesn't match checksum #{checksum}"
+ end
+
+ puts ""
+end
+
+# Make sure that YJIT actually compiled the tests we ran
+# Should be run with --yjit-call-threshold=1
+iseqs_compiled_end = RubyVM::YJIT.runtime_stats[:compiled_iseq_entry]
+if iseqs_compiled_end - iseqs_compiled_start < num_iters
+ raise "YJIT did not compile enough ISEQs"
+end
+
+puts "Code region size: #{ format_number(0, RubyVM::YJIT.runtime_stats[:code_region_size]) }"
+
+end_time = Time.now.to_f
+itrs_per_sec = num_iters / (end_time - start_time)
+itrs_per_hour = 3600 * itrs_per_sec
+puts "#{'%.1f' % itrs_per_sec} iterations/s"
+puts "#{format_number(0, itrs_per_hour.round)} iterations/hour"
diff --git a/misc/call_fuzzer.sh b/misc/call_fuzzer.sh
new file mode 100755
index 0000000000..cf4ec76fe8
--- /dev/null
+++ b/misc/call_fuzzer.sh
@@ -0,0 +1,13 @@
+# Stop at first error
+set -e
+
+# TODO
+# TODO: boost --num-iters to 1M+ for actual test
+# TODO
+export NUM_ITERS=25000
+
+# Enable code GC so we don't stop compiling when we hit the code size limit
+ruby --yjit-call-threshold=1 --yjit-code-gc misc/call_fuzzer.rb --num-iters=$NUM_ITERS
+
+# Do another pass with --verify-ctx
+ruby --yjit-call-threshold=1 --yjit-code-gc --yjit-verify-ctx misc/call_fuzzer.rb --num-iters=$NUM_ITERS
diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb
new file mode 100755
index 0000000000..d26568eefc
--- /dev/null
+++ b/misc/expand_tabs.rb
@@ -0,0 +1,178 @@
+#!/usr/bin/env ruby --disable-gems
+# Add the following line to your `.git/hooks/pre-commit`:
+#
+# $ ruby --disable-gems misc/expand_tabs.rb
+#
+
+require 'shellwords'
+require 'tmpdir'
+ENV['LC_ALL'] = 'C'
+
+class Git
+ def initialize(oldrev, newrev)
+ @oldrev = oldrev
+ @newrev = newrev
+ end
+
+ # ["foo/bar.c", "baz.h", ...]
+ def updated_paths
+ with_clean_env do
+ IO.popen(['git', 'diff', '--cached', '--name-only', @newrev], &:readlines).each(&:chomp!)
+ end
+ end
+
+ # [0, 1, 4, ...]
+ def updated_lines(file)
+ lines = []
+ revs_pattern = ("0"*40) + " "
+ with_clean_env { IO.popen(['git', 'blame', '-l', '--', file], &:readlines) }.each_with_index do |line, index|
+ if line.b.start_with?(revs_pattern)
+ lines << index
+ end
+ end
+ lines
+ end
+
+ def add(file)
+ git('add', file)
+ end
+
+ def toplevel
+ IO.popen(['git', 'rev-parse', '--show-toplevel'], &:read).chomp
+ end
+
+ private
+
+ def git(*args)
+ cmd = ['git', *args].shelljoin
+ unless with_clean_env { system(cmd) }
+ abort "Failed to run: #{cmd}"
+ end
+ end
+
+ def with_clean_env
+ git_dir = ENV.delete('GIT_DIR') # this overcomes '-C' or pwd
+ yield
+ ensure
+ ENV['GIT_DIR'] = git_dir if git_dir
+ end
+end
+
+DEFAULT_GEM_LIBS = %w[
+ bundler
+ delegate
+ did_you_mean
+ english
+ erb
+ error_highlight
+ fileutils
+ find
+ forwardable
+ ipaddr
+ net-http
+ net-protocol
+ open3
+ open-uri
+ optparse
+ ostruct
+ pp
+ prettyprint
+ prism
+ resolv
+ rubygems
+ securerandom
+ shellwords
+ singleton
+ tempfile
+ time
+ timeout
+ tmpdir
+ un
+ tsort
+ uri
+ weakref
+ yaml
+]
+
+DEFAULT_GEM_EXTS = %w[
+ date
+ digest
+ etc
+ fcntl
+ io-console
+ io-nonblock
+ io-wait
+ json
+ openssl
+ pathname
+ psych
+ stringio
+ strscan
+ zlib
+]
+
+EXPANDTAB_IGNORED_FILES = [
+ # default gems whose master is GitHub
+ %r{\Abin/(?!erb)\w+\z},
+ *DEFAULT_GEM_LIBS.flat_map { |lib|
+ [
+ %r{\Alib/#{lib}/},
+ %r{\Alib/#{lib}\.gemspec\z},
+ %r{\Alib/#{lib}\.rb\z},
+ %r{\Atest/#{lib}/},
+ ]
+ },
+ *DEFAULT_GEM_EXTS.flat_map { |ext|
+ [
+ %r{\Aext/#{ext}/},
+ %r{\Atest/#{ext}/},
+ ]
+ },
+
+ # vendoring (ccan)
+ %r{\Accan/},
+
+ # vendoring (onigmo)
+ %r{\Aenc/},
+ %r{\Ainclude/ruby/onigmo\.h\z},
+ %r{\Areg.+\.(c|h)\z},
+
+ # explicit or implicit `c-file-style: "linux"`
+ %r{\Aaddr2line\.c\z},
+ %r{\Amissing/},
+ %r{\Astrftime\.c\z},
+ %r{\Avsnprintf\.c\z},
+]
+
+git = Git.new('HEAD^', 'HEAD')
+
+Dir.chdir(git.toplevel) do
+ paths = git.updated_paths
+ paths.select! {|f|
+ (f.end_with?('.c') || f.end_with?('.h') || f == 'insns.def') && EXPANDTAB_IGNORED_FILES.all? { |re| !f.match(re) }
+ }
+ files = paths.select {|n| File.file?(n)}
+ exit if files.empty?
+
+ files.each do |f|
+ src = File.binread(f) rescue next
+
+ expanded = false
+ updated_lines = git.updated_lines(f)
+ unless updated_lines.empty?
+ src.gsub!(/^.*$/).with_index do |line, lineno|
+ if updated_lines.include?(lineno) && line.start_with?("\t") # last-committed line with hard tabs
+ expanded = true
+ line.sub(/\A\t+/) { |tabs| ' ' * (8 * tabs.length) }
+ else
+ line
+ end
+ end
+ end
+
+ if expanded
+ File.binwrite(f, src)
+ git.add(f)
+ end
+ end
+end
diff --git a/misc/gdb.py b/misc/gdb.py
new file mode 100644
index 0000000000..6034a389bb
--- /dev/null
+++ b/misc/gdb.py
@@ -0,0 +1,181 @@
+import argparse
+import textwrap
+
+# usage: [-h] [-a | --all | --no-all] [-s STACK_SIZE] [uplevel]
+#
+# Dump a control frame
+#
+# positional arguments:
+# uplevel CFP offset from the stack top
+#
+# options:
+# -h, --help show this help message and exit
+# -a, --all, --no-all dump all frames
+# -s STACK_SIZE, --stack-size STACK_SIZE
+# override stack_size (useful for JIT frames)
+class CFP(gdb.Command):
+ FRAME_MAGICS = [
+ # frame types
+ 'VM_FRAME_MAGIC_METHOD',
+ 'VM_FRAME_MAGIC_BLOCK',
+ 'VM_FRAME_MAGIC_CLASS',
+ 'VM_FRAME_MAGIC_TOP',
+ 'VM_FRAME_MAGIC_CFUNC',
+ 'VM_FRAME_MAGIC_IFUNC',
+ 'VM_FRAME_MAGIC_EVAL',
+ 'VM_FRAME_MAGIC_RESCUE',
+ 'VM_FRAME_MAGIC_DUMMY',
+ ]
+ FRAME_FLAGS = [
+ # frame flag
+ 'VM_FRAME_FLAG_FINISH',
+ 'VM_FRAME_FLAG_BMETHOD',
+ 'VM_FRAME_FLAG_CFRAME',
+ 'VM_FRAME_FLAG_LAMBDA',
+ 'VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM',
+ 'VM_FRAME_FLAG_CFRAME_KW',
+ 'VM_FRAME_FLAG_PASSED',
+ # env flag
+ 'VM_ENV_FLAG_LOCAL',
+ 'VM_ENV_FLAG_ESCAPED',
+ 'VM_ENV_FLAG_WB_REQUIRED',
+ 'VM_ENV_FLAG_ISOLATED',
+ ]
+
+ def __init__(self):
+ super(CFP, self).__init__('cfp', gdb.COMMAND_USER)
+
+ self.parser = argparse.ArgumentParser(description='Dump a control frame')
+ self.parser.add_argument('uplevel', type=int, nargs='?', default=0, help='CFP offset from the stack top')
+ self.parser.add_argument('-a', '--all', action=argparse.BooleanOptionalAction, help='dump all frames')
+ self.parser.add_argument('-s', '--stack-size', type=int, help='override stack_size (useful for JIT frames)')
+
+ def invoke(self, args, from_tty):
+ try:
+ args = self.parser.parse_args(args.split())
+ except SystemExit:
+ return
+ cfp = f'(ruby_current_ec->cfp + ({args.uplevel}))'
+ end_cfp = self.get_int('ruby_current_ec->vm_stack + ruby_current_ec->vm_stack_size')
+ cfp_index = int((end_cfp - self.get_int(cfp) - 1) / self.get_int('sizeof(rb_control_frame_t)'))
+
+ if args.all:
+ cfp_count = int((end_cfp - self.get_int('ruby_current_ec->cfp')) / self.get_int('sizeof(rb_control_frame_t)')) - 1 # exclude dummy CFP
+ for i in range(cfp_count):
+ print('-' * 80)
+ self.invoke(str(cfp_count - i - 1), from_tty)
+ return
+
+ print('CFP (addr=0x{:x}, index={}):'.format(self.get_int(cfp), cfp_index))
+ gdb.execute(f'p *({cfp})')
+ print()
+
+ if self.get_int(f'{cfp}->iseq'):
+ local_size = self.get_int(f'{cfp}->iseq->body->local_table_size - {cfp}->iseq->body->param.size')
+ param_size = self.get_int(f'{cfp}->iseq->body->param.size')
+
+ if local_size:
+ print(f'Params (size={param_size}):')
+ for i in range(-3 - local_size - param_size, -3 - local_size):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print()
+
+ if param_size:
+ print(f'Locals (size={local_size}):')
+ for i in range(-3 - local_size, -3):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print()
+
+ print('Env:')
+ self.print_env(cfp, -3, self.rp_env(cfp, -3))
+ self.print_env(cfp, -2, self.specval(cfp, -2))
+ self.print_env(cfp, -1, self.frame_types(cfp, -1))
+ print()
+
+ # We can't calculate BP for the first frame.
+ # vm_base_ptr doesn't work for C frames either.
+ if cfp_index > 0 and self.get_int(f'{cfp}->iseq'):
+ if args.stack_size is not None:
+ stack_size = args.stack_size
+ else:
+ stack_size = int((self.get_int(f'{cfp}->sp') - self.get_int(f'vm_base_ptr({cfp})')) / 8)
+ print(f'Stack (size={stack_size}):')
+ for i in range(0, stack_size):
+ self.print_stack(cfp, i, self.rp(cfp, i))
+ print(self.regs(cfp, stack_size))
+
+ def print_env(self, cfp, bp_index, content):
+ ep_index = bp_index + 1
+ address = self.get_int(f'((rb_control_frame_t *){cfp})->ep + {ep_index}')
+ value = self.get_env(cfp, bp_index)
+ regs = self.regs(cfp, bp_index)
+ if content:
+ content = textwrap.indent(content, ' ' * 3).lstrip() # Leave the regs column empty
+ content = f'{content} '
+ print('{:2} 0x{:x} [{}] {}(0x{:x})'.format(regs, address, bp_index, content, value))
+
+ def print_stack(self, cfp, bp_index, content):
+ address = self.get_int(f'vm_base_ptr({cfp}) + {bp_index}')
+ value = self.get_value(cfp, bp_index)
+ regs = self.regs(cfp, bp_index)
+ if content:
+ content = textwrap.indent(content, ' ' * 3).lstrip() # Leave the regs column empty
+ content = f'{content} '
+ print('{:2} 0x{:x} [{}] {}(0x{:x})'.format(regs, address, bp_index, content, value))
+
+ def regs(self, cfp, bp_index):
+ address = self.get_int(f'vm_base_ptr({cfp}) + {bp_index}')
+ regs = []
+ for reg, field in { 'EP': 'ep', 'SP': 'sp' }.items():
+ if address == self.get_int(f'{cfp}->{field}'):
+ regs.append(reg)
+ return ' '.join(regs)
+
+ def rp(self, cfp, bp_index):
+ value = self.get_value(cfp, bp_index)
+ return self.get_string(f'rp {value}').rstrip()
+
+ def rp_env(self, cfp, bp_index):
+ value = self.get_env(cfp, bp_index)
+ return self.get_string(f'rp {value}').rstrip()
+
+ # specval: block_handler or previous EP
+ def specval(self, cfp, bp_index):
+ value = self.get_env(cfp, bp_index)
+ if value == 0:
+ return 'VM_BLOCK_HANDLER_NONE'
+ if value == self.get_int('rb_block_param_proxy'):
+ return 'rb_block_param_proxy'
+ return ''
+
+ def frame_types(self, cfp, bp_index):
+ types = []
+ value = self.get_env(cfp, bp_index)
+
+ magic_mask = self.get_int('VM_FRAME_MAGIC_MASK')
+ for magic in self.FRAME_MAGICS:
+ magic_value = self.get_int(magic)
+ if value & magic_mask == magic_value:
+ types.append(magic)
+
+ for flag in self.FRAME_FLAGS:
+ flag_value = self.get_int(flag)
+ if value & flag_value:
+ types.append(flag)
+
+ return ' | '.join(types)
+
+ def get_env(self, cfp, bp_index):
+ ep_index = bp_index + 1
+ return self.get_int(f'((rb_control_frame_t *){cfp})->ep[{ep_index}]')
+
+ def get_value(self, cfp, bp_index):
+ return self.get_int(f'vm_base_ptr({cfp})[{bp_index}]')
+
+ def get_int(self, expr):
+ return int(self.get_string(f'printf "%ld", ({expr})'))
+
+ def get_string(self, expr):
+ return gdb.execute(expr, to_string=True)
+
+CFP()
diff --git a/misc/inf-ruby.el b/misc/inf-ruby.el
deleted file mode 100644
index ea03eee23d..0000000000
--- a/misc/inf-ruby.el
+++ /dev/null
@@ -1,416 +0,0 @@
-;;; -*-Emacs-Lisp-*-
-;;;
-;;; $Id$
-;;; $Author$
-;;;
-;;; Inferior Ruby Mode - ruby process in a buffer.
-;;; adapted from cmuscheme.el
-;;;
-;;; Usage:
-;;;
-;;; (0) check ruby-program-name variable that can run your environment.
-;;;
-;;; (1) modify .emacs to use ruby-mode
-;;; for example :
-;;;
-;;; (autoload 'ruby-mode "ruby-mode"
-;;; "Mode for editing ruby source files" t)
-;;; (setq auto-mode-alist
-;;; (append '(("\\.rb$" . ruby-mode)) auto-mode-alist))
-;;; (setq interpreter-mode-alist (append '(("ruby" . ruby-mode))
-;;; interpreter-mode-alist))
-;;;
-;;; (2) set to load inf-ruby and set inf-ruby key definition in ruby-mode.
-;;;
-;;; (autoload 'run-ruby "inf-ruby"
-;;; "Run an inferior Ruby process")
-;;; (autoload 'inf-ruby-keys "inf-ruby"
-;;; "Set local key defs for inf-ruby in ruby-mode")
-;;; (add-hook 'ruby-mode-hook
-;;; '(lambda ()
-;;; (inf-ruby-keys)
-;;; ))
-;;;
-;;; HISTORY
-;;; senda - 8 Apr 1998: Created.
-;;; $Log$
-;;; Revision 1.7 2004/07/27 08:11:36 matz
-;;; * eval.c (rb_eval): copy on write for argument local variable
-;;; assignment.
-;;;
-;;; * eval.c (assign): ditto.
-;;;
-;;; * eval.c (rb_call0): update ruby_frame->argv with the default
-;;; value used for the optional arguments.
-;;;
-;;; * object.c (Init_Object): "===" calls rb_obj_equal() directly.
-;;; [ruby-list:39937]
-;;;
-;;; Revision 1.6 2002/09/07 14:35:46 nobu
-;;; * misc/inf-ruby.el (inferior-ruby-error-regexp-alist): regexp
-;;; alist for error message from ruby.
-;;;
-;;; * misc/inf-ruby.el (inferior-ruby-mode): fixed for Emacs.
-;;;
-;;; * misc/inf-ruby.el (ruby-send-region): compilation-parse-errors
-;;; doesn't parse first line, so insert separators before each
-;;; evaluations.
-;;;
-;;; Revision 1.5 2002/08/19 10:05:47 nobu
-;;; * misc/inf-ruby.el (inf-ruby-keys): ruby-send-definition
-;;; conflicted with ruby-insert-end.
-;;;
-;;; * misc/inf-ruby.el (inferior-ruby-mode): compilation-minor-mode.
-;;;
-;;; * misc/inf-ruby.el (ruby-send-region): send as here document to
-;;; adjust source file/line. [ruby-talk:47113], [ruby-dev:17965]
-;;;
-;;; * misc/inf-ruby.el (ruby-send-terminator): added to make unique
-;;; terminator.
-;;;
-;;; Revision 1.4 2002/01/29 07:16:09 matz
-;;; * file.c (rb_stat_rdev_major): added. [new]
-;;;
-;;; * file.c (rb_stat_rdev_minor): added. [new]
-;;;
-;;; * file.c (rb_stat_inspect): print mode in octal.
-;;;
-;;; Revision 1.3 1999/12/01 09:24:18 matz
-;;; 19991201
-;;;
-;;; Revision 1.2 1999/08/13 05:45:18 matz
-;;; 1.4.0
-;;;
-;;; Revision 1.1.1.1.2.1 1999/07/15 07:59:59 matz
-;;; 990715
-;;;
-;;; Revision 1.1.1.1 1999/01/20 04:59:36 matz
-;;; ruby 1.3 cycle
-;;;
-;;; Revision 1.1.2.1 1998/12/16 07:30:36 matz
-;;; first public release of 1.1d (pre1.2) series
-;;;
-;;; Revision 1.4 1998/05/20 02:45:58 senda
-;;; default program to irb
-;;;
-;;; Revision 1.3 1998/04/10 04:11:30 senda
-;;; modification by Matsumoto san (1.1b9_09)
-;;; remove-in-string defined
-;;; global variable :
-;;; inferior-ruby-first-prompt-pattern
-;;; inferior-ruby-prompt-pattern
-;;; defined
-;;;
-;;; Revision 1.2 1998/04/09 07:53:42 senda
-;;; remove M-C-x in inferior-ruby-mode
-;;;
-;;; Revision 1.1 1998/04/09 07:28:36 senda
-;;; Initial revision
-;;;
-;;;
-
-(require 'comint)
-(require 'compile)
-(require 'ruby-mode)
-
-;;
-;; you may change these variables
-;;
-;(defvar ruby-program-name "rbc --noreadline"
-; "*Program invoked by the run-ruby command")
-;
-;(defvar inferior-ruby-first-prompt-pattern "^rbc0> *"
-; "first prompt regex pattern of ruby interpreter.")
-;
-;(defvar inferior-ruby-prompt-pattern "^\\(rbc.[>*\"'] *\\)+"
-; "prompt regex pattern of ruby interpreter.")
-
-;;;; for irb
-(defvar ruby-program-name "irb --inf-ruby-mode"
- "*Program invoked by the run-ruby command")
-
-(defvar inferior-ruby-first-prompt-pattern "^irb(.*)[0-9:]+0> *"
- "first prompt regex pattern of ruby interpreter.")
-
-(defvar inferior-ruby-prompt-pattern "^\\(irb(.*)[0-9:]+[>*\"'] *\\)+"
- "prompt regex pattern of ruby interpreter.")
-
-;;
-;; mode variables
-;;
-(defvar inferior-ruby-mode-hook nil
- "*Hook for customising inferior-ruby mode.")
-(defvar inferior-ruby-mode-map nil
- "*Mode map for inferior-ruby-mode")
-
-(defconst inferior-ruby-error-regexp-alist
- '(("SyntaxError: compile error\n^\\([^\(].*\\):\\([1-9][0-9]*\\):" 1 2)
- ("^\tfrom \\([^\(].*\\):\\([1-9][0-9]*\\)\\(:in `.*'\\)?$" 1 2)))
-
-(cond ((not inferior-ruby-mode-map)
- (setq inferior-ruby-mode-map
- (copy-keymap comint-mode-map))
-; (define-key inferior-ruby-mode-map "\M-\C-x" ;gnu convention
-; 'ruby-send-definition)
-; (define-key inferior-ruby-mode-map "\C-x\C-e" 'ruby-send-last-sexp)
- (define-key inferior-ruby-mode-map "\C-c\C-l" 'ruby-load-file)
-))
-
-(defun inf-ruby-keys ()
- "Set local key defs for inf-ruby in ruby-mode"
- (define-key ruby-mode-map "\M-\C-x" 'ruby-send-definition)
-; (define-key ruby-mode-map "\C-x\C-e" 'ruby-send-last-sexp)
- (define-key ruby-mode-map "\C-c\C-b" 'ruby-send-block)
- (define-key ruby-mode-map "\C-c\M-b" 'ruby-send-block-and-go)
- (define-key ruby-mode-map "\C-c\C-x" 'ruby-send-definition)
- (define-key ruby-mode-map "\C-c\M-x" 'ruby-send-definition-and-go)
- (define-key ruby-mode-map "\C-c\C-r" 'ruby-send-region)
- (define-key ruby-mode-map "\C-c\M-r" 'ruby-send-region-and-go)
- (define-key ruby-mode-map "\C-c\C-z" 'switch-to-ruby)
- (define-key ruby-mode-map "\C-c\C-l" 'ruby-load-file)
- (define-key ruby-mode-map "\C-c\C-s" 'run-ruby)
-)
-
-(defvar ruby-buffer nil "current ruby (actually irb) process buffer.")
-
-(defun inferior-ruby-mode ()
- "Major mode for interacting with an inferior ruby (irb) process.
-
-The following commands are available:
-\\{inferior-ruby-mode-map}
-
-A ruby process can be fired up with M-x run-ruby.
-
-Customisation: Entry to this mode runs the hooks on comint-mode-hook and
-inferior-ruby-mode-hook (in that order).
-
-You can send text to the inferior ruby process from other buffers containing
-Ruby source.
- switch-to-ruby switches the current buffer to the ruby process buffer.
- ruby-send-definition sends the current definition to the ruby process.
- ruby-send-region sends the current region to the ruby process.
-
- ruby-send-definition-and-go, ruby-send-region-and-go,
- switch to the ruby process buffer after sending their text.
-For information on running multiple processes in multiple buffers, see
-documentation for variable ruby-buffer.
-
-Commands:
-Return after the end of the process' output sends the text from the
- end of process to point.
-Return before the end of the process' output copies the sexp ending at point
- to the end of the process' output, and sends it.
-Delete converts tabs to spaces as it moves back.
-Tab indents for ruby; with argument, shifts rest
- of expression rigidly with the current line.
-C-M-q does Tab on each line starting within following expression.
-Paragraphs are separated only by blank lines. # start comments.
-If you accidentally suspend your process, use \\[comint-continue-subjob]
-to continue it."
- (interactive)
- (comint-mode)
- ;; Customise in inferior-ruby-mode-hook
- ;(setq comint-prompt-regexp "^[^>\n]*>+ *")
- (setq comint-prompt-regexp inferior-ruby-prompt-pattern)
- ;;(scheme-mode-variables)
- (ruby-mode-variables)
- (setq major-mode 'inferior-ruby-mode)
- (setq mode-name "Inferior Ruby")
- (setq mode-line-process '(":%s"))
- (use-local-map inferior-ruby-mode-map)
- (setq comint-input-filter (function ruby-input-filter))
- (setq comint-get-old-input (function ruby-get-old-input))
- (make-local-variable 'compilation-error-regexp-alist)
- (setq compilation-error-regexp-alist inferior-ruby-error-regexp-alist)
- (compilation-shell-minor-mode t)
- (run-hooks 'inferior-ruby-mode-hook))
-
-(defvar inferior-ruby-filter-regexp "\\`\\s *\\S ?\\S ?\\s *\\'"
- "*Input matching this regexp are not saved on the history list.
-Defaults to a regexp ignoring all inputs of 0, 1, or 2 letters.")
-
-(defun ruby-input-filter (str)
- "Don't save anything matching inferior-ruby-filter-regexp"
- (not (string-match inferior-ruby-filter-regexp str)))
-
-;; adapted from replace-in-string in XEmacs (subr.el)
-(defun remove-in-string (str regexp)
- "Remove all matches in STR for REGEXP and returns the new string."
- (let ((rtn-str "") (start 0) match prev-start)
- (while (setq match (string-match regexp str start))
- (setq prev-start start
- start (match-end 0)
- rtn-str (concat rtn-str (substring str prev-start match))))
- (concat rtn-str (substring str start))))
-
-(defun ruby-get-old-input ()
- "Snarf the sexp ending at point"
- (save-excursion
- (let ((end (point)))
- (re-search-backward inferior-ruby-first-prompt-pattern)
- (remove-in-string (buffer-substring (point) end)
- inferior-ruby-prompt-pattern)
- )))
-
-(defun ruby-args-to-list (string)
- (let ((where (string-match "[ \t]" string)))
- (cond ((null where) (list string))
- ((not (= where 0))
- (cons (substring string 0 where)
- (ruby-args-to-list (substring string (+ 1 where)
- (length string)))))
- (t (let ((pos (string-match "[^ \t]" string)))
- (if (null pos)
- nil
- (ruby-args-to-list (substring string pos
- (length string)))))))))
-
-(defun run-ruby (cmd)
- "Run an inferior Ruby process, input and output via buffer *ruby*.
-If there is a process already running in `*ruby*', switch to that buffer.
-With argument, allows you to edit the command line (default is value
-of `ruby-program-name'). Runs the hooks `inferior-ruby-mode-hook'
-\(after the `comint-mode-hook' is run).
-\(Type \\[describe-mode] in the process buffer for a list of commands.)"
-
- (interactive (list (if current-prefix-arg
- (read-string "Run Ruby: " ruby-program-name)
- ruby-program-name)))
- (if (not (comint-check-proc "*ruby*"))
- (let ((cmdlist (ruby-args-to-list cmd)))
- (set-buffer (apply 'make-comint "ruby" (car cmdlist)
- nil (cdr cmdlist)))
- (inferior-ruby-mode)))
- (setq ruby-program-name cmd)
- (setq ruby-buffer "*ruby*")
- (pop-to-buffer "*ruby*"))
-
-(defconst ruby-send-terminator "--inf-ruby-%x-%d-%d-%d--"
- "Template for irb here document terminator.
-Must not contain ruby meta characters.")
-
-(defconst ruby-eval-separator "")
-
-(defun ruby-send-region (start end)
- "Send the current region to the inferior Ruby process."
- (interactive "r")
- (let (term (file (buffer-file-name)) line)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char start)
- (setq line (+ start (forward-line (- start)) 1))
- (goto-char start)
- (while (progn
- (setq term (apply 'format ruby-send-terminator (random) (current-time)))
- (re-search-forward (concat "^" (regexp-quote term) "$") end t)))))
- ;; compilation-parse-errors parses from second line.
- (save-excursion
- (let ((m (process-mark (ruby-proc))))
- (set-buffer (marker-buffer m))
- (goto-char m)
- (insert ruby-eval-separator "\n")
- (set-marker m (point))))
- (comint-send-string (ruby-proc) (format "eval <<'%s', nil, %S, %d\n" term file line))
- (comint-send-region (ruby-proc) start end)
- (comint-send-string (ruby-proc) (concat "\n" term "\n"))))
-
-(defun ruby-send-definition ()
- "Send the current definition to the inferior Ruby process."
- (interactive)
- (save-excursion
- (ruby-end-of-defun)
- (let ((end (point)))
- (ruby-beginning-of-defun)
- (ruby-send-region (point) end))))
-
-;(defun ruby-send-last-sexp ()
-; "Send the previous sexp to the inferior Ruby process."
-; (interactive)
-; (ruby-send-region (save-excursion (backward-sexp) (point)) (point)))
-
-(defun ruby-send-block ()
- "Send the current block to the inferior Ruby process."
- (interactive)
- (save-excursion
- (ruby-end-of-block)
- (end-of-line)
- (let ((end (point)))
- (ruby-beginning-of-block)
- (ruby-send-region (point) end))))
-
-(defun switch-to-ruby (eob-p)
- "Switch to the ruby process buffer.
-With argument, positions cursor at end of buffer."
- (interactive "P")
- (if (get-buffer ruby-buffer)
- (pop-to-buffer ruby-buffer)
- (error "No current process buffer. See variable ruby-buffer."))
- (cond (eob-p
- (push-mark)
- (goto-char (point-max)))))
-
-(defun ruby-send-region-and-go (start end)
- "Send the current region to the inferior Ruby process.
-Then switch to the process buffer."
- (interactive "r")
- (ruby-send-region start end)
- (switch-to-ruby t))
-
-(defun ruby-send-definition-and-go ()
- "Send the current definition to the inferior Ruby.
-Then switch to the process buffer."
- (interactive)
- (ruby-send-definition)
- (switch-to-ruby t))
-
-(defun ruby-send-block-and-go ()
- "Send the current block to the inferior Ruby.
-Then switch to the process buffer."
- (interactive)
- (ruby-send-block)
- (switch-to-ruby t))
-
-(defvar ruby-source-modes '(ruby-mode)
- "*Used to determine if a buffer contains Ruby source code.
-If it's loaded into a buffer that is in one of these major modes, it's
-considered a ruby source file by ruby-load-file.
-Used by these commands to determine defaults.")
-
-(defvar ruby-prev-l/c-dir/file nil
- "Caches the last (directory . file) pair.
-Caches the last pair used in the last ruby-load-file command.
-Used for determining the default in the
-next one.")
-
-(defun ruby-load-file (file-name)
- "Load a Ruby file into the inferior Ruby process."
- (interactive (comint-get-source "Load Ruby file: " ruby-prev-l/c-dir/file
- ruby-source-modes t)) ; T because LOAD
- ; needs an exact name
- (comint-check-source file-name) ; Check to see if buffer needs saved.
- (setq ruby-prev-l/c-dir/file (cons (file-name-directory file-name)
- (file-name-nondirectory file-name)))
- (comint-send-string (ruby-proc) (concat "(load \""
- file-name
- "\"\)\n")))
-
-(defun ruby-proc ()
- "Returns the current ruby process. See variable ruby-buffer."
- (let ((proc (get-buffer-process (if (eq major-mode 'inferior-ruby-mode)
- (current-buffer)
- ruby-buffer))))
- (or proc
- (error "No current process. See variable ruby-buffer"))))
-
-;;; Do the user's customisation...
-
-(defvar inf-ruby-load-hook nil
- "This hook is run when inf-ruby is loaded in.
-This is a good place to put keybindings.")
-
-(run-hooks 'inf-ruby-load-hook)
-
-(provide 'inf-ruby)
-
-;;; inf-ruby.el ends here
diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py
new file mode 100644
index 0000000000..b3d4fb509a
--- /dev/null
+++ b/misc/lldb_cruby.py
@@ -0,0 +1,747 @@
+#coding: utf-8
+#
+# Usage: run `command script import -r misc/lldb_cruby.py` on LLDB
+#
+# Test: misc/test_lldb_cruby.rb
+#
+
+from __future__ import print_function
+import lldb
+import os
+import inspect
+import sys
+import shlex
+import platform
+import glob
+import math
+
+from lldb_rb.constants import *
+
+# BEGIN FUNCTION STYLE DECLS
+# This will be refactored to use class style decls in the misc/commands dir
+class BackTrace:
+ VM_FRAME_MAGIC_METHOD = 0x11110001
+ VM_FRAME_MAGIC_BLOCK = 0x22220001
+ VM_FRAME_MAGIC_CLASS = 0x33330001
+ VM_FRAME_MAGIC_TOP = 0x44440001
+ VM_FRAME_MAGIC_CFUNC = 0x55550001
+ VM_FRAME_MAGIC_IFUNC = 0x66660001
+ VM_FRAME_MAGIC_EVAL = 0x77770001
+ VM_FRAME_MAGIC_RESCUE = 0x78880001
+ VM_FRAME_MAGIC_DUMMY = 0x79990001
+
+ VM_FRAME_MAGIC_MASK = 0x7fff0001
+
+ VM_FRAME_MAGIC_NAME = {
+ VM_FRAME_MAGIC_TOP: "TOP",
+ VM_FRAME_MAGIC_METHOD: "METHOD",
+ VM_FRAME_MAGIC_CLASS: "CLASS",
+ VM_FRAME_MAGIC_BLOCK: "BLOCK",
+ VM_FRAME_MAGIC_CFUNC: "CFUNC",
+ VM_FRAME_MAGIC_IFUNC: "IFUNC",
+ VM_FRAME_MAGIC_EVAL: "EVAL",
+ VM_FRAME_MAGIC_RESCUE: "RESCUE",
+ 0: "-----"
+ }
+
+ def __init__(self, debugger, command, result, internal_dict):
+ self.debugger = debugger
+ self.command = command
+ self.result = result
+
+ self.target = debugger.GetSelectedTarget()
+ self.process = self.target.GetProcess()
+ self.thread = self.process.GetSelectedThread()
+ self.frame = self.thread.GetSelectedFrame()
+ self.tRString = self.target.FindFirstType("struct RString").GetPointerType()
+ self.tRArray = self.target.FindFirstType("struct RArray").GetPointerType()
+
+ rb_cft_len = len("rb_control_frame_t")
+ method_type_length = sorted(map(len, self.VM_FRAME_MAGIC_NAME.values()), reverse=True)[0]
+ # cfp address, method type, function name
+ self.fmt = "%%-%ds %%-%ds %%s" % (rb_cft_len, method_type_length)
+
+ def vm_frame_magic(self, cfp):
+ ep = cfp.GetValueForExpressionPath("->ep")
+ frame_type = ep.GetChildAtIndex(0).GetValueAsUnsigned() & self.VM_FRAME_MAGIC_MASK
+ return self.VM_FRAME_MAGIC_NAME.get(frame_type, "(none)")
+
+ def rb_iseq_path_str(self, iseq):
+ tRBasic = self.target.FindFirstType("::RBasic").GetPointerType()
+
+ pathobj = iseq.GetValueForExpressionPath("->body->location.pathobj")
+ pathobj = pathobj.Cast(tRBasic)
+ flags = pathobj.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+ flType = flags & RUBY_T_MASK
+
+ if flType == RUBY_T_ARRAY:
+ pathobj = pathobj.Cast(self.tRArray)
+
+ if flags & RUBY_FL_USER1:
+ len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4|RUBY_FL_USER5|RUBY_FL_USER6|RUBY_FL_USER7|RUBY_FL_USER8|RUBY_FL_USER9)) >> (RUBY_FL_USHIFT+3))
+ ptr = pathobj.GetValueForExpressionPath("->as.ary")
+ else:
+ len = pathobj.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
+ ptr = pathobj.GetValueForExpressionPath("->as.heap.ptr")
+
+ pathobj = ptr.GetChildAtIndex(0)
+
+ pathobj = pathobj.Cast(self.tRString)
+ ptr, len = string2cstr(pathobj)
+ err = lldb.SBError()
+ path = self.target.process.ReadMemory(ptr, len, err)
+ if err.Success():
+ return path.decode("utf-8")
+ else:
+ return "unknown"
+
+ def dump_iseq_frame(self, cfp, iseq):
+ m = self.vm_frame_magic(cfp)
+
+ if iseq.GetValueAsUnsigned():
+ iseq_label = iseq.GetValueForExpressionPath("->body->location.label")
+ path = self.rb_iseq_path_str(iseq)
+ ptr, len = string2cstr(iseq_label.Cast(self.tRString))
+
+ err = lldb.SBError()
+ iseq_name = self.target.process.ReadMemory(ptr, len, err)
+ if err.Success():
+ iseq_name = iseq_name.decode("utf-8")
+ else:
+ iseq_name = "error!!"
+
+ else:
+ print("No iseq", file=self.result)
+
+ print(self.fmt % (("%0#12x" % cfp.GetAddress().GetLoadAddress(self.target)), m, "%s %s" % (path, iseq_name)), file=self.result)
+
+ def dump_cfunc_frame(self, cfp):
+ print(self.fmt % ("%0#12x" % (cfp.GetAddress().GetLoadAddress(self.target)), "CFUNC", ""), file=self.result)
+
+ def print_bt(self, ec):
+ tRbExecutionContext_t = self.target.FindFirstType("rb_execution_context_t")
+ ec = ec.Cast(tRbExecutionContext_t.GetPointerType())
+ vm_stack = ec.GetValueForExpressionPath("->vm_stack")
+ vm_stack_size = ec.GetValueForExpressionPath("->vm_stack_size")
+
+ last_cfp_frame = ec.GetValueForExpressionPath("->cfp")
+ cfp_type_p = last_cfp_frame.GetType()
+
+ stack_top = vm_stack.GetValueAsUnsigned() + (
+ vm_stack_size.GetValueAsUnsigned() * vm_stack.GetType().GetByteSize())
+
+ cfp_frame_size = cfp_type_p.GetPointeeType().GetByteSize()
+
+ start_cfp = stack_top
+ # Skip dummy frames
+ start_cfp -= cfp_frame_size
+ start_cfp -= cfp_frame_size
+
+ last_cfp = last_cfp_frame.GetValueAsUnsigned()
+
+ size = ((start_cfp - last_cfp) / cfp_frame_size) + 1
+
+ print(self.fmt % ("rb_control_frame_t", "TYPE", ""), file=self.result)
+
+ curr_addr = start_cfp
+
+ while curr_addr >= last_cfp:
+ cfp = self.target.CreateValueFromAddress("cfp", lldb.SBAddress(curr_addr, self.target), cfp_type_p.GetPointeeType())
+ ep = cfp.GetValueForExpressionPath("->ep")
+ iseq = cfp.GetValueForExpressionPath("->iseq")
+
+ frame_type = ep.GetChildAtIndex(0).GetValueAsUnsigned() & self.VM_FRAME_MAGIC_MASK
+
+ if iseq.GetValueAsUnsigned():
+ pc = cfp.GetValueForExpressionPath("->pc")
+ if pc.GetValueAsUnsigned():
+ self.dump_iseq_frame(cfp, iseq)
+ else:
+ if frame_type == self.VM_FRAME_MAGIC_CFUNC:
+ self.dump_cfunc_frame(cfp)
+
+ curr_addr -= cfp_frame_size
+
+def lldb_init(debugger):
+ target = debugger.GetSelectedTarget()
+ global SIZEOF_VALUE
+ SIZEOF_VALUE = target.FindFirstType("VALUE").GetByteSize()
+
+ value_types = []
+ g = globals()
+
+ imemo_types = target.FindFirstType('enum imemo_type')
+ enum_members = imemo_types.GetEnumMembers()
+
+ for i in range(enum_members.GetSize()):
+ member = enum_members.GetTypeEnumMemberAtIndex(i)
+ g[member.GetName()] = member.GetValueAsUnsigned()
+
+ for enum in target.FindFirstGlobalVariable('ruby_dummy_gdb_enums'):
+ enum = enum.GetType()
+ members = enum.GetEnumMembers()
+ for i in range(0, members.GetSize()):
+ member = members.GetTypeEnumMemberAtIndex(i)
+ name = member.GetName()
+ value = member.GetValueAsUnsigned()
+ g[name] = value
+
+ if name.startswith('RUBY_T_'):
+ value_types.append(name)
+ g['value_types'] = value_types
+
+def string2cstr(rstring):
+ """Returns the pointer to the C-string in the given String object"""
+ if rstring.TypeIsPointerType():
+ rstring = rstring.Dereference()
+ flags = rstring.GetValueForExpressionPath(".basic->flags").unsigned
+ if flags & RUBY_T_MASK != RUBY_T_STRING:
+ raise TypeError("not a string")
+ clen = int(rstring.GetValueForExpressionPath(".len").value, 0)
+ if flags & RUBY_FL_USER1:
+ cptr = int(rstring.GetValueForExpressionPath(".as.heap.ptr").value, 0)
+ else:
+ cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0)
+ return cptr, clen
+
+def output_string(debugger, result, rstring):
+ cptr, clen = string2cstr(rstring)
+ append_expression(debugger, "*(const char (*)[%d])%0#x" % (clen, cptr), result)
+
+def fixnum_p(x):
+ return x & RUBY_FIXNUM_FLAG != 0
+
+def flonum_p(x):
+ return (x&RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG
+
+def static_sym_p(x):
+ return (x&~(~0<<RUBY_SPECIAL_SHIFT)) == RUBY_SYMBOL_FLAG
+
+def append_command_output(debugger, command, result):
+ output1 = result.GetOutput()
+ debugger.GetCommandInterpreter().HandleCommand(command, result)
+ output2 = result.GetOutput()
+ result.Clear()
+ result.write(output1)
+ result.write(output2)
+
+def append_expression(debugger, expression, result):
+ append_command_output(debugger, "expression " + expression, result)
+
+def lldb_rp(debugger, command, result, internal_dict):
+ if not ('RUBY_Qfalse' in globals()):
+ lldb_init(debugger)
+
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ if frame.IsValid():
+ val = frame.EvaluateExpression(command)
+ else:
+ val = target.EvaluateExpression(command)
+ error = val.GetError()
+ if error.Fail():
+ print(error, file=result)
+ return
+ lldb_inspect(debugger, target, result, val)
+
+def lldb_inspect(debugger, target, result, val):
+ num = val.GetValueAsSigned()
+ if num == RUBY_Qfalse:
+ print('false', file=result)
+ elif num == RUBY_Qtrue:
+ print('true', file=result)
+ elif num == RUBY_Qnil:
+ print('nil', file=result)
+ elif num == RUBY_Qundef:
+ print('undef', file=result)
+ elif fixnum_p(num):
+ print(num >> 1, file=result)
+ elif flonum_p(num):
+ append_expression(debugger, "rb_float_value(%0#x)" % val.GetValueAsUnsigned(), result)
+ elif static_sym_p(num):
+ if num < 128:
+ print("T_SYMBOL: %c" % num, file=result)
+ else:
+ print("T_SYMBOL: (%x)" % num, file=result)
+ append_expression(debugger, "rb_id2name(%0#x)" % (num >> 8), result)
+ elif num & RUBY_IMMEDIATE_MASK:
+ print('immediate(%x)' % num, file=result)
+ else:
+ tRBasic = target.FindFirstType("::RBasic").GetPointerType()
+
+ val = val.Cast(tRBasic)
+ flags = val.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+ flaginfo = ""
+
+ page = get_page(lldb, target, val)
+ page_type = target.FindFirstType("struct heap_page").GetPointerType()
+ page.Cast(page_type)
+
+ dump_bits(target, result, page, val.GetValueAsUnsigned())
+
+ if (flags & RUBY_FL_PROMOTED) == RUBY_FL_PROMOTED:
+ flaginfo += "[PROMOTED] "
+ if (flags & RUBY_FL_FREEZE) == RUBY_FL_FREEZE:
+ flaginfo += "[FROZEN] "
+ flType = flags & RUBY_T_MASK
+ if flType == RUBY_T_NONE:
+ print('T_NONE: %s%s' % (flaginfo, val.Dereference()), file=result)
+ elif flType == RUBY_T_NIL:
+ print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=result)
+ elif flType == RUBY_T_OBJECT:
+ result.write('T_OBJECT: %s' % flaginfo)
+ append_expression(debugger, "*(struct RObject*)%0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_CLASS or flType == RUBY_T_MODULE or flType == RUBY_T_ICLASS:
+ result.write('T_%s: %s' % ('CLASS' if flType == RUBY_T_CLASS else 'MODULE' if flType == RUBY_T_MODULE else 'ICLASS', flaginfo))
+ append_expression(debugger, "*(struct RClass*)%0#x" % val.GetValueAsUnsigned(), result)
+ tRClass = target.FindFirstType("struct RClass")
+ if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
+ append_expression(debugger, "*(struct rb_classext_struct*)%0#x" % (val.GetValueAsUnsigned() + tRClass.GetByteSize()), result)
+ elif flType == RUBY_T_STRING:
+ result.write('T_STRING: %s' % flaginfo)
+ encidx = ((flags & RUBY_ENCODING_MASK)>>RUBY_ENCODING_SHIFT)
+ encname = target.FindFirstType("enum ruby_preserved_encindex").GetEnumMembers().GetTypeEnumMemberAtIndex(encidx).GetName()
+ if encname is not None:
+ result.write('[%s] ' % encname[14:])
+ else:
+ result.write('[enc=%d] ' % encidx)
+ tRString = target.FindFirstType("struct RString").GetPointerType()
+ ptr, len = string2cstr(val.Cast(tRString))
+ if len == 0:
+ result.write("(empty)\n")
+ else:
+ append_expression(debugger, "*(const char (*)[%d])%0#x" % (len, ptr), result)
+ elif flType == RUBY_T_SYMBOL:
+ result.write('T_SYMBOL: %s' % flaginfo)
+ tRSymbol = target.FindFirstType("struct RSymbol").GetPointerType()
+ val = val.Cast(tRSymbol)
+ append_expression(debugger, '(ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned(), result)
+ tRString = target.FindFirstType("struct RString").GetPointerType()
+ output_string(debugger, result, val.GetValueForExpressionPath("->fstr").Cast(tRString))
+ elif flType == RUBY_T_ARRAY:
+ tRArray = target.FindFirstType("struct RArray").GetPointerType()
+ val = val.Cast(tRArray)
+ if flags & RUBY_FL_USER1:
+ len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4|RUBY_FL_USER5|RUBY_FL_USER6|RUBY_FL_USER7|RUBY_FL_USER8|RUBY_FL_USER9)) >> (RUBY_FL_USHIFT+3))
+ ptr = val.GetValueForExpressionPath("->as.ary")
+ else:
+ len = val.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
+ ptr = val.GetValueForExpressionPath("->as.heap.ptr")
+ result.write("T_ARRAY: %slen=%d" % (flaginfo, len))
+ if flags & RUBY_FL_USER1:
+ result.write(" (embed)")
+ elif flags & RUBY_FL_USER2:
+ shared = val.GetValueForExpressionPath("->as.heap.aux.shared").GetValueAsUnsigned()
+ result.write(" (shared) shared=%016x" % shared)
+ else:
+ capa = val.GetValueForExpressionPath("->as.heap.aux.capa").GetValueAsSigned()
+ result.write(" (ownership) capa=%d" % capa)
+ if len == 0:
+ result.write(" {(empty)}\n")
+ else:
+ result.write("\n")
+ if ptr.GetValueAsSigned() == 0:
+ append_expression(debugger, "-fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned(), result)
+ else:
+ append_expression(debugger, "-Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()), result)
+ elif flType == RUBY_T_HASH:
+ result.write("T_HASH: %s" % flaginfo)
+ append_expression(debugger, "*(struct RHash *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_BIGNUM:
+ tRBignum = target.FindFirstType("struct RBignum").GetPointerType()
+ val = val.Cast(tRBignum)
+ sign = '+' if (flags & RUBY_FL_USER1) != 0 else '-'
+ if flags & RUBY_FL_USER2:
+ len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4|RUBY_FL_USER5)) >> (RUBY_FL_USHIFT+3))
+ print("T_BIGNUM: sign=%s len=%d (embed)" % (sign, len), file=result)
+ append_expression(debugger, "((struct RBignum *) %0#x)->as.ary" % val.GetValueAsUnsigned(), result)
+ else:
+ len = val.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
+ print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=result)
+ print(val.Dereference(), file=result)
+ append_expression(debugger, "-Z %x -fx -- (const BDIGIT*)((struct RBignum*)%d)->as.heap.digits" % (len, val.GetValueAsUnsigned()), result)
+ # append_expression(debugger, "((struct RBignum *) %0#x)->as.heap.digits / %d" % (val.GetValueAsUnsigned(), len), result)
+ elif flType == RUBY_T_FLOAT:
+ append_expression(debugger, "((struct RFloat *)%d)->float_value" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_RATIONAL:
+ tRRational = target.FindFirstType("struct RRational").GetPointerType()
+ val = val.Cast(tRRational)
+ lldb_inspect(debugger, target, result, val.GetValueForExpressionPath("->num"))
+ output = result.GetOutput()
+ result.Clear()
+ result.write("(Rational) " + output.rstrip() + " / ")
+ lldb_inspect(debugger, target, result, val.GetValueForExpressionPath("->den"))
+ elif flType == RUBY_T_COMPLEX:
+ tRComplex = target.FindFirstType("struct RComplex").GetPointerType()
+ val = val.Cast(tRComplex)
+ lldb_inspect(debugger, target, result, val.GetValueForExpressionPath("->real"))
+ real = result.GetOutput().rstrip()
+ result.Clear()
+ lldb_inspect(debugger, target, result, val.GetValueForExpressionPath("->imag"))
+ imag = result.GetOutput().rstrip()
+ result.Clear()
+ if not imag.startswith("-"):
+ imag = "+" + imag
+ print("(Complex) " + real + imag + "i", file=result)
+ elif flType == RUBY_T_REGEXP:
+ tRRegex = target.FindFirstType("struct RRegexp").GetPointerType()
+ val = val.Cast(tRRegex)
+ print("(Regex) ->src {", file=result)
+ lldb_inspect(debugger, target, result, val.GetValueForExpressionPath("->src"))
+ print("}", file=result)
+ elif flType == RUBY_T_DATA:
+ tRTypedData = target.FindFirstType("struct RTypedData").GetPointerType()
+ val = val.Cast(tRTypedData)
+ flag = val.GetValueForExpressionPath("->typed_flag")
+ if flag.GetValueAsUnsigned() == 1:
+ print("T_DATA: %s" % val.GetValueForExpressionPath("->type->wrap_struct_name"), file=result)
+ append_expression(debugger, "*(struct RTypedData *) %0#x" % val.GetValueAsUnsigned(), result)
+ else:
+ print("T_DATA:", file=result)
+ append_expression(debugger, "*(struct RData *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_NODE:
+ tRTypedData = target.FindFirstType("struct RNode").GetPointerType()
+ nd_type = (flags & RUBY_NODE_TYPEMASK) >> RUBY_NODE_TYPESHIFT
+ append_expression(debugger, "(node_type) %d" % nd_type, result)
+ val = val.Cast(tRTypedData)
+ append_expression(debugger, "*(struct RNode *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_MOVED:
+ tRTypedData = target.FindFirstType("struct RMoved").GetPointerType()
+ val = val.Cast(tRTypedData)
+ append_expression(debugger, "*(struct RMoved *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_MATCH:
+ tRTypedData = target.FindFirstType("struct RMatch").GetPointerType()
+ val = val.Cast(tRTypedData)
+ append_expression(debugger, "*(struct RMatch *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_IMEMO:
+ # I'm not sure how to get IMEMO_MASK out of lldb. It's not in globals()
+ imemo_type = (flags >> RUBY_FL_USHIFT) & 0x0F # IMEMO_MASK
+
+ print("T_IMEMO: ", file=result)
+ append_expression(debugger, "(enum imemo_type) %d" % imemo_type, result)
+ append_expression(debugger, "*(struct MEMO *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_STRUCT:
+ tRTypedData = target.FindFirstType("struct RStruct").GetPointerType()
+ val = val.Cast(tRTypedData)
+ append_expression(debugger, "*(struct RStruct *) %0#x" % val.GetValueAsUnsigned(), result)
+ elif flType == RUBY_T_ZOMBIE:
+ tRZombie = target.FindFirstType("struct RZombie").GetPointerType()
+ val = val.Cast(tRZombie)
+ append_expression(debugger, "*(struct RZombie *) %0#x" % val.GetValueAsUnsigned(), result)
+ else:
+ print("Not-handled type %0#x" % flType, file=result)
+ print(val, file=result)
+
+def count_objects(debugger, command, ctx, result, internal_dict):
+ objspace = ctx.frame.EvaluateExpression("ruby_current_vm->objspace")
+ num_pages = objspace.GetValueForExpressionPath(".heap_pages.allocated_pages").unsigned
+
+ counts = {}
+ total = 0
+ for t in range(0x00, RUBY_T_MASK+1):
+ counts[t] = 0
+
+ for i in range(0, num_pages):
+ print("\rcounting... %d/%d" % (i, num_pages), end="")
+ page = objspace.GetValueForExpressionPath('.heap_pages.sorted[%d]' % i)
+ p = page.GetChildMemberWithName('start')
+ num_slots = page.GetChildMemberWithName('total_slots').unsigned
+ for j in range(0, num_slots):
+ obj = p.GetValueForExpressionPath('[%d]' % j)
+ flags = obj.GetValueForExpressionPath('.as.basic.flags').unsigned
+ obj_type = flags & RUBY_T_MASK
+ counts[obj_type] += 1
+ total += num_slots
+
+ print("\rTOTAL: %d, FREE: %d" % (total, counts[0x00]))
+ for sym in value_types:
+ print("%s: %d" % (sym, counts[globals()[sym]]))
+
+def stack_dump_raw(debugger, command, ctx, result, internal_dict):
+ ctx.frame.EvaluateExpression("rb_vmdebug_stack_dump_raw_current()")
+
+def check_bits(page, bitmap_name, bitmap_index, bitmap_bit, v):
+ bits = page.GetChildMemberWithName(bitmap_name)
+ plane = bits.GetChildAtIndex(bitmap_index).GetValueAsUnsigned()
+ if (plane & bitmap_bit) != 0:
+ return v
+ else:
+ return ' '
+
+def heap_page_body(debugger, command, ctx, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ val = frame.EvaluateExpression(command)
+ page = get_page_body(lldb, target, val)
+ print("Page body address: ", page.GetAddress(), file=result)
+ print(page, file=result)
+
+def get_page_body(lldb, target, val):
+ tHeapPageBody = target.FindFirstType("struct heap_page_body")
+ addr = val.GetValueAsUnsigned()
+ page_addr = addr & ~(HEAP_PAGE_ALIGN_MASK)
+ address = lldb.SBAddress(page_addr, target)
+ return target.CreateValueFromAddress("page", address, tHeapPageBody)
+
+def get_page(lldb, target, val):
+ body = get_page_body(lldb, target, val)
+ return body.GetValueForExpressionPath("->header.page")
+
+def dump_node(debugger, command, ctx, result, internal_dict):
+ args = shlex.split(command)
+ if not args:
+ return
+ node = args[0]
+
+ dump = ctx.frame.EvaluateExpression("(struct RString*)rb_parser_dump_tree((NODE*)(%s), 0)" % node)
+ output_string(ctx, result, dump)
+
+def rb_backtrace(debugger, command, result, internal_dict):
+ if not ('RUBY_Qfalse' in globals()):
+ lldb_init(debugger)
+ bt = BackTrace(debugger, command, result, internal_dict)
+ frame = bt.frame
+
+ if command:
+ if frame.IsValid():
+ val = frame.EvaluateExpression(command)
+ else:
+ val = target.EvaluateExpression(command)
+
+ error = val.GetError()
+ if error.Fail():
+ print >> result, error
+ return
+ else:
+ print("Need an EC for now")
+
+ bt.print_bt(val)
+
+def dump_bits(target, result, page, object_address, end = "\n"):
+ slot_size = page.GetChildMemberWithName("heap").GetChildMemberWithName("slot_size").unsigned
+ byte_size = 40 ** math.floor(math.log(slot_size, 40))
+ tUintPtr = target.FindFirstType("uintptr_t") # bits_t
+
+ num_in_page = (object_address & HEAP_PAGE_ALIGN_MASK) // byte_size;
+ bits_bitlength = tUintPtr.GetByteSize() * 8
+ bitmap_index = num_in_page // bits_bitlength
+ bitmap_offset = num_in_page & (bits_bitlength - 1)
+ bitmap_bit = 1 << bitmap_offset
+
+ print("bits: [%s%s%s%s%s]" % (
+ check_bits(page, "uncollectible_bits", bitmap_index, bitmap_bit, "L"),
+ check_bits(page, "mark_bits", bitmap_index, bitmap_bit, "M"),
+ check_bits(page, "pinned_bits", bitmap_index, bitmap_bit, "P"),
+ check_bits(page, "marking_bits", bitmap_index, bitmap_bit, "R"),
+ check_bits(page, "wb_unprotected_bits", bitmap_index, bitmap_bit, "U"),
+ ), end=end, file=result)
+
+class HeapPageIter:
+ def __init__(self, page, target):
+ self.page = page
+ self.target = target
+ self.start = page.GetChildMemberWithName('start').GetValueAsUnsigned();
+ self.num_slots = page.GetChildMemberWithName('total_slots').unsigned
+ self.slot_size = page.GetChildMemberWithName('heap').GetChildMemberWithName('slot_size').unsigned
+ self.counter = 0
+ self.tRBasic = target.FindFirstType("::RBasic")
+
+ def is_valid(self):
+ heap_page_header_size = self.target.FindFirstType("struct heap_page_header").GetByteSize()
+ rvalue_size = self.slot_size
+ heap_page_obj_limit = int((HEAP_PAGE_SIZE - heap_page_header_size) / self.slot_size)
+
+ return (heap_page_obj_limit - 1) <= self.num_slots <= heap_page_obj_limit
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.counter < self.num_slots:
+ obj_addr_i = self.start + (self.counter * self.slot_size)
+ obj_addr = lldb.SBAddress(obj_addr_i, self.target)
+ slot_info = (self.counter, obj_addr_i, self.target.CreateValueFromAddress("object", obj_addr, self.tRBasic))
+ self.counter += 1
+
+ return slot_info
+ else:
+ raise StopIteration
+
+
+def dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=None):
+ if not ('RUBY_Qfalse' in globals()):
+ lldb_init(debugger)
+
+ ruby_type_map = ruby_types(debugger)
+
+ freelist = []
+ fl_start = page.GetChildMemberWithName('freelist').GetValueAsUnsigned()
+ free_slot = target.FindFirstType("struct free_slot")
+
+ while fl_start > 0:
+ freelist.append(fl_start)
+ obj_addr = lldb.SBAddress(fl_start, target)
+ obj = target.CreateValueFromAddress("object", obj_addr, free_slot)
+ fl_start = obj.GetChildMemberWithName("next").GetValueAsUnsigned()
+
+ page_iter = HeapPageIter(page, target)
+ if page_iter.is_valid():
+ for (page_index, obj_addr, obj) in page_iter:
+ dump_bits(target, result, page, obj_addr, end= " ")
+ flags = obj.GetChildMemberWithName('flags').GetValueAsUnsigned()
+ flType = flags & RUBY_T_MASK
+
+ flidx = ' '
+ if flType == RUBY_T_NONE:
+ try:
+ flidx = "%3d" % freelist.index(obj_addr)
+ except ValueError:
+ flidx = ' -1'
+
+ if flType == RUBY_T_NONE:
+ klass = obj.GetChildMemberWithName('klass').GetValueAsUnsigned()
+ result_str = "%s idx: [%3d] freelist_idx: {%s} Addr: %0#x (flags: %0#x, next: %0#x)" % (rb_type(flags, ruby_type_map), page_index, flidx, obj_addr, flags, klass)
+ else:
+ result_str = "%s idx: [%3d] freelist_idx: {%s} Addr: %0#x (flags: %0#x)" % (rb_type(flags, ruby_type_map), page_index, flidx, obj_addr, flags)
+
+ if highlight == obj_addr:
+ result_str = ' '.join([result_str, "<<<<<"])
+
+ print(result_str, file=result)
+ else:
+ print("%s is not a valid heap page" % page, file=result)
+
+
+
+def dump_page(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ tHeapPageP = target.FindFirstType("struct heap_page").GetPointerType()
+ page = frame.EvaluateExpression(command)
+ page = page.Cast(tHeapPageP)
+
+ dump_page_internal(page, target, process, thread, frame, result, debugger)
+
+
+def dump_page_rvalue(debugger, command, result, internal_dict):
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ val = frame.EvaluateExpression(command)
+ page = get_page(lldb, target, val)
+ page_type = target.FindFirstType("struct heap_page").GetPointerType()
+ page.Cast(page_type)
+
+ dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=val.GetValueAsUnsigned())
+
+
+
+def rb_type(flags, ruby_types):
+ flType = flags & RUBY_T_MASK
+ return "%-10s" % (ruby_types.get(flType, ("%0#x" % flType)))
+
+def ruby_types(debugger):
+ target = debugger.GetSelectedTarget()
+
+ types = {}
+ for enum in target.FindFirstGlobalVariable('ruby_dummy_gdb_enums'):
+ enum = enum.GetType()
+ members = enum.GetEnumMembers()
+ for i in range(0, members.GetSize()):
+ member = members.GetTypeEnumMemberAtIndex(i)
+ name = member.GetName()
+ value = member.GetValueAsUnsigned()
+
+ if name.startswith('RUBY_T_'):
+ types[value] = name.replace('RUBY_', '')
+
+ return types
+
+def rb_ary_entry(target, ary, idx, result):
+ tRArray = target.FindFirstType("struct RArray").GetPointerType()
+ ary = ary.Cast(tRArray)
+ flags = ary.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ if flags & RUBY_FL_USER1:
+ ptr = ary.GetValueForExpressionPath("->as.ary")
+ else:
+ ptr = ary.GetValueForExpressionPath("->as.heap.ptr")
+
+ ptr_addr = ptr.GetValueAsUnsigned() + (idx * ptr.GetType().GetByteSize())
+ return target.CreateValueFromAddress("ary_entry[%d]" % idx, lldb.SBAddress(ptr_addr, target), ptr.GetType().GetPointeeType())
+
+def rb_id_to_serial(id_val):
+ if id_val > tLAST_OP_ID:
+ return id_val >> RUBY_ID_SCOPE_SHIFT
+ else:
+ return id_val
+
+def rb_id2str(debugger, command, result, internal_dict):
+ if not ('RUBY_Qfalse' in globals()):
+ lldb_init(debugger)
+
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ global_symbols = target.FindFirstGlobalVariable("ruby_global_symbols")
+
+ id_val = frame.EvaluateExpression(command).GetValueAsUnsigned()
+ num = rb_id_to_serial(id_val)
+
+ last_id = global_symbols.GetChildMemberWithName("last_id").GetValueAsUnsigned()
+ ID_ENTRY_SIZE = 2
+ ID_ENTRY_UNIT = int(target.FindFirstGlobalVariable("ID_ENTRY_UNIT").GetValue())
+
+ ids = global_symbols.GetChildMemberWithName("ids")
+
+ if (num <= last_id):
+ idx = num // ID_ENTRY_UNIT
+ ary = rb_ary_entry(target, ids, idx, result)
+ pos = (num % ID_ENTRY_UNIT) * ID_ENTRY_SIZE
+ id_str = rb_ary_entry(target, ary, pos, result)
+ lldb_inspect(debugger, target, result, id_str)
+# END FUNCTION STYLE DECLS
+
+
+load_dir, _ = os.path.split(os.path.realpath(__file__))
+
+for fname in glob.glob(f"{load_dir}/lldb_rb/commands/*_command.py"):
+ _, basename = os.path.split(fname)
+ mname, _ = os.path.splitext(basename)
+
+ exec(f"import lldb_rb.commands.{mname}")
+
+def __lldb_init_module(debugger, internal_dict):
+ # Register all classes that subclass RbBaseCommand
+
+ for memname, mem in inspect.getmembers(sys.modules["lldb_rb.rb_base_command"]):
+ if memname == "RbBaseCommand":
+ for sclass in mem.__subclasses__():
+ sclass.register_lldb_command(debugger, f"{__name__}.{sclass.__module__}")
+
+
+ ## FUNCTION INITS - These should be removed when converted to class commands
+ debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp old_rp")
+ debugger.HandleCommand("command script add -f lldb_cruby.count_objects rb_count_objects")
+ debugger.HandleCommand("command script add -f lldb_cruby.stack_dump_raw SDR")
+ debugger.HandleCommand("command script add -f lldb_cruby.dump_node dump_node")
+ debugger.HandleCommand("command script add -f lldb_cruby.heap_page_body heap_page_body")
+ debugger.HandleCommand("command script add -f lldb_cruby.rb_backtrace rbbt")
+ debugger.HandleCommand("command script add -f lldb_cruby.dump_page dump_page")
+ debugger.HandleCommand("command script add -f lldb_cruby.dump_page_rvalue dump_page_rvalue")
+ debugger.HandleCommand("command script add -f lldb_cruby.rb_id2str old_rb_id2str")
+
+ lldb_rb.rb_base_command.RbBaseCommand.lldb_init(debugger)
+
+ print("lldb scripts for ruby has been installed.")
diff --git a/misc/lldb_disasm.py b/misc/lldb_disasm.py
new file mode 100644
index 0000000000..ab759f009a
--- /dev/null
+++ b/misc/lldb_disasm.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+#coding: utf-8
+#
+# Usage: run `command script import -r misc/lldb_disasm.py` on LLDB
+#
+#
+# (lldb) p iseq
+# (rb_iseq_t *) $147 = 0x0000000101068400
+# (lldb) rbdisasm iseq
+# 0000 putspecialobject( 3 )
+# 0002 putnil
+# 0003 defineclass( ID: 0x560b, (rb_iseq_t *)0x1010681d0, 2 )
+# 0007 pop
+# 0008 putspecialobject( 3 )
+# 0010 putnil
+# 0011 defineclass( ID: 0x56eb, (rb_iseq_t *)0x101063b58, 2 )
+# 0015 leave
+
+
+import lldb
+import os
+import shlex
+
+class IseqDisassembler:
+ TS_VARIABLE = b'.'[0]
+ TS_CALLDATA = b'C'[0]
+ TS_CDHASH = b'H'[0]
+ TS_IC = b'K'[0]
+ TS_IVC = b'A'[0]
+ TS_ICVARC = b'J'[0]
+ TS_ID = b'I'[0]
+ TS_ISE = b'T'[0]
+ TS_ISEQ = b'S'[0]
+ TS_OFFSET = b'O'[0]
+ TS_VALUE = b'V'[0]
+ TS_LINDEX = b'L'[0]
+ TS_FUNCPTR = b'F'[0]
+ TS_NUM = b'N'[0]
+ TS_BUILTIN = b'R'[0]
+
+ ISEQ_OPT_DISPATCH = {
+ TS_BUILTIN: "(rb_builtin_function *)%0#x",
+ TS_NUM: "%d",
+ TS_FUNCPTR: "(rb_insn_func_t) %0#x",
+ TS_LINDEX: "%d",
+ TS_VALUE: "(VALUE)%0#x",
+ TS_OFFSET: "%d",
+ TS_ISEQ: "(rb_iseq_t *)%0#x",
+ TS_ISE: "(iseq_inline_storage_entry *)%0#x",
+ TS_ID: "ID: %0#x",
+ TS_IVC: "(struct iseq_inline_iv_cache_entry *)%0#x",
+ TS_ICVARC: "(struct iseq_inline_cvar_cache_entry *)%0#x",
+ TS_IC: "(struct iseq_inline_cache_entry *)%0#x",
+ TS_CDHASH: "CDHASH (VALUE)%0#x",
+ TS_CALLDATA: "(struct rb_call_data *)%0#x",
+ TS_VARIABLE: "VARIABLE %0#x",
+ }
+
+ def __init__(self, debugger, command, result, internal_dict):
+ self.debugger = debugger
+ self.command = command
+ self.result = result
+ self.internal_dict = internal_dict
+
+ self.target = debugger.GetSelectedTarget()
+ self.insns_address_table = self.__get_insns_address_table()
+ self.process = self.target.GetProcess()
+ self.thread = self.process.GetSelectedThread()
+ self.frame = self.thread.GetSelectedFrame()
+ self.addr2insn = self.build_addr2insn(self.target)
+ self.tChar = self.target.FindFirstType("char")
+
+ def disasm(self, val):
+ tRbISeq = self.target.FindFirstType("struct rb_iseq_struct").GetPointerType()
+ val = val.Cast(tRbISeq)
+ iseq_size = val.GetValueForExpressionPath("->body->iseq_size").GetValueAsUnsigned()
+ iseqs = val.GetValueForExpressionPath("->body->iseq_encoded")
+ idx = 0
+ print("PC IDX insn_name(operands) ", file=self.result)
+ while idx < iseq_size:
+ m = self.iseq_extract_values(self.debugger, self.target, self.process, self.result, iseqs, idx)
+ if m < 1:
+ print("Error decoding", file=self.result)
+ return
+ else:
+ idx += m
+
+ def build_addr2insn(self, target):
+ tIntPtr = target.FindFirstType("intptr_t")
+ size = target.EvaluateExpression('ruby_vminsn_type::VM_INSTRUCTION_SIZE').unsigned
+ sizeOfIntPtr = tIntPtr.GetByteSize()
+ addr_of_table = self.insns_address_table.GetStartAddress().GetLoadAddress(target)
+
+ my_dict = {}
+
+ for insn in range(size):
+ addr_in_table = addr_of_table + (insn * sizeOfIntPtr)
+ addr = lldb.SBAddress(addr_in_table, target)
+ machine_insn = target.CreateValueFromAddress("insn", addr, tIntPtr).GetValueAsUnsigned()
+ my_dict[machine_insn] = insn
+
+ return my_dict
+
+ def rb_vm_insn_addr2insn2(self, target, result, wanted_addr):
+ return self.addr2insn.get(wanted_addr)
+
+ def iseq_extract_values(self, debugger, target, process, result, iseqs, n):
+ tValueP = target.FindFirstType("VALUE")
+ sizeofValueP = tValueP.GetByteSize()
+ pc = iseqs.unsigned + (n * sizeofValueP)
+ insn = target.CreateValueFromAddress("i", lldb.SBAddress(pc, target), tValueP)
+ addr = insn.GetValueAsUnsigned()
+ orig_insn = self.rb_vm_insn_addr2insn2(target, result, addr)
+
+ name = self.insn_name(target, process, result, orig_insn)
+ length = self.insn_len(target, orig_insn)
+ op_str = self.insn_op_types(target, process, result, orig_insn)
+ op_types = bytes(op_str, 'utf-8')
+
+ if length != (len(op_types) + 1):
+ print("error decoding iseqs", file=result)
+ return -1
+
+ print("%0#14x %04d %s" % (pc, n, name), file=result, end="")
+
+ if length == 1:
+ print("", file=result)
+ return length
+
+ print("(", end="", file=result)
+ for idx, op_type in enumerate(op_types):
+ if idx == 0:
+ print(" ", end="", file=result)
+ else:
+ print(", ", end="", file=result)
+
+ opAddr = lldb.SBAddress(iseqs.unsigned + ((n + idx + 1) * sizeofValueP), target)
+ opValue = target.CreateValueFromAddress("op", opAddr, tValueP)
+ op = opValue.GetValueAsUnsigned()
+ print(self.ISEQ_OPT_DISPATCH.get(op_type) % op, end="", file=result)
+
+ print(" )", file=result)
+ return length
+
+ def insn_len(self, target, offset):
+ size_of_char = self.tChar.GetByteSize()
+
+ symbol = target.FindSymbols("rb_vm_insn_len_info")[0].GetSymbol()
+ section = symbol.GetStartAddress().GetSection()
+ addr_of_table = symbol.GetStartAddress().GetOffset()
+
+ error = lldb.SBError()
+ length = section.GetSectionData().GetUnsignedInt8(error, addr_of_table + (offset * size_of_char))
+
+ if error.Success():
+ return length
+ else:
+ print("error getting length: ", error)
+
+ def insn_op_types(self, target, process, result, insn):
+ tUShort = target.FindFirstType("unsigned short")
+
+ size_of_short = tUShort.GetByteSize()
+ size_of_char = self.tChar.GetByteSize()
+
+ symbol = target.FindSymbols("rb_vm_insn_op_offset")[0].GetSymbol()
+ section = symbol.GetStartAddress().GetSection()
+ addr_of_table = symbol.GetStartAddress().GetOffset()
+
+ addr_in_table = addr_of_table + (insn * size_of_short)
+
+ error = lldb.SBError()
+ offset = section.GetSectionData().GetUnsignedInt16(error, addr_in_table)
+
+ if not error.Success():
+ print("error getting op type offset: ", error)
+
+ symbol = target.FindSymbols("rb_vm_insn_op_base")[0].GetSymbol()
+ section = symbol.GetStartAddress().GetSection()
+ addr_of_table = symbol.GetStartAddress().GetOffset()
+ addr_in_name_table = addr_of_table + (offset * size_of_char)
+
+ error = lldb.SBError()
+ types = section.GetSectionData().GetString(error, addr_in_name_table)
+ if error.Success():
+ return types
+ else:
+ print("error getting op types: ", error)
+
+ def insn_name_table_offset(self, target, offset):
+ tUShort = target.FindFirstType("unsigned short")
+ size_of_short = tUShort.GetByteSize()
+
+ symbol = target.FindSymbols("rb_vm_insn_name_offset")[0].GetSymbol()
+ section = symbol.GetStartAddress().GetSection()
+ table_offset = symbol.GetStartAddress().GetOffset()
+
+ table_offset = table_offset + (offset * size_of_short)
+
+ error = lldb.SBError()
+ offset = section.GetSectionData().GetUnsignedInt16(error, table_offset)
+
+ if error.Success():
+ return offset
+ else:
+ print("error getting insn name table offset: ", error)
+
+ def insn_name(self, target, process, result, offset):
+ symbol = target.FindSymbols("rb_vm_insn_name_base")[0].GetSymbol()
+ section = symbol.GetStartAddress().GetSection()
+ addr_of_table = symbol.GetStartAddress().GetOffset()
+
+ name_table_offset = self.insn_name_table_offset(target, offset)
+ addr_in_name_table = addr_of_table + name_table_offset
+
+ error = lldb.SBError()
+ name = section.GetSectionData().GetString(error, addr_in_name_table)
+
+ if error.Success():
+ return name
+ else:
+ print('error getting insn name', error)
+
+ def __get_insns_address_table(self):
+ module = self.target.FindSymbols("vm_exec_core")[0].GetModule()
+
+ for symbol in module:
+ if "insns_address_table" in symbol.name and symbol.GetType() == lldb.eSymbolTypeData:
+ print(f"found symbol {symbol.name}")
+ return symbol
+
+
+def disasm(debugger, command, result, internal_dict):
+ disassembler = IseqDisassembler(debugger, command, result, internal_dict)
+ frame = disassembler.frame
+
+ if frame.IsValid():
+ val = frame.EvaluateExpression(command)
+ else:
+ val = target.EvaluateExpression(command)
+ error = val.GetError()
+ if error.Fail():
+ print >> result, error
+ return
+
+ disassembler.disasm(val);
+
+def __lldb_init_module(debugger, internal_dict):
+ debugger.HandleCommand("command script add -f lldb_disasm.disasm rbdisasm")
+ print("lldb Ruby disasm installed.")
diff --git a/misc/lldb_rb/commands/command_template.py b/misc/lldb_rb/commands/command_template.py
new file mode 100644
index 0000000000..21014a993e
--- /dev/null
+++ b/misc/lldb_rb/commands/command_template.py
@@ -0,0 +1,30 @@
+# This is a command template for implementing a helper function inside LLDB. To
+# use this file
+# 1. Copy it and rename the copy so it ends with `_command.py`.
+# 2. Rename the class to something descriptive that ends with Command.
+# 3. Change the program variable to be a descriptive command name
+# 4. Ensure you are inheriting from RbBaseCommand or another command that
+# implements the same interface
+
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+# This test command inherits from RbBaseCommand which provides access to Ruby
+# globals and utility helpers
+class TestCommand(RbBaseCommand):
+ # program is the keyword the user will type in lldb to execute this command
+ program = "test"
+
+ # help_string will be displayed in lldb when the user uses the help functions
+ help_string = "This is a test command to show how to implement lldb commands"
+
+ # call is where our command logic will be implemented
+ def call(self, debugger, command, exe_ctx, result):
+ # This method will be called once the LLDB environment has been setup.
+ # You will have access to self.target, self.process, self.frame, and
+ # self.thread
+ #
+ # This is where we should implement our command logic
+ pass
diff --git a/misc/lldb_rb/commands/heap_page_command.py b/misc/lldb_rb/commands/heap_page_command.py
new file mode 100644
index 0000000000..2eed3c3bee
--- /dev/null
+++ b/misc/lldb_rb/commands/heap_page_command.py
@@ -0,0 +1,27 @@
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class HeapPageCommand(RbBaseCommand):
+ program = "heap_page"
+ help_string = "prints out 'struct heap_page' for a VALUE pointer in the page"
+
+ def call(self, debugger, command, exe_ctx, result):
+ self.result = result
+ self.t_heap_page_body = self.target.FindFirstType("struct heap_page_body")
+ self.t_heap_page_ptr = self.target.FindFirstType("struct heap_page").GetPointerType()
+
+ page = self._get_page(self.frame.EvaluateExpression(command))
+ page.Cast(self.t_heap_page_ptr)
+
+ self._append_expression("(struct heap_page *) %0#x" % page.GetValueAsUnsigned())
+ self._append_expression("*(struct heap_page *) %0#x" % page.GetValueAsUnsigned())
+
+ def _get_page(self, val):
+ addr = val.GetValueAsUnsigned()
+ page_addr = addr & ~(HEAP_PAGE_ALIGN_MASK)
+ address = lldb.SBAddress(page_addr, self.target)
+ body = self.target.CreateValueFromAddress("page", address, self.t_heap_page_body)
+
+ return body.GetValueForExpressionPath("->header.page")
diff --git a/misc/lldb_rb/commands/print_flags_command.py b/misc/lldb_rb/commands/print_flags_command.py
new file mode 100644
index 0000000000..bc494ae01a
--- /dev/null
+++ b/misc/lldb_rb/commands/print_flags_command.py
@@ -0,0 +1,31 @@
+import lldb
+import re
+
+from lldb_rb.constants import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class PrintFlagsCommand(RbBaseCommand):
+ program = "print_flags"
+
+ help_string = "Print out the individial flags of an RVALUE object in human readable format"
+
+ # call is where our command logic will be implemented
+ def call(self, debugger, command, exe_ctx, result):
+ rclass_t = self.target.FindFirstType("::RBasic")
+ rcass_ptr = self.target.EvaluateExpression(command).Cast(rclass_t.GetPointerType())
+ obj_flags = rcass_ptr.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ flags = [
+ "RUBY_FL_WB_PROTECTED", "RUBY_FL_PROMOTED", "RUBY_FL_FINALIZE",
+ "RUBY_FL_SHAREABLE", "RUBY_FL_FREEZE",
+ "RUBY_FL_USER0", "RUBY_FL_USER1", "RUBY_FL_USER2", "RUBY_FL_USER3", "RUBY_FL_USER4",
+ "RUBY_FL_USER5", "RUBY_FL_USER6", "RUBY_FL_USER7", "RUBY_FL_USER8", "RUBY_FL_USER9",
+ "RUBY_FL_USER10", "RUBY_FL_USER11", "RUBY_FL_USER12", "RUBY_FL_USER13", "RUBY_FL_USER14",
+ "RUBY_FL_USER15", "RUBY_FL_USER16", "RUBY_FL_USER17", "RUBY_FL_USER18"
+ ]
+
+ types_index = {v: k for k, v in self.ruby_globals.items() if re.match(r'RUBY_T_', k)}
+ print("TYPE: {}".format(types_index[obj_flags & self.ruby_globals["RUBY_T_MASK"]]))
+ for flag in flags:
+ output = "{} : {}".format(flag, "1" if (obj_flags & self.ruby_globals[flag]) else "0")
+ print(output, file=result)
diff --git a/misc/lldb_rb/commands/rb_id2str_command.py b/misc/lldb_rb/commands/rb_id2str_command.py
new file mode 100644
index 0000000000..6ee859ebf6
--- /dev/null
+++ b/misc/lldb_rb/commands/rb_id2str_command.py
@@ -0,0 +1,49 @@
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.utils import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class RbID2StrCommand(RbBaseCommand):
+ program = "rb_id2str"
+
+ help_string = "convert and print a Ruby ID to a C string and print it to the LLDB console"
+
+ def call(self, debugger, command, exe_ctx, result):
+ global_symbols = self.target.FindFirstGlobalVariable("ruby_global_symbols")
+
+ id_val = self.frame.EvaluateExpression(command).GetValueAsUnsigned()
+ num = self.rb_id_to_serial(id_val)
+
+ last_id = global_symbols.GetChildMemberWithName("last_id").GetValueAsUnsigned()
+ ID_ENTRY_SIZE = 2
+ ID_ENTRY_UNIT = int(self.target.FindFirstGlobalVariable("ID_ENTRY_UNIT").GetValue())
+
+ ids = global_symbols.GetChildMemberWithName("ids")
+
+ if num <= last_id:
+ idx = num // ID_ENTRY_UNIT
+ ary = self.rb_ary_entry(ids, idx, result)
+ pos = (num % ID_ENTRY_UNIT) * ID_ENTRY_SIZE
+ id_str = self.rb_ary_entry(ary, pos, result)
+
+ RbInspector(debugger, result, self.ruby_globals).inspect(id_str)
+
+ def rb_id_to_serial(self, id_val):
+ if id_val > self.ruby_globals["tLAST_OP_ID"]:
+ return id_val >> self.ruby_globals["RUBY_ID_SCOPE_SHIFT"]
+ else:
+ return id_val
+
+ def rb_ary_entry(self, ary, idx, result):
+ tRArray = self.target.FindFirstType("struct RArray").GetPointerType()
+ ary = ary.Cast(tRArray)
+ flags = ary.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ if flags & self.ruby_globals["RUBY_FL_USER1"]:
+ ptr = ary.GetValueForExpressionPath("->as.ary")
+ else:
+ ptr = ary.GetValueForExpressionPath("->as.heap.ptr")
+
+ ptr_addr = ptr.GetValueAsUnsigned() + (idx * ptr.GetType().GetByteSize())
+ return self.target.CreateValueFromAddress("ary_entry[%d]" % idx, lldb.SBAddress(ptr_addr, self.target), ptr.GetType().GetPointeeType())
diff --git a/misc/lldb_rb/commands/rclass_ext_command.py b/misc/lldb_rb/commands/rclass_ext_command.py
new file mode 100644
index 0000000000..8bae911457
--- /dev/null
+++ b/misc/lldb_rb/commands/rclass_ext_command.py
@@ -0,0 +1,14 @@
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class RclassExtCommand(RbBaseCommand):
+ program = "rclass_ext"
+ help_string = "retrieves and prints the rb_classext_struct for the VALUE pointer passed in"
+
+ def call(self, debugger, command, exe_ctx, result):
+ uintptr_t = self.target.FindFirstType("uintptr_t")
+ rclass_t = self.target.FindFirstType("struct RClass")
+ rclass_ext_t = self.target.FindFirstType("rb_classext_t")
+
+ rclass_addr = self.target.EvaluateExpression(command).Cast(uintptr_t)
+ rclass_ext_addr = (rclass_addr.GetValueAsUnsigned() + rclass_t.GetByteSize())
+ debugger.HandleCommand("p *(rb_classext_t *)%0#x" % rclass_ext_addr)
diff --git a/misc/lldb_rb/commands/rp_command.py b/misc/lldb_rb/commands/rp_command.py
new file mode 100644
index 0000000000..06b2516d50
--- /dev/null
+++ b/misc/lldb_rb/commands/rp_command.py
@@ -0,0 +1,15 @@
+import lldb
+
+from lldb_rb.constants import *
+from lldb_rb.utils import *
+from lldb_rb.rb_base_command import RbBaseCommand
+
+class RbID2StrCommand(RbBaseCommand):
+ program = "rp"
+
+ help_string = "convert and print a Ruby ID to a C string and print it to the LLDB console"
+
+ def call(self, debugger, command, exe_ctx, result):
+ val = self.frame.EvaluateExpression(command)
+ inspector = RbInspector(debugger, result, self.ruby_globals)
+ inspector.inspect(val)
diff --git a/misc/lldb_rb/constants.py b/misc/lldb_rb/constants.py
new file mode 100644
index 0000000000..9cd56eccb0
--- /dev/null
+++ b/misc/lldb_rb/constants.py
@@ -0,0 +1,6 @@
+HEAP_PAGE_ALIGN_LOG = 16
+HEAP_PAGE_ALIGN_MASK = (~(~0 << HEAP_PAGE_ALIGN_LOG))
+HEAP_PAGE_ALIGN = (1 << HEAP_PAGE_ALIGN_LOG)
+HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN
+
+IMEMO_MASK = 0x0F
diff --git a/misc/lldb_rb/lldb_interface.py b/misc/lldb_rb/lldb_interface.py
new file mode 100644
index 0000000000..25930b2e16
--- /dev/null
+++ b/misc/lldb_rb/lldb_interface.py
@@ -0,0 +1,18 @@
+class LLDBInterface:
+ def build_environment(self, debugger):
+ self.debugger = debugger
+ self.target = debugger.GetSelectedTarget()
+ self.process = self.target.GetProcess()
+ self.thread = self.process.GetSelectedThread()
+ self.frame = self.thread.GetSelectedFrame()
+
+ def _append_command_output(self, command):
+ output1 = self.result.GetOutput()
+ self.debugger.GetCommandInterpreter().HandleCommand(command, self.result)
+ output2 = self.result.GetOutput()
+ self.result.Clear()
+ self.result.write(output1)
+ self.result.write(output2)
+
+ def _append_expression(self, expression):
+ self._append_command_output("expression " + expression)
diff --git a/misc/lldb_rb/rb_base_command.py b/misc/lldb_rb/rb_base_command.py
new file mode 100644
index 0000000000..70a5addd6d
--- /dev/null
+++ b/misc/lldb_rb/rb_base_command.py
@@ -0,0 +1,57 @@
+import lldb
+from pydoc import locate
+from lldb_rb.constants import *
+from lldb_rb.utils import *
+
+class RbBaseCommand(LLDBInterface):
+ @classmethod
+ def register_lldb_command(cls, debugger, module_name):
+ # Add any commands contained in this module to LLDB
+ command = f"command script add -c {module_name}.{cls.__name__} {cls.program}"
+ debugger.HandleCommand(command)
+
+ @classmethod
+ def lldb_init(cls, debugger):
+ target = debugger.GetSelectedTarget()
+ global SIZEOF_VALUE
+ SIZEOF_VALUE = target.FindFirstType("VALUE").GetByteSize()
+
+ value_types = []
+ g = globals()
+
+ imemo_types = target.FindFirstType("enum imemo_type")
+
+ #for member in imemo_types.GetEnumMembers():
+ # g[member.GetName()] = member.GetValueAsUnsigned()
+
+ for enum in target.FindFirstGlobalVariable("ruby_dummy_gdb_enums"):
+ enum = enum.GetType()
+ members = enum.GetEnumMembers()
+ for i in range(0, members.GetSize()):
+ member = members.GetTypeEnumMemberAtIndex(i)
+ name = member.GetName()
+ value = member.GetValueAsUnsigned()
+ g[name] = value
+
+ if name.startswith("RUBY_T_"):
+ value_types.append(name)
+ g["value_types"] = value_types
+ return g
+
+ def __init__(self, debugger, _internal_dict):
+ self.ruby_globals = RbBaseCommand.lldb_init(debugger)
+ self.internal_dict = _internal_dict
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ self.ruby_globals = RbBaseCommand.lldb_init(debugger)
+ self.build_environment(debugger)
+ self.call(debugger, command, exe_ctx, result)
+
+ def call(self, debugger, command, exe_ctx, result):
+ raise NotImplementedError("subclasses must implement call")
+
+ def get_short_help(self):
+ return self.__class__.help_string
+
+ def get_long_help(self):
+ return self.__class__.help_string
diff --git a/misc/lldb_rb/rb_heap_structs.py b/misc/lldb_rb/rb_heap_structs.py
new file mode 100644
index 0000000000..798b838080
--- /dev/null
+++ b/misc/lldb_rb/rb_heap_structs.py
@@ -0,0 +1,152 @@
+import lldb
+import math
+from lldb_rb.lldb_interface import LLDBInterface
+from lldb_rb.constants import *
+
+class HeapPage(LLDBInterface):
+ def __init__(self, debugger, val):
+ self.build_environment(debugger)
+ self.page_type = self.target.FindFirstType("struct heap_page").GetPointerType()
+ self.val = val
+
+ def heap_page_body(self, command, ctx, result, internal_dict):
+ process = self.target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ val = frame.EvaluateExpression(command)
+ page = self.get_page_body(val)
+ print("Page body address: ", page.GetAddress(), file=result)
+ print(page, file=result)
+
+ def get_page_body(self, val):
+ tHeapPageBody = self.target.FindFirstType("struct heap_page_body")
+ addr = val.GetValueAsUnsigned()
+ page_addr = addr & ~(HEAP_PAGE_ALIGN_MASK)
+ address = lldb.SBAddress(page_addr, self.target)
+ return self.target.CreateValueFromAddress("page", address, tHeapPageBody)
+
+ def get_page_raw(self, val):
+ body = self.get_page_body(val)
+ return body.GetValueForExpressionPath("->header.page")
+
+ def to_heap_page_struct(self):
+ pagePtr = self.get_page_raw(self.val)
+ return pagePtr.Cast(self.page_type)
+
+
+class RbObject(LLDBInterface):
+ def __init__(self, ptr, debugger, ruby_globals):
+ self.build_environment(debugger)
+ self.ruby_globals = ruby_globals
+
+ self.flUser1 = self.ruby_globals["RUBY_FL_USER1"]
+ self.flUser2 = self.ruby_globals["RUBY_FL_USER2"]
+ self.flUser3 = self.ruby_globals["RUBY_FL_USER3"]
+ self.flUser4 = self.ruby_globals["RUBY_FL_USER4"]
+ self.flUser5 = self.ruby_globals["RUBY_FL_USER5"]
+ self.flUser6 = self.ruby_globals["RUBY_FL_USER6"]
+ self.flUser7 = self.ruby_globals["RUBY_FL_USER7"]
+ self.flUser8 = self.ruby_globals["RUBY_FL_USER8"]
+ self.flUser9 = self.ruby_globals["RUBY_FL_USER9"]
+ self.flUshift = self.ruby_globals["RUBY_FL_USHIFT"]
+
+ self.tRBasic = self.target.FindFirstType("::RBasic").GetPointerType()
+
+ self.val = ptr.Cast(self.tRBasic)
+ self.page = HeapPage(self.debugger, self.val)
+ self.flags = self.val.GetValueForExpressionPath("->flags").GetValueAsUnsigned()
+
+ self.type = None
+ self.type_name = ""
+
+ def check_bits(self, bitmap_name, bitmap_index, bitmap_bit, v):
+ page = self.page.to_heap_page_struct()
+ bits = page.GetChildMemberWithName(bitmap_name)
+ plane = bits.GetChildAtIndex(bitmap_index).GetValueAsUnsigned()
+ if (plane & bitmap_bit) != 0:
+ return v
+ else:
+ return ' '
+
+ def dump_bits(self, result, end = "\n"):
+ tUintPtr = self.target.FindFirstType("uintptr_t") # bits_t
+
+ slot_size = self.page.to_heap_page_struct().GetChildMemberWithName("heap").GetChildMemberWithName("slot_size").unsigned
+ byte_size = 40 ** math.floor(math.log(slot_size, 40))
+
+ num_in_page = (self.val.GetValueAsUnsigned() & HEAP_PAGE_ALIGN_MASK) // byte_size;
+ bits_bitlength = tUintPtr.GetByteSize() * 8
+ bitmap_index = num_in_page // bits_bitlength
+ bitmap_offset = num_in_page & (bits_bitlength - 1)
+ bitmap_bit = 1 << bitmap_offset
+
+ page = self.page.to_heap_page_struct()
+ print("bits: [%s%s%s%s%s]" % (
+ self.check_bits("uncollectible_bits", bitmap_index, bitmap_bit, "L"),
+ self.check_bits("mark_bits", bitmap_index, bitmap_bit, "M"),
+ self.check_bits("pinned_bits", bitmap_index, bitmap_bit, "P"),
+ self.check_bits("marking_bits", bitmap_index, bitmap_bit, "R"),
+ self.check_bits("wb_unprotected_bits", bitmap_index, bitmap_bit, "U"),
+ ), end=end, file=result)
+
+ def promoted_p(self):
+ rbFlPromoted = self.ruby_globals["RUBY_FL_PROMOTED"]
+ return (self.flags & rbFlPromoted) == rbFlPromoted
+
+ def frozen_p(self):
+ rbFlFreeze = self.ruby_globals["RUBY_FL_FREEZE"]
+ return (self.flags & rbFlFreeze) == rbFlFreeze
+
+ def is_type(self, type_name):
+ if self.type is None:
+ flTMask = self.ruby_globals["RUBY_T_MASK"]
+ flType = self.flags & flTMask
+ self.type = flType
+
+ if self.type == self.ruby_globals[type_name]:
+ self.type_name = type_name
+ return True
+ else:
+ return False
+
+ def as_type(self, type_name):
+ if type_name == "array":
+ tRarray = self.target.FindFirstType("struct RArray")
+ return self.val.Cast(tRarray.GetPointerType())
+ elif type_name == "bignum":
+ tRbignum = self.target.FindFirstType("struct RBignum")
+ return self.val.Cast(tRbignum.GetPointerType())
+ else:
+ print("as_type is not implemented for:", type_name)
+
+ def ary_ptr(self):
+ rval = self.as_type("array")
+ if self.flags & self.ruby_globals["RUBY_FL_USER1"]:
+ ptr = rval.GetValueForExpressionPath("->as.ary")
+ else:
+ ptr = rval.GetValueForExpressionPath("->as.heap.ptr")
+ return ptr
+
+ def ary_len(self):
+ if self.flags & self.flUser1:
+ len = ((self.flags &
+ (self.flUser3 | self.flUser4 | self.flUser5 | self.flUser6 |
+ self.flUser7 | self.flUser8 | self.flUser9)
+ ) >> (self.flUshift + 3))
+ else:
+ rval = self.as_type("array")
+ len = rval.GetValueForExpressionPath("->as.heap.len").GetValueAsSigned()
+
+ return len
+
+ def bignum_len(self):
+ if self.flags & self.flUser2:
+ len = ((self.flags &
+ (self.flUser3 | self.flUser4 | self.flUser5)
+ ) >> (self.flUshift + 3))
+ else:
+ len = (self.as_type("bignum").GetValueForExpressionPath("->as.heap.len").
+ GetValueAsUnsigned())
+
+ return len
diff --git a/misc/lldb_rb/utils.py b/misc/lldb_rb/utils.py
new file mode 100644
index 0000000000..1415dd3f33
--- /dev/null
+++ b/misc/lldb_rb/utils.py
@@ -0,0 +1,515 @@
+from lldb_rb.lldb_interface import LLDBInterface
+from lldb_rb.rb_heap_structs import HeapPage, RbObject
+from lldb_rb.constants import *
+
+class RbInspector(LLDBInterface):
+ def __init__(self, debugger, result, ruby_globals):
+ self.build_environment(debugger)
+ self.result = result
+ self.ruby_globals = ruby_globals
+
+ def string2cstr(self, rstring):
+ """Returns the pointer to the C-string in the given String object"""
+ if rstring.TypeIsPointerType():
+ rstring = rstring.Dereference()
+
+ flags = rstring.GetValueForExpressionPath(".basic->flags").unsigned
+ clen = int(rstring.GetValueForExpressionPath(".len").value, 0)
+ if flags & self.ruby_globals["RUBY_FL_USER1"]:
+ cptr = int(rstring.GetValueForExpressionPath(".as.heap.ptr").value, 0)
+ else:
+ cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0)
+
+ return cptr, clen
+
+ def output_string(self, rstring):
+ cptr, clen = self.string2cstr(rstring)
+ self._append_expression("*(const char (*)[%d])%0#x" % (clen, cptr))
+
+ def fixnum_p(self, x):
+ return x & self.ruby_globals["RUBY_FIXNUM_FLAG"] != 0
+
+ def flonum_p(self, x):
+ return (x & self.ruby_globals["RUBY_FLONUM_MASK"]) == self.ruby_globals["RUBY_FLONUM_FLAG"]
+
+ def static_sym_p(self, x):
+ special_shift = self.ruby_globals["RUBY_SPECIAL_SHIFT"]
+ symbol_flag = self.ruby_globals["RUBY_SYMBOL_FLAG"]
+ return (x & ~(~0 << special_shift)) == symbol_flag
+
+ def generic_inspect(self, val, rtype):
+ tRType = self.target.FindFirstType("struct %s" % rtype).GetPointerType()
+ val = val.Cast(tRType)
+ self._append_expression("*(struct %s *) %0#x" % (rtype, val.GetValueAsUnsigned()))
+
+ def inspect(self, val):
+ rbTrue = self.ruby_globals["RUBY_Qtrue"]
+ rbFalse = self.ruby_globals["RUBY_Qfalse"]
+ rbNil = self.ruby_globals["RUBY_Qnil"]
+ rbUndef = self.ruby_globals["RUBY_Qundef"]
+ rbImmediateMask = self.ruby_globals["RUBY_IMMEDIATE_MASK"]
+
+ if self.inspect_node(val):
+ return
+
+ num = val.GetValueAsSigned()
+ if num == rbFalse:
+ print('false', file=self.result)
+ elif num == rbTrue:
+ print('true', file=self.result)
+ elif num == rbNil:
+ print('nil', file=self.result)
+ elif num == rbUndef:
+ print('undef', file=self.result)
+ elif self.fixnum_p(num):
+ print(num >> 1, file=self.result)
+ elif self.flonum_p(num):
+ self._append_expression("rb_float_value(%0#x)" % val.GetValueAsUnsigned())
+ elif self.static_sym_p(num):
+ if num < 128:
+ print("T_SYMBOL: %c" % num, file=self.result)
+ else:
+ print("T_SYMBOL: (%x)" % num, file=self.result)
+ self._append_expression("rb_id2name(%0#x)" % (num >> 8))
+
+ elif num & rbImmediateMask:
+ print('immediate(%x)' % num, file=self.result)
+ else:
+ rval = RbObject(val, self.debugger, self.ruby_globals)
+ rval.dump_bits(self.result)
+
+ flaginfo = ""
+ if rval.promoted_p():
+ flaginfo += "[PROMOTED] "
+ if rval.frozen_p():
+ flaginfo += "[FROZEN] "
+
+ if rval.is_type("RUBY_T_NONE"):
+ print('T_NONE: %s%s' % (flaginfo, val.Dereference()), file=self.result)
+
+ elif rval.is_type("RUBY_T_NIL"):
+ print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=self.result)
+
+ elif rval.is_type("RUBY_T_OBJECT"):
+ self.result.write('T_OBJECT: %s' % flaginfo)
+ self._append_expression("*(struct RObject*)%0#x" % val.GetValueAsUnsigned())
+
+ elif (rval.is_type("RUBY_T_CLASS") or
+ rval.is_type("RUBY_T_MODULE") or
+ rval.is_type("RUBY_T_ICLASS")):
+ self.result.write('T_%s: %s' % (rval.type_name.split('_')[-1], flaginfo))
+ tRClass = self.target.FindFirstType("struct RClass")
+
+ self._append_expression("*(struct RClass*)%0#x" % val.GetValueAsUnsigned())
+ if not val.Cast(tRClass).GetChildMemberWithName("ptr").IsValid():
+ self._append_expression(
+ "*(struct rb_classext_struct*)%0#x" %
+ (val.GetValueAsUnsigned() + tRClass.GetByteSize())
+ )
+
+ elif rval.is_type("RUBY_T_STRING"):
+ self.result.write('T_STRING: %s' % flaginfo)
+ tRString = self.target.FindFirstType("struct RString").GetPointerType()
+
+ chilled = self.ruby_globals["RUBY_FL_USER3"]
+ if (rval.flags & chilled) != 0:
+ self.result.write("[CHILLED] ")
+
+ rb_enc_mask = self.ruby_globals["RUBY_ENCODING_MASK"]
+ rb_enc_shift = self.ruby_globals["RUBY_ENCODING_SHIFT"]
+ encidx = ((rval.flags & rb_enc_mask) >> rb_enc_shift)
+ encname = self.target.FindFirstType("enum ruby_preserved_encindex") \
+ .GetEnumMembers().GetTypeEnumMemberAtIndex(encidx) \
+ .GetName()
+
+ if encname is not None:
+ self.result.write('[%s] ' % encname[14:])
+ else:
+ self.result.write('[enc=%d] ' % encidx)
+
+ coderange = rval.flags & self.ruby_globals["RUBY_ENC_CODERANGE_MASK"]
+ if coderange == self.ruby_globals["RUBY_ENC_CODERANGE_7BIT"]:
+ self.result.write('[7BIT] ')
+ elif coderange == self.ruby_globals["RUBY_ENC_CODERANGE_VALID"]:
+ self.result.write('[VALID] ')
+ elif coderange == self.ruby_globals["RUBY_ENC_CODERANGE_BROKEN"]:
+ self.result.write('[BROKEN] ')
+ else:
+ self.result.write('[UNKNOWN] ')
+
+ ptr, len = self.string2cstr(val.Cast(tRString))
+ if len == 0:
+ self.result.write("(empty)\n")
+ else:
+ self._append_expression("*(const char (*)[%d])%0#x" % (len, ptr))
+
+ elif rval.is_type("RUBY_T_SYMBOL"):
+ self.result.write('T_SYMBOL: %s' % flaginfo)
+ tRSymbol = self.target.FindFirstType("struct RSymbol").GetPointerType()
+ tRString = self.target.FindFirstType("struct RString").GetPointerType()
+
+ val = val.Cast(tRSymbol)
+ self._append_expression('(ID)%0#x ' % val.GetValueForExpressionPath("->id").GetValueAsUnsigned())
+ self.output_string(val.GetValueForExpressionPath("->fstr").Cast(tRString))
+
+ elif rval.is_type("RUBY_T_ARRAY"):
+ len = rval.ary_len()
+ ptr = rval.ary_ptr()
+
+ self.result.write("T_ARRAY: %slen=%d" % (flaginfo, len))
+
+ if rval.flags & self.ruby_globals["RUBY_FL_USER1"]:
+ self.result.write(" (embed)")
+ elif rval.flags & self.ruby_globals["RUBY_FL_USER2"]:
+ shared = val.GetValueForExpressionPath("->as.heap.aux.shared").GetValueAsUnsigned()
+ self.result.write(" (shared) shared=%016x" % shared)
+ else:
+ capa = val.GetValueForExpressionPath("->as.heap.aux.capa").GetValueAsSigned()
+ self.result.write(" (ownership) capa=%d" % capa)
+ if len == 0:
+ self.result.write(" {(empty)}\n")
+ else:
+ self.result.write("\n")
+ if ptr.GetValueAsSigned() == 0:
+ self._append_expression("-fx -- ((struct RArray*)%0#x)->as.ary" % val.GetValueAsUnsigned())
+ else:
+ self._append_expression("-Z %d -fx -- (const VALUE*)%0#x" % (len, ptr.GetValueAsUnsigned()))
+
+ elif rval.is_type("RUBY_T_HASH"):
+ self.result.write("T_HASH: %s" % flaginfo)
+ ptr = val.GetValueAsUnsigned()
+ self._append_expression("*(struct RHash *) %0#x" % ptr)
+ if rval.flags & self.ruby_globals["RUBY_FL_USER3"]:
+ self._append_expression("*(struct st_table *) (%0#x + sizeof(struct RHash))" % ptr)
+ else:
+ self._append_expression("*(struct ar_table *) (%0#x + sizeof(struct RHash))" % ptr)
+
+ elif rval.is_type("RUBY_T_BIGNUM"):
+ sign = '-'
+ if (rval.flags & self.ruby_globals["RUBY_FL_USER1"]) != 0:
+ sign = '+'
+ len = rval.bignum_len()
+
+ if rval.flags & self.ruby_globals["RUBY_FL_USER2"]:
+ print("T_BIGNUM: sign=%s len=%d (embed)" % (sign, len), file=self.result)
+ self._append_expression("((struct RBignum *) %0#x)->as.ary"
+ % val.GetValueAsUnsigned())
+ else:
+ print("T_BIGNUM: sign=%s len=%d" % (sign, len), file=self.result)
+ print(rval.as_type("bignum"), file=self.result)
+ self._append_expression("-Z %d -fx -- ((struct RBignum*)%d)->as.heap.digits" %
+ (len, val.GetValueAsUnsigned()))
+
+ elif rval.is_type("RUBY_T_FLOAT"):
+ self._append_expression("((struct RFloat *)%d)->float_value"
+ % val.GetValueAsUnsigned())
+
+ elif rval.is_type("RUBY_T_RATIONAL"):
+ tRRational = self.target.FindFirstType("struct RRational").GetPointerType()
+ val = val.Cast(tRRational)
+ self.inspect(val.GetValueForExpressionPath("->num"))
+ output = self.result.GetOutput()
+ self.result.Clear()
+ self.result.write("(Rational) " + output.rstrip() + " / ")
+ self.inspect(val.GetValueForExpressionPath("->den"))
+
+ elif rval.is_type("RUBY_T_COMPLEX"):
+ tRComplex = self.target.FindFirstType("struct RComplex").GetPointerType()
+ val = val.Cast(tRComplex)
+ self.inspect(val.GetValueForExpressionPath("->real"))
+ real = self.result.GetOutput().rstrip()
+ self.result.Clear()
+ self.inspect(val.GetValueForExpressionPath("->imag"))
+ imag = self.result.GetOutput().rstrip()
+ self.result.Clear()
+ if not imag.startswith("-"):
+ imag = "+" + imag
+ print("(Complex) " + real + imag + "i", file=self.result)
+
+ elif rval.is_type("RUBY_T_REGEXP"):
+ tRRegex = self.target.FindFirstType("struct RRegexp").GetPointerType()
+ val = val.Cast(tRRegex)
+ print("(Regex) ->src {", file=self.result)
+ self.inspect(val.GetValueForExpressionPath("->src"))
+ print("}", file=self.result)
+
+ elif rval.is_type("RUBY_T_DATA"):
+ tRTypedData = self.target.FindFirstType("struct RTypedData").GetPointerType()
+ val = val.Cast(tRTypedData)
+ is_typed_data = self.ruby_globals.get("RUBY_TYPED_FL_IS_TYPED_DATA", None)
+ if is_typed_data:
+ typed = rval.flags & is_typed_data
+ else:
+ typed = val.GetValueForExpressionPath("->typed_flag").GetValueAsUnsigned() == 1
+
+ if typed:
+ type = val.GetValueForExpressionPath("->type").GetValueAsUnsigned()
+ embed = (type & 1)
+ if embed:
+ flaginfo += "[EMBED] "
+ type = self.frame.EvaluateExpression("(rb_data_type_t *)%0#x" % (type & ~1))
+ print("T_DATA: %s%s" %
+ (flaginfo, type.GetValueForExpressionPath("->wrap_struct_name")),
+ file=self.result)
+ print("%s", type.Dereference(), file=self.result)
+ ptr = val.GetValueForExpressionPath("->data")
+ if embed:
+ ptr = ptr.AddressOf()
+ self._append_expression("(void *)%0#x" % ptr.GetValueAsUnsigned())
+ else:
+ print("T_DATA:", file=self.result)
+ self._append_expression("*(struct RData *) %0#x" % val.GetValueAsUnsigned())
+
+ elif rval.is_type("RUBY_T_IMEMO"):
+ imemo_type = ((rval.flags >> self.ruby_globals["RUBY_FL_USHIFT"])
+ & IMEMO_MASK)
+ print("T_IMEMO: ", file=self.result)
+
+ self._append_expression("(enum imemo_type) %d" % imemo_type)
+ self._append_expression("*(struct MEMO *) %0#x" % val.GetValueAsUnsigned())
+
+ elif rval.is_type("RUBY_T_FILE"):
+ self.generic_inspect(val, "RFile")
+
+ elif rval.is_type("RUBY_T_MOVED"):
+ self.generic_inspect(val, "RMoved")
+
+ elif rval.is_type("RUBY_T_MATCH"):
+ self.generic_inspect(val, "RMatch")
+
+ elif rval.is_type("RUBY_T_STRUCT"):
+ self.generic_inspect(val, "RStruct")
+
+ elif rval.is_type("RUBY_T_ZOMBIE"):
+ self.generic_inspect(val, "RZombie")
+
+ else:
+ print("Not-handled type %0#x" % rval.type, file=self.result)
+ print(val, file=self.result)
+
+ def inspect_node(self, val):
+ tRNode = self.target.FindFirstType("struct RNode").GetPointerType()
+
+ # if val.GetType() != tRNode: does not work for unknown reason
+
+ if val.GetType().GetPointeeType().name != "NODE":
+ return False
+
+ rbNodeTypeMask = self.ruby_globals["RUBY_NODE_TYPEMASK"]
+ rbNodeTypeShift = self.ruby_globals["RUBY_NODE_TYPESHIFT"]
+ flags = val.Cast(tRNode).GetChildMemberWithName("flags").GetValueAsUnsigned()
+ nd_type = (flags & rbNodeTypeMask) >> rbNodeTypeShift
+
+ self._append_expression("(node_type) %d" % nd_type)
+
+ if nd_type == self.ruby_globals["NODE_SCOPE"]:
+ self._append_expression("*(rb_node_scope_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BLOCK"]:
+ self._append_expression("*(rb_node_block_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IF"]:
+ self._append_expression("*(rb_node_if_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNLESS"]:
+ self._append_expression("*(rb_node_unless_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE"]:
+ self._append_expression("*(rb_node_case_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE2"]:
+ self._append_expression("*(rb_node_case2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CASE3"]:
+ self._append_expression("*(rb_node_case3_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_WHEN"]:
+ self._append_expression("*(rb_node_when_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IN"]:
+ self._append_expression("*(rb_node_in_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_WHILE"]:
+ self._append_expression("*(rb_node_while_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNTIL"]:
+ self._append_expression("*(rb_node_until_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ITER"]:
+ self._append_expression("*(rb_node_iter_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FOR"]:
+ self._append_expression("*(rb_node_for_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FOR_MASGN"]:
+ self._append_expression("*(rb_node_for_masgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BREAK"]:
+ self._append_expression("*(rb_node_break_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NEXT"]:
+ self._append_expression("*(rb_node_next_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_REDO"]:
+ self._append_expression("*(rb_node_redo_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RETRY"]:
+ self._append_expression("*(rb_node_retry_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BEGIN"]:
+ self._append_expression("*(rb_node_begin_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RESCUE"]:
+ self._append_expression("*(rb_node_rescue_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RESBODY"]:
+ self._append_expression("*(rb_node_resbody_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ENSURE"]:
+ self._append_expression("*(rb_node_ensure_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_AND"]:
+ self._append_expression("*(rb_node_and_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OR"]:
+ self._append_expression("*(rb_node_or_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MASGN"]:
+ self._append_expression("*(rb_node_masgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LASGN"]:
+ self._append_expression("*(rb_node_lasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DASGN"]:
+ self._append_expression("*(rb_node_dasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_GASGN"]:
+ self._append_expression("*(rb_node_gasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_IASGN"]:
+ self._append_expression("*(rb_node_iasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CDECL"]:
+ self._append_expression("*(rb_node_cdecl_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CVASGN"]:
+ self._append_expression("*(rb_node_cvasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN1"]:
+ self._append_expression("*(rb_node_op_asgn1_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN2"]:
+ self._append_expression("*(rb_node_op_asgn2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN_AND"]:
+ self._append_expression("*(rb_node_op_asgn_and_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_ASGN_OR"]:
+ self._append_expression("*(rb_node_op_asgn_or_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OP_CDECL"]:
+ self._append_expression("*(rb_node_op_cdecl_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CALL"]:
+ self._append_expression("*(rb_node_call_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OPCALL"]:
+ self._append_expression("*(rb_node_opcall_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FCALL"]:
+ self._append_expression("*(rb_node_fcall_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_VCALL"]:
+ self._append_expression("*(rb_node_vcall_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_QCALL"]:
+ self._append_expression("*(rb_node_qcall_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SUPER"]:
+ self._append_expression("*(rb_node_super_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ZSUPER"]:
+ self._append_expression("*(rb_node_zsuper_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LIST"]:
+ self._append_expression("*(rb_node_list_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ZLIST"]:
+ self._append_expression("*(rb_node_zlist_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_HASH"]:
+ self._append_expression("*(rb_node_hash_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_RETURN"]:
+ self._append_expression("*(rb_node_return_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_YIELD"]:
+ self._append_expression("*(rb_node_yield_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LVAR"]:
+ self._append_expression("*(rb_node_lvar_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DVAR"]:
+ self._append_expression("*(rb_node_dvar_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_GVAR"]:
+ self._append_expression("*(rb_node_gvar_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CONST"]:
+ self._append_expression("*(rb_node_const_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CVAR"]:
+ self._append_expression("*(rb_node_cvar_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NTH_REF"]:
+ self._append_expression("*(rb_node_nth_ref_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_BACK_REF"]:
+ self._append_expression("*(rb_node_back_ref_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH"]:
+ self._append_expression("*(rb_node_match_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH2"]:
+ self._append_expression("*(rb_node_match2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MATCH3"]:
+ self._append_expression("*(rb_node_match3_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_STR"]:
+ self._append_expression("*(rb_node_str_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DSTR"]:
+ self._append_expression("*(rb_node_dstr_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_XSTR"]:
+ self._append_expression("*(rb_node_xstr_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DXSTR"]:
+ self._append_expression("*(rb_node_dxstr_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_EVSTR"]:
+ self._append_expression("*(rb_node_evstr_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_REGX"]:
+ self._append_expression("*(rb_node_regx_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DREGX"]:
+ self._append_expression("*(rb_node_dregx_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ONCE"]:
+ self._append_expression("*(rb_node_once_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGS"]:
+ self._append_expression("*(rb_node_args_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGS_AUX"]:
+ self._append_expression("*(rb_node_args_aux_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_OPT_ARG"]:
+ self._append_expression("*(rb_node_opt_arg_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_KW_ARG"]:
+ self._append_expression("*(rb_node_kw_arg_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_POSTARG"]:
+ self._append_expression("*(rb_node_postarg_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGSCAT"]:
+ self._append_expression("*(rb_node_argscat_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARGSPUSH"]:
+ self._append_expression("*(rb_node_argspush_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SPLAT"]:
+ self._append_expression("*(rb_node_splat_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFN"]:
+ self._append_expression("*(rb_node_defn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFS"]:
+ self._append_expression("*(rb_node_defs_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ALIAS"]:
+ self._append_expression("*(rb_node_alias_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_VALIAS"]:
+ self._append_expression("*(rb_node_valias_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_UNDEF"]:
+ self._append_expression("*(rb_node_undef_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_CLASS"]:
+ self._append_expression("*(rb_node_class_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_MODULE"]:
+ self._append_expression("*(rb_node_module_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SCLASS"]:
+ self._append_expression("*(rb_node_sclass_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_COLON2"]:
+ self._append_expression("*(rb_node_colon2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_COLON3"]:
+ self._append_expression("*(rb_node_colon3_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DOT2"]:
+ self._append_expression("*(rb_node_dot2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DOT3"]:
+ self._append_expression("*(rb_node_dot3_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FLIP2"]:
+ self._append_expression("*(rb_node_flip2_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FLIP3"]:
+ self._append_expression("*(rb_node_flip3_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_SELF"]:
+ self._append_expression("*(rb_node_self_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_NIL"]:
+ self._append_expression("*(rb_node_nil_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_TRUE"]:
+ self._append_expression("*(rb_node_true_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FALSE"]:
+ self._append_expression("*(rb_node_false_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ERRINFO"]:
+ self._append_expression("*(rb_node_errinfo_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DEFINED"]:
+ self._append_expression("*(rb_node_defined_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_POSTEXE"]:
+ self._append_expression("*(rb_node_postexe_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_DSYM"]:
+ self._append_expression("*(rb_node_dsym_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ATTRASGN"]:
+ self._append_expression("*(rb_node_attrasgn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LAMBDA"]:
+ self._append_expression("*(rb_node_lambda_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ARYPTN"]:
+ self._append_expression("*(rb_node_aryptn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_HSHPTN"]:
+ self._append_expression("*(rb_node_hshptn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FNDPTN"]:
+ self._append_expression("*(rb_node_fndptn_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_ERROR"]:
+ self._append_expression("*(rb_node_error_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_LINE"]:
+ self._append_expression("*(rb_node_line_t *) %0#x" % val.GetValueAsUnsigned())
+ elif nd_type == self.ruby_globals["NODE_FILE"]:
+ self._append_expression("*(rb_node_file_t *) %0#x" % val.GetValueAsUnsigned())
+ else:
+ self._append_expression("*(NODE *) %0#x" % val.GetValueAsUnsigned())
+ return True
diff --git a/misc/rb_optparse.bash b/misc/rb_optparse.bash
index 8a59ec2dda..f77d937c87 100644
--- a/misc/rb_optparse.bash
+++ b/misc/rb_optparse.bash
@@ -1,4 +1,5 @@
-#! /bin/bash
+# -*- bash -*-
+#
# Completion for bash:
#
# (1) install this file,
@@ -16,5 +17,5 @@ _rb_optparse() {
}
rb_optparse () {
- [ $# = 0 ] || complete -o default -F _rb_optparse "$@"
+ [ $# = 0 ] || complete -o default -F _rb_optparse "$@"
}
diff --git a/misc/rb_optparse.zsh b/misc/rb_optparse.zsh
index d53170c5f7..258d4f856c 100755..100644
--- a/misc/rb_optparse.zsh
+++ b/misc/rb_optparse.zsh
@@ -1,4 +1,5 @@
-#!/bin/zsh
+# -*- zsh -*-
+#
# Completion for zsh:
# (based on <http://d.hatena.ne.jp/rubikitch/20071002/zshcomplete>)
#
@@ -7,13 +8,13 @@
# cp rb_optparse.zsh ~/.zsh.d/rb_optparse.zsh
#
# (2) load the script, and add a directory to fpath before compinit.
-# echo '. ~/.zsh.d/rb_optparse.zsh' >> ~/.zshrc
-# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> ~/.zshrc
-# echo 'autoload -U compinit; compinit' >> ~/.zshrc
+# echo '. ~/.zsh.d/rb_optparse.zsh' >> "${ZDOTDIR:-~}/.zshrc"
+# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> "${ZDOTDIR:-~}/.zshrc"
+# echo 'autoload -U compinit; compinit' >> "${ZDOTDIR:-~}/.zshrc"
#
# (3) restart zsh.
#
-# (4) geneate completion files once.
+# (4) generate completion files once.
# generate-complete-function/ruby/optparse COMMAND1
# generate-complete-function/ruby/optparse COMMAND2
#
@@ -24,8 +25,8 @@ generate-complete-function/ruby/optparse ()
mkdir -p "${ZSH_COMPLETION_DIR-$HOME/.zsh.d/Completion}"
$1 "--*-completion-zsh=${1:t}" >! "${ZSH_COMPLETION_DIR-$HOME/.zsh.d/Completion}/$cmpl"
if [[ $(type -w "$cmpl") == "${cmpl}: function" ]]; then
- unfunction "$cmpl"
- autoload -U "$cmpl"
+ unfunction "$cmpl"
+ autoload -U "$cmpl"
else
compinit "$cmpl"
fi
diff --git a/misc/rdoc-mode.el b/misc/rdoc-mode.el
deleted file mode 100644
index ec715798ae..0000000000
--- a/misc/rdoc-mode.el
+++ /dev/null
@@ -1,130 +0,0 @@
-;;
-;; rdoc-mode.el
-;; Major mode for RDoc editing
-;;
-
-;; Created: Fri Sep 18 09:04:49 JST 2009
-
-;; License: Ruby's
-
-(require 'derived)
-(define-derived-mode rdoc-mode text-mode "RDoc"
- "Major mode for RD editing.
-\\{rdoc-mode-map}"
- (make-local-variable 'paragraph-separate)
- (setq paragraph-separate "^\\(=+\\|\\*+\\)[ \t\v\f]*\\|^\\s *$")
- (make-local-variable 'paragraph-start)
- (setq paragraph-start paragraph-separate)
- (make-local-variable 'require-final-newline)
- (setq require-final-newline t)
- (make-local-variable 'font-lock-defaults)
- (setq font-lock-defaults '((rdoc-font-lock-keywords) t nil))
- (make-local-variable 'font-lock-keywords)
- (setq font-lock-keywords rdoc-font-lock-keywords)
- (make-local-variable 'outline-regexp)
- (setq outline-regexp "^\\(=+\\)[ \t\v\f]*")
- (outline-minor-mode t)
- (setq show-trailing-whitespace t)
- (rdoc-setup-keys)
- (setq indent-tabs-mode nil)
- (run-hooks 'rdoc-mode-hook)
- )
-
-(defun rdoc-fill-paragraph (&rest args)
- "Fills paragraph, except for cited region"
- (interactive (progn
- (barf-if-buffer-read-only)
- (list (if current-prefix-arg 'full))))
- (save-excursion
- (beginning-of-line)
- (unless (looking-at "^ +")
- (apply 'fill-paragraph args))))
-
-(defun rdoc-setup-keys ()
- (interactive)
- (define-key rdoc-mode-map "\M-q" 'rdoc-fill-paragraph)
- )
-
-(defvar rdoc-heading1-face 'font-lock-keywordoc-face)
-(defvar rdoc-heading2-face 'font-lock-type-face)
-(defvar rdoc-heading3-face 'font-lock-variable-name-face)
-(defvar rdoc-heading4-face 'font-lock-comment-face)
-(defvar rdoc-bold-face 'font-lock-function-name-face)
-(defvar rdoc-emphasis-face 'font-lock-function-name-face)
-(defvar rdoc-code-face 'font-lock-keyword-face)
-(defvar rdoc-description-face 'font-lock-constant-face)
-
-(defvar rdoc-font-lock-keywords
- (list
- (list "^=([^=\r\n].*)?$"
- 0 rdoc-heading1-face)
- (list "^==([^=\r\n].*)?$"
- 0 rdoc-heading2-face)
- (list "^===([^=\r\n].*)?$"
- 0 rdoc-heading3-face)
- (list "^====+.*$"
- 0 rdoc-heading4-face)
- (list "\\(^\\|[ \t\v\f]\\)\\(\\*\\(\\sw\\|[-_:]\\)+\\*\\)\\($\\|[ \t\v\f]\\)"
- 2 rdoc-bold-face) ; *bold*
- (list "\\(^\\|[ \t\v\f]\\)\\(_\\(\\sw\\|[-_:]\\)+_\\)\\($\\|[ \t\v\f]\\)"
- 2 rdoc-emphasis-face) ; _emphasis_
- (list "\\(^\\|[ \t\v\f]\\)\\(\\+\\(\\sw\\|[-_:]\\)+\\+\\)\\($\\|[ \t\v\f]\\)"
- 2 rdoc-code-face) ; +code+
- (list "<em>[^<>]*</em>" 0 rdoc-emphasis-face)
- (list "<i>[^<>]*</i>" 0 rdoc-emphasis-face)
- (list "<b>[^<>]*</b>" 0 rdoc-bold-face)
- (list "<tt>[^<>]*</tt>" 0 rdoc-code-face)
- (list "<code>[^<>]*</code>" 0 rdoc-code-face)
- (list "^\\([-*]\\|[0-9]+\\.\\|[A-Za-z]\\.\\)\\s "
- 1 rdoc-description-face) ; bullet | numbered | alphabetically numbered
- (list "^\\[[^\]]*\\]\\|\\S .*::\\)\\([ \t\v\f]\\|$\\)"
- 1 rdoc-description-face) ; labeled | node
- ;(list "^[ \t\v\f]+\\(.*\\)" 1 rdoc-verbatim-face)
- ))
-
-(defun rdoc-imenu-create-index ()
- (let ((root '(nil . nil))
- cur-alist
- (cur-level 0)
- (pattern (concat outline-regexp "\\(.*?\\)[ \t\v\f]*$"))
- (empty-heading "-")
- (self-heading ".")
- pos level heading alist)
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward pattern (point-max) t)
- (setq heading (match-string-no-properties 2)
- level (min 6 (length (match-string-no-properties 1)))
- pos (match-beginning 1))
- (if (= (length heading) 0)
- (setq heading empty-heading))
- (setq alist (list (cons heading pos)))
- (cond
- ((= cur-level level) ; new sibling
- (setcdr cur-alist alist)
- (setq cur-alist alist))
- ((< cur-level level) ; first child
- (dotimes (i (- level cur-level 1))
- (setq alist (list (cons empty-heading alist))))
- (if cur-alist
- (let* ((parent (car cur-alist))
- (self-pos (cdr parent)))
- (setcdr parent (cons (cons self-heading self-pos) alist)))
- (setcdr root alist)) ; primogenitor
- (setq cur-alist alist
- cur-level level))
- (t ; new sibling of an ancestor
- (let ((sibling-alist (last (cdr root))))
- (dotimes (i (1- level))
- (setq sibling-alist (last (cdar sibling-alist))))
- (setcdr sibling-alist alist)
- (setq cur-alist alist
- cur-level level))))))
- (cdr root)))
-
-(defun rdoc-set-imenu-create-index-function ()
- (setq imenu-create-index-function 'rdoc-imenu-create-index))
-
-(add-hook 'rdoc-mode-hook 'rdoc-set-imenu-create-index-function)
-
-(provide 'rdoc-mode)
diff --git a/misc/ruby-additional.el b/misc/ruby-additional.el
deleted file mode 100644
index 152067e48e..0000000000
--- a/misc/ruby-additional.el
+++ /dev/null
@@ -1,100 +0,0 @@
-;; missing functions in Emacs 24.
-
-(eval-after-load "\\(\\`\\|/\\)ruby-mode\\.elc?\\(\\.gz\\)?\\'"
- (progn
- (define-key ruby-mode-map "\C-c\C-e" 'ruby-insert-end)
- (define-key ruby-mode-map "\C-c{" 'ruby-toggle-block)
-
- (defun ruby-insert-end ()
- (interactive)
- (if (eq (char-syntax (char-before)) ?w)
- (insert " "))
- (insert "end")
- (save-excursion
- (if (eq (char-syntax (char-after)) ?w)
- (insert " "))
- (ruby-indent-line t)
- (end-of-line)))
-
- (defun ruby-brace-to-do-end ()
- (when (looking-at "{")
- (let ((orig (point)) (end (progn (ruby-forward-sexp) (point))))
- (when (eq (char-before) ?\})
- (delete-char -1)
- (if (eq (char-syntax (char-before)) ?w)
- (insert " "))
- (insert "end")
- (if (eq (char-syntax (char-after)) ?w)
- (insert " "))
- (goto-char orig)
- (delete-char 1)
- (if (eq (char-syntax (char-before)) ?w)
- (insert " "))
- (insert "do")
- (when (looking-at "\\sw\\||")
- (insert " ")
- (backward-char))
- t))))
-
- (defun ruby-do-end-to-brace ()
- (when (and (or (bolp)
- (not (memq (char-syntax (char-before)) '(?w ?_))))
- (looking-at "\\<do\\(\\s \\|$\\)"))
- (let ((orig (point)) (end (progn (ruby-forward-sexp) (point))))
- (backward-char 3)
- (when (looking-at ruby-block-end-re)
- (delete-char 3)
- (insert "}")
- (goto-char orig)
- (delete-char 2)
- (insert "{")
- (if (looking-at "\\s +|")
- (delete-char (- (match-end 0) (match-beginning 0) 1)))
- t))))
-
- (defun ruby-toggle-block ()
- (interactive)
- (or (ruby-brace-to-do-end)
- (ruby-do-end-to-brace)))
-
- (defun ruby-mode-set-encoding ()
- "Insert a magic comment header with the proper encoding always.
-Now encoding needs to be set always explicitly actually."
- (save-excursion
- (let ((coding-system))
- (widen)
- (goto-char (point-min))
- (if (re-search-forward "[^\0-\177]" nil t)
- (progn
- (goto-char (point-min))
- (setq coding-system
- (or coding-system-for-write
- buffer-file-coding-system))
- (if coding-system
- (setq coding-system
- (or (coding-system-get coding-system 'mime-charset)
- (coding-system-change-eol-conversion coding-system nil))))
- (setq coding-system
- (if coding-system
- (symbol-name
- (or (and ruby-use-encoding-map
- (cdr (assq coding-system ruby-encoding-map)))
- coding-system))
- "ascii-8bit")))
- (setq coding-system "us-ascii"))
- (if (looking-at "^#!") (beginning-of-line 2))
- (cond ((looking-at "\\s *#.*-\*-\\s *\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)\\s *\\(;\\|-\*-\\)")
- (unless (string= (match-string 2) coding-system)
- (goto-char (match-beginning 2))
- (delete-region (point) (match-end 2))
- (and (looking-at "-\*-")
- (let ((n (skip-chars-backward " ")))
- (cond ((= n 0) (insert " ") (backward-char))
- ((= n -1) (insert " "))
- ((forward-char)))))
- (insert coding-system)))
- ((looking-at "\\s *#.*coding\\s *[:=]"))
- (t (when ruby-insert-encoding-magic-comment
- (insert "# -*- coding: " coding-system " -*-\n")))))))
-
- ))
diff --git a/misc/ruby-electric.el b/misc/ruby-electric.el
deleted file mode 100644
index 95644e24cd..0000000000
--- a/misc/ruby-electric.el
+++ /dev/null
@@ -1,205 +0,0 @@
-;; -*-Emacs-Lisp-*-
-;;
-;; ruby-electric.el --- electric editing commands for ruby files
-;;
-;; Copyright (C) 2005 by Dee Zsombor <dee dot zsombor at gmail dot com>.
-;; Released under same license terms as Ruby.
-;;
-;; Due credit: this work was inspired by a code snippet posted by
-;; Frederick Ros at http://rubygarden.org/ruby?EmacsExtensions.
-;;
-;; Following improvements where added:
-;;
-;; - handling of strings of type 'here document'
-;; - more keywords, with special handling for 'do'
-;; - packaged into a minor mode
-;;
-;; Usage:
-;;
-;; 0) copy ruby-electric.el into directory where emacs can find it.
-;;
-;; 1) modify your startup file (.emacs or whatever) by adding
-;; following line:
-;;
-;; (require 'ruby-electric)
-;;
-;; note that you need to have font lock enabled beforehand.
-;;
-;; 2) toggle Ruby Electric Mode on/off with ruby-electric-mode.
-;;
-;; Changelog:
-;;
-;; 2005/Jan/14: inserts matching pair delimiters like {, [, (, ', ",
-;; ' and | .
-;;
-;; 2005/Jan/14: added basic Custom support for configuring keywords
-;; with electric closing.
-;;
-;; 2005/Jan/18: more Custom support for configuring characters for
-;; which matching expansion should occur.
-;;
-;; 2005/Jan/18: no longer uses 'looking-back' or regexp character
-;; classes like [:space:] since they are not implemented on XEmacs.
-;;
-;; 2005/Feb/01: explicitly provide default argument of 1 to
-;; 'backward-word' as it requires it on Emacs 21.3
-;;
-;; 2005/Mar/06: now stored inside ruby CVS; customize pages now have
-;; ruby as parent; cosmetic fixes.
-
-
-(require 'ruby-mode)
-
-(defgroup ruby-electric nil
- "Minor mode providing electric editing commands for ruby files"
- :group 'ruby)
-
-(defconst ruby-electric-expandable-do-re
- "do\\s-$")
-
-(defconst ruby-electric-expandable-bar
- "\\s-\\(do\\|{\\)\\s-+|")
-
-(defvar ruby-electric-matching-delimeter-alist
- '((?\[ . ?\])
- (?\( . ?\))
- (?\' . ?\')
- (?\` . ?\`)
- (?\" . ?\")))
-
-(defcustom ruby-electric-simple-keywords-re
- (regexp-opt '("def" "if" "class" "module" "unless" "case" "while" "do" "until" "for" "begin") t)
- "*Regular expresion matching keywords for which closing 'end'
-is to be inserted."
- :type 'regexp :group 'ruby-electric)
-
-(defcustom ruby-electric-expand-delimiters-list '(all)
- "*List of contexts where matching delimiter should be
-inserted. The word 'all' will do all insertions."
- :type '(set :extra-offset 8
- (const :tag "Everything" all )
- (const :tag "Curly brace" ?\{ )
- (const :tag "Square brace" ?\[ )
- (const :tag "Round brace" ?\( )
- (const :tag "Quote" ?\' )
- (const :tag "Double quote" ?\" )
- (const :tag "Back quote" ?\` )
- (const :tag "Vertical bar" ?\| ))
- :group 'ruby-electric)
-
-(defcustom ruby-electric-newline-before-closing-bracket nil
- "*Controls whether a newline should be inserted before the
-closing bracket or not."
- :type 'boolean :group 'ruby-electric)
-
-(define-minor-mode ruby-electric-mode
- "Toggle Ruby Electric minor mode.
-With no argument, this command toggles the mode. Non-null prefix
-argument turns on the mode. Null prefix argument turns off the
-mode.
-
-When Ruby Electric mode is enabled, an indented 'end' is
-heuristicaly inserted whenever typing a word like 'module',
-'class', 'def', 'if', 'unless', 'case', 'until', 'for', 'begin',
-'do'. Simple, double and back quotes as well as braces are paired
-auto-magically. Expansion does not occur inside comments and
-strings. Note that you must have Font Lock enabled."
- ;; initial value.
- nil
- ;;indicator for the mode line.
- " REl"
- ;;keymap
- ruby-mode-map
- (ruby-electric-setup-keymap))
-
-(defun ruby-electric-setup-keymap()
- (define-key ruby-mode-map " " 'ruby-electric-space)
- (define-key ruby-mode-map "{" 'ruby-electric-curlies)
- (define-key ruby-mode-map "(" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "[" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "\"" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "\'" 'ruby-electric-matching-char)
- (define-key ruby-mode-map "|" 'ruby-electric-bar))
-
-(defun ruby-electric-space (arg)
- (interactive "P")
- (self-insert-command (prefix-numeric-value arg))
- (if (ruby-electric-space-can-be-expanded-p)
- (save-excursion
- (ruby-indent-line t)
- (newline)
- (ruby-insert-end))))
-
-(defun ruby-electric-code-at-point-p()
- (and ruby-electric-mode
- (let* ((properties (text-properties-at (point))))
- (and (null (memq 'font-lock-string-face properties))
- (null (memq 'font-lock-comment-face properties))))))
-
-(defun ruby-electric-string-at-point-p()
- (and ruby-electric-mode
- (consp (memq 'font-lock-string-face (text-properties-at (point))))))
-
-(defun ruby-electric-is-last-command-char-expandable-punct-p()
- (or (memq 'all ruby-electric-expand-delimiters-list)
- (memq last-command-event ruby-electric-expand-delimiters-list)))
-
-(defun ruby-electric-space-can-be-expanded-p()
- (if (ruby-electric-code-at-point-p)
- (let* ((ruby-electric-keywords-re
- (concat ruby-electric-simple-keywords-re "\\s-$"))
- (ruby-electric-single-keyword-in-line-re
- (concat "\\s-*" ruby-electric-keywords-re)))
- (save-excursion
- (backward-word 1)
- (or (looking-at ruby-electric-expandable-do-re)
- (and (looking-at ruby-electric-keywords-re)
- (not (string= "do" (match-string 1)))
- (progn
- (beginning-of-line)
- (looking-at ruby-electric-single-keyword-in-line-re))))))))
-
-
-(defun ruby-electric-curlies(arg)
- (interactive "P")
- (self-insert-command (prefix-numeric-value arg))
- (if (ruby-electric-is-last-command-char-expandable-punct-p)
- (cond ((ruby-electric-code-at-point-p)
- (insert " ")
- (save-excursion
- (if ruby-electric-newline-before-closing-bracket
- (progn
- (newline)
- (insert "}")
- (ruby-indent-line t))
- (insert "}"))))
- ((ruby-electric-string-at-point-p)
- (if (eq last-command-event ?{)
- (save-excursion
- (backward-char 1)
- (or (char-equal ?\# (preceding-char))
- (insert "#"))
- (forward-char 1)
- (insert "}")))))))
-
-(defun ruby-electric-matching-char(arg)
- (interactive "P")
- (self-insert-command (prefix-numeric-value arg))
- (and (ruby-electric-is-last-command-char-expandable-punct-p)
- (ruby-electric-code-at-point-p)
- (save-excursion
- (insert (cdr (assoc last-command-event
- ruby-electric-matching-delimeter-alist))))))
-
-(defun ruby-electric-bar(arg)
- (interactive "P")
- (self-insert-command (prefix-numeric-value arg))
- (and (ruby-electric-is-last-command-char-expandable-punct-p)
- (ruby-electric-code-at-point-p)
- (and (save-excursion (re-search-backward ruby-electric-expandable-bar nil t))
- (= (point) (match-end 0))) ;looking-back is missing on XEmacs
- (save-excursion
- (insert "|"))))
-
-
-(provide 'ruby-electric)
diff --git a/misc/ruby-mode.el b/misc/ruby-mode.el
deleted file mode 100644
index dcaa396a2e..0000000000
--- a/misc/ruby-mode.el
+++ /dev/null
@@ -1,1514 +0,0 @@
-;;; ruby-mode.el --- Major mode for editing Ruby files
-
-;; Copyright (C) 1994, 1995, 1996 1997, 1998, 1999, 2000, 2001,
-;; 2002,2003, 2004, 2005, 2006, 2007, 2008
-;; Free Software Foundation, Inc.
-
-;; Authors: Yukihiro Matsumoto, Nobuyoshi Nakada
-;; URL: http://www.emacswiki.org/cgi-bin/wiki/RubyMode
-;; Created: Fri Feb 4 14:49:13 JST 1994
-;; Keywords: languages ruby
-;; Version: 0.9
-
-;; This file is not part of GNU Emacs. However, a newer version of
-;; ruby-mode is included in recent releases of GNU Emacs (version 23
-;; and up), but the new version is not guaranteed to be compatible
-;; with older versions of Emacs or XEmacs. This file is the last
-;; version that aims to keep this compatibility.
-
-;; You can also get the latest version from the Emacs Lisp Package
-;; Archive: http://tromey.com/elpa
-
-;; This file is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; It is distributed in the hope that it will be useful, but WITHOUT
-;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
-;; License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with it. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Provides font-locking, indentation support, and navigation for Ruby code.
-;;
-;; If you're installing manually, you should add this to your .emacs
-;; file after putting it on your load path:
-;;
-;; (autoload 'ruby-mode "ruby-mode" "Major mode for ruby files" t)
-;; (add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
-;; (add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))
-;;
-
-;;; Code:
-
-(defconst ruby-mode-revision "$Revision$"
- "Ruby mode revision string.")
-
-(defconst ruby-mode-version
- (and (string-match "[0-9.]+" ruby-mode-revision)
- (substring ruby-mode-revision (match-beginning 0) (match-end 0)))
- "Ruby mode version number.")
-
-(defconst ruby-keyword-end-re
- (if (string-match "\\_>" "ruby")
- "\\_>"
- "\\>"))
-
-(defconst ruby-block-beg-keywords
- '("class" "module" "def" "if" "unless" "case" "while" "until" "for" "begin" "do")
- "Keywords at the beginning of blocks.")
-
-(defconst ruby-block-beg-re
- (regexp-opt ruby-block-beg-keywords)
- "Regexp to match the beginning of blocks.")
-
-(defconst ruby-non-block-do-re
- (concat (regexp-opt '("while" "until" "for" "rescue") t) ruby-keyword-end-re)
- "Regexp to match")
-
-(defconst ruby-indent-beg-re
- (concat "\\(\\s *" (regexp-opt '("class" "module" "def") t) "\\)\\|"
- (regexp-opt '("if" "unless" "case" "while" "until" "for" "begin")))
- "Regexp to match where the indentation gets deeper.")
-
-(defconst ruby-modifier-beg-keywords
- '("if" "unless" "while" "until")
- "Modifiers that are the same as the beginning of blocks.")
-
-(defconst ruby-modifier-beg-re
- (regexp-opt ruby-modifier-beg-keywords)
- "Regexp to match modifiers same as the beginning of blocks.")
-
-(defconst ruby-modifier-re
- (regexp-opt (cons "rescue" ruby-modifier-beg-keywords))
- "Regexp to match modifiers.")
-
-(defconst ruby-block-mid-keywords
- '("then" "else" "elsif" "when" "rescue" "ensure")
- "Keywords where the indentation gets shallower in middle of block statements.")
-
-(defconst ruby-block-mid-re
- (regexp-opt ruby-block-mid-keywords)
- "Regexp to match where the indentation gets shallower in middle of block statements.")
-
-(defconst ruby-block-op-keywords
- '("and" "or" "not")
- "Block operators.")
-
-(defconst ruby-block-hanging-re
- (regexp-opt (append ruby-modifier-beg-keywords ruby-block-op-keywords))
- "Regexp to match hanging block modifiers.")
-
-(defconst ruby-block-end-re "\\_<end\\_>")
-
-(defconst ruby-here-doc-beg-re
- "\\(<\\)<\\(-\\)?\\(\\([a-zA-Z0-9_]+\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^']+\\)[']\\)")
-
-(defconst ruby-here-doc-end-re
- "^\\([ \t]+\\)?\\(.*\\)\\(.\\)$")
-
-(defun ruby-here-doc-end-match ()
- (concat "^"
- (if (match-string 2) "[ \t]*" nil)
- (regexp-quote
- (or (match-string 4)
- (match-string 5)
- (match-string 6)))))
-
-(defun ruby-here-doc-beg-match ()
- (let ((contents (concat
- (regexp-quote (concat (match-string 2) (match-string 3)))
- (if (string= (match-string 3) "_") "\\B" "\\b"))))
- (concat "<<"
- (let ((match (match-string 1)))
- (if (and match (> (length match) 0))
- (concat "\\(?:-\\([\"']?\\)\\|\\([\"']\\)" (match-string 1) "\\)"
- contents "\\(\\1\\|\\2\\)")
- (concat "-?\\([\"']\\|\\)" contents "\\1"))))))
-
-(defconst ruby-delimiter
- (concat "[?$/%(){}#\"'`.:]\\|<<\\|\\[\\|\\]\\|\\_<\\("
- ruby-block-beg-re
- "\\)\\_>\\|" ruby-block-end-re
- "\\|^=begin\\|" ruby-here-doc-beg-re)
- )
-
-(defconst ruby-negative
- (concat "^[ \t]*\\(\\(" ruby-block-mid-re "\\)\\>\\|"
- ruby-block-end-re "\\|}\\|\\]\\)")
- "Regexp to match where the indentation gets shallower.")
-
-(defconst ruby-operator-chars "-,.+*/%&|^~=<>:")
-(defconst ruby-operator-re (concat "[" ruby-operator-chars "]"))
-
-(defconst ruby-symbol-chars "a-zA-Z0-9_")
-(defconst ruby-symbol-re (concat "[" ruby-symbol-chars "]"))
-
-(defvar ruby-mode-abbrev-table nil
- "Abbrev table in use in ruby-mode buffers.")
-
-(define-abbrev-table 'ruby-mode-abbrev-table ())
-
-(defvar ruby-mode-map nil "Keymap used in ruby mode.")
-
-(if ruby-mode-map
- nil
- (setq ruby-mode-map (make-sparse-keymap))
- (define-key ruby-mode-map "{" 'ruby-electric-brace)
- (define-key ruby-mode-map "}" 'ruby-electric-brace)
- (define-key ruby-mode-map "\e\C-a" 'ruby-beginning-of-defun)
- (define-key ruby-mode-map "\e\C-e" 'ruby-end-of-defun)
- (define-key ruby-mode-map "\e\C-b" 'ruby-backward-sexp)
- (define-key ruby-mode-map "\e\C-f" 'ruby-forward-sexp)
- (define-key ruby-mode-map "\e\C-p" 'ruby-beginning-of-block)
- (define-key ruby-mode-map "\e\C-n" 'ruby-end-of-block)
- (define-key ruby-mode-map "\e\C-h" 'ruby-mark-defun)
- (define-key ruby-mode-map "\e\C-q" 'ruby-indent-exp)
- (define-key ruby-mode-map "\t" 'ruby-indent-command)
- (define-key ruby-mode-map "\C-c\C-e" 'ruby-insert-end)
- (define-key ruby-mode-map "\C-j" 'ruby-reindent-then-newline-and-indent)
- (define-key ruby-mode-map "\C-c{" 'ruby-toggle-block)
- (define-key ruby-mode-map "\C-c\C-u" 'uncomment-region))
-
-(defvar ruby-mode-syntax-table nil
- "Syntax table in use in ruby-mode buffers.")
-
-(if ruby-mode-syntax-table
- ()
- (setq ruby-mode-syntax-table (make-syntax-table))
- (modify-syntax-entry ?\' "\"" ruby-mode-syntax-table)
- (modify-syntax-entry ?\" "\"" ruby-mode-syntax-table)
- (modify-syntax-entry ?\` "\"" ruby-mode-syntax-table)
- (modify-syntax-entry ?# "<" ruby-mode-syntax-table)
- (modify-syntax-entry ?\n ">" ruby-mode-syntax-table)
- (modify-syntax-entry ?\\ "\\" ruby-mode-syntax-table)
- (modify-syntax-entry ?$ "." ruby-mode-syntax-table)
- (modify-syntax-entry ?? "_" ruby-mode-syntax-table)
- (modify-syntax-entry ?_ "_" ruby-mode-syntax-table)
- (modify-syntax-entry ?: "_" ruby-mode-syntax-table)
- (modify-syntax-entry ?< "." ruby-mode-syntax-table)
- (modify-syntax-entry ?> "." ruby-mode-syntax-table)
- (modify-syntax-entry ?& "." ruby-mode-syntax-table)
- (modify-syntax-entry ?| "." ruby-mode-syntax-table)
- (modify-syntax-entry ?% "." ruby-mode-syntax-table)
- (modify-syntax-entry ?= "." ruby-mode-syntax-table)
- (modify-syntax-entry ?/ "." ruby-mode-syntax-table)
- (modify-syntax-entry ?+ "." ruby-mode-syntax-table)
- (modify-syntax-entry ?* "." ruby-mode-syntax-table)
- (modify-syntax-entry ?- "." ruby-mode-syntax-table)
- (modify-syntax-entry ?\; "." ruby-mode-syntax-table)
- (modify-syntax-entry ?\( "()" ruby-mode-syntax-table)
- (modify-syntax-entry ?\) ")(" ruby-mode-syntax-table)
- (modify-syntax-entry ?\{ "(}" ruby-mode-syntax-table)
- (modify-syntax-entry ?\} "){" ruby-mode-syntax-table)
- (modify-syntax-entry ?\[ "(]" ruby-mode-syntax-table)
- (modify-syntax-entry ?\] ")[" ruby-mode-syntax-table)
- )
-
-(defcustom ruby-indent-tabs-mode nil
- "*Indentation can insert tabs in ruby mode if this is non-nil."
- :type 'boolean :group 'ruby)
-(put 'ruby-indent-tabs-mode 'safe-local-variable 'booleanp)
-
-(defcustom ruby-indent-level 2
- "*Indentation of ruby statements."
- :type 'integer :group 'ruby)
-(put 'ruby-indent-level 'safe-local-variable 'integerp)
-
-(defcustom ruby-comment-column 32
- "*Indentation column of comments."
- :type 'integer :group 'ruby)
-(put 'ruby-comment-column 'safe-local-variable 'integerp)
-
-(defcustom ruby-deep-arglist t
- "*Deep indent lists in parenthesis when non-nil.
-Also ignores spaces after parenthesis when 'space."
- :group 'ruby)
-(put 'ruby-deep-arglist 'safe-local-variable 'booleanp)
-
-(defcustom ruby-deep-indent-paren '(?\( ?\[ ?\] t)
- "*Deep indent lists in parenthesis when non-nil. t means continuous line.
-Also ignores spaces after parenthesis when 'space."
- :group 'ruby)
-
-(defcustom ruby-deep-indent-paren-style 'space
- "Default deep indent style."
- :options '(t nil space) :group 'ruby)
-
-(defcustom ruby-encoding-map '((shift_jis . cp932) (shift-jis . cp932))
- "Alist to map encoding name from emacs to ruby."
- :group 'ruby)
-
-(defcustom ruby-use-encoding-map t
- "*Use `ruby-encoding-map' to set encoding magic comment if this is non-nil."
- :type 'boolean :group 'ruby)
-
-(defvar ruby-indent-point nil "internal variable")
-
-(eval-when-compile (require 'cl))
-(defun ruby-imenu-create-index-in-block (prefix beg end)
- (let ((index-alist '()) (case-fold-search nil)
- name next pos decl sing)
- (goto-char beg)
- (while (re-search-forward "^\\s *\\(\\(class\\s +\\|\\(class\\s *<<\\s *\\)\\|module\\s +\\)\\([^\(<\n ]+\\)\\|\\(def\\|alias\\)\\s +\\([^\(\n ]+\\)\\)" end t)
- (setq sing (match-beginning 3))
- (setq decl (match-string 5))
- (setq next (match-end 0))
- (setq name (or (match-string 4) (match-string 6)))
- (setq pos (match-beginning 0))
- (cond
- ((string= "alias" decl)
- (if prefix (setq name (concat prefix name)))
- (push (cons name pos) index-alist))
- ((string= "def" decl)
- (if prefix
- (setq name
- (cond
- ((string-match "^self\." name)
- (concat (substring prefix 0 -1) (substring name 4)))
- (t (concat prefix name)))))
- (push (cons name pos) index-alist)
- (ruby-accurate-end-of-block end))
- (t
- (if (string= "self" name)
- (if prefix (setq name (substring prefix 0 -1)))
- (if prefix (setq name (concat (substring prefix 0 -1) "::" name)))
- (push (cons name pos) index-alist))
- (ruby-accurate-end-of-block end)
- (setq beg (point))
- (setq index-alist
- (nconc (ruby-imenu-create-index-in-block
- (concat name (if sing "." "#"))
- next beg) index-alist))
- (goto-char beg))))
- index-alist))
-
-(defun ruby-imenu-create-index ()
- (nreverse (ruby-imenu-create-index-in-block nil (point-min) nil)))
-
-(defun ruby-accurate-end-of-block (&optional end)
- (let (state)
- (or end (setq end (point-max)))
- (while (and (setq state (apply 'ruby-parse-partial end state))
- (>= (nth 2 state) 0) (< (point) end)))))
-
-(defun ruby-mode-variables ()
- (set-syntax-table ruby-mode-syntax-table)
- (setq show-trailing-whitespace t)
- (setq local-abbrev-table ruby-mode-abbrev-table)
- (make-local-variable 'indent-line-function)
- (setq indent-line-function 'ruby-indent-line)
- (make-local-variable 'require-final-newline)
- (setq require-final-newline t)
- (make-local-variable 'comment-start)
- (setq comment-start "# ")
- (make-local-variable 'comment-end)
- (setq comment-end "")
- (make-local-variable 'comment-column)
- (setq comment-column ruby-comment-column)
- (make-local-variable 'comment-start-skip)
- (setq comment-start-skip "#+ *")
- (setq indent-tabs-mode ruby-indent-tabs-mode)
- (make-local-variable 'parse-sexp-ignore-comments)
- (setq parse-sexp-ignore-comments t)
- (make-local-variable 'parse-sexp-lookup-properties)
- (setq parse-sexp-lookup-properties t)
- (make-local-variable 'paragraph-start)
- (setq paragraph-start (concat "$\\|" page-delimiter))
- (make-local-variable 'paragraph-separate)
- (setq paragraph-separate paragraph-start)
- (make-local-variable 'paragraph-ignore-fill-prefix)
- (setq paragraph-ignore-fill-prefix t))
-
-(defun ruby-mode-set-encoding ()
- (save-excursion
- (widen)
- (goto-char (point-min))
- (when (re-search-forward "[^\0-\177]" nil t)
- (goto-char (point-min))
- (let ((coding-system
- (or coding-system-for-write
- buffer-file-coding-system)))
- (if coding-system
- (setq coding-system
- (or (coding-system-get coding-system 'mime-charset)
- (coding-system-change-eol-conversion coding-system nil))))
- (setq coding-system
- (if coding-system
- (symbol-name
- (or (and ruby-use-encoding-map
- (cdr (assq coding-system ruby-encoding-map)))
- coding-system))
- "ascii-8bit"))
- (if (looking-at "^#!") (beginning-of-line 2))
- (cond ((looking-at "\\s *#.*-\*-\\s *\\(en\\)?coding\\s *:\\s *\\([-a-z0-9_]*\\)\\s *\\(;\\|-\*-\\)")
- (unless (string= (match-string 2) coding-system)
- (goto-char (match-beginning 2))
- (delete-region (point) (match-end 2))
- (and (looking-at "-\*-")
- (let ((n (skip-chars-backward " ")))
- (cond ((= n 0) (insert " ") (backward-char))
- ((= n -1) (insert " "))
- ((forward-char)))))
- (insert coding-system)))
- ((looking-at "\\s *#.*coding\\s *[:=]"))
- (t (insert "# -*- coding: " coding-system " -*-\n"))
- )))))
-
-(defun ruby-current-indentation ()
- (save-excursion
- (beginning-of-line)
- (back-to-indentation)
- (current-column)))
-
-(defun ruby-indent-line (&optional flag)
- "Correct indentation of the current ruby line."
- (ruby-indent-to (ruby-calculate-indent)))
-
-(defun ruby-indent-command ()
- (interactive)
- (ruby-indent-line t))
-
-(defun ruby-indent-to (x)
- (if x
- (let (shift top beg)
- (and (< x 0) (error "invalid nest"))
- (setq shift (current-column))
- (beginning-of-line)
- (setq beg (point))
- (back-to-indentation)
- (setq top (current-column))
- (skip-chars-backward " \t")
- (if (>= shift top) (setq shift (- shift top))
- (setq shift 0))
- (if (and (bolp)
- (= x top))
- (move-to-column (+ x shift))
- (move-to-column top)
- (delete-region beg (point))
- (beginning-of-line)
- (indent-to x)
- (move-to-column (+ x shift))))))
-
-(defun ruby-special-char-p (&optional pnt)
- (setq pnt (or pnt (point)))
- (let ((c (char-before pnt)) (b (and (< (point-min) pnt) (char-before (1- pnt)))))
- (cond ((or (eq c ??) (eq c ?$)))
- ((and (eq c ?:) (or (not b) (eq (char-syntax b) ? ))))
- ((eq c ?\\) (eq b ??)))))
-
-(defun ruby-singleton-class-p ()
- (save-excursion
- (forward-word -1)
- (and (or (bolp) (not (eq (char-before (point)) ?_)))
- (looking-at "class\\s *<<"))))
-
-(defun ruby-expr-beg (&optional option)
- (save-excursion
- (store-match-data nil)
- (let ((space (skip-chars-backward " \t"))
- (start (point)))
- (cond
- ((bolp) t)
- ((progn
- (forward-char -1)
- (and (looking-at "\\?")
- (or (eq (char-syntax (char-before (point))) ?w)
- (ruby-special-char-p))))
- nil)
- ((and (eq option 'heredoc) (< space 0))
- (not (progn (goto-char start) (ruby-singleton-class-p))))
- ((or (looking-at ruby-operator-re)
- (looking-at "[\\[({,;]")
- (and (looking-at "[!?]")
- (or (not (eq option 'modifier))
- (bolp)
- (save-excursion (forward-char -1) (looking-at "\\Sw$"))))
- (and (looking-at ruby-symbol-re)
- (skip-chars-backward ruby-symbol-chars)
- (cond
- ((looking-at (regexp-opt
- (append ruby-block-beg-keywords
- ruby-block-op-keywords
- ruby-block-mid-keywords)
- 'words))
- (goto-char (match-end 0))
- (not (looking-at "\\s_\\|[!?:]")))
- ((eq option 'expr-qstr)
- (looking-at "[a-zA-Z][a-zA-z0-9_]* +%[^ \t]"))
- ((eq option 'expr-re)
- (looking-at "[a-zA-Z][a-zA-z0-9_]* +/[^ \t]"))
- (t nil)))))))))
-
-(defun ruby-forward-string (term &optional end no-error expand)
- (let ((n 1) (c (string-to-char term))
- (re (if expand
- (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
- (concat "[^\\]\\(\\\\\\\\\\)*[" term "]"))))
- (while (and (re-search-forward re end no-error)
- (if (match-beginning 3)
- (ruby-forward-string "}{" end no-error nil)
- (> (setq n (if (eq (char-before (point)) c)
- (1- n) (1+ n))) 0)))
- (forward-char -1))
- (cond ((zerop n))
- (no-error nil)
- ((error "unterminated string")))))
-
-(defun ruby-deep-indent-paren-p (c &optional pos)
- (cond ((save-excursion
- (if pos (goto-char pos))
- (ruby-expr-beg))
- nil)
- ((listp ruby-deep-indent-paren)
- (let ((deep (assoc c ruby-deep-indent-paren)))
- (cond (deep
- (or (cdr deep) ruby-deep-indent-paren-style))
- ((memq c ruby-deep-indent-paren)
- ruby-deep-indent-paren-style))))
- ((eq c ruby-deep-indent-paren) ruby-deep-indent-paren-style)
- ((eq c ?\( ) ruby-deep-arglist)))
-
-(defun ruby-parse-partial (&optional end in-string nest depth pcol indent)
- (or depth (setq depth 0))
- (or indent (setq indent 0))
- (when (re-search-forward ruby-delimiter end 'move)
- (let ((pnt (point)) w re expand)
- (goto-char (match-beginning 0))
- (cond
- ((and (memq (char-before) '(?@ ?$)) (looking-at "\\sw"))
- (goto-char pnt))
- ((looking-at "[\"`]") ;skip string
- (cond
- ((and (not (eobp))
- (ruby-forward-string (buffer-substring (point) (1+ (point))) end t t))
- nil)
- (t
- (setq in-string (point))
- (goto-char end))))
- ((looking-at "'")
- (cond
- ((and (not (eobp))
- (re-search-forward "[^\\]\\(\\\\\\\\\\)*'" end t))
- nil)
- (t
- (setq in-string (point))
- (goto-char end))))
- ((looking-at "/=")
- (goto-char pnt))
- ((looking-at "/")
- (cond
- ((and (not (eobp)) (ruby-expr-beg 'expr-re))
- (if (ruby-forward-string "/" end t t)
- nil
- (setq in-string (point))
- (goto-char end)))
- (t
- (goto-char pnt))))
- ((looking-at "%")
- (cond
- ((and (not (eobp))
- (ruby-expr-beg 'expr-qstr)
- (not (looking-at "%="))
- (looking-at "%[QqrxWw]?\\([^a-zA-Z0-9 \t\n]\\)"))
- (goto-char (match-beginning 1))
- (setq expand (not (memq (char-before) '(?q ?w))))
- (setq w (match-string 1))
- (cond
- ((string= w "[") (setq re "]["))
- ((string= w "{") (setq re "}{"))
- ((string= w "(") (setq re ")("))
- ((string= w "<") (setq re "><"))
- ((and expand (string= w "\\"))
- (setq w (concat "\\" w))))
- (unless (cond (re (ruby-forward-string re end t expand))
- (expand (ruby-forward-string w end t t))
- (t (re-search-forward
- (if (string= w "\\")
- "\\\\[^\\]*\\\\"
- (concat "[^\\]\\(\\\\\\\\\\)*" w))
- end t)))
- (setq in-string (point))
- (goto-char end)))
- (t
- (goto-char pnt))))
- ((looking-at "\\?") ;skip ?char
- (cond
- ((and (ruby-expr-beg)
- (looking-at "?\\(\\\\C-\\|\\\\M-\\)*\\\\?."))
- (goto-char (match-end 0)))
- (t
- (goto-char pnt))))
- ((looking-at "\\$") ;skip $char
- (goto-char pnt)
- (forward-char 1))
- ((looking-at "#") ;skip comment
- (forward-line 1)
- (goto-char (point))
- )
- ((looking-at "[\\[{(]")
- (let ((deep (ruby-deep-indent-paren-p (char-after))))
- (if (and deep (or (not (eq (char-after) ?\{)) (ruby-expr-beg)))
- (progn
- (and (eq deep 'space) (looking-at ".\\s +[^# \t\n]")
- (setq pnt (1- (match-end 0))))
- (setq nest (cons (cons (char-after (point)) (point)) nest))
- (setq pcol (cons (cons pnt depth) pcol))
- (setq depth 0))
- (setq nest (cons (cons (char-after (point)) pnt) nest))
- (setq depth (1+ depth))))
- (goto-char pnt)
- )
- ((looking-at "[])}]")
- (if (ruby-deep-indent-paren-p (matching-paren (char-after))
- (if nest
- (cdr (nth 0 nest))
- (save-excursion
- (forward-char)
- (ruby-backward-sexp)
- (point))))
- (setq depth (cdr (car pcol)) pcol (cdr pcol))
- (setq depth (1- depth)))
- (setq nest (cdr nest))
- (goto-char pnt))
- ((looking-at ruby-block-end-re)
- (if (or (and (not (bolp))
- (progn
- (forward-char -1)
- (setq w (char-after (point)))
- (or (eq ?_ w)
- (eq ?. w))))
- (progn
- (goto-char pnt)
- (setq w (char-after (point)))
- (or (eq ?_ w)
- (eq ?! w)
- (eq ?? w))))
- nil
- (setq nest (cdr nest))
- (setq depth (1- depth)))
- (goto-char pnt))
- ((looking-at "def\\s +[^(\n;]*")
- (if (or (bolp)
- (progn
- (forward-char -1)
- (not (eq ?_ (char-after (point))))))
- (progn
- (setq nest (cons (cons nil pnt) nest))
- (setq depth (1+ depth))))
- (goto-char (match-end 0)))
- ((looking-at (concat "\\_<\\(" ruby-block-beg-re "\\)\\_>"))
- (and
- (save-match-data
- (or (not (looking-at (concat "do" ruby-keyword-end-re)))
- (save-excursion
- (back-to-indentation)
- (not (looking-at ruby-non-block-do-re)))))
- (or (bolp)
- (progn
- (forward-char -1)
- (setq w (char-after (point)))
- (not (or (eq ?_ w)
- (eq ?. w)))))
- (goto-char pnt)
- (setq w (char-after (point)))
- (not (eq ?_ w))
- (not (eq ?! w))
- (not (eq ?? w))
- (not (eq ?: w))
- (skip-chars-forward " \t")
- (goto-char (match-beginning 0))
- (or (not (looking-at ruby-modifier-re))
- (ruby-expr-beg 'modifier))
- (goto-char pnt)
- (setq nest (cons (cons nil pnt) nest))
- (setq depth (1+ depth)))
- (goto-char pnt))
- ((looking-at ":\\(['\"]\\)")
- (goto-char (match-beginning 1))
- (ruby-forward-string (buffer-substring (match-beginning 1) (match-end 1)) end))
- ((looking-at ":\\([-,.+*/%&|^~<>]=?\\|===?\\|<=>\\|![~=]?\\)")
- (goto-char (match-end 0)))
- ((looking-at ":\\([a-zA-Z_][a-zA-Z_0-9]*[!?=]?\\)?")
- (goto-char (match-end 0)))
- ((or (looking-at "\\.\\.\\.?")
- (looking-at "\\.[0-9]+")
- (looking-at "\\.[a-zA-Z_0-9]+")
- (looking-at "\\."))
- (goto-char (match-end 0)))
- ((looking-at "^=begin")
- (if (re-search-forward "^=end" end t)
- (forward-line 1)
- (setq in-string (match-end 0))
- (goto-char end)))
- ((looking-at "<<")
- (cond
- ((and (ruby-expr-beg 'heredoc)
- (looking-at "<<\\(-\\)?\\(\\([\"'`]\\)\\([^\n]+?\\)\\3\\|\\(?:\\sw\\|\\s_\\)+\\)"))
- (setq re (regexp-quote (or (match-string 4) (match-string 2))))
- (if (match-beginning 1) (setq re (concat "\\s *" re)))
- (let* ((id-end (goto-char (match-end 0)))
- (line-end-position (save-excursion (end-of-line) (point)))
- (state (list in-string nest depth pcol indent)))
- ;; parse the rest of the line
- (while (and (> line-end-position (point))
- (setq state (apply 'ruby-parse-partial
- line-end-position state))))
- (setq in-string (car state)
- nest (nth 1 state)
- depth (nth 2 state)
- pcol (nth 3 state)
- indent (nth 4 state))
- ;; skip heredoc section
- (if (re-search-forward (concat "^" re "$") end 'move)
- (forward-line 1)
- (setq in-string id-end)
- (goto-char end))))
- (t
- (goto-char pnt))))
- ((looking-at "^__END__$")
- (goto-char pnt))
- ((looking-at ruby-here-doc-beg-re)
- (if (re-search-forward (ruby-here-doc-end-match)
- ruby-indent-point t)
- (forward-line 1)
- (setq in-string (match-end 0))
- (goto-char ruby-indent-point)))
- (t
- (error (format "bad string %s"
- (buffer-substring (point) pnt)
- ))))))
- (list in-string nest depth pcol))
-
-(defun ruby-parse-region (start end)
- (let (state)
- (save-excursion
- (if start
- (goto-char start)
- (ruby-beginning-of-indent))
- (save-restriction
- (narrow-to-region (point) end)
- (while (and (> end (point))
- (setq state (apply 'ruby-parse-partial end state))))))
- (list (nth 0 state) ; in-string
- (car (nth 1 state)) ; nest
- (nth 2 state) ; depth
- (car (car (nth 3 state))) ; pcol
- ;(car (nth 5 state)) ; indent
- )))
-
-(defun ruby-indent-size (pos nest)
- (+ pos (* (or nest 1) ruby-indent-level)))
-
-(defun ruby-calculate-indent (&optional parse-start)
- (save-excursion
- (beginning-of-line)
- (let ((ruby-indent-point (point))
- (case-fold-search nil)
- state bol eol begin op-end
- (paren (progn (skip-syntax-forward " ")
- (and (char-after) (matching-paren (char-after)))))
- (indent 0))
- (if parse-start
- (goto-char parse-start)
- (ruby-beginning-of-indent)
- (setq parse-start (point)))
- (back-to-indentation)
- (setq indent (current-column))
- (setq state (ruby-parse-region parse-start ruby-indent-point))
- (cond
- ((nth 0 state) ; within string
- (setq indent nil)) ; do nothing
- ((car (nth 1 state)) ; in paren
- (goto-char (setq begin (cdr (nth 1 state))))
- (let ((deep (ruby-deep-indent-paren-p (car (nth 1 state))
- (1- (cdr (nth 1 state))))))
- (if deep
- (cond ((and (eq deep t) (eq (car (nth 1 state)) paren))
- (skip-syntax-backward " ")
- (setq indent (1- (current-column))))
- ((eq deep 'space)
- (goto-char (cdr (nth 1 state)))
- (setq indent (1+ (current-column))))
- ((let ((s (ruby-parse-region (point) ruby-indent-point)))
- (and (nth 2 s) (> (nth 2 s) 0)
- (or (goto-char (cdr (nth 1 s))) t)))
- (forward-word -1)
- (setq indent (ruby-indent-size (current-column) (nth 2 state))))
- (t
- (setq indent (current-column))
- (cond ((eq deep 'space))
- (paren (setq indent (1- indent)))
- (t (setq indent (ruby-indent-size (1- indent) 1))))))
- (if (nth 3 state) (goto-char (nth 3 state))
- (goto-char parse-start) (back-to-indentation))
- (setq indent (ruby-indent-size (current-column) (nth 2 state))))
- (and (eq (car (nth 1 state)) paren)
- (ruby-deep-indent-paren-p (matching-paren paren)
- (1- (cdr (nth 1 state))))
- (search-backward (char-to-string paren))
- (setq indent (current-column)))))
- ((and (nth 2 state) (> (nth 2 state) 0)) ; in nest
- (if (null (cdr (nth 1 state)))
- (error "invalid nest"))
- (goto-char (cdr (nth 1 state)))
- (forward-word -1) ; skip back a keyword
- (setq begin (point))
- (cond
- ((looking-at "do\\>[^_]") ; iter block is a special case
- (if (nth 3 state) (goto-char (nth 3 state))
- (goto-char parse-start) (back-to-indentation))
- (setq indent (ruby-indent-size (current-column) (nth 2 state))))
- (t
- (setq indent (+ (current-column) ruby-indent-level)))))
-
- ((and (nth 2 state) (< (nth 2 state) 0)) ; in negative nest
- (setq indent (ruby-indent-size (current-column) (nth 2 state)))))
- (when indent
- (goto-char ruby-indent-point)
- (end-of-line)
- (setq eol (point))
- (beginning-of-line)
- (cond
- ((and (not (ruby-deep-indent-paren-p paren
- (and (cdr (nth 1 state))
- (1- (cdr (nth 1 state))))))
- (re-search-forward ruby-negative eol t))
- (and (not (eq ?_ (char-after (match-end 0))))
- (setq indent (- indent ruby-indent-level))))
- ((and
- (save-excursion
- (beginning-of-line)
- (not (bobp)))
- (or (ruby-deep-indent-paren-p t)
- (null (car (nth 1 state)))))
- ;; goto beginning of non-empty no-comment line
- (let (end done)
- (while (not done)
- (skip-chars-backward " \t\n")
- (setq end (point))
- (beginning-of-line)
- (if (re-search-forward "^\\s *#" end t)
- (beginning-of-line)
- (setq done t))))
- (setq bol (point))
- (end-of-line)
- ;; skip the comment at the end
- (skip-chars-backward " \t")
- (let (end (pos (point)))
- (beginning-of-line)
- (while (and (re-search-forward "#" pos t)
- (setq end (1- (point)))
- (or (ruby-special-char-p end)
- (and (setq state (ruby-parse-region parse-start end))
- (nth 0 state))))
- (setq end nil))
- (goto-char (or end pos))
- (skip-chars-backward " \t")
- (setq begin (if (and end (nth 0 state)) pos (cdr (nth 1 state))))
- (setq state (ruby-parse-region parse-start (point))))
- (or (bobp) (forward-char -1))
- (and
- (or (and (looking-at ruby-symbol-re)
- (skip-chars-backward ruby-symbol-chars)
- (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>"))
- (not (eq (point) (nth 3 state)))
- (save-excursion
- (goto-char (match-end 0))
- (not (looking-at "[a-z_]"))))
- (and (looking-at ruby-operator-re)
- (not (ruby-special-char-p))
- ;; operator at the end of line
- (let ((c (char-after (point))))
- (and
-;; (or (null begin)
-;; (save-excursion
-;; (goto-char begin)
-;; (skip-chars-forward " \t")
-;; (not (or (eolp) (looking-at "#")
-;; (and (eq (car (nth 1 state)) ?{)
-;; (looking-at "|"))))))
- (or (not (eq ?/ c))
- (null (nth 0 (ruby-parse-region (or begin parse-start) (point)))))
- (or (not (eq ?| (char-after (point))))
- (save-excursion
- (or (eolp) (forward-char -1))
- (cond
- ((search-backward "|" nil t)
- (skip-chars-backward " \t\n")
- (and (not (eolp))
- (progn
- (forward-char -1)
- (not (looking-at "{")))
- (progn
- (forward-word -1)
- (not (looking-at "do\\>[^_]")))))
- (t t))))
- (not (eq ?, c))
- (setq op-end t)))))
- (setq indent
- (cond
- ((and
- (null op-end)
- (not (looking-at (concat "\\<\\(" ruby-block-hanging-re "\\)\\>")))
- (eq (ruby-deep-indent-paren-p t) 'space)
- (not (bobp)))
- (widen)
- (goto-char (or begin parse-start))
- (skip-syntax-forward " ")
- (current-column))
- ((car (nth 1 state)) indent)
- (t
- (+ indent ruby-indent-level))))))))
- (goto-char ruby-indent-point)
- (beginning-of-line)
- (skip-syntax-forward " ")
- (if (looking-at "\\.[^.]")
- (+ indent ruby-indent-level)
- indent))))
-
-(defun ruby-electric-brace (arg)
- (interactive "P")
- (insert-char last-command-char 1)
- (ruby-indent-line t)
- (delete-char -1)
- (self-insert-command (prefix-numeric-value arg)))
-
-(eval-when-compile
- (defmacro defun-region-command (func args &rest body)
- (let ((intr (car body)))
- (when (featurep 'xemacs)
- (if (stringp intr) (setq intr (cadr body)))
- (and (eq (car intr) 'interactive)
- (setq intr (cdr intr))
- (setcar intr (concat "_" (car intr)))))
- (cons 'defun (cons func (cons args body))))))
-
-(defun-region-command ruby-beginning-of-defun (&optional arg)
- "Move backward to next beginning-of-defun.
-With argument, do this that many times.
-Returns t unless search stops due to end of buffer."
- (interactive "p")
- (and (re-search-backward (concat "^\\(" ruby-block-beg-re "\\)\\_>")
- nil 'move (or arg 1))
- (progn (beginning-of-line) t)))
-
-(defun ruby-beginning-of-indent ()
- (and (re-search-backward (concat "^\\(" ruby-indent-beg-re "\\)\\_>")
- nil 'move)
- (progn
- (beginning-of-line)
- t)))
-
-(defun-region-command ruby-end-of-defun (&optional arg)
- "Move forward to next end of defun.
-An end of a defun is found by moving forward from the beginning of one."
- (interactive "p")
- (and (re-search-forward (concat "^\\(" ruby-block-end-re "\\)\\($\\|\\b[^_]\\)")
- nil 'move (or arg 1))
- (progn (beginning-of-line) t))
- (forward-line 1))
-
-(defun ruby-move-to-block (n)
- (let (start pos done down (orig (point)))
- (setq start (ruby-calculate-indent))
- (setq down (looking-at (if (< n 0) ruby-block-end-re
- (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))))
- (while (and (not done) (not (if (< n 0) (bobp) (eobp))))
- (forward-line n)
- (cond
- ((looking-at "^\\s *$"))
- ((looking-at "^\\s *#"))
- ((and (> n 0) (looking-at "^=begin\\>"))
- (re-search-forward "^=end\\>"))
- ((and (< n 0) (looking-at "^=end\\>"))
- (re-search-backward "^=begin\\>"))
- (t
- (setq pos (current-indentation))
- (cond
- ((< start pos)
- (setq down t))
- ((and down (= pos start))
- (setq done t))
- ((> start pos)
- (setq done t)))))
- (if done
- (save-excursion
- (back-to-indentation)
- (if (looking-at (concat "\\<\\(" ruby-block-mid-re "\\)\\>"))
- (setq done nil)))))
- (back-to-indentation)
- (when (< n 0)
- (let ((eol (point-at-eol)) state next)
- (if (< orig eol) (setq eol orig))
- (setq orig (point))
- (while (and (setq next (apply 'ruby-parse-partial eol state))
- (< (point) eol))
- (setq state next))
- (when (cdaadr state)
- (goto-char (cdaadr state)))
- (backward-word)))))
-
-(defun-region-command ruby-beginning-of-block (&optional arg)
- "Move backward to next beginning-of-block"
- (interactive "p")
- (ruby-move-to-block (- (or arg 1))))
-
-(defun-region-command ruby-end-of-block (&optional arg)
- "Move forward to next beginning-of-block"
- (interactive "p")
- (ruby-move-to-block (or arg 1)))
-
-(defun-region-command ruby-forward-sexp (&optional cnt)
- (interactive "p")
- (if (and (numberp cnt) (< cnt 0))
- (ruby-backward-sexp (- cnt))
- (let ((i (or cnt 1)))
- (condition-case nil
- (while (> i 0)
- (skip-syntax-forward " ")
- (if (looking-at ",\\s *") (goto-char (match-end 0)))
- (cond ((looking-at "\\?\\(\\\\[CM]-\\)*\\\\?\\S ")
- (goto-char (match-end 0)))
- ((progn
- (skip-chars-forward ",.:;|&^~=!?\\+\\-\\*")
- (looking-at "\\s("))
- (goto-char (scan-sexps (point) 1)))
- ((and (looking-at (concat "\\<\\(" ruby-block-beg-re "\\)\\>"))
- (not (eq (char-before (point)) ?.))
- (not (eq (char-before (point)) ?:)))
- (ruby-end-of-block)
- (forward-word 1))
- ((looking-at "\\(\\$\\|@@?\\)?\\sw")
- (while (progn
- (while (progn (forward-word 1) (looking-at "_")))
- (cond ((looking-at "::") (forward-char 2) t)
- ((> (skip-chars-forward ".") 0))
- ((looking-at "\\?\\|!\\(=[~=>]\\|[^~=]\\)")
- (forward-char 1) nil)))))
- ((let (state expr)
- (while
- (progn
- (setq expr (or expr (ruby-expr-beg)
- (looking-at "%\\sw?\\Sw\\|[\"'`/]")))
- (nth 1 (setq state (apply 'ruby-parse-partial nil state))))
- (setq expr t)
- (skip-chars-forward "<"))
- (not expr))))
- (setq i (1- i)))
- ((error) (forward-word 1)))
- i)))
-
-(defun-region-command ruby-backward-sexp (&optional cnt)
- (interactive "p")
- (if (and (numberp cnt) (< cnt 0))
- (ruby-forward-sexp (- cnt))
- (let ((i (or cnt 1)))
- (condition-case nil
- (while (> i 0)
- (skip-chars-backward " \t\n,.:;|&^~=!?\\+\\-\\*")
- (forward-char -1)
- (cond ((looking-at "\\s)")
- (goto-char (scan-sexps (1+ (point)) -1))
- (case (char-before)
- (?% (forward-char -1))
- ('(?q ?Q ?w ?W ?r ?x)
- (if (eq (char-before (1- (point))) ?%) (forward-char -2))))
- nil)
- ((looking-at "\\s\"\\|\\\\\\S_")
- (let ((c (char-to-string (char-before (match-end 0)))))
- (while (and (search-backward c)
- (oddp (skip-chars-backward "\\")))))
- nil)
- ((looking-at "\\s.\\|\\s\\")
- (if (ruby-special-char-p) (forward-char -1)))
- ((looking-at "\\s(") nil)
- (t
- (forward-char 1)
- (while (progn (forward-word -1)
- (case (char-before)
- (?_ t)
- (?. (forward-char -1) t)
- ((?$ ?@)
- (forward-char -1)
- (and (eq (char-before) (char-after)) (forward-char -1)))
- (?:
- (forward-char -1)
- (eq (char-before) :)))))
- (if (looking-at ruby-block-end-re)
- (ruby-beginning-of-block))
- nil))
- (setq i (1- i)))
- ((error)))
- i)))
-
-(defun ruby-reindent-then-newline-and-indent ()
- (interactive "*")
- (newline)
- (save-excursion
- (end-of-line 0)
- (indent-according-to-mode)
- (delete-region (point) (progn (skip-chars-backward " \t") (point))))
- (indent-according-to-mode))
-
-(fset 'ruby-encomment-region (symbol-function 'comment-region))
-
-(defun ruby-decomment-region (beg end)
- (interactive "r")
- (save-excursion
- (goto-char beg)
- (while (re-search-forward "^\\([ \t]*\\)#" end t)
- (replace-match "\\1" nil nil)
- (save-excursion
- (ruby-indent-line)))))
-
-(defun ruby-insert-end ()
- (interactive)
- (insert "end")
- (ruby-indent-line t)
- (end-of-line))
-
-(defun ruby-mark-defun ()
- "Put mark at end of this Ruby function, point at beginning."
- (interactive)
- (push-mark (point))
- (ruby-end-of-defun)
- (push-mark (point) nil t)
- (ruby-beginning-of-defun)
- (re-search-backward "^\n" (- (point) 1) t))
-
-(defun ruby-indent-exp (&optional shutup-p)
- "Indent each line in the balanced expression following point syntactically.
-If optional SHUTUP-P is non-nil, no errors are signalled if no
-balanced expression is found."
- (interactive "*P")
- (let ((here (point-marker)) start top column (nest t))
- (set-marker-insertion-type here t)
- (unwind-protect
- (progn
- (beginning-of-line)
- (setq start (point) top (current-indentation))
- (while (and (not (eobp))
- (progn
- (setq column (ruby-calculate-indent start))
- (cond ((> column top)
- (setq nest t))
- ((and (= column top) nest)
- (setq nest nil) t))))
- (ruby-indent-to column)
- (beginning-of-line 2)))
- (goto-char here)
- (set-marker here nil))))
-
-(defun ruby-add-log-current-method ()
- "Return current method string."
- (condition-case nil
- (save-excursion
- (let (mname mlist (indent 0))
- ;; get current method (or class/module)
- (if (re-search-backward
- (concat "^[ \t]*\\(def\\|class\\|module\\)[ \t]+"
- "\\("
- ;; \\. and :: for class method
- "\\([A-Za-z_]" ruby-symbol-re "*\\|\\.\\|::" "\\)"
- "+\\)")
- nil t)
- (progn
- (setq mname (match-string 2))
- (unless (string-equal "def" (match-string 1))
- (setq mlist (list mname) mname nil))
- (goto-char (match-beginning 1))
- (setq indent (current-column))
- (beginning-of-line)))
- ;; nest class/module
- (while (and (> indent 0)
- (re-search-backward
- (concat
- "^[ \t]*\\(class\\|module\\)[ \t]+"
- "\\([A-Z]" ruby-symbol-re "*\\)")
- nil t))
- (goto-char (match-beginning 1))
- (if (< (current-column) indent)
- (progn
- (setq mlist (cons (match-string 2) mlist))
- (setq indent (current-column))
- (beginning-of-line))))
- (when mname
- (let ((mn (split-string mname "\\.\\|::")))
- (if (cdr mn)
- (progn
- (cond
- ((string-equal "" (car mn))
- (setq mn (cdr mn) mlist nil))
- ((string-equal "self" (car mn))
- (setq mn (cdr mn)))
- ((let ((ml (nreverse mlist)))
- (while ml
- (if (string-equal (car ml) (car mn))
- (setq mlist (nreverse (cdr ml)) ml nil))
- (or (setq ml (cdr ml)) (nreverse mlist))))))
- (if mlist
- (setcdr (last mlist) mn)
- (setq mlist mn))
- (setq mn (last mn 2))
- (setq mname (concat "." (cadr mn)))
- (setcdr mn nil))
- (setq mname (concat "#" mname)))))
- ;; generate string
- (if (consp mlist)
- (setq mlist (mapconcat (function identity) mlist "::")))
- (if mname
- (if mlist (concat mlist mname) mname)
- mlist)))))
-
-(defun ruby-brace-to-do-end ()
- (when (looking-at "{")
- (let ((orig (point)) (end (progn (ruby-forward-sexp) (point))))
- (when (eq (char-before) ?\})
- (delete-char -1)
- (if (eq (char-syntax (char-before)) ?w)
- (insert " "))
- (insert "end")
- (if (eq (char-syntax (char-after)) ?w)
- (insert " "))
- (goto-char orig)
- (delete-char 1)
- (if (eq (char-syntax (char-before)) ?w)
- (insert " "))
- (insert "do")
- (when (looking-at "\\sw\\||")
- (insert " ")
- (backward-char))
- t))))
-
-(defun ruby-do-end-to-brace ()
- (when (and (or (bolp)
- (not (memq (char-syntax (char-before)) '(?w ?_))))
- (looking-at "\\<do\\(\\s \\|$\\)"))
- (let ((orig (point)) (end (progn (ruby-forward-sexp) (point))))
- (backward-char 3)
- (when (looking-at ruby-block-end-re)
- (delete-char 3)
- (insert "}")
- (goto-char orig)
- (delete-char 2)
- (insert "{")
- (if (looking-at "\\s +|")
- (delete-char (- (match-end 0) (match-beginning 0) 1)))
- t))))
-
-(defun ruby-toggle-block ()
- (interactive)
- (or (ruby-brace-to-do-end)
- (ruby-do-end-to-brace)))
-
-(eval-when-compile
- (if (featurep 'font-lock)
- (defmacro eval-when-font-lock-available (&rest args) (cons 'progn args))
- (defmacro eval-when-font-lock-available (&rest args))))
-
-(eval-when-compile
- (if (featurep 'hilit19)
- (defmacro eval-when-hilit19-available (&rest args) (cons 'progn args))
- (defmacro eval-when-hilit19-available (&rest args))))
-
-(eval-when-font-lock-available
- (or (boundp 'font-lock-variable-name-face)
- (setq font-lock-variable-name-face font-lock-type-face))
-
- (defconst ruby-font-lock-syntactic-keywords
- `(
- ;; #{ }, #$hoge, #@foo are not comments
- ("\\(#\\)[{$@]" 1 (1 . nil))
- ;; the last $', $", $` in the respective string is not variable
- ;; the last ?', ?", ?` in the respective string is not ascii code
- ("\\(^\\|[\[ \t\n<+\(,=]\\)\\(['\"`]\\)\\(\\\\.\\|\\2\\|[^'\"`\n\\\\]\\)*?\\\\?[?$]\\(\\2\\)"
- (2 (7 . nil))
- (4 (7 . nil)))
- ;; $' $" $` .... are variables
- ;; ?' ?" ?` are ascii codes
- ("\\(^\\|[^\\\\]\\)\\(\\\\\\\\\\)*[?$]\\([#\"'`]\\)" 3 (1 . nil))
- ;; regexps
- ("\\(^\\|[[=(,~?:;<>]\\|\\(^\\|\\s \\)\\(if\\|elsif\\|unless\\|while\\|until\\|when\\|and\\|or\\|&&\\|||\\)\\|g?sub!?\\|scan\\|split!?\\)\\s *\\(/\\)[^/\n\\\\]*\\(\\\\.[^/\n\\\\]*\\)*\\(/\\)"
- (4 (7 . ?/))
- (6 (7 . ?/)))
- ("^\\(=\\)begin\\(\\s \\|$\\)" 1 (7 . nil))
- ("^\\(=\\)end\\(\\s \\|$\\)" 1 (7 . nil))
- (,(concat ruby-here-doc-beg-re ".*\\(\n\\)")
- ,(+ 1 (regexp-opt-depth ruby-here-doc-beg-re))
- (ruby-here-doc-beg-syntax))
- (,ruby-here-doc-end-re 3 (ruby-here-doc-end-syntax))))
-
- (unless (functionp 'syntax-ppss)
- (defun syntax-ppss (&optional pos)
- (parse-partial-sexp (point-min) (or pos (point)))))
-
- (defun ruby-in-ppss-context-p (context &optional ppss)
- (let ((ppss (or ppss (syntax-ppss (point)))))
- (if (cond
- ((eq context 'anything)
- (or (nth 3 ppss)
- (nth 4 ppss)))
- ((eq context 'string)
- (nth 3 ppss))
- ((eq context 'heredoc)
- (and (nth 3 ppss)
- ;; If it's generic string, it's a heredoc and we don't care
- ;; See `parse-partial-sexp'
- (not (numberp (nth 3 ppss)))))
- ((eq context 'non-heredoc)
- (and (ruby-in-ppss-context-p 'anything)
- (not (ruby-in-ppss-context-p 'heredoc))))
- ((eq context 'comment)
- (nth 4 ppss))
- (t
- (error (concat
- "Internal error on `ruby-in-ppss-context-p': "
- "context name `" (symbol-name context) "' is unknown"))))
- t)))
-
- (defun ruby-in-here-doc-p ()
- (save-excursion
- (let ((old-point (point)) (case-fold-search nil))
- (beginning-of-line)
- (catch 'found-beg
- (while (and (re-search-backward ruby-here-doc-beg-re nil t)
- (not (ruby-singleton-class-p)))
- (if (not (or (ruby-in-ppss-context-p 'anything)
- (ruby-here-doc-find-end old-point)))
- (throw 'found-beg t)))))))
-
- (defun ruby-here-doc-find-end (&optional limit)
- "Expects the point to be on a line with one or more heredoc
-openers. Returns the buffer position at which all heredocs on the
-line are terminated, or nil if they aren't terminated before the
-buffer position `limit' or the end of the buffer."
- (save-excursion
- (beginning-of-line)
- (catch 'done
- (let ((eol (save-excursion (end-of-line) (point)))
- (case-fold-search nil)
- ;; Fake match data such that (match-end 0) is at eol
- (end-match-data (progn (looking-at ".*$") (match-data)))
- beg-match-data end-re)
- (while (re-search-forward ruby-here-doc-beg-re eol t)
- (setq beg-match-data (match-data))
- (setq end-re (ruby-here-doc-end-match))
-
- (set-match-data end-match-data)
- (goto-char (match-end 0))
- (unless (re-search-forward end-re limit t) (throw 'done nil))
- (setq end-match-data (match-data))
-
- (set-match-data beg-match-data)
- (goto-char (match-end 0)))
- (set-match-data end-match-data)
- (goto-char (match-end 0))
- (point)))))
-
- (defun ruby-here-doc-beg-syntax ()
- (save-excursion
- (goto-char (match-beginning 0))
- (unless (or (ruby-in-ppss-context-p 'non-heredoc)
- (ruby-in-here-doc-p))
- (string-to-syntax "|"))))
-
- (defun ruby-here-doc-end-syntax ()
- (let ((pss (syntax-ppss)) (case-fold-search nil))
- (when (ruby-in-ppss-context-p 'heredoc pss)
- (save-excursion
- (goto-char (nth 8 pss)) ; Go to the beginning of heredoc.
- (let ((eol (point)))
- (beginning-of-line)
- (if (and (re-search-forward (ruby-here-doc-beg-match) eol t) ; If there is a heredoc that matches this line...
- (not (ruby-in-ppss-context-p 'anything)) ; And that's not inside a heredoc/string/comment...
- (progn (goto-char (match-end 0)) ; And it's the last heredoc on its line...
- (not (re-search-forward ruby-here-doc-beg-re eol t))))
- (string-to-syntax "|")))))))
-
- (eval-when-compile
- (put 'ruby-mode 'font-lock-defaults
- '((ruby-font-lock-keywords)
- nil nil nil
- beginning-of-line
- (font-lock-syntactic-keywords
- . ruby-font-lock-syntactic-keywords))))
-
- (defun ruby-font-lock-docs (limit)
- (if (re-search-forward "^=begin\\(\\s \\|$\\)" limit t)
- (let (beg)
- (beginning-of-line)
- (setq beg (point))
- (forward-line 1)
- (if (re-search-forward "^=end\\(\\s \\|$\\)" limit t)
- (progn
- (set-match-data (list beg (point)))
- t)))))
-
- (defun ruby-font-lock-maybe-docs (limit)
- (let (beg)
- (save-excursion
- (if (and (re-search-backward "^=\\(begin\\|end\\)\\(\\s \\|$\\)" nil t)
- (string= (match-string 1) "begin"))
- (progn
- (beginning-of-line)
- (setq beg (point)))))
- (if (and beg (and (re-search-forward "^=\\(begin\\|end\\)\\(\\s \\|$\\)" nil t)
- (string= (match-string 1) "end")))
- (progn
- (set-match-data (list beg (point)))
- t)
- nil)))
-
- (defvar ruby-font-lock-syntax-table
- (let* ((tbl (copy-syntax-table ruby-mode-syntax-table)))
- (modify-syntax-entry ?_ "w" tbl)
- tbl))
-
- (defconst ruby-font-lock-keywords
- (list
- ;; functions
- '("^\\s *def\\s +\\([^( \t\n]+\\)"
- 1 font-lock-function-name-face)
- ;; keywords
- (cons (concat
- "\\(^\\|[^_:.@$]\\|\\.\\.\\)\\_<\\(defined\\?\\|"
- (regexp-opt
- '("alias"
- "and"
- "begin"
- "break"
- "case"
- "catch"
- "class"
- "def"
- "do"
- "elsif"
- "else"
- "fail"
- "ensure"
- "for"
- "end"
- "if"
- "in"
- "module"
- "next"
- "not"
- "or"
- "raise"
- "redo"
- "rescue"
- "retry"
- "return"
- "then"
- "throw"
- "super"
- "unless"
- "undef"
- "until"
- "when"
- "while"
- "yield"
- )
- t)
- "\\)"
- ruby-keyword-end-re)
- 2)
- ;; here-doc beginnings
- (list ruby-here-doc-beg-re 0 'font-lock-string-face)
- ;; variables
- '("\\(^\\|[^_:.@$]\\|\\.\\.\\)\\_<\\(nil\\|self\\|true\\|false\\)\\>"
- 2 font-lock-variable-name-face)
- ;; variables
- '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W"
- 1 font-lock-variable-name-face)
- '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+"
- 0 font-lock-variable-name-face)
- ;; embedded document
- '(ruby-font-lock-docs
- 0 font-lock-comment-face t)
- '(ruby-font-lock-maybe-docs
- 0 font-lock-comment-face t)
- ;; general delimited string
- '("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)"
- (2 font-lock-string-face))
- ;; constants
- '("\\(^\\|[^_]\\)\\_<\\([A-Z]+\\(\\w\\|_\\)*\\)"
- 2 font-lock-type-face)
- ;; symbols
- '("\\(^\\|[^:]\\)\\(:\\([-+~]@?\\|[/%&|^`]\\|\\*\\*?\\|<\\(<\\|=>?\\)?\\|>[>=]?\\|===?\\|=~\\|![~=]?\\|\\[\\]=?\\|\\(\\w\\|_\\)+\\([!?=]\\|\\b_*\\)\\|#{[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\)\\)"
- 2 font-lock-reference-face)
- '("\\(^\\s *\\|[\[\{\(,]\\s *\\|\\sw\\s +\\)\\(\\(\\sw\\|_\\)+\\):[^:]" 2 font-lock-reference-face)
- ;; expression expansion
- '("#\\({[^}\n\\\\]*\\(\\\\.[^}\n\\\\]*\\)*}\\|\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+\\)"
- 0 font-lock-variable-name-face t)
- ;; warn lower camel case
- ;'("\\<[a-z]+[a-z0-9]*[A-Z][A-Za-z0-9]*\\([!?]?\\|\\>\\)"
- ; 0 font-lock-warning-face)
- )
- "*Additional expressions to highlight in ruby mode."))
-
-(eval-when-hilit19-available
- (hilit-set-mode-patterns
- 'ruby-mode
- '(("[^$\\?]\\(\"[^\\\"]*\\(\\\\\\(.\\|\n\\)[^\\\"]*\\)*\"\\)" 1 string)
- ("[^$\\?]\\('[^\\']*\\(\\\\\\(.\\|\n\\)[^\\']*\\)*'\\)" 1 string)
- ("[^$\\?]\\(`[^\\`]*\\(\\\\\\(.\\|\n\\)[^\\`]*\\)*`\\)" 1 string)
- ("^\\s *#.*$" nil comment)
- ("[^$@?\\]\\(#[^$@{\n].*$\\)" 1 comment)
- ("[^a-zA-Z_]\\(\\?\\(\\\\[CM]-\\)*.\\)" 1 string)
- ("^\\s *\\(require\\|load\\).*$" nil include)
- ("^\\s *\\(include\\|alias\\|undef\\).*$" nil decl)
- ("^\\s *\\<\\(class\\|def\\|module\\)\\>" "[)\n;]" defun)
- ("[^_]\\<\\(begin\\|case\\|else\\|elsif\\|end\\|ensure\\|for\\|if\\|unless\\|rescue\\|then\\|when\\|while\\|until\\|do\\|yield\\)\\>\\([^_]\\|$\\)" 1 defun)
- ("[^_]\\<\\(and\\|break\\|next\\|raise\\|fail\\|in\\|not\\|or\\|redo\\|retry\\|return\\|super\\|yield\\|catch\\|throw\\|self\\|nil\\)\\>\\([^_]\\|$\\)" 1 keyword)
- ("\\$\\(.\\|\\sw+\\)" nil type)
- ("[$@].[a-zA-Z_0-9]*" nil struct)
- ("^__END__" nil label))))
-
-
-;;;###autoload
-(defun ruby-mode ()
- "Major mode for editing ruby scripts.
-\\[ruby-indent-command] properly indents subexpressions of multi-line
-class, module, def, if, while, for, do, and case statements, taking
-nesting into account.
-
-The variable ruby-indent-level controls the amount of indentation.
-\\{ruby-mode-map}"
- (interactive)
- (kill-all-local-variables)
- (use-local-map ruby-mode-map)
- (setq mode-name "Ruby")
- (setq major-mode 'ruby-mode)
- (ruby-mode-variables)
-
- (make-local-variable 'imenu-create-index-function)
- (setq imenu-create-index-function 'ruby-imenu-create-index)
-
- (make-local-variable 'add-log-current-defun-function)
- (setq add-log-current-defun-function 'ruby-add-log-current-method)
-
- (add-hook
- (cond ((boundp 'before-save-hook)
- (make-local-variable 'before-save-hook)
- 'before-save-hook)
- ((boundp 'write-contents-functions) 'write-contents-functions)
- ((boundp 'write-contents-hooks) 'write-contents-hooks))
- 'ruby-mode-set-encoding)
-
- (set (make-local-variable 'font-lock-defaults) '((ruby-font-lock-keywords) nil nil))
- (set (make-local-variable 'font-lock-keywords) ruby-font-lock-keywords)
- (set (make-local-variable 'font-lock-syntax-table) ruby-font-lock-syntax-table)
- (set (make-local-variable 'font-lock-syntactic-keywords) ruby-font-lock-syntactic-keywords)
-
- (if (fboundp 'run-mode-hooks)
- (run-mode-hooks 'ruby-mode-hook)
- (run-hooks 'ruby-mode-hook)))
-
-(provide 'ruby-mode)
diff --git a/misc/ruby-style.el b/misc/ruby-style.el
index 3ce55cd1ab..03d0830d3a 100644
--- a/misc/ruby-style.el
+++ b/misc/ruby-style.el
@@ -7,6 +7,8 @@
;;; $Author$
;;; created at: Thu Apr 26 13:54:01 JST 2007
;;;
+;;; Put this file under a directory contained in ``load-path'', and
+;;; then load it.
;;; To switch to the "ruby" style automatically if it looks like a
;;; source file of ruby, add ruby-style-c-mode to c-mode-hook:
;;;
@@ -53,8 +55,10 @@
'("bsd"
(c-basic-offset . 4)
(tab-width . 8)
- (indent-tabs-mode . t)
- (setq show-trailing-whitespace t)
+ (indent-tabs-mode . nil)
+ (show-trailing-whitespace . t)
+ (c-backslash-column . 1)
+ (c-backslash-max-column . 1)
(c-offsets-alist
(case-label . *)
(label . (ruby-style-label-indent *))
@@ -64,6 +68,18 @@
(access-label /)
)))
+(c-add-style
+ "prism"
+ '("bsd"
+ (c-basic-offset . 4)
+ (tab-width . 8)
+ (indent-tabs-mode . nil)
+ (show-trailing-whitespace . t)
+ (c-offsets-alist
+ (case-label . +)
+ )))
+
+;;;###autoload
(defun ruby-style-c-mode ()
(interactive)
(if (or (let ((name (buffer-file-name))) (and name (string-match "/ruby\\>" name)))
@@ -72,7 +88,20 @@
(let ((head (progn (forward-line 100) (point)))
(case-fold-search nil))
(goto-char (point-min))
- (re-search-forward "Copyright (C) .* Yukihiro Matsumoto" head t))))
+ (re-search-forward "Copyright (C) .* Yukihiro Matsumoto" head t)))
+ (condition-case ()
+ (with-temp-buffer
+ (when (= 0 (call-process "git" nil t nil "remote" "get-url" "origin"))
+ (goto-char (point-min))
+ (looking-at ".*/ruby\\(\\.git\\)?$")))
+ (error))
+ (condition-case ()
+ (with-temp-buffer
+ (when (= 0 (call-process "svn" nil t nil "info" "--xml"))
+ (goto-char (point-min))
+ (search-forward-regexp "<root>.*/ruby</root>" nil)))
+ (error))
+ nil)
(c-set-style "ruby")))
(provide 'ruby-style)
diff --git a/misc/rubydb2x.el b/misc/rubydb2x.el
deleted file mode 100644
index a74265fb0e..0000000000
--- a/misc/rubydb2x.el
+++ /dev/null
@@ -1,104 +0,0 @@
-(require 'gud)
-(provide 'rubydb)
-
-;; ======================================================================
-;; rubydb functions
-
-;;; History of argument lists passed to rubydb.
-(defvar gud-rubydb-history nil)
-
-(defun gud-rubydb-massage-args (file args)
- (cons "-I" (cons "." (cons "-r" (cons "debug" (cons file args))))))
-
-;; There's no guarantee that Emacs will hand the filter the entire
-;; marker at once; it could be broken up across several strings. We
-;; might even receive a big chunk with several markers in it. If we
-;; receive a chunk of text which looks like it might contain the
-;; beginning of a marker, we save it here between calls to the
-;; filter.
-(defvar gud-rubydb-marker-acc "")
-
-(defun gud-rubydb-marker-filter (string)
- (save-match-data
- (setq gud-marker-acc (concat gud-marker-acc string))
- (let ((output ""))
-
- ;; Process all the complete markers in this chunk.
- (while (string-match "\032\032\\([^:\n]*\\):\\([0-9]*\\):.*\n"
- gud-marker-acc)
- (setq
-
- ;; Extract the frame position from the marker.
- gud-last-frame
- (cons (substring gud-marker-acc (match-beginning 1) (match-end 1))
- (string-to-int (substring gud-marker-acc
- (match-beginning 2)
- (match-end 2))))
-
- ;; Append any text before the marker to the output we're going
- ;; to return - we don't include the marker in this text.
- output (concat output
- (substring gud-marker-acc 0 (match-beginning 0)))
-
- ;; Set the accumulator to the remaining text.
- gud-marker-acc (substring gud-marker-acc (match-end 0))))
-
- ;; Does the remaining text look like it might end with the
- ;; beginning of another marker? If it does, then keep it in
- ;; gud-marker-acc until we receive the rest of it. Since we
- ;; know the full marker regexp above failed, it's pretty simple to
- ;; test for marker starts.
- (if (string-match "\032.*\\'" gud-marker-acc)
- (progn
- ;; Everything before the potential marker start can be output.
- (setq output (concat output (substring gud-marker-acc
- 0 (match-beginning 0))))
-
- ;; Everything after, we save, to combine with later input.
- (setq gud-marker-acc
- (substring gud-marker-acc (match-beginning 0))))
-
- (setq output (concat output gud-marker-acc)
- gud-marker-acc ""))
-
- output)))
-
-(defun gud-rubydb-find-file (f)
- (find-file-noselect f))
-
-(defvar rubydb-command-name "ruby"
- "File name for executing ruby.")
-
-;;;###autoload
-(defun rubydb (command-line)
- "Run rubydb on program FILE in buffer *gud-FILE*.
-The directory containing FILE becomes the initial working directory
-and source-file directory for your debugger."
- (interactive
- (list (read-from-minibuffer "Run rubydb (like this): "
- (if (consp gud-rubydb-history)
- (car gud-rubydb-history)
- (concat rubydb-command-name " "))
- nil nil
- '(gud-rubydb-history . 1))))
-
- (gud-overload-functions '((gud-massage-args . gud-rubydb-massage-args)
- (gud-marker-filter . gud-rubydb-marker-filter)
- (gud-find-file . gud-rubydb-find-file)
- ))
- (gud-common-init command-line)
-
- (gud-def gud-break "b %l" "\C-b" "Set breakpoint at current line.")
-; (gud-def gud-remove "clear %l" "\C-d" "Remove breakpoint at current line")
- (gud-def gud-step "s" "\C-s" "Step one source line with display.")
- (gud-def gud-next "n" "\C-n" "Step one line (skip functions).")
- (gud-def gud-cont "c" "\C-r" "Continue with display.")
- (gud-def gud-finish "finish" "\C-f" "Finish executing current function.")
- (gud-def gud-up "up %p" "<" "Up N stack frames (numeric arg).")
- (gud-def gud-down "down %p" ">" "Down N stack frames (numeric arg).")
- (gud-def gud-print "p %e" "\C-p" "Evaluate ruby expression at point.")
-
- (setq comint-prompt-regexp "^(rdb:-) ")
- (setq paragraph-start comint-prompt-regexp)
- (run-hooks 'rubydb-mode-hook)
- )
diff --git a/misc/rubydb3x.el b/misc/rubydb3x.el
deleted file mode 100644
index 9d6bc57d5a..0000000000
--- a/misc/rubydb3x.el
+++ /dev/null
@@ -1,115 +0,0 @@
-(require 'gud)
-(provide 'rubydb)
-
-;; ======================================================================
-;; rubydb functions
-
-;;; History of argument lists passed to rubydb.
-(defvar gud-rubydb-history nil)
-
-(if (fboundp 'gud-overload-functions)
- (defun gud-rubydb-massage-args (file args)
- (cons "-r" (cons "debug" (cons file args))))
- (defun gud-rubydb-massage-args (file args)
- (cons "-r" (cons "debug" args))))
-
-;; There's no guarantee that Emacs will hand the filter the entire
-;; marker at once; it could be broken up across several strings. We
-;; might even receive a big chunk with several markers in it. If we
-;; receive a chunk of text which looks like it might contain the
-;; beginning of a marker, we save it here between calls to the
-;; filter.
-(defvar gud-rubydb-marker-acc "")
-(make-variable-buffer-local 'gud-rubydb-marker-acc)
-
-(defun gud-rubydb-marker-filter (string)
- (setq gud-rubydb-marker-acc (concat gud-rubydb-marker-acc string))
- (let ((output ""))
-
- ;; Process all the complete markers in this chunk.
- (while (string-match "\032\032\\([^:\n]*\\):\\([0-9]*\\):.*\n"
- gud-rubydb-marker-acc)
- (setq
-
- ;; Extract the frame position from the marker.
- gud-last-frame
- (cons (substring gud-rubydb-marker-acc (match-beginning 1) (match-end 1))
- (string-to-int (substring gud-rubydb-marker-acc
- (match-beginning 2)
- (match-end 2))))
-
- ;; Append any text before the marker to the output we're going
- ;; to return - we don't include the marker in this text.
- output (concat output
- (substring gud-rubydb-marker-acc 0 (match-beginning 0)))
-
- ;; Set the accumulator to the remaining text.
- gud-rubydb-marker-acc (substring gud-rubydb-marker-acc (match-end 0))))
-
- ;; Does the remaining text look like it might end with the
- ;; beginning of another marker? If it does, then keep it in
- ;; gud-rubydb-marker-acc until we receive the rest of it. Since we
- ;; know the full marker regexp above failed, it's pretty simple to
- ;; test for marker starts.
- (if (string-match "\032.*\\'" gud-rubydb-marker-acc)
- (progn
- ;; Everything before the potential marker start can be output.
- (setq output (concat output (substring gud-rubydb-marker-acc
- 0 (match-beginning 0))))
-
- ;; Everything after, we save, to combine with later input.
- (setq gud-rubydb-marker-acc
- (substring gud-rubydb-marker-acc (match-beginning 0))))
-
- (setq output (concat output gud-rubydb-marker-acc)
- gud-rubydb-marker-acc ""))
-
- output))
-
-(defun gud-rubydb-find-file (f)
- (save-excursion
- (let ((buf (find-file-noselect f)))
- (set-buffer buf)
-;; (gud-make-debug-menu)
- buf)))
-
-(defvar rubydb-command-name "ruby"
- "File name for executing ruby.")
-
-;;;###autoload
-(defun rubydb (command-line)
- "Run rubydb on program FILE in buffer *gud-FILE*.
-The directory containing FILE becomes the initial working directory
-and source-file directory for your debugger."
- (interactive
- (list (read-from-minibuffer "Run rubydb (like this): "
- (if (consp gud-rubydb-history)
- (car gud-rubydb-history)
- (concat rubydb-command-name " "))
- nil nil
- '(gud-rubydb-history . 1))))
-
- (if (not (fboundp 'gud-overload-functions))
- (gud-common-init command-line 'gud-rubydb-massage-args
- 'gud-rubydb-marker-filter 'gud-rubydb-find-file)
- (gud-overload-functions '((gud-massage-args . gud-rubydb-massage-args)
- (gud-marker-filter . gud-rubydb-marker-filter)
- (gud-find-file . gud-rubydb-find-file)))
- (gud-common-init command-line rubydb-command-name))
-
- (gud-def gud-break "b %l" "\C-b" "Set breakpoint at current line.")
-; (gud-def gud-remove "clear %l" "\C-d" "Remove breakpoint at current line")
- (gud-def gud-step "s" "\C-s" "Step one source line with display.")
- (gud-def gud-next "n" "\C-n" "Step one line (skip functions).")
- (gud-def gud-cont "c" "\C-r" "Continue with display.")
- (gud-def gud-finish "finish" "\C-f" "Finish executing current function.")
- (gud-def gud-up "up %p" "<" "Up N stack frames (numeric arg).")
- (gud-def gud-down "down %p" ">" "Down N stack frames (numeric arg).")
- (gud-def gud-print "p %e" "\C-p" "Evaluate ruby expression at point.")
-
- (setq comint-prompt-regexp "^(rdb:-) ")
- (if (boundp 'comint-last-output-start)
- (set-marker comint-last-output-start (point)))
- (set (make-local-variable 'paragraph-start) comint-prompt-regexp)
- (run-hooks 'rubydb-mode-hook)
- )
diff --git a/misc/test_lldb_cruby.rb b/misc/test_lldb_cruby.rb
new file mode 100644
index 0000000000..bd58619ac2
--- /dev/null
+++ b/misc/test_lldb_cruby.rb
@@ -0,0 +1,40 @@
+#!/usr/bin/env ruby
+require 'open3'
+require 'tempfile'
+require 'test/unit'
+
+class TestLLDBInit < Test::Unit::TestCase
+ def assert_rp(expr, pattern, message=nil)
+ Tempfile.create('lldb') do |tf|
+ tf.puts <<eom
+target create ./miniruby
+command script import -r misc/lldb_cruby.py
+b rb_inspect
+run -e'p #{expr}'
+rp obj
+eom
+ tf.flush
+ o, s = Open3.capture2('lldb', '-b', '-s', tf.path)
+ assert_true s.success?, message
+ assert_match /^\(lldb\) rp obj\n#{pattern}/, o, message
+ end
+ end
+
+ def test_rp_object
+ assert_rp 'Object.new', 'T_OBJECT'
+ end
+
+ def test_rp_regex
+ assert_rp '/foo/', '[(]Regex'
+ end
+
+ def test_rp_symbol
+ assert_rp ':abcde', /T_SYMBOL: \(\h+\)/
+ end
+
+ def test_rp_string
+ assert_rp '"abc"', /\(char \[\d+\]\) ary = "abc"/
+ assert_rp "\"\u3042\"", /\(char \[\d+\]\) ary = "\u3042"/
+ assert_rp '"' + "\u3042"*10 + '"', /\(RString::\(anonymous struct\)\) heap = \{/
+ end
+end
diff --git a/misc/tsan_suppressions.txt b/misc/tsan_suppressions.txt
new file mode 100644
index 0000000000..5492500e7f
--- /dev/null
+++ b/misc/tsan_suppressions.txt
@@ -0,0 +1,109 @@
+# TSan: ThreadSanitizer
+# https://github.com/google/sanitizers/wiki/threadsanitizersuppressions
+#
+# This file describes a number of places where TSAN detects problems in CRuby.
+# Many of these indicate bugs. Others are benign (ex. data races that can be
+# replaced with relaxed atomic loads)
+#
+# Usage:
+# Configure with:
+# ./configure cflags='-fsanitize=thread' CC=clang
+# Build and run with:
+# TSAN_OPTIONS="suppressions=$(pwd)/misc/tsan_suppressions.txt:die_after_fork=0"
+#
+# Other useful TSAN_OPTIONS:
+# * halt_on_error=1
+# * strip_path_prefix=$(pwd)/
+
+# Namespaces
+race_top:push_subclass_entry_to_list
+
+# sub_nounderflow includes non-atomic read, possibly other issue
+race:objspace_malloc_increase_body
+
+# Signals and ubf
+race:unregister_ubf_list
+
+# It's already crashing. We're doing our best
+signal:rb_vm_bugreport
+race:check_reserved_signal_
+
+race_top:rb_check_deadlock
+
+# vm->ractor.sched.grq_cnt++
+race_top:ractor_sched_enq
+race_top:ractor_sched_deq
+
+# Race between vm_remove_ractor writing ractor count and
+# native_thread_check_and_create_shared reading it during thread creation.
+# The write happens when a ractor thread exits, the read happens when
+# checking if new shared threads need to be created.
+race:vm_remove_ractor
+
+# th->sched.finished at end of co_start
+race_top:rb_thread_sched_mark_zombies
+
+# Races against timer thread setting th->sched.waiting_reason.flags
+race_top:thread_sched_wait_events
+
+# At thread start
+race_top:rb_ractor_set_current_ec_
+
+# TSan reports a lock-order-inversion between thread_sched_lock_ and this lock.
+# It's unclear if that can cause a deadlock since the lock is on self
+deadlock:ractor_lock_self
+
+# TSan reports a deadlock when reacquiring the this lock after a barrier, but
+# we know the other threads have been stopped
+deadlock:rb_ractor_sched_barrier_start
+
+# RVALUE_AGE_SET manipulates flag bits on objects which may be accessed in Ractors
+race_top:RVALUE_AGE_SET
+
+# Inline caches and call cache updates
+# Multiple threads can race when updating shared call caches during method lookups
+# and argument forwarding. These races involve reading/writing cd->cc fields.
+race_top:vm_cc_call_set
+race_top:vm_cc_class_check
+race_top:vm_search_cc
+race_top:vm_search_method_slowpath0
+race_top:rb_vm_opt_getconstant_path
+race_top:vm_ic_attr_index_set
+race:vm_ic_update
+race:vm_caller_setup_fwd_args
+
+# Race in shape_get_next where multiple threads simultaneously access and modify
+# RCLASS_MAX_IV_COUNT and RCLASS_VARIATION_COUNT fields in class objects.
+# One thread reads the field while another thread calls RCLASS_SET_MAX_IV_COUNT.
+# This happens during instance variable shape transitions in multi-threaded code.
+race:shape_get_next
+
+# Non-atomic reads/writes
+race:gccct_method_search
+
+# Ignore exit for now
+race:rb_ec_finalize
+race:rb_ec_cleanup
+
+# TSan doesn't work well post-fork, this raises errors when creating the new
+# timer thread
+race:after_fork_ruby
+
+# Sets objspace->flags.dont_incremental while writebarrier may be running
+race_top:objspace_each_exec
+race_top:objspace_each_objects_ensure
+
+# Non-atomic lazy initialized static variable
+race_top:rbimpl_intern_const
+
+# Setting def->aliased bitfield non-atomically
+race_top:method_definition_addref
+
+# Switching to setting up tracing. Likely other ractors should be stopped for this.
+race_top:encoded_iseq_trace_instrument
+race:rb_iseq_trace_set_all
+race:rb_tracepoint_enable
+
+# GC enable/disable flag modifications race with object allocation flag reads
+race_top:rb_gc_impl_gc_disable
+race_top:rb_gc_impl_gc_enable
diff --git a/misc/yjit_perf.py b/misc/yjit_perf.py
new file mode 100755
index 0000000000..61434e5eb4
--- /dev/null
+++ b/misc/yjit_perf.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+import os
+import sys
+from collections import Counter, defaultdict
+import os.path
+
+# Aggregating cycles per symbol and dso
+total_cycles = 0
+category_cycles = Counter()
+detailed_category_cycles = defaultdict(Counter)
+categories = set()
+
+def truncate_symbol(symbol, max_length=50):
+ """ Truncate the symbol name to a maximum length """
+ return symbol if len(symbol) <= max_length else symbol[:max_length-3] + '...'
+
+def categorize_symbol(dso, symbol):
+ """ Categorize the symbol based on the defined criteria """
+ if dso == 'sqlite3_native.so':
+ return '[sqlite3]'
+ elif 'SHA256' in symbol:
+ return '[sha256]'
+ elif symbol.startswith('[JIT] gen_send'):
+ return '[JIT send]'
+ elif symbol.startswith('[JIT]'):
+ return '[JIT code]'
+ elif '::' in symbol or symbol.startswith('yjit::') or symbol.startswith('_ZN4yjit'):
+ return '[YJIT compile]'
+ elif symbol.startswith('rb_vm_') or symbol.startswith('vm_') or symbol in {
+ "rb_call0", "callable_method_entry_or_negative", "invoke_block_from_c_bh",
+ "rb_funcallv_scope", "setup_parameters_complex", "rb_yield"}:
+ return '[interpreter]'
+ elif symbol.startswith('rb_hash_') or symbol.startswith('hash_'):
+ return '[rb_hash_*]'
+ elif symbol.startswith('rb_ary_') or symbol.startswith('ary_'):
+ return '[rb_ary_*]'
+ elif symbol.startswith('rb_str_') or symbol.startswith('str_'):
+ return '[rb_str_*]'
+ elif symbol.startswith('rb_sym') or symbol.startswith('sym_'):
+ return '[rb_sym_*]'
+ elif symbol.startswith('rb_st_') or symbol.startswith('st_'):
+ return '[rb_st_*]'
+ elif symbol.startswith('rb_ivar_') or 'shape' in symbol:
+ return '[ivars]'
+ elif 'match' in symbol or symbol.startswith('rb_reg') or symbol.startswith('onig'):
+ return '[regexp]'
+ elif 'alloc' in symbol or 'free' in symbol or 'gc' in symbol:
+ return '[GC]'
+ elif 'pthread' in symbol and 'lock' in symbol:
+ return '[pthread lock]'
+ else:
+ return symbol # Return the symbol itself for uncategorized symbols
+
+def process_event(event):
+ global total_cycles, category_cycles, detailed_category_cycles, categories
+
+ full_dso = event.get("dso", "Unknown_dso")
+ dso = os.path.basename(full_dso)
+ symbol = event.get("symbol", "[unknown]")
+ cycles = event["sample"]["period"]
+ total_cycles += cycles
+
+ category = categorize_symbol(dso, symbol)
+ category_cycles[category] += cycles
+ detailed_category_cycles[category][(dso, symbol)] += cycles
+
+ if category.startswith('[') and category.endswith(']'):
+ categories.add(category)
+
+def trace_end():
+ if total_cycles == 0:
+ return
+
+ print("Aggregated Event Data:")
+ print("{:<20} {:<50} {:>20} {:>15}".format("[dso]", "[symbol or category]", "[top-most cycle ratio]", "[num cycles]"))
+
+ for category, cycles in category_cycles.most_common():
+ ratio = (cycles / total_cycles) * 100
+ dsos = {dso for dso, _ in detailed_category_cycles[category]}
+ dso_display = next(iter(dsos)) if len(dsos) == 1 else "Multiple DSOs"
+ print("{:<20} {:<50} {:>20.2f}% {:>15}".format(dso_display, truncate_symbol(category), ratio, cycles))
+
+ # Category breakdown
+ for category in categories:
+ symbols = detailed_category_cycles[category]
+ category_total = sum(symbols.values())
+ category_ratio = (category_total / total_cycles) * 100
+ print(f"\nCategory: {category} ({category_ratio:.2f}%)")
+ print("{:<20} {:<50} {:>20} {:>15}".format("[dso]", "[symbol]", "[top-most cycle ratio]", "[num cycles]"))
+ for (dso, symbol), cycles in symbols.most_common():
+ symbol_ratio = (cycles / category_total) * 100
+ print("{:<20} {:<50} {:>20.2f}% {:>15}".format(dso, truncate_symbol(symbol), symbol_ratio, cycles))
+
+# There are two ways to use this script:
+# 1) perf script -s misc/yjit_perf.py -- native interface
+# 2) perf script > perf.txt && misc/yjit_perf.py perf.txt -- hack, which doesn't require perf with Python support
+#
+# In both cases, __name__ is "__main__". The following code implements (2) when sys.argv is 2.
+if __name__ == "__main__" and len(sys.argv) == 2:
+ if len(sys.argv) != 2:
+ print("Usage: yjit_perf.py <filename>")
+ sys.exit(1)
+
+ with open(sys.argv[1], "r") as file:
+ for line in file:
+ # [Example]
+ # ruby 78207 3482.848465: 1212775 cpu_core/cycles:P/: 5c0333f682e1 [JIT] getlocal_WC_0+0x0 (/tmp/perf-78207.map)
+ row = line.split(maxsplit=6)
+
+ period = row[3] # "1212775"
+ symbol, dso = row[6].split(" (") # "[JIT] getlocal_WC_0+0x0", "/tmp/perf-78207.map)\n"
+ symbol = symbol.split("+")[0] # "[JIT] getlocal_WC_0"
+ dso = dso.split(")")[0] # "/tmp/perf-78207.map"
+
+ process_event({"dso": dso, "symbol": symbol, "sample": {"period": int(period)}})
+ trace_end()