diff options
Diffstat (limited to 'misc')
33 files changed, 3228 insertions, 2742 deletions
diff --git a/misc/.vscode/launch.json b/misc/.vscode/launch.json deleted file mode 100644 index 51bfef09d7..0000000000 --- a/misc/.vscode/launch.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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 deleted file mode 100644 index 7b1a38c536..0000000000 --- a/misc/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rust-analyzer.cargo.features": [ - "disasm", - ], - "rust-analyzer.cargo.unsetTest": [ - "yjit", - ], -} diff --git a/misc/.vscode/tasks.json b/misc/.vscode/tasks.json deleted file mode 100644 index 045fe7e5c0..0000000000 --- a/misc/.vscode/tasks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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 86b680e724..08a9010f58 100644 --- a/misc/README +++ b/misc/README @@ -1,7 +1,12 @@ -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 +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 https://github.com/ruby-debug/ also. diff --git a/misc/call_fuzzer.rb b/misc/call_fuzzer.rb deleted file mode 100644 index c3f9f90490..0000000000 --- a/misc/call_fuzzer.rb +++ /dev/null @@ -1,372 +0,0 @@ -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 deleted file mode 100755 index cf4ec76fe8..0000000000 --- a/misc/call_fuzzer.sh +++ /dev/null @@ -1,13 +0,0 @@ -# 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 deleted file mode 100755 index a94eea5046..0000000000 --- a/misc/expand_tabs.rb +++ /dev/null @@ -1,208 +0,0 @@ -#!/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[ - abbrev - base64 - benchmark - bundler - cmath - csv - debug - delegate - did_you_mean - drb - english - erb - fileutils - find - forwardable - getoptlong - ipaddr - irb - logger - mutex_m - net-http - net-protocol - observer - open3 - open-uri - optparse - ostruct - pp - prettyprint - prime - pstore - rdoc - readline - reline - resolv - resolv-replace - rexml - rinda - rss - rubygems - scanf - securerandom - set - shellwords - singleton - tempfile - thwait - time - timeout - tmpdir - un - tsort - uri - weakref - yaml -] - -DEFAULT_GEM_EXTS = %w[ - bigdecimal - cgi - date - digest - etc - fcntl - fiddle - io-console - io-nonblock - io-wait - json - nkf - openssl - pathname - psych - racc - readline-ext - stringio - strscan - syslog - win32ole - 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 deleted file mode 100644 index 6034a389bb..0000000000 --- a/misc/gdb.py +++ /dev/null @@ -1,181 +0,0 @@ -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 new file mode 100644 index 0000000000..b3f4f10267 --- /dev/null +++ b/misc/inf-ruby.el @@ -0,0 +1,418 @@ +;;; -*-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) +)) + +;;;###autoload +(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))))))))) + +;;;###autoload +(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 index 0707bb42d3..e954d1be00 100755 --- a/misc/lldb_cruby.py +++ b/misc/lldb_cruby.py @@ -6,161 +6,10 @@ # Test: misc/test_lldb_cruby.rb # -from __future__ import print_function import lldb +import commands import os -import inspect -import sys import shlex -import platform -import glob - -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("struct 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() @@ -169,18 +18,10 @@ def lldb_init(debugger): 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()): + for i in xrange(0, members.GetSize()): member = members.GetTypeEnumMemberAtIndex(i) name = member.GetName() value = member.GetValueAsUnsigned() @@ -192,21 +33,21 @@ def lldb_init(debugger): 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) + clen = int(rstring.GetValueForExpressionPath(".as.heap.len").value, 0) else: - cptr = int(rstring.GetValueForExpressionPath(".as.embed.ary").location, 0) + cptr = int(rstring.GetValueForExpressionPath(".as.ary").value, 0) + clen = (flags & RSTRING_EMBED_LEN_MASK) >> RSTRING_EMBED_LEN_SHIFT return cptr, clen -def output_string(debugger, result, rstring): +def output_string(ctx, rstring): cptr, clen = string2cstr(rstring) - append_expression(debugger, "*(const char (*)[%d])%0#x" % (clen, cptr), result) + expr = 'printf("%%.*s", (size_t)%d, (const char*)%d)' % (clen, cptr) + ctx.frame.EvaluateExpression(expr) def fixnum_p(x): return x & RUBY_FIXNUM_FLAG != 0 @@ -214,24 +55,7 @@ def fixnum_p(x): 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() @@ -242,198 +66,70 @@ def lldb_rp(debugger, command, result, internal_dict): val = target.EvaluateExpression(command) error = val.GetError() if error.Fail(): - print(error, file=result) + print >> result, error 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) + print >> result, 'false' elif num == RUBY_Qtrue: - print('true', file=result) + print >> result, 'true' elif num == RUBY_Qnil: - print('nil', file=result) + print >> result, 'nil' elif num == RUBY_Qundef: - print('undef', file=result) + print >> result, 'undef' elif fixnum_p(num): - print(num >> 1, file=result) + print >> result, num >> 1 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) + debugger.HandleCommand("print rb_float_value(%0#x)" % val.GetValueAsUnsigned()) elif num & RUBY_IMMEDIATE_MASK: - print('immediate(%x)' % num, file=result) + print >> result, 'immediate(%x)' % num else: tRBasic = target.FindFirstType("struct RBasic").GetPointerType() - tRValue = target.FindFirstType("struct RVALUE") - 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] " + print >> result, "[PROMOTED] " flType = flags & RUBY_T_MASK if flType == RUBY_T_NONE: - print('T_NONE: %s%s' % (flaginfo, val.Dereference()), file=result) + print >> result, 'T_NONE: %s' % val.Dereference() elif flType == RUBY_T_NIL: - print('T_NIL: %s%s' % (flaginfo, val.Dereference()), file=result) + print >> result, 'T_NIL: %s' % val.Dereference() 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) + tRObject = target.FindFirstType("struct RObject").GetPointerType() + val = val.Cast(tRObject) + print >> result, 'T_OBJECT: %s' % val.Dereference() 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") + val = val.Cast(tRString) + if flags & RSTRING_NOEMBED: + print >> result, val.GetValueForExpressionPath("->as.heap") 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)) + print >> result, val.GetValueForExpressionPath("->as.ary") 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) + len = ((flags & (RUBY_FL_USER3|RUBY_FL_USER4)) >> (RUBY_FL_USHIFT+3)) + print >> result, "T_ARRAY: len=%d (embed)" % len + if len == 0: + print >> result, "{(empty)}" 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) + print >> result, val.GetValueForExpressionPath("->as.ary") 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) + print >> result, "T_ARRAY: len=%d " % len + #print >> result, val.GetValueForExpressionPath("->as.heap") + if flags & RUBY_FL_USER2: + shared = val.GetValueForExpressionPath("->as.heap.aux.shared").GetValueAsUnsigned() + print >> result, "(shared) shared=%016x " % shared + else: + capa = val.GetValueForExpressionPath("->as.heap.aux.capa").GetValueAsSigned() + print >> result, "(ownership) capa=%d " % capa + if len == 0: + print >> result, "{(empty)}" + else: + debugger.HandleCommand("expression -Z %d -fx -- (const VALUE*)((struct RArray*)%d)->as.heap.ptr" % (len, val.GetValueAsUnsigned())) + debugger.HandleCommand("p (struct RArray *) %0#x" % val.GetValueAsUnsigned()) def count_objects(debugger, command, ctx, result, internal_dict): objspace = ctx.frame.EvaluateExpression("ruby_current_vm->objspace") @@ -445,7 +141,7 @@ def count_objects(debugger, command, ctx, result, internal_dict): counts[t] = 0 for i in range(0, num_pages): - print("\rcounting... %d/%d" % (i, num_pages), end="") + print "\rcounting... %d/%d" % (i, num_pages), page = objspace.GetValueForExpressionPath('.heap_pages.sorted[%d]' % i) p = page.GetChildMemberWithName('start') num_slots = page.GetChildMemberWithName('total_slots').unsigned @@ -456,43 +152,13 @@ def count_objects(debugger, command, ctx, result, internal_dict): counts[obj_type] += 1 total += num_slots - print("\rTOTAL: %d, FREE: %d" % (total, counts[0x00])) + print "\rTOTAL: %d, FREE: %d" % (total, counts[0x00]) for sym in value_types: - print("%s: %d" % (sym, counts[globals()[sym]])) + 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: @@ -500,250 +166,12 @@ def dump_node(debugger, command, ctx, result, internal_dict): 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"): - tRValue = target.FindFirstType("struct RVALUE") - tUintPtr = target.FindFirstType("uintptr_t") # bits_t - - num_in_page = (object_address & HEAP_PAGE_ALIGN_MASK) // tRValue.GetByteSize(); - 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("struct RBasic") - self.tRValue = target.FindFirstType("struct RVALUE") - - 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() - tRVALUE = target.FindFirstType("struct RVALUE") - - while fl_start > 0: - freelist.append(fl_start) - obj_addr = lldb.SBAddress(fl_start, target) - obj = target.CreateValueFromAddress("object", obj_addr, tRVALUE) - fl_start = obj.GetChildMemberWithName("as").GetChildMemberWithName("free").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}") + output_string(ctx, dump) 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.lldb_rp 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.") + lldb_init(debugger) + print "lldb scripts for ruby has been installed." diff --git a/misc/lldb_disasm.py b/misc/lldb_disasm.py deleted file mode 100644 index ab759f009a..0000000000 --- a/misc/lldb_disasm.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/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 deleted file mode 100644 index 21014a993e..0000000000 --- a/misc/lldb_rb/commands/command_template.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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 deleted file mode 100644 index b56a3eae4e..0000000000 --- a/misc/lldb_rb/commands/heap_page_command.py +++ /dev/null @@ -1,26 +0,0 @@ -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.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(debugger, "(struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result) - self._append_expression(debugger, "*(struct heap_page *) %0#x" % page.GetValueAsUnsigned(), result) - - 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 deleted file mode 100644 index 00da4834bf..0000000000 --- a/misc/lldb_rb/commands/print_flags_command.py +++ /dev/null @@ -1,31 +0,0 @@ -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("struct 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_PROMOTED0", "RUBY_FL_PROMOTED1", "RUBY_FL_FINALIZE", - "RUBY_FL_SHAREABLE", "RUBY_FL_EXIVAR", "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 deleted file mode 100644 index 6ee859ebf6..0000000000 --- a/misc/lldb_rb/commands/rb_id2str_command.py +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 8bae911457..0000000000 --- a/misc/lldb_rb/commands/rclass_ext_command.py +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 06b2516d50..0000000000 --- a/misc/lldb_rb/commands/rp_command.py +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 9cd56eccb0..0000000000 --- a/misc/lldb_rb/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 785a54b3e3..0000000000 --- a/misc/lldb_rb/lldb_interface.py +++ /dev/null @@ -1,7 +0,0 @@ -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() diff --git a/misc/lldb_rb/rb_base_command.py b/misc/lldb_rb/rb_base_command.py deleted file mode 100644 index 70a5addd6d..0000000000 --- a/misc/lldb_rb/rb_base_command.py +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index 86b38dbbbd..0000000000 --- a/misc/lldb_rb/rb_heap_structs.py +++ /dev/null @@ -1,143 +0,0 @@ -import lldb -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("struct RBasic").GetPointerType() - self.tRValue = self.target.FindFirstType("struct RVALUE") - - 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"): - tRValue = self.target.FindFirstType("struct RVALUE") - tUintPtr = self.target.FindFirstType("uintptr_t") # bits_t - - num_in_page = (self.val.GetValueAsUnsigned() & HEAP_PAGE_ALIGN_MASK) // tRValue.GetByteSize(); - 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): - return self.val.Cast(self.tRValue.GetPointerType()).GetValueForExpressionPath("->as."+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 deleted file mode 100644 index a6bbd385cd..0000000000 --- a/misc/lldb_rb/utils.py +++ /dev/null @@ -1,513 +0,0 @@ -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 _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) - - 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) - flag = val.GetValueForExpressionPath("->typed_flag") - - if flag.GetValueAsUnsigned() == 1: - print("T_DATA: %s" % - val.GetValueForExpressionPath("->type->wrap_struct_name"), - file=self.result) - self._append_expression("*(struct RTypedData *) %0#x" % val.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 f77d937c87..8a59ec2dda 100644 --- a/misc/rb_optparse.bash +++ b/misc/rb_optparse.bash @@ -1,5 +1,4 @@ -# -*- bash -*- -# +#! /bin/bash # Completion for bash: # # (1) install this file, @@ -17,5 +16,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 258d4f856c..d53170c5f7 100644..100755 --- a/misc/rb_optparse.zsh +++ b/misc/rb_optparse.zsh @@ -1,5 +1,4 @@ -# -*- zsh -*- -# +#!/bin/zsh # Completion for zsh: # (based on <http://d.hatena.ne.jp/rubikitch/20071002/zshcomplete>) # @@ -8,13 +7,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' >> "${ZDOTDIR:-~}/.zshrc" -# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> "${ZDOTDIR:-~}/.zshrc" -# echo 'autoload -U compinit; compinit' >> "${ZDOTDIR:-~}/.zshrc" +# echo '. ~/.zsh.d/rb_optparse.zsh' >> ~/.zshrc +# echo 'fpath=(~/.zsh.d/Completion $fpath)' >> ~/.zshrc +# echo 'autoload -U compinit; compinit' >> ~/.zshrc # # (3) restart zsh. # -# (4) generate completion files once. +# (4) geneate completion files once. # generate-complete-function/ruby/optparse COMMAND1 # generate-complete-function/ruby/optparse COMMAND2 # @@ -25,8 +24,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 new file mode 100644 index 0000000000..c26c2ee564 --- /dev/null +++ b/misc/rdoc-mode.el @@ -0,0 +1,166 @@ +;; +;; rdoc-mode.el +;; Major mode for RDoc editing +;; + +;; Created: Fri Sep 18 09:04:49 JST 2009 + +;; License: Ruby's + +(require 'derived) + +;;;###autoload +(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 (&optional justify region) + "Fills paragraph, except for cited region" + (interactive (progn + (barf-if-buffer-read-only) + (list (if current-prefix-arg 'full)))) + (save-excursion + (beginning-of-line) + (save-restriction + (let ((pos (point)) beg end indent hanging) + (cond + ((looking-at "^ +\\(\\*\\s *\\)") + (setq indent (- (match-end 0) (match-beginning 0)) + hanging (- (match-end 1) (match-beginning 1)))) + ((looking-at "^ +") + (setq indent (- (match-end 0) (match-beginning 0))) + (when (and (re-search-backward "^[^ ]\\|^\\( *\\(\\* *\\)\\)" nil t) + (match-beginning 1) + (= indent (- (match-end 1) (match-beginning 1)))) + (setq hanging (- (match-end 2) (match-beginning 2))) + (setq beg (match-beginning 1)))) + ((setq beg t))) + (when beg + (when indent + (goto-char pos) + (while (progn (beginning-of-line 2) + (and (looking-at "^\\( +\\)\\S ") + (= indent (- (match-end 1) (match-beginning 1)))))) + (setq end (point)) + (when (and beg (not region)) + (setq region (list beg end)) + (narrow-to-region beg end) + )) + (goto-char pos) + (fill-paragraph justify region) + (when (and indent + (or (goto-char beg) t) + (or (beginning-of-line 2) t) + (looking-at "^\\( +\\)") + (= (- indent hanging) (- (match-end 0) (match-beginning 0)))) + (insert-char ?\s hanging) + (beginning-of-line) + (narrow-to-region (point) end) + (fill-paragraph justify (list (point) end)))))))) + +(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 new file mode 100644 index 0000000000..432adfedb6 --- /dev/null +++ b/misc/ruby-additional.el @@ -0,0 +1,181 @@ +;;; ruby-additional.el --- ruby-mode extensions yet to be merged into Emacs + +;; Authors: Yukihiro Matsumoto, Nobuyoshi Nakada, Akinori MUSHA +;; URL: https://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/misc/ +;; Created: 3 Sep 2012 +;; Package-Requires: ((emacs "24.3") (ruby-mode "1.2")) +;; Keywords: ruby, languages + +;;; Commentary: +;; +;; This package contains ruby-mode extensions yet to be merged into +;; the latest released version of Emacs distribution. For older +;; versions of Emacs, use ruby-mode.el bundled with CRuby. + +;;; Code: + +(eval-when-compile + (require 'ruby-mode)) + +(eval-after-load 'ruby-mode + '(progn + (define-key ruby-mode-map "\C-c\C-e" 'ruby-insert-end) + + (defun ruby-insert-end () + (interactive) + (if (eq (char-syntax (preceding-char)) ?w) + (insert " ")) + (insert "end") + (save-excursion + (if (eq (char-syntax (following-char)) ?w) + (insert " ")) + (ruby-indent-line t) + (end-of-line))) + + (defconst ruby-default-encoding-map + '((us-ascii . nil) ;; Do not put coding: us-ascii + (utf-8 . nil) ;; Do not put coding: utf-8 + (shift-jis . cp932) ;; Emacs charset name of Shift_JIS + (shift_jis . cp932) ;; MIME charset name of Shift_JIS + (japanese-cp932 . cp932)) ;; Emacs charset name of CP932 + ) + + (custom-set-default 'ruby-encoding-map ruby-default-encoding-map) + + (defcustom ruby-encoding-map ruby-default-encoding-map + "Alist to map encoding name from Emacs to Ruby. +Associating an encoding name with nil means it needs not be +explicitly declared in magic comment." + :type '(repeat (cons (symbol :tag "From") (symbol :tag "To"))) + :group 'ruby) + + (defun ruby-mode-set-encoding () + "Insert or update a magic comment header with the proper encoding. +`ruby-encoding-map' is looked up to convert an encoding name from +Emacs to Ruby." + (let* ((nonascii + (save-excursion + (widen) + (goto-char (point-min)) + (re-search-forward "[^\0-\177]" nil t))) + (coding-system + (or coding-system-for-write + buffer-file-coding-system)) + (coding-system + (and coding-system + (coding-system-change-eol-conversion coding-system nil))) + (coding-system + (and coding-system + (or + (coding-system-get coding-system :mime-charset) + (let ((coding-type (coding-system-get coding-system :coding-type))) + (cond ((eq coding-type 'undecided) + (if nonascii + (or (and (coding-system-get coding-system :prefer-utf-8) + 'utf-8) + (coding-system-get default-buffer-file-coding-system :coding-type) + 'ascii-8bit))) + ((memq coding-type '(utf-8 shift-jis)) + coding-type) + (t coding-system)))))) + (coding-system + (or coding-system + 'us-ascii)) + (coding-system + (let ((cons (assq coding-system ruby-encoding-map))) + (if cons (cdr cons) coding-system))) + (coding-system + (and coding-system + (symbol-name coding-system)))) + (if coding-system + (save-excursion + (widen) + (goto-char (point-min)) + (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")))))))) + + (define-key ruby-mode-map "\C-cU" 'ruby-encode-decode-unicode) + + (defun ruby-encode-unicode (beg end) + "Convert non-ascii string in the given region to \\u{} form." + (interactive "r") + (setq end (set-marker (make-marker) end)) + (goto-char beg) + (while (and (< (point) end) + (re-search-forward "\\([\C-@-\C-I\C-K\C-_\C-?]+\\)\\|[^\C-@-\C-?]+" end t)) + (let ((str (match-string-no-properties 0)) sep b e f) + (if (match-beginning 1) + (setq b "" e "" sep "" + f (lambda (c) + (cond ((= c ?\t) "\\t") + ((= c ?\r) "\\r") + ((= c ?\e) "\\e") + ((= c ?\f) "\\f") + ((= c ?\b) "\\b") + ((= c ?\v) "\\v") + ((= c ?\C-?) "\\c?") + ((concat "\\c" (char-to-string (logior c #x40))))))) + (setq b "\\u{" e "}" sep " " f (lambda (c) (format "%x" c)))) + (setq str (mapconcat f str sep)) + (delete-region (match-beginning 0) (match-end 0)) + (insert b str e)))) + + (defun ruby-decode-unicode (beg end) + "Convert escaped Unicode in the given region to raw string." + (interactive "r") + (setq end (set-marker (make-marker) end)) + (goto-char beg) + (while (and (< (point) end) + (re-search-forward "\\\\u\\([0-9a-fA-F]\\{4\\}\\)\\|\\\\u{\\([0-9a-fA-F \t]+\\)}" end t)) + (let ((b (match-beginning 0)) (e (match-end 0)) + (s (match-string-no-properties 1))) + (if s + (setq s (cons s nil)) + (goto-char (match-beginning 2)) + (while (looking-at "[ \t]*\\([0-9a-fA-F]+\\)") + (setq s (cons (match-string-no-properties 1) s)) + (goto-char (match-end 0)))) + (setq s (mapconcat (lambda (c) (format "%c" (string-to-int c 16))) + (nreverse s) "")) + (delete-region b e) + (insert s)) + )) + + (defun ruby-encode-decode-unicode (dec beg end) + "Convert Unicode <-> \\u{} in the given region." + (interactive "P\nr") + (if dec (ruby-decode-unicode beg end) (ruby-encode-unicode beg end))) + + (defun ruby-insert-heredoc-code-block (arg) + "Insert indented here document code block" + (interactive "P") + (let ((c (if arg "~" "-"))) + (insert "\"#{<<" c "\"{#\"}\\n#{<<" c "'};'}\"")) + (end-of-line) + (if (eobp) (insert "\n") (forward-char)) + (indent-region (point) + (progn (insert "{#\n" "};\n") (point))) + (beginning-of-line 0)) + (define-key ruby-mode-map "\C-cH" 'ruby-insert-heredoc-code-block) + )) + +;; monkey-patching ruby-mode.el in Emacs 24, as r49872. +(when (and (boundp 'ruby-syntax-before-regexp-re) + (not (string-match ruby-syntax-before-regexp-re "foo {|" 1))) + (replace-regexp-in-string "\\[\\[" "\\&{|" ruby-syntax-before-regexp-re)) + +(provide 'ruby-additional) + +;;; ruby-additional.el ends here diff --git a/misc/ruby-electric.el b/misc/ruby-electric.el new file mode 100644 index 0000000000..61e84d2adb --- /dev/null +++ b/misc/ruby-electric.el @@ -0,0 +1,583 @@ +;;; ruby-electric.el --- Minor mode for electrically editing ruby code +;; +;; Authors: Dee Zsombor <dee dot zsombor at gmail dot com> +;; Yukihiro Matsumoto +;; Nobuyoshi Nakada +;; Akinori MUSHA <knu@iDaemons.org> +;; Jakub Kuźma <qoobaa@gmail.com> +;; Maintainer: Akinori MUSHA <knu@iDaemons.org> +;; Created: 6 Mar 2005 +;; URL: https://github.com/knu/ruby-electric.el +;; Keywords: languages ruby +;; License: The same license terms as Ruby +;; Version: 2.3.1 + +;;; Commentary: +;; +;; `ruby-electric-mode' accelerates code writing in ruby by making +;; some keys "electric" and automatically supplying with closing +;; parentheses and "end" as appropriate. +;; +;; This work was originally inspired by a code snippet posted by +;; [Frederick Ros](https://github.com/sleeper). +;; +;; Add the following line to enable ruby-electric-mode under +;; ruby-mode. +;; +;; (eval-after-load "ruby-mode" +;; '(add-hook 'ruby-mode-hook 'ruby-electric-mode)) +;; +;; Type M-x customize-group ruby-electric for configuration. + +;;; Code: + +(require 'ruby-mode) + +(eval-when-compile + (require 'cl)) + +(defgroup ruby-electric nil + "Minor mode providing electric editing commands for ruby files" + :group 'ruby) + +(defconst ruby-electric-expandable-bar-re + "\\s-\\(do\\|{\\)\\s-*|") + +(defconst ruby-electric-delimiters-alist + '((?\{ :name "Curly brace" :handler ruby-electric-curlies :closing ?\}) + (?\[ :name "Square brace" :handler ruby-electric-matching-char :closing ?\]) + (?\( :name "Round brace" :handler ruby-electric-matching-char :closing ?\)) + (?\' :name "Quote" :handler ruby-electric-matching-char) + (?\" :name "Double quote" :handler ruby-electric-matching-char) + (?\` :name "Back quote" :handler ruby-electric-matching-char) + (?\| :name "Vertical bar" :handler ruby-electric-bar) + (?\# :name "Hash" :handler ruby-electric-hash))) + +(defvar ruby-electric-matching-delimeter-alist + (apply 'nconc + (mapcar #'(lambda (x) + (let ((delim (car x)) + (plist (cdr x))) + (if (eq (plist-get plist :handler) 'ruby-electric-matching-char) + (list (cons delim (or (plist-get plist :closing) + delim)))))) + ruby-electric-delimiters-alist))) + +(defvar ruby-electric-expandable-keyword-re) + +(defmacro ruby-electric--try-insert-and-do (string &rest body) + (declare (indent 1)) + `(let ((before (point)) + (after (progn + (insert ,string) + (point)))) + (unwind-protect + (progn ,@body) + (delete-region before after) + (goto-char before)))) + +(defconst ruby-modifier-beg-symbol-re + (regexp-opt ruby-modifier-beg-keywords 'symbols)) + +(defun ruby-electric--modifier-keyword-at-point-p () + "Test if there is a modifier keyword at point." + (and (looking-at ruby-modifier-beg-symbol-re) + (let ((end (match-end 1))) + (not (looking-back "\\.")) + (save-excursion + (let ((indent1 (ruby-electric--try-insert-and-do "\n" + (ruby-calculate-indent))) + (indent2 (save-excursion + (goto-char end) + (ruby-electric--try-insert-and-do " x\n" + (ruby-calculate-indent))))) + (= indent1 indent2)))))) + +(defconst ruby-block-mid-symbol-re + (regexp-opt ruby-block-mid-keywords 'symbols)) + +(defun ruby-electric--block-mid-keyword-at-point-p () + "Test if there is a block mid keyword at point." + (and (looking-at ruby-block-mid-symbol-re) + (looking-back "^\\s-*"))) + +(defconst ruby-block-beg-symbol-re + (regexp-opt ruby-block-beg-keywords 'symbols)) + +(defun ruby-electric--block-beg-keyword-at-point-p () + "Test if there is a block beginning keyword at point." + (and (looking-at ruby-block-beg-symbol-re) + (if (string= (match-string 1) "do") + (looking-back "\\s-") + (not (looking-back "\\."))) + ;; (not (ruby-electric--modifier-keyword-at-point-p)) ;; implicit assumption + )) + +(defcustom ruby-electric-keywords-alist + '(("begin" . end) + ("case" . end) + ("class" . end) + ("def" . end) + ("do" . end) + ("else" . reindent) + ("elsif" . reindent) + ("end" . reindent) + ("ensure" . reindent) + ("for" . end) + ("if" . end) + ("module" . end) + ("rescue" . reindent) + ("unless" . end) + ("until" . end) + ("when" . reindent) + ("while" . end)) + "Alist of keywords and actions to define how to react to space +or return right after each keyword. In each (KEYWORD . ACTION) +cons, ACTION can be set to one of the following values: + + `reindent' Reindent the line. + + `end' Reindent the line and auto-close the keyword with + end if applicable. + + `nil' Do nothing. +" + :type '(repeat (cons (string :tag "Keyword") + (choice :tag "Action" + :menu-tag "Action" + (const :tag "Auto-close with end" + :value end) + (const :tag "Auto-reindent" + :value reindent) + (const :tag "None" + :value nil)))) + :set (lambda (sym val) + (set sym val) + (let (keywords) + (dolist (x val) + (let ((keyword (car x)) + (action (cdr x))) + (if action + (setq keywords (cons keyword keywords))))) + (setq ruby-electric-expandable-keyword-re + (concat (regexp-opt keywords 'symbols) + "$")))) + :group 'ruby-electric) + +(defvar ruby-electric-mode-map + (let ((map (make-sparse-keymap))) + (define-key map " " 'ruby-electric-space/return) + (define-key map [remap delete-backward-char] 'ruby-electric-delete-backward-char) + (define-key map [remap newline] 'ruby-electric-space/return) + (define-key map [remap newline-and-indent] 'ruby-electric-space/return) + (define-key map [remap electric-newline-and-maybe-indent] 'ruby-electric-space/return) + (define-key map [remap reindent-then-newline-and-indent] 'ruby-electric-space/return) + (dolist (x ruby-electric-delimiters-alist) + (let* ((delim (car x)) + (plist (cdr x)) + (name (plist-get plist :name)) + (func (plist-get plist :handler)) + (closing (plist-get plist :closing))) + (define-key map (char-to-string delim) func) + (if closing + (define-key map (char-to-string closing) 'ruby-electric-closing-char)))) + map) + "Keymap used in ruby-electric-mode") + +(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) + ,@(apply 'list + (mapcar #'(lambda (x) + `(const :tag ,(plist-get (cdr x) :name) + ,(car x))) + ruby-electric-delimiters-alist))) + :group 'ruby-electric) + +(defcustom ruby-electric-newline-before-closing-bracket nil + "*Non-nil means a newline should be inserted before an +automatically inserted closing bracket." + :type 'boolean :group 'ruby-electric) + +(defcustom ruby-electric-autoindent-on-closing-char nil + "*Non-nil means the current line should be automatically +indented when a closing character is manually typed in." + :type 'boolean :group 'ruby-electric) + +(defvar ruby-electric-mode-hook nil + "Called after `ruby-electric-mode' is turned on.") + +;;;###autoload +(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' followed by a space. Single, 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-electric-mode-map + (if ruby-electric-mode + (run-hooks 'ruby-electric-mode-hook))) + +(defun ruby-electric-space/return-fallback () + (if (or (eq this-original-command 'ruby-electric-space/return) + (null (ignore-errors + ;; ac-complete may fail if there is nothing left to complete + (call-interactively this-original-command) + (setq this-command this-original-command)))) + ;; fall back to a globally bound command + (let ((command (global-key-binding (char-to-string last-command-event) t))) + (and command + (call-interactively (setq this-command command)))))) + +(defun ruby-electric-space/return (arg) + (interactive "*P") + (and (boundp 'sp-last-operation) + (setq sp-delayed-pair nil)) + (cond ((or arg + (region-active-p)) + (or (= last-command-event ?\s) + (setq last-command-event ?\n)) + (ruby-electric-replace-region-or-insert)) + ((ruby-electric-space/return-can-be-expanded-p) + (let (action) + (save-excursion + (goto-char (match-beginning 0)) + (let* ((keyword (match-string 1)) + (allowed-actions + (cond ((ruby-electric--modifier-keyword-at-point-p) + '(reindent)) ;; no end necessary + ((ruby-electric--block-mid-keyword-at-point-p) + '(reindent)) ;; ditto + ((ruby-electric--block-beg-keyword-at-point-p) + '(end reindent))))) + (if allowed-actions + (setq action + (let ((action (cdr (assoc keyword ruby-electric-keywords-alist)))) + (and (memq action allowed-actions) + action)))))) + (cond ((eq action 'end) + (ruby-indent-line) + (save-excursion + (newline) + (ruby-electric-end))) + ((eq action 'reindent) + (ruby-indent-line))) + (ruby-electric-space/return-fallback))) + ((and (eq this-original-command 'newline-and-indent) + (ruby-electric-comment-at-point-p)) + (call-interactively (setq this-command 'comment-indent-new-line))) + (t + (ruby-electric-space/return-fallback)))) + +(defun ruby-electric--get-faces-at-point () + (let* ((point (point)) + (value (or + (get-text-property point 'read-face-name) + (get-text-property point 'face)))) + (if (listp value) value (list value)))) + +(defun ruby-electric--faces-include-p (pfaces &rest faces) + (and ruby-electric-mode + (loop for face in faces + thereis (memq face pfaces)))) + +(defun ruby-electric--faces-at-point-include-p (&rest faces) + (apply 'ruby-electric--faces-include-p + (ruby-electric--get-faces-at-point) + faces)) + +(defun ruby-electric-code-face-p (faces) + (not (ruby-electric--faces-include-p + faces + 'font-lock-string-face + 'font-lock-comment-face + 'enh-ruby-string-delimiter-face + 'enh-ruby-heredoc-delimiter-face + 'enh-ruby-regexp-delimiter-face + 'enh-ruby-regexp-face))) + +(defun ruby-electric-code-at-point-p () + (ruby-electric-code-face-p + (ruby-electric--get-faces-at-point))) + +(defun ruby-electric-string-face-p (faces) + (ruby-electric--faces-include-p + faces + 'font-lock-string-face + 'enh-ruby-string-delimiter-face + 'enh-ruby-heredoc-delimiter-face + 'enh-ruby-regexp-delimiter-face + 'enh-ruby-regexp-face)) + +(defun ruby-electric-string-at-point-p () + (ruby-electric-string-face-p + (ruby-electric--get-faces-at-point))) + +(defun ruby-electric-comment-at-point-p () + (ruby-electric--faces-at-point-include-p + 'font-lock-comment-face)) + +(defun ruby-electric-escaped-p() + (let ((f nil)) + (save-excursion + (while (char-equal ?\\ (preceding-char)) + (backward-char 1) + (setq f (not f)))) + f)) + +(defun ruby-electric-command-char-expandable-punct-p(char) + (or (memq 'all ruby-electric-expand-delimiters-list) + (memq char ruby-electric-expand-delimiters-list))) + +(defun ruby-electric-space/return-can-be-expanded-p() + (and (ruby-electric-code-at-point-p) + (looking-back ruby-electric-expandable-keyword-re))) + +(defun ruby-electric-replace-region-or-insert () + (and (region-active-p) + (bound-and-true-p delete-selection-mode) + (fboundp 'delete-selection-helper) + (delete-selection-helper (get 'self-insert-command 'delete-selection))) + (insert (make-string (prefix-numeric-value current-prefix-arg) + last-command-event)) + (setq this-command 'self-insert-command)) + +(defmacro ruby-electric-insert (arg &rest body) + `(cond ((and + (null ,arg) + (ruby-electric-command-char-expandable-punct-p last-command-event)) + (let ((region-beginning + (cond ((region-active-p) + (prog1 + (save-excursion + (goto-char (region-beginning)) + (insert last-command-event) + (point)) + (goto-char (region-end)))) + (t + (insert last-command-event) + nil))) + (faces-at-point + (ruby-electric--get-faces-at-point))) + ,@body + (and region-beginning + ;; If no extra character is inserted, go back to the + ;; region beginning. + (eq this-command 'self-insert-command) + (goto-char region-beginning)))) + ((ruby-electric-replace-region-or-insert)))) + +(defun ruby-electric-curlies (arg) + (interactive "*P") + (ruby-electric-insert + arg + (cond + ((or (ruby-electric-code-at-point-p) + (ruby-electric--faces-include-p + faces-at-point + 'enh-ruby-string-delimiter-face + 'enh-ruby-regexp-delimiter-face)) + (save-excursion + (insert "}") + (font-lock-fontify-region (line-beginning-position) (point))) + (cond + ((or (ruby-electric-string-at-point-p) ;; %w{}, %r{}, etc. + (looking-back "%[QqWwRrxIis]{")) + (if region-beginning + (forward-char 1))) + (ruby-electric-newline-before-closing-bracket + (cond (region-beginning + (save-excursion + (goto-char region-beginning) + (newline)) + (newline) + (forward-char 1) + (indent-region region-beginning (line-end-position))) + (t + (insert " ") + (save-excursion + (newline) + (ruby-indent-line t))))) + (t + (if region-beginning + (save-excursion + (goto-char region-beginning) + (insert " ")) + (insert " ")) + (insert " ") + (backward-char 1) + (and region-beginning + (forward-char 1))))) + ((ruby-electric-string-at-point-p) + (let ((start-position (1- (or region-beginning (point))))) + (cond + ((char-equal ?\# (char-before start-position)) + (unless (save-excursion + (goto-char (1- start-position)) + (ruby-electric-escaped-p)) + (insert "}") + (or region-beginning + (backward-char 1)))) + ((or + (ruby-electric-command-char-expandable-punct-p ?\#) + (save-excursion + (goto-char start-position) + (ruby-electric-escaped-p))) + (if region-beginning + (goto-char region-beginning)) + (setq this-command 'self-insert-command)) + (t + (save-excursion + (goto-char start-position) + (insert "#")) + (insert "}") + (or region-beginning + (backward-char 1)))))) + (t + (delete-char -1) + (ruby-electric-replace-region-or-insert))))) + +(defun ruby-electric-hash (arg) + (interactive "*P") + (ruby-electric-insert + arg + (if (ruby-electric-string-at-point-p) + (let ((start-position (1- (or region-beginning (point))))) + (cond + ((char-equal (following-char) ?')) ;; likely to be in '' + ((save-excursion + (goto-char start-position) + (ruby-electric-escaped-p))) + (region-beginning + (save-excursion + (goto-char (1+ start-position)) + (insert "{")) + (insert "}")) + (t + (insert "{") + (save-excursion + (insert "}"))))) + (delete-char -1) + (ruby-electric-replace-region-or-insert)))) + +(defun ruby-electric-matching-char (arg) + (interactive "*P") + (ruby-electric-insert + arg + (let ((closing (cdr (assoc last-command-event + ruby-electric-matching-delimeter-alist)))) + (cond + ;; quotes + ((char-equal closing last-command-event) + (cond ((not (ruby-electric-string-face-p faces-at-point)) + (if region-beginning + ;; escape quotes of the same kind, backslash and hash + (let ((re (format "[%c\\%s]" + last-command-event + (if (char-equal last-command-event ?\") + "#" ""))) + (bound (point))) + (save-excursion + (goto-char region-beginning) + (while (re-search-forward re bound t) + (let ((end (point))) + (replace-match "\\\\\\&") + (setq bound (+ bound (- (point) end)))))))) + (insert closing) + (or region-beginning + (backward-char 1))) + (t + (and (eq last-command 'ruby-electric-matching-char) + (char-equal (following-char) closing) ;; repeated quotes + (delete-char 1)) + (setq this-command 'self-insert-command)))) + ((ruby-electric-code-at-point-p) + (insert closing) + (or region-beginning + (backward-char 1))))))) + +(defun ruby-electric-closing-char(arg) + (interactive "*P") + (cond + (arg + (ruby-electric-replace-region-or-insert)) + ((and + (eq last-command 'ruby-electric-curlies) + (= last-command-event ?}) + (not (char-equal (preceding-char) last-command-event))) ;; {} + (if (char-equal (following-char) ?\n) (delete-char 1)) + (delete-horizontal-space) + (forward-char)) + ((and + (= last-command-event (following-char)) + (not (char-equal (preceding-char) last-command-event)) + (memq last-command '(ruby-electric-matching-char + ruby-electric-closing-char))) ;; ()/[] and (())/[[]] + (forward-char)) + (t + (ruby-electric-replace-region-or-insert) + (if ruby-electric-autoindent-on-closing-char + (ruby-indent-line))))) + +(defun ruby-electric-bar(arg) + (interactive "*P") + (ruby-electric-insert + arg + (cond ((and (ruby-electric-code-at-point-p) + (looking-back ruby-electric-expandable-bar-re)) + (save-excursion (insert "|"))) + (t + (delete-char -1) + (ruby-electric-replace-region-or-insert))))) + +(defun ruby-electric-delete-backward-char(arg) + (interactive "*p") + (cond ((memq last-command '(ruby-electric-matching-char + ruby-electric-bar)) + (delete-char 1)) + ((eq last-command 'ruby-electric-curlies) + (cond ((eolp) + (cond ((char-equal (preceding-char) ?\s) + (setq this-command last-command)) + ((char-equal (preceding-char) ?{) + (and (looking-at "[ \t\n]*}") + (delete-char (- (match-end 0) (match-beginning 0))))))) + ((char-equal (following-char) ?\s) + (setq this-command last-command) + (delete-char 1)) + ((char-equal (following-char) ?}) + (delete-char 1)))) + ((eq last-command 'ruby-electric-hash) + (and (char-equal (preceding-char) ?{) + (delete-char 1)))) + (delete-char (- arg))) + +(put 'ruby-electric-delete-backward-char 'delete-selection 'supersede) + +(defun ruby-electric-end () + (interactive) + (if (eq (char-syntax (preceding-char)) ?w) + (insert " ")) + (insert "end") + (save-excursion + (if (eq (char-syntax (following-char)) ?w) + (insert " ")) + (ruby-indent-line t))) + +(provide 'ruby-electric) + +;;; ruby-electric.el ends here diff --git a/misc/ruby-mode.el b/misc/ruby-mode.el new file mode 100644 index 0000000000..b1abd18a9e --- /dev/null +++ b/misc/ruby-mode.el @@ -0,0 +1,1584 @@ +;;; 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 + '((us-ascii . nil) ;; Do not put coding: us-ascii + (utf-8 . nil) ;; Do not put coding: utf-8 + (shift-jis . cp932) ;; Emacs charset name of Shift_JIS + (shift_jis . cp932) ;; MIME charset name of Shift_JIS + (japanese-cp932 . cp932)) ;; Emacs charset name of CP932 + "Alist to map encoding name from Emacs to Ruby. +Associating an encoding name with nil means it needs not be +explicitly declared in magic comment." + :type '(repeat (cons (symbol :tag "From") (symbol :tag "To"))) + :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 () + "Insert or update a magic comment header with the proper encoding. +`ruby-encoding-map' is looked up to convert an encoding name from +Emacs to Ruby." + (let* ((nonascii + (save-excursion + (widen) + (goto-char (point-min)) + (re-search-forward "[^\0-\177]" nil t))) + (coding-system + (or coding-system-for-write + buffer-file-coding-system)) + (coding-system + (and coding-system + (coding-system-change-eol-conversion coding-system nil))) + (coding-system + (and coding-system + (or + (coding-system-get coding-system :mime-charset) + (let ((coding-type (coding-system-get coding-system :coding-type))) + (cond ((eq coding-type 'undecided) + (if nonascii + (or (and (coding-system-get coding-system :prefer-utf-8) + 'utf-8) + (coding-system-get default-buffer-file-coding-system :coding-type) + 'ascii-8bit))) + ((memq coding-type '(utf-8 shift-jis)) + coding-type) + (t coding-system)))))) + (coding-system + (or coding-system + 'us-ascii)) + (coding-system + (let ((cons (assq coding-system ruby-encoding-map))) + (if cons (cdr cons) coding-system))) + (coding-system + (and coding-system + (symbol-name coding-system)))) + (if coding-system + (save-excursion + (widen) + (goto-char (point-min)) + (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")))))))) + +(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 (preceding-char)) ?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-event 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))) + oneline (end (make-marker))) + (setq oneline (and (eolp) (<= (point-at-bol) orig))) + (when (eq (char-before) ?\}) + (delete-char -1) + (cond + (oneline + (insert "\n") + (set-marker end (point))) + ((eq (char-syntax (preceding-char)) ?w) + (insert " "))) + (insert "end") + (if (eq (char-syntax (following-char)) ?w) + (insert " ")) + (goto-char orig) + (delete-char 1) + (if (eq (char-syntax (preceding-char)) ?w) + (insert " ")) + (insert "do") + (when (looking-at "\\sw\\||") + (insert " ") + (backward-char)) + (when oneline + (setq orig (point)) + (when (cond + ((looking-at "\\s *|") + (goto-char (match-end 0)) + (and (search-forward "|" (point-at-eol) 'move) + (not (eolp)))) + (t)) + (while (progn + (insert "\n") + (ruby-forward-sexp) + (looking-at "\\s *;\\s *")) + (delete-char (- (match-end 0) (match-beginning 0)))) + (goto-char orig) + (beginning-of-line 2) + (indent-region (point) end)) + (goto-char orig)) + t)))) + +(defun ruby-do-end-to-brace () + (when (and (or (bolp) + (not (memq (char-syntax (preceding-char)) '(?w ?_)))) + (looking-at "\\<do\\(\\s \\|$\\)")) + (let ((orig (point)) (end (progn (ruby-forward-sexp) (point))) + first last) + (backward-char 3) + (when (looking-at ruby-block-end-re) + (delete-char 3) + (insert "}") + (setq last (and (eolp) + (progn (backward-char 1) + (skip-syntax-backward " ") + (bolp)) + (1- (point-at-eol -1)))) + (goto-char orig) + (delete-char 2) + (insert "{") + (setq orig (point)) + (when (and last (<= last (point)) + (not (search-forward "#" (setq first (point-at-eol)) t))) + (goto-char (- end 4)) + (end-of-line 0) + (if (looking-at "\n\\s *") + (delete-char (- (match-end 0) (match-beginning 0))) t) + (goto-char first) + (if (looking-at "\n\\s *") + (delete-char (- (match-end 0) (match-beginning 0))) t)) + (goto-char orig) + (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 03d0830d3a..b8593b202a 100644 --- a/misc/ruby-style.el +++ b/misc/ruby-style.el @@ -55,10 +55,8 @@ '("bsd" (c-basic-offset . 4) (tab-width . 8) - (indent-tabs-mode . nil) - (show-trailing-whitespace . t) - (c-backslash-column . 1) - (c-backslash-max-column . 1) + (indent-tabs-mode . t) + (setq show-trailing-whitespace t) (c-offsets-alist (case-label . *) (label . (ruby-style-label-indent *)) @@ -68,17 +66,6 @@ (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) @@ -88,20 +75,7 @@ (let ((head (progn (forward-line 100) (point))) (case-fold-search nil)) (goto-char (point-min)) - (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) + (re-search-forward "Copyright (C) .* Yukihiro Matsumoto" head t)))) (c-set-style "ruby"))) (provide 'ruby-style) diff --git a/misc/rubydb2x.el b/misc/rubydb2x.el new file mode 100644 index 0000000000..a74265fb0e --- /dev/null +++ b/misc/rubydb2x.el @@ -0,0 +1,104 @@ +(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 new file mode 100644 index 0000000000..9d6bc57d5a --- /dev/null +++ b/misc/rubydb3x.el @@ -0,0 +1,115 @@ +(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 index bd58619ac2..4d1cc499f5 100644 --- a/misc/test_lldb_cruby.rb +++ b/misc/test_lldb_cruby.rb @@ -9,7 +9,7 @@ class TestLLDBInit < Test::Unit::TestCase tf.puts <<eom target create ./miniruby command script import -r misc/lldb_cruby.py -b rb_inspect +b rb_p run -e'p #{expr}' rp obj eom @@ -24,12 +24,8 @@ eom 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+\)/ + assert_rp ':abcde', /immediate\(\h+\)/ end def test_rp_string diff --git a/misc/yjit_perf.py b/misc/yjit_perf.py deleted file mode 100755 index 61434e5eb4..0000000000 --- a/misc/yjit_perf.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/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() |
