summaryrefslogtreecommitdiff
path: root/lib/irb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/irb')
-rw-r--r--lib/irb/.document1
-rw-r--r--lib/irb/cmd/chws.rb25
-rw-r--r--lib/irb/cmd/fork.rb46
-rw-r--r--lib/irb/cmd/help.rb35
-rw-r--r--lib/irb/cmd/info.rb32
-rw-r--r--lib/irb/cmd/load.rb72
-rw-r--r--lib/irb/cmd/ls.rb101
-rw-r--r--lib/irb/cmd/measure.rb43
-rw-r--r--lib/irb/cmd/nop.rb32
-rw-r--r--lib/irb/cmd/pushws.rb31
-rw-r--r--lib/irb/cmd/show_source.rb93
-rw-r--r--lib/irb/cmd/subirb.rb32
-rw-r--r--lib/irb/cmd/whereami.rb20
-rw-r--r--lib/irb/color.rb246
-rw-r--r--lib/irb/color_printer.rb47
-rw-r--r--lib/irb/completion.rb576
-rw-r--r--lib/irb/context.rb469
-rw-r--r--lib/irb/easter-egg.rb138
-rw-r--r--lib/irb/ext/change-ws.rb59
-rw-r--r--lib/irb/ext/history.rb131
-rw-r--r--lib/irb/ext/loader.rb175
-rw-r--r--lib/irb/ext/math-mode.rb37
-rw-r--r--lib/irb/ext/multi-irb.rb250
-rw-r--r--lib/irb/ext/save-history.rb129
-rw-r--r--lib/irb/ext/tracer.rb61
-rw-r--r--lib/irb/ext/use-loader.rb56
-rw-r--r--lib/irb/ext/workspaces.rb48
-rw-r--r--lib/irb/extend-command.rb362
-rw-r--r--lib/irb/frame.rb45
-rw-r--r--lib/irb/help.rb37
-rw-r--r--lib/irb/init.rb407
-rw-r--r--lib/irb/input-method.rb431
-rw-r--r--lib/irb/inspector.rb136
-rw-r--r--lib/irb/irb.gemspec40
-rw-r--r--lib/irb/lc/error.rb81
-rw-r--r--lib/irb/lc/help-message70
-rw-r--r--lib/irb/lc/ja/encoding_aliases.rb11
-rw-r--r--lib/irb/lc/ja/error.rb81
-rw-r--r--lib/irb/lc/ja/help-message75
-rw-r--r--lib/irb/locale.rb195
-rw-r--r--lib/irb/magic-file.rb38
-rw-r--r--lib/irb/notifier.rb209
-rw-r--r--lib/irb/output-method.rb81
-rw-r--r--lib/irb/ruby-lex.rb1778
-rw-r--r--lib/irb/ruby-token.rb273
-rw-r--r--lib/irb/ruby_logo.aa37
-rw-r--r--lib/irb/slex.rb285
-rw-r--r--lib/irb/src_encoding.rb7
-rw-r--r--lib/irb/version.rb13
-rw-r--r--lib/irb/workspace.rb216
-rw-r--r--lib/irb/ws-for-case-2.rb10
-rw-r--r--lib/irb/xmp.rb120
52 files changed, 4927 insertions, 3096 deletions
diff --git a/lib/irb/.document b/lib/irb/.document
new file mode 100644
index 0000000000..3b0d6fa4ed
--- /dev/null
+++ b/lib/irb/.document
@@ -0,0 +1 @@
+**/*.rb
diff --git a/lib/irb/cmd/chws.rb b/lib/irb/cmd/chws.rb
index 88585b778b..e9f257791c 100644
--- a/lib/irb/cmd/chws.rb
+++ b/lib/irb/cmd/chws.rb
@@ -1,33 +1,34 @@
+# frozen_string_literal: false
#
-# change-ws.rb -
-# $Release Version: 0.9.5$
+# change-ws.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "irb/cmd/nop.rb"
-require "irb/ext/change-ws.rb"
+require_relative "nop"
+require_relative "../ext/change-ws"
+# :stopdoc:
module IRB
module ExtendCommand
- class CurrentWorkingWorkspace<Nop
+ class CurrentWorkingWorkspace < Nop
def execute(*obj)
- irb_context.main
+ irb_context.main
end
end
- class ChangeWorkspace<Nop
+ class ChangeWorkspace < Nop
def execute(*obj)
- irb_context.change_workspace(*obj)
- irb_context.main
+ irb_context.change_workspace(*obj)
+ irb_context.main
end
end
end
end
-
+# :startdoc:
diff --git a/lib/irb/cmd/fork.rb b/lib/irb/cmd/fork.rb
index 2866b1373b..7566d10be0 100644
--- a/lib/irb/cmd/fork.rb
+++ b/lib/irb/cmd/fork.rb
@@ -1,39 +1,37 @@
+# frozen_string_literal: false
#
-# fork.rb -
-# $Release Version: 0.9.5 $
+# fork.rb -
+# $Release Version: 0.9.6 $
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-
-@RCS_ID='-$Id$-'
+#
+# :stopdoc:
module IRB
module ExtendCommand
- class Fork<Nop
- def execute(&block)
- pid = send ExtendCommand.irb_original_method_name("fork")
- unless pid
- class<<self
- alias_method :exit, ExtendCommand.irb_original_method_name('exit')
- end
- if iterator?
- begin
- yield
- ensure
- exit
- end
- end
- end
- pid
+ class Fork < Nop
+ def execute
+ pid = __send__ ExtendCommand.irb_original_method_name("fork")
+ unless pid
+ class << self
+ alias_method :exit, ExtendCommand.irb_original_method_name('exit')
+ end
+ if block_given?
+ begin
+ yield
+ ensure
+ exit
+ end
+ end
+ end
+ pid
end
end
end
end
-
-
+# :startdoc:
diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb
index 3e8d1388e0..d82e78fb57 100644
--- a/lib/irb/cmd/help.rb
+++ b/lib/irb/cmd/help.rb
@@ -1,34 +1,47 @@
+# frozen_string_literal: false
#
# help.rb - helper using ri
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
#
# --
#
-#
+#
#
-require 'rdoc/ri/ri_driver'
+require_relative "nop"
+# :stopdoc:
module IRB
module ExtendCommand
- module Help
- begin
- @ri = RiDriver.new
- rescue SystemExit
+ class Help < Nop
+ def execute(*names)
+ require 'rdoc/ri/driver'
+ opts = RDoc::RI::Driver.process_args([])
+ IRB::ExtendCommand::Help.const_set(:Ri, RDoc::RI::Driver.new(opts))
+ rescue LoadError, SystemExit
+ IRB::ExtendCommand::Help.remove_method(:execute)
+ # raise NoMethodError in ensure
else
- def self.execute(context, *names)
+ def execute(*names)
+ if names.empty?
+ Ri.interactive
+ return
+ end
names.each do |name|
begin
- @ri.get_info_for(name.to_s)
- rescue RiError
+ Ri.display_name(name.to_s)
+ rescue RDoc::RI::Error
puts $!.message
end
end
nil
end
+ nil
+ ensure
+ execute(*names)
end
end
end
end
+# :startdoc:
diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb
new file mode 100644
index 0000000000..dd93352ff0
--- /dev/null
+++ b/lib/irb/cmd/info.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: false
+
+require_relative "nop"
+
+# :stopdoc:
+module IRB
+ module ExtendCommand
+ class Info < Nop
+ def execute
+ Class.new {
+ def inspect
+ str = "Ruby version: #{RUBY_VERSION}\n"
+ str += "IRB version: #{IRB.version}\n"
+ str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n"
+ str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file)
+ str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n"
+ str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty?
+ str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty?
+ str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n"
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
+ codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1')
+ str += "Code page: #{codepage}\n"
+ end
+ str
+ end
+ alias_method :to_s, :inspect
+ }.new
+ end
+ end
+ end
+end
+# :startdoc:
diff --git a/lib/irb/cmd/load.rb b/lib/irb/cmd/load.rb
index cbc5d91d03..b6769a4124 100644
--- a/lib/irb/cmd/load.rb
+++ b/lib/irb/cmd/load.rb
@@ -1,67 +1,67 @@
+# frozen_string_literal: false
#
-# load.rb -
-# $Release Version: 0.9.5$
+# load.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "irb/cmd/nop.rb"
-require "irb/ext/loader"
+require_relative "nop"
+require_relative "../ext/loader"
+# :stopdoc:
module IRB
module ExtendCommand
- class Load<Nop
+ class Load < Nop
include IrbLoader
def execute(file_name, priv = nil)
-# return ruby_load(file_name) unless IRB.conf[:USE_LOADER]
- return irb_load(file_name, priv)
+ return irb_load(file_name, priv)
end
end
- class Require<Nop
+ class Require < Nop
include IrbLoader
-
+
def execute(file_name)
-# return ruby_require(file_name) unless IRB.conf[:USE_LOADER]
- rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?")
- return false if $".find{|f| f =~ rex}
+ rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?")
+ return false if $".find{|f| f =~ rex}
+
+ case file_name
+ when /\.rb$/
+ begin
+ if irb_load(file_name)
+ $".push file_name
+ return true
+ end
+ rescue LoadError
+ end
+ when /\.(so|o|sl)$/
+ return ruby_require(file_name)
+ end
- case file_name
- when /\.rb$/
- begin
- if irb_load(file_name)
- $".push file_name
- return true
- end
- rescue LoadError
- end
- when /\.(so|o|sl)$/
- return ruby_require(file_name)
- end
-
- begin
- irb_load(f = file_name + ".rb")
- $".push f
- return true
- rescue LoadError
- return ruby_require(file_name)
- end
+ begin
+ irb_load(f = file_name + ".rb")
+ $".push f
+ return true
+ rescue LoadError
+ return ruby_require(file_name)
+ end
end
end
- class Source<Nop
+ class Source < Nop
include IrbLoader
def execute(file_name)
- source_file(file_name)
+ source_file(file_name)
end
end
end
end
+# :startdoc:
diff --git a/lib/irb/cmd/ls.rb b/lib/irb/cmd/ls.rb
new file mode 100644
index 0000000000..cbbf96210e
--- /dev/null
+++ b/lib/irb/cmd/ls.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require "reline"
+require_relative "nop"
+require_relative "../color"
+
+# :stopdoc:
+module IRB
+ module ExtendCommand
+ class Ls < Nop
+ def execute(*arg, grep: nil)
+ o = Output.new(grep: grep)
+
+ obj = arg.empty? ? irb_context.workspace.main : arg.first
+ locals = arg.empty? ? irb_context.workspace.binding.local_variables : []
+ klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
+
+ o.dump("constants", obj.constants) if obj.respond_to?(:constants)
+ dump_methods(o, klass, obj)
+ o.dump("instance variables", obj.instance_variables)
+ o.dump("class variables", klass.class_variables)
+ o.dump("locals", locals)
+ end
+
+ def dump_methods(o, klass, obj)
+ singleton_class = begin obj.singleton_class; rescue TypeError; nil end
+ maps = class_method_map((singleton_class || klass).ancestors)
+ maps.each do |mod, methods|
+ name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
+ o.dump(name, methods)
+ end
+ end
+
+ def class_method_map(classes)
+ dumped = Array.new
+ classes.reject { |mod| mod >= Object }.map do |mod|
+ methods = mod.public_instance_methods(false).select do |m|
+ dumped.push(m) unless dumped.include?(m)
+ end
+ [mod, methods]
+ end.reverse
+ end
+
+ class Output
+ MARGIN = " "
+
+ def initialize(grep: nil)
+ @grep = grep
+ @line_width = screen_width - MARGIN.length # right padding
+ end
+
+ def dump(name, strs)
+ strs = strs.grep(@grep) if @grep
+ strs = strs.sort
+ return if strs.empty?
+
+ # Attempt a single line
+ print "#{Color.colorize(name, [:BOLD, :BLUE])}: "
+ if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length)
+ puts strs.join(MARGIN)
+ return
+ end
+ puts
+
+ # Dump with the largest # of columns that fits on a line
+ cols = strs.size
+ until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1
+ cols -= 1
+ end
+ widths = col_widths(strs, cols: cols)
+ strs.each_slice(cols) do |ss|
+ puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join
+ end
+ end
+
+ private
+
+ def fits_on_line?(strs, cols:, offset: 0)
+ width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1)
+ width <= @line_width - offset
+ end
+
+ def col_widths(strs, cols:)
+ cols.times.map do |col|
+ (col...strs.size).step(cols).map do |i|
+ strs[i].length
+ end.max
+ end
+ end
+
+ def screen_width
+ Reline.get_screen_size.last
+ rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
+ 80
+ end
+ end
+ private_constant :Output
+ end
+ end
+end
+# :startdoc:
diff --git a/lib/irb/cmd/measure.rb b/lib/irb/cmd/measure.rb
new file mode 100644
index 0000000000..adea540e92
--- /dev/null
+++ b/lib/irb/cmd/measure.rb
@@ -0,0 +1,43 @@
+require_relative "nop"
+
+# :stopdoc:
+module IRB
+ module ExtendCommand
+ class Measure < Nop
+ def initialize(*args)
+ super(*args)
+ end
+
+ def execute(type = nil, arg = nil, &block)
+ # Please check IRB.init_config in lib/irb/init.rb that sets
+ # IRB.conf[:MEASURE_PROC] to register default "measure" methods,
+ # "measure :time" (abbreviated as "measure") and "measure :stackprof".
+ case type
+ when :off
+ IRB.conf[:MEASURE] = nil
+ IRB.unset_measure_callback(arg)
+ when :list
+ IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val|
+ puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '')
+ end
+ when :on
+ IRB.conf[:MEASURE] = true
+ added = IRB.set_measure_callback(type, arg)
+ puts "#{added[0]} is added." if added
+ else
+ if block_given?
+ IRB.conf[:MEASURE] = true
+ added = IRB.set_measure_callback(&block)
+ puts "#{added[0]} is added." if added
+ else
+ IRB.conf[:MEASURE] = true
+ added = IRB.set_measure_callback(type, arg)
+ puts "#{added[0]} is added." if added
+ end
+ end
+ nil
+ end
+ end
+ end
+end
+# :startdoc:
diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb
index aa553c959e..d6f7a611a6 100644
--- a/lib/irb/cmd/nop.rb
+++ b/lib/irb/cmd/nop.rb
@@ -1,39 +1,45 @@
+# frozen_string_literal: false
#
-# nop.rb -
-# $Release Version: 0.9.5$
+# nop.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
+#
+# :stopdoc:
module IRB
module ExtendCommand
class Nop
-
- @RCS_ID='-$Id$-'
- def self.execute(conf, *opts)
- command = new(conf)
- command.execute(*opts)
+ if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
+ def self.execute(conf, *opts, **kwargs, &block)
+ command = new(conf)
+ command.execute(*opts, **kwargs, &block)
+ end
+ else
+ def self.execute(conf, *opts, &block)
+ command = new(conf)
+ command.execute(*opts, &block)
+ end
end
def initialize(conf)
- @irb_context = conf
+ @irb_context = conf
end
attr_reader :irb_context
def irb
- @irb_context.irb
+ @irb_context.irb
end
def execute(*opts)
- #nop
+ #nop
end
end
end
end
-
+# :startdoc:
diff --git a/lib/irb/cmd/pushws.rb b/lib/irb/cmd/pushws.rb
index eddaeae631..612157d8a0 100644
--- a/lib/irb/cmd/pushws.rb
+++ b/lib/irb/cmd/pushws.rb
@@ -1,39 +1,40 @@
+# frozen_string_literal: false
#
-# change-ws.rb -
-# $Release Version: 0.9.5$
+# change-ws.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "irb/cmd/nop.rb"
-require "irb/ext/workspaces.rb"
+require_relative "nop"
+require_relative "../ext/workspaces"
+# :stopdoc:
module IRB
module ExtendCommand
- class Workspaces<Nop
+ class Workspaces < Nop
def execute(*obj)
- irb_context.workspaces.collect{|ws| ws.main}
+ irb_context.workspaces.collect{|ws| ws.main}
end
end
- class PushWorkspace<Workspaces
+ class PushWorkspace < Workspaces
def execute(*obj)
- irb_context.push_workspace(*obj)
- super
+ irb_context.push_workspace(*obj)
+ super
end
end
- class PopWorkspace<Workspaces
+ class PopWorkspace < Workspaces
def execute(*obj)
- irb_context.pop_workspace(*obj)
- super
+ irb_context.pop_workspace(*obj)
+ super
end
end
end
end
-
+# :startdoc:
diff --git a/lib/irb/cmd/show_source.rb b/lib/irb/cmd/show_source.rb
new file mode 100644
index 0000000000..8f203ef125
--- /dev/null
+++ b/lib/irb/cmd/show_source.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require_relative "nop"
+require_relative "../color"
+require_relative "../ruby-lex"
+
+# :stopdoc:
+module IRB
+ module ExtendCommand
+ class ShowSource < Nop
+ def execute(str = nil)
+ unless str.is_a?(String)
+ puts "Error: Expected a string but got #{str.inspect}"
+ return
+ end
+ source = find_source(str)
+ if source && File.exist?(source.file)
+ show_source(source)
+ else
+ puts "Error: Couldn't locate a definition for #{str}"
+ end
+ nil
+ end
+
+ private
+
+ # @param [IRB::ExtendCommand::ShowSource::Source] source
+ def show_source(source)
+ puts
+ puts "#{bold("From")}: #{source.file}:#{source.first_line}"
+ puts
+ code = IRB::Color.colorize_code(File.read(source.file))
+ puts code.lines[(source.first_line - 1)...source.last_line].join
+ puts
+ end
+
+ def find_source(str)
+ case str
+ when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
+ eval(str, irb_context.workspace.binding) # trigger autoload
+ base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
+ file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
+ when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
+ owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
+ method = Regexp.last_match[:method]
+ if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
+ file, line = owner.instance_method(method).source_location
+ end
+ when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
+ receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
+ method = Regexp.last_match[:method]
+ file, line = receiver.method(method).source_location if receiver.respond_to?(method)
+ end
+ if file && line
+ Source.new(file: file, first_line: line, last_line: find_end(file, line))
+ end
+ end
+
+ def find_end(file, first_line)
+ return first_line unless File.exist?(file)
+ lex = RubyLex.new
+ lines = File.read(file).lines[(first_line - 1)..-1]
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
+ prev_tokens = []
+
+ # chunk with line number
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
+ code = lines[0..lnum].join
+ prev_tokens.concat chunk
+ continue = lex.process_continue(prev_tokens)
+ code_block_open = lex.check_code_block(code, prev_tokens)
+ if !continue && !code_block_open
+ return first_line + lnum
+ end
+ end
+ first_line
+ end
+
+ def bold(str)
+ Color.colorize(str, [:BOLD])
+ end
+
+ Source = Struct.new(
+ :file, # @param [String] - file name
+ :first_line, # @param [String] - first line
+ :last_line, # @param [String] - last line
+ keyword_init: true,
+ )
+ private_constant :Source
+ end
+ end
+end
+# :startdoc:
diff --git a/lib/irb/cmd/subirb.rb b/lib/irb/cmd/subirb.rb
index 79d654b172..1e18607d1a 100644
--- a/lib/irb/cmd/subirb.rb
+++ b/lib/irb/cmd/subirb.rb
@@ -1,43 +1,43 @@
-#!/usr/local/bin/ruby
-#
-# multi.rb -
-# $Release Version: 0.9.5$
+# frozen_string_literal: false
+# multi.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "irb/cmd/nop.rb"
-require "irb/ext/multi-irb"
+require_relative "nop"
+require_relative "../ext/multi-irb"
+# :stopdoc:
module IRB
module ExtendCommand
- class IrbCommand<Nop
+ class IrbCommand < Nop
def execute(*obj)
- IRB.irb(nil, *obj)
+ IRB.irb(nil, *obj)
end
end
- class Jobs<Nop
+ class Jobs < Nop
def execute
- IRB.JobManager
+ IRB.JobManager
end
end
- class Foreground<Nop
+ class Foreground < Nop
def execute(key)
- IRB.JobManager.switch(key)
+ IRB.JobManager.switch(key)
end
end
- class Kill<Nop
+ class Kill < Nop
def execute(*keys)
- IRB.JobManager.kill(*keys)
+ IRB.JobManager.kill(*keys)
end
end
end
end
+# :startdoc:
diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/cmd/whereami.rb
new file mode 100644
index 0000000000..b3def11b93
--- /dev/null
+++ b/lib/irb/cmd/whereami.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require_relative "nop"
+
+# :stopdoc:
+module IRB
+ module ExtendCommand
+ class Whereami < Nop
+ def execute(*)
+ code = irb_context.workspace.code_around_binding
+ if code
+ puts code
+ else
+ puts "The current context doesn't have code."
+ end
+ end
+ end
+ end
+end
+# :startdoc:
diff --git a/lib/irb/color.rb b/lib/irb/color.rb
new file mode 100644
index 0000000000..40e9e04c97
--- /dev/null
+++ b/lib/irb/color.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+require 'reline'
+require 'ripper'
+require 'irb/ruby-lex'
+
+module IRB # :nodoc:
+ module Color
+ CLEAR = 0
+ BOLD = 1
+ UNDERLINE = 4
+ REVERSE = 7
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+
+ TOKEN_KEYWORDS = {
+ on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'],
+ on_const: ['ENV'],
+ }
+ private_constant :TOKEN_KEYWORDS
+
+ # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq
+ ALL = -1
+ private_constant :ALL
+
+ begin
+ # Following pry's colors where possible, but sometimes having a compromise like making
+ # backtick and regexp as red (string's color, because they're sharing tokens).
+ TOKEN_SEQ_EXPRS = {
+ on_CHAR: [[BLUE, BOLD], ALL],
+ on_backtick: [[RED, BOLD], ALL],
+ on_comment: [[BLUE, BOLD], ALL],
+ on_const: [[BLUE, BOLD, UNDERLINE], ALL],
+ on_embexpr_beg: [[RED], ALL],
+ on_embexpr_end: [[RED], ALL],
+ on_embvar: [[RED], ALL],
+ on_float: [[MAGENTA, BOLD], ALL],
+ on_gvar: [[GREEN, BOLD], ALL],
+ on_heredoc_beg: [[RED], ALL],
+ on_heredoc_end: [[RED], ALL],
+ on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN],
+ on_imaginary: [[BLUE, BOLD], ALL],
+ on_int: [[BLUE, BOLD], ALL],
+ on_kw: [[GREEN], ALL],
+ on_label: [[MAGENTA], ALL],
+ on_label_end: [[RED, BOLD], ALL],
+ on_qsymbols_beg: [[RED, BOLD], ALL],
+ on_qwords_beg: [[RED, BOLD], ALL],
+ on_rational: [[BLUE, BOLD], ALL],
+ on_regexp_beg: [[RED, BOLD], ALL],
+ on_regexp_end: [[RED, BOLD], ALL],
+ on_symbeg: [[YELLOW], ALL],
+ on_symbols_beg: [[RED, BOLD], ALL],
+ on_tstring_beg: [[RED, BOLD], ALL],
+ on_tstring_content: [[RED], ALL],
+ on_tstring_end: [[RED, BOLD], ALL],
+ on_words_beg: [[RED, BOLD], ALL],
+ on_parse_error: [[RED, REVERSE], ALL],
+ compile_error: [[RED, REVERSE], ALL],
+ on_assign_error: [[RED, REVERSE], ALL],
+ on_alias_error: [[RED, REVERSE], ALL],
+ on_class_name_error:[[RED, REVERSE], ALL],
+ on_param_error: [[RED, REVERSE], ALL],
+ on___end__: [[GREEN], ALL],
+ }
+ rescue NameError
+ # Give up highlighting Ripper-incompatible older Ruby
+ TOKEN_SEQ_EXPRS = {}
+ end
+ private_constant :TOKEN_SEQ_EXPRS
+
+ ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') }
+ private_constant :ERROR_TOKENS
+
+ class << self
+ def colorable?
+ $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
+ end
+
+ def inspect_colorable?(obj, seen: {}.compare_by_identity)
+ case obj
+ when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass
+ true
+ when Hash
+ without_circular_ref(obj, seen: seen) do
+ obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) }
+ end
+ when Array
+ without_circular_ref(obj, seen: seen) do
+ obj.all? { |o| inspect_colorable?(o, seen: seen) }
+ end
+ when Range
+ inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen)
+ when Module
+ !obj.name.nil?
+ else
+ false
+ end
+ end
+
+ def clear(colorable: colorable?)
+ return '' unless colorable
+ "\e[#{CLEAR}m"
+ end
+
+ def colorize(text, seq, colorable: colorable?)
+ return text unless colorable
+ seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
+ "#{seq}#{text}#{clear(colorable: colorable)}"
+ end
+
+ # If `complete` is false (code is incomplete), this does not warn compile_error.
+ # This option is needed to avoid warning a user when the compile_error is happening
+ # because the input is not wrong but just incomplete.
+ def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?)
+ return code unless colorable
+
+ symbol_state = SymbolState.new
+ colored = +''
+ length = 0
+ end_seen = false
+
+ scan(code, allow_last_error: !complete) do |token, str, expr|
+ # IRB::ColorPrinter skips colorizing fragments with any invalid token
+ if ignore_error && ERROR_TOKENS.include?(token)
+ return Reline::Unicode.escape_for_print(code)
+ end
+
+ in_symbol = symbol_state.scan_token(token)
+ str.each_line do |line|
+ line = Reline::Unicode.escape_for_print(line)
+ if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol)
+ colored << seq.map { |s| "\e[#{s}m" }.join('')
+ colored << line.sub(/\Z/, clear(colorable: colorable))
+ else
+ colored << line
+ end
+ end
+ length += str.bytesize
+ end_seen = true if token == :on___end__
+ end
+
+ # give up colorizing incomplete Ripper tokens
+ unless end_seen or length == code.bytesize
+ return Reline::Unicode.escape_for_print(code)
+ end
+
+ colored
+ end
+
+ private
+
+ def without_circular_ref(obj, seen:, &block)
+ return false if seen.key?(obj)
+ seen[obj] = true
+ block.call
+ ensure
+ seen.delete(obj)
+ end
+
+ def scan(code, allow_last_error:)
+ pos = [1, 0]
+
+ verbose, $VERBOSE = $VERBOSE, nil
+ RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no|
+ lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no)
+ if lexer.respond_to?(:scan) # Ruby 2.7+
+ lexer.scan.each do |elem|
+ str = elem.tok
+ next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
+ next if ([elem.pos[0], elem.pos[1] + str.bytesize] <=> pos) <= 0
+
+ str.each_line do |line|
+ if line.end_with?("\n")
+ pos[0] += 1
+ pos[1] = 0
+ else
+ pos[1] += line.bytesize
+ end
+ end
+
+ yield(elem.event, str, elem.state)
+ end
+ else
+ lexer.parse.each do |elem|
+ yield(elem.event, elem.tok, elem.state)
+ end
+ end
+ end
+ ensure
+ $VERBOSE = verbose
+ end
+
+ def dispatch_seq(token, expr, str, in_symbol:)
+ if ERROR_TOKENS.include?(token)
+ TOKEN_SEQ_EXPRS[token][0]
+ elsif in_symbol
+ [YELLOW]
+ elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
+ [CYAN, BOLD]
+ elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
+ seq
+ else
+ nil
+ end
+ end
+ end
+
+ # A class to manage a state to know whether the current token is for Symbol or not.
+ class SymbolState
+ def initialize
+ # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
+ @stack = []
+ end
+
+ # Return true if the token is a part of Symbol.
+ def scan_token(token)
+ prev_state = @stack.last
+ case token
+ when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg
+ @stack << true
+ when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw
+ if @stack.last # Pop only when it's Symbol
+ @stack.pop
+ return prev_state
+ end
+ when :on_tstring_beg
+ @stack << false
+ when :on_embexpr_beg
+ @stack << false
+ return prev_state
+ when :on_tstring_end # :on_tstring_end may close Symbol
+ @stack.pop
+ return prev_state
+ when :on_embexpr_end
+ @stack.pop
+ end
+ @stack.last
+ end
+ end
+ private_constant :SymbolState
+ end
+end
diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb
new file mode 100644
index 0000000000..30c6825750
--- /dev/null
+++ b/lib/irb/color_printer.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+require 'pp'
+require 'irb/color'
+
+module IRB
+ class ColorPrinter < ::PP
+ class << self
+ def pp(obj, out = $>, width = screen_width)
+ q = ColorPrinter.new(out, width)
+ q.guard_inspect_key {q.pp obj}
+ q.flush
+ out << "\n"
+ end
+
+ private
+
+ def screen_width
+ Reline.get_screen_size.last
+ rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN>
+ 79
+ end
+ end
+
+ def pp(obj)
+ if obj.is_a?(String)
+ # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n"
+ text(obj.inspect)
+ else
+ super
+ end
+ end
+
+ def text(str, width = nil)
+ unless str.is_a?(String)
+ str = str.inspect
+ end
+ width ||= str.length
+
+ case str
+ when /\A#</, '=', '>'
+ super(Color.colorize(str, [:GREEN]), width)
+ else
+ super(Color.colorize_code(str, ignore_error: true), width)
+ end
+ end
+ end
+end
diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb
index 000658e2a3..9121174a50 100644
--- a/lib/irb/completion.rb
+++ b/lib/irb/completion.rb
@@ -1,205 +1,443 @@
+# frozen_string_literal: false
#
-# irb/completor.rb -
+# irb/completion.rb -
# $Release Version: 0.9$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
# From Original Idea of shugo@ruby-lang.org
#
-require "readline"
+require_relative 'ruby-lex'
module IRB
- module InputCompletor
-
- @RCS_ID='-$Id$-'
-
- ReservedWords = [
- "BEGIN", "END",
- "alias", "and",
- "begin", "break",
- "case", "class",
- "def", "defined", "do",
- "else", "elsif", "end", "ensure",
- "false", "for",
- "if", "in",
- "module",
- "next", "nil", "not",
- "or",
- "redo", "rescue", "retry", "return",
- "self", "super",
- "then", "true",
- "undef", "unless", "until",
- "when", "while",
- "yield",
+ module InputCompletor # :nodoc:
+
+
+ # Set of reserved words used by Ruby, you should not use these for
+ # constants or variables
+ ReservedWords = %w[
+ __ENCODING__ __LINE__ __FILE__
+ BEGIN END
+ alias and
+ begin break
+ case class
+ def defined? do
+ else elsif end ensure
+ false for
+ if in
+ module
+ next nil not
+ or
+ redo rescue retry return
+ self super
+ then true
+ undef unless until
+ when while
+ yield
]
-
- CompletionProc = proc { |input|
- bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
-
-# puts "input: #{input}"
+ BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{("
+
+ def self.absolute_path?(p) # TODO Remove this method after 2.6 EOL.
+ if File.respond_to?(:absolute_path?)
+ File.absolute_path?(p)
+ else
+ if File.absolute_path(p) == p
+ true
+ else
+ false
+ end
+ end
+ end
+
+ def self.retrieve_gem_and_system_load_path
+ gem_paths = Gem::Specification.latest_specs(true).map { |s|
+ s.require_paths.map { |p|
+ if absolute_path?(p)
+ p
+ else
+ File.join(s.full_gem_path, p)
+ end
+ }
+ }.flatten if defined?(Gem::Specification)
+ (gem_paths.to_a | $LOAD_PATH).sort
+ end
+
+ def self.retrieve_files_to_require_from_load_path
+ @@files_from_load_path ||=
+ (
+ shortest = []
+ rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result|
+ begin
+ names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path)
+ rescue Errno::ENOENT
+ nil
+ end
+ next if names.empty?
+ names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort!
+ shortest << names.shift
+ result.concat(names)
+ }
+ shortest.sort! | rest
+ )
+ end
+
+ def self.retrieve_files_to_require_relative_from_current_dir
+ @@files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path|
+ path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '')
+ }
+ end
+
+ CompletionRequireProc = lambda { |target, preposing = nil, postposing = nil|
+ if target =~ /\A(['"])([^'"]+)\Z/
+ quote = $1
+ actual_target = $2
+ else
+ return nil # It's not String literal
+ end
+ tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, ''))
+ tok = nil
+ tokens.reverse_each do |t|
+ unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event)
+ tok = t
+ break
+ end
+ end
+ result = []
+ if tok && tok.event == :on_ident && tok.state == Ripper::EXPR_CMDARG
+ case tok.tok
+ when 'require'
+ result = retrieve_files_to_require_from_load_path.select { |path|
+ path.start_with?(actual_target)
+ }.map { |path|
+ quote + path
+ }
+ when 'require_relative'
+ result = retrieve_files_to_require_relative_from_current_dir.select { |path|
+ path.start_with?(actual_target)
+ }.map { |path|
+ quote + path
+ }
+ end
+ end
+ result
+ }
+
+ CompletionProc = lambda { |target, preposing = nil, postposing = nil|
+ if preposing && postposing
+ result = CompletionRequireProc.(target, preposing, postposing)
+ unless result
+ result = retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) }
+ end
+ result
+ else
+ retrieve_completion_data(target).compact.map{ |i| i.encode(Encoding.default_external) }
+ end
+ }
+
+ def self.retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false)
case input
+ when /^((["'`]).*\2)\.([^.]*)$/
+ # String
+ receiver = $1
+ message = $3
+
+ candidates = String.instance_methods.collect{|m| m.to_s}
+ if doc_namespace
+ "String.#{message}"
+ else
+ select_message(receiver, message, candidates)
+ end
+
when /^(\/[^\/]*\/)\.([^.]*)$/
- # Regexp
- receiver = $1
- message = Regexp.quote($2)
+ # Regexp
+ receiver = $1
+ message = $2
- candidates = Regexp.instance_methods(true)
- select_message(receiver, message, candidates)
+ candidates = Regexp.instance_methods.collect{|m| m.to_s}
+ if doc_namespace
+ "Regexp.#{message}"
+ else
+ select_message(receiver, message, candidates)
+ end
when /^([^\]]*\])\.([^.]*)$/
- # Array
- receiver = $1
- message = Regexp.quote($2)
+ # Array
+ receiver = $1
+ message = $2
- candidates = Array.instance_methods(true)
- select_message(receiver, message, candidates)
+ candidates = Array.instance_methods.collect{|m| m.to_s}
+ if doc_namespace
+ "Array.#{message}"
+ else
+ select_message(receiver, message, candidates)
+ end
when /^([^\}]*\})\.([^.]*)$/
- # Proc or Hash
- receiver = $1
- message = Regexp.quote($2)
+ # Proc or Hash
+ receiver = $1
+ message = $2
+
+ proc_candidates = Proc.instance_methods.collect{|m| m.to_s}
+ hash_candidates = Hash.instance_methods.collect{|m| m.to_s}
+ if doc_namespace
+ ["Proc.#{message}", "Hash.#{message}"]
+ else
+ select_message(receiver, message, proc_candidates | hash_candidates)
+ end
- candidates = Proc.instance_methods(true) | Hash.instance_methods(true)
- select_message(receiver, message, candidates)
-
when /^(:[^:.]*)$/
- # Symbol
- if Symbol.respond_to?(:all_symbols)
- sym = $1
- candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name}
- candidates.grep(/^#{sym}/)
- else
- []
- end
-
- when /^::([A-Z][^:\.\(]*)$/
- # Absolute Constant or class methods
- receiver = $1
- candidates = Object.constants
- candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
-
- when /^(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/
- # Constant or class methods
- receiver = $1
- message = Regexp.quote($4)
- begin
- candidates = eval("#{receiver}.constants | #{receiver}.methods", bind)
- rescue Exception
- candidates = []
- end
- candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
-
- when /^(:[^:.]+)\.([^.]*)$/
- # Symbol
- receiver = $1
- message = Regexp.quote($2)
-
- candidates = Symbol.instance_methods(true)
- select_message(receiver, message, candidates)
-
- when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/
- # Numeric
- receiver = $1
- message = Regexp.quote($5)
-
- begin
- candidates = eval(receiver, bind).methods
- rescue Exception
- candidates = []
- end
- select_message(receiver, message, candidates)
-
- when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/
- # Numeric(0xFFFF)
- receiver = $1
- message = Regexp.quote($2)
-
- begin
- candidates = eval(receiver, bind).methods
- rescue Exception
- candidates = []
- end
- select_message(receiver, message, candidates)
+ # Symbol
+ return nil if doc_namespace
+ sym = $1
+ candidates = Symbol.all_symbols.collect do |s|
+ ":" + s.id2name.encode(Encoding.default_external)
+ rescue EncodingError
+ # ignore
+ end
+ candidates.grep(/^#{Regexp.quote(sym)}/)
+
+ when /^::([A-Z][^:\.\(\)]*)$/
+ # Absolute Constant or class methods
+ receiver = $1
+ candidates = Object.constants.collect{|m| m.to_s}
+ if doc_namespace
+ candidates.find { |i| i == receiver }
+ else
+ candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
+ end
+
+ when /^([A-Z].*)::([^:.]*)$/
+ # Constant or class methods
+ receiver = $1
+ message = $2
+ begin
+ candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
+ candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
+ rescue Exception
+ candidates = []
+ end
+ if doc_namespace
+ "#{receiver}::#{message}"
+ else
+ select_message(receiver, message, candidates, "::")
+ end
+
+ when /^(:[^:.]+)(\.|::)([^.]*)$/
+ # Symbol
+ receiver = $1
+ sep = $2
+ message = $3
+
+ candidates = Symbol.instance_methods.collect{|m| m.to_s}
+ if doc_namespace
+ "Symbol.#{message}"
+ else
+ select_message(receiver, message, candidates, sep)
+ end
+
+ when /^(?<num>-?(?:0[dbo])?[0-9_]+(?:\.[0-9_]+)?(?:(?:[eE][+-]?[0-9]+)?i?|r)?)(?<sep>\.|::)(?<mes>[^.]*)$/
+ # Numeric
+ receiver = $~[:num]
+ sep = $~[:sep]
+ message = $~[:mes]
+
+ begin
+ instance = eval(receiver, bind)
+ if doc_namespace
+ "#{instance.class.name}.#{message}"
+ else
+ candidates = instance.methods.collect{|m| m.to_s}
+ select_message(receiver, message, candidates, sep)
+ end
+ rescue Exception
+ if doc_namespace
+ nil
+ else
+ candidates = []
+ end
+ end
+
+ when /^(-?0x[0-9a-fA-F_]+)(\.|::)([^.]*)$/
+ # Numeric(0xFFFF)
+ receiver = $1
+ sep = $2
+ message = $3
+
+ begin
+ instance = eval(receiver, bind)
+ if doc_namespace
+ "#{instance.class.name}.#{message}"
+ else
+ candidates = instance.methods.collect{|m| m.to_s}
+ select_message(receiver, message, candidates, sep)
+ end
+ rescue Exception
+ if doc_namespace
+ nil
+ else
+ candidates = []
+ end
+ end
when /^(\$[^.]*)$/
- candidates = global_variables.grep(Regexp.new(Regexp.quote($1)))
-
-# when /^(\$?(\.?[^.]+)+)\.([^.]*)$/
- when /^((\.?[^.]+)+)\.([^.]*)$/
- # variable
- receiver = $1
- message = Regexp.quote($3)
-
- gv = eval("global_variables", bind)
- lv = eval("local_variables", bind)
- cv = eval("self.class.constants", bind)
-
- if (gv | lv | cv).include?(receiver)
- # foo.func and foo is local var.
- candidates = eval("#{receiver}.methods", bind)
- elsif /^[A-Z]/ =~ receiver and /\./ !~ receiver
- # Foo::Bar.func
- begin
- candidates = eval("#{receiver}.methods", bind)
- rescue Exception
- candidates = []
- end
- else
- # func1.func2
- candidates = []
- ObjectSpace.each_object(Module){|m|
- begin
- name = m.name
- rescue Exception
- name = ""
- end
- next if name != "IRB::Context" and
- /^(IRB|SLex|RubyLex|RubyToken)/ =~ name
- candidates.concat m.instance_methods(false)
- }
- candidates.sort!
- candidates.uniq!
- end
- select_message(receiver, message, candidates)
+ # global var
+ gvar = $1
+ all_gvars = global_variables.collect{|m| m.to_s}
+ if doc_namespace
+ all_gvars.find{ |i| i == gvar }
+ else
+ all_gvars.grep(Regexp.new(Regexp.quote(gvar)))
+ end
+
+ when /^([^.:"].*)(\.|::)([^.]*)$/
+ # variable.func or func.func
+ receiver = $1
+ sep = $2
+ message = $3
+
+ gv = eval("global_variables", bind).collect{|m| m.to_s}.push("true", "false", "nil")
+ lv = eval("local_variables", bind).collect{|m| m.to_s}
+ iv = eval("instance_variables", bind).collect{|m| m.to_s}
+ cv = eval("self.class.constants", bind).collect{|m| m.to_s}
+
+ if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
+ # foo.func and foo is var. OR
+ # foo::func and foo is var. OR
+ # foo::Const and foo is var. OR
+ # Foo::Bar.func
+ begin
+ candidates = []
+ rec = eval(receiver, bind)
+ if sep == "::" and rec.kind_of?(Module)
+ candidates = rec.constants.collect{|m| m.to_s}
+ end
+ candidates |= rec.methods.collect{|m| m.to_s}
+ rescue Exception
+ candidates = []
+ end
+ else
+ # func1.func2
+ candidates = []
+ to_ignore = ignored_modules
+ ObjectSpace.each_object(Module){|m|
+ next if (to_ignore.include?(m) rescue true)
+ candidates.concat m.instance_methods(false).collect{|x| x.to_s}
+ }
+ candidates.sort!
+ candidates.uniq!
+ end
+ if doc_namespace
+ rec_class = rec.is_a?(Module) ? rec : rec.class
+ "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}"
+ else
+ select_message(receiver, message, candidates, sep)
+ end
when /^\.([^.]*)$/
- # unknown(maybe String)
+ # unknown(maybe String)
- receiver = ""
- message = Regexp.quote($1)
+ receiver = ""
+ message = $1
- candidates = String.instance_methods(true)
- select_message(receiver, message, candidates)
+ candidates = String.instance_methods(true).collect{|m| m.to_s}
+ if doc_namespace
+ "String.#{candidates.find{ |i| i == message }}"
+ else
+ select_message(receiver, message, candidates)
+ end
else
- candidates = eval("methods | private_methods | local_variables | self.class.constants", bind)
-
- (candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/)
+ if doc_namespace
+ vars = eval("local_variables | instance_variables", bind).collect{|m| m.to_s}
+ perfect_match_var = vars.find{|m| m.to_s == input}
+ if perfect_match_var
+ eval("#{perfect_match_var}.class.name", bind)
+ else
+ candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s}
+ candidates |= ReservedWords
+ candidates.find{ |i| i == input }
+ end
+ else
+ candidates = eval("methods | private_methods | local_variables | instance_variables | self.class.constants", bind).collect{|m| m.to_s}
+ candidates |= ReservedWords
+ candidates.grep(/^#{Regexp.quote(input)}/)
+ end
+ end
+ end
+
+ PerfectMatchedProc = ->(matched, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) {
+ begin
+ require 'rdoc'
+ rescue LoadError
+ return
+ end
+
+ RDocRIDriver ||= RDoc::RI::Driver.new
+
+ if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER']
+ IRB.__send__(:easter_egg)
+ return
+ end
+
+ namespace = retrieve_completion_data(matched, bind: bind, doc_namespace: true)
+ return unless namespace
+
+ if namespace.is_a?(Array)
+ out = RDoc::Markup::Document.new
+ namespace.each do |m|
+ begin
+ RDocRIDriver.add_method(out, m)
+ rescue RDoc::RI::Driver::NotFoundError
+ end
+ end
+ RDocRIDriver.display(out)
+ else
+ begin
+ RDocRIDriver.display_names([namespace])
+ rescue RDoc::RI::Driver::NotFoundError
+ end
end
}
- Operators = ["%", "&", "*", "**", "+", "-", "/",
- "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>",
- "[]", "[]=", "^",]
-
- def self.select_message(receiver, message, candidates)
- candidates.grep(/^#{message}/).collect do |e|
- case e
- when /^[a-zA-Z_]/
- receiver + "." + e
- when /^[0-9]/
- when *Operators
- #receiver + " " + e
- end
+ # Set of available operators in Ruby
+ Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~]
+
+ def self.select_message(receiver, message, candidates, sep = ".")
+ candidates.grep(/^#{Regexp.quote(message)}/).collect do |e|
+ case e
+ when /^[a-zA-Z_]/
+ receiver + sep + e
+ when /^[0-9]/
+ when *Operators
+ #receiver + " " + e
+ end
end
end
- end
-end
-if Readline.respond_to?("basic_word_break_characters=")
- Readline.basic_word_break_characters= " \t\n\"\\'`><=;|&{("
+ def self.ignored_modules
+ # We could cache the result, but this is very fast already.
+ # By using this approach, we avoid Module#name calls, which are
+ # relatively slow when there are a lot of anonymous modules defined.
+ s = {}
+
+ scanner = lambda do |m|
+ next if s.include?(m) # IRB::ExtendCommandBundle::EXCB recurses.
+ s[m] = true
+ m.constants(false).each do |c|
+ value = m.const_get(c)
+ scanner.call(value) if value.is_a?(Module)
+ end
+ end
+
+ %i(IRB RubyLex).each do |sym|
+ next unless Object.const_defined?(sym)
+ scanner.call(Object.const_get(sym))
+ end
+
+ s.delete(IRB::Context) if defined?(IRB::Context)
+
+ s
+ end
+ end
end
-Readline.completion_append_character = nil
-Readline.completion_proc = IRB::InputCompletor::CompletionProc
diff --git a/lib/irb/context.rb b/lib/irb/context.rb
index d01bd4aefa..0a46c1b1d4 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -1,43 +1,64 @@
+# frozen_string_literal: false
#
# irb/context.rb - irb context
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-require "irb/workspace"
+#
+require_relative "workspace"
+require_relative "inspector"
+require_relative "input-method"
+require_relative "output-method"
module IRB
+ # A class that wraps the current state of the irb session, including the
+ # configuration of IRB.conf.
class Context
+ # Creates a new IRB context.
#
- # Arguments:
- # input_method: nil -- stdin or readline
- # String -- File
- # other -- using this as InputMethod
+ # The optional +input_method+ argument:
#
- def initialize(irb, workspace = nil, input_method = nil, output_method = nil)
+ # +nil+:: uses stdin or Reidline or Readline
+ # +String+:: uses a File
+ # +other+:: uses this as InputMethod
+ def initialize(irb, workspace = nil, input_method = nil)
@irb = irb
if workspace
- @workspace = workspace
+ @workspace = workspace
else
- @workspace = WorkSpace.new
+ @workspace = WorkSpace.new
end
@thread = Thread.current if defined? Thread
-# @irb_level = 0
# copy of default configuration
@ap_name = IRB.conf[:AP_NAME]
@rc = IRB.conf[:RC]
@load_modules = IRB.conf[:LOAD_MODULES]
- @use_readline = IRB.conf[:USE_READLINE]
- @inspect_mode = IRB.conf[:INSPECT_MODE]
+ if IRB.conf.has_key?(:USE_SINGLELINE)
+ @use_singleline = IRB.conf[:USE_SINGLELINE]
+ elsif IRB.conf.has_key?(:USE_READLINE) # backward compatibility
+ @use_singleline = IRB.conf[:USE_READLINE]
+ else
+ @use_singleline = nil
+ end
+ if IRB.conf.has_key?(:USE_MULTILINE)
+ @use_multiline = IRB.conf[:USE_MULTILINE]
+ elsif IRB.conf.has_key?(:USE_REIDLINE) # backward compatibility
+ @use_multiline = IRB.conf[:USE_REIDLINE]
+ else
+ @use_multiline = nil
+ end
+ @use_colorize = IRB.conf[:USE_COLORIZE]
+ @use_autocomplete = IRB.conf[:USE_AUTOCOMPLETE]
+ @verbose = IRB.conf[:VERBOSE]
+ @io = nil
- self.math_mode = IRB.conf[:MATH_MODE] if IRB.conf[:MATH_MODE]
+ self.inspect_mode = IRB.conf[:INSPECT_MODE]
self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER]
self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER]
self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY]
@@ -46,126 +67,322 @@ module IRB
@ignore_eof = IRB.conf[:IGNORE_EOF]
@back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT]
-
+
self.prompt_mode = IRB.conf[:PROMPT_MODE]
- if IRB.conf[:SINGLE_IRB] or !defined?(JobManager)
- @irb_name = IRB.conf[:IRB_NAME]
+ if IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager)
+ @irb_name = IRB.conf[:IRB_NAME]
else
- @irb_name = "irb#"+IRB.JobManager.n_jobs.to_s
+ @irb_name = IRB.conf[:IRB_NAME]+"#"+IRB.JobManager.n_jobs.to_s
end
@irb_path = "(" + @irb_name + ")"
case input_method
when nil
- case use_readline?
- when nil
- if (defined?(ReadlineInputMethod) && STDIN.tty? &&
- IRB.conf[:PROMPT_MODE] != :INF_RUBY)
- @io = ReadlineInputMethod.new
- else
- @io = StdioInputMethod.new
- end
- when false
- @io = StdioInputMethod.new
- when true
- if defined?(ReadlineInputMethod)
- @io = ReadlineInputMethod.new
- else
- @io = StdioInputMethod.new
- end
- end
+ @io = nil
+ case use_multiline?
+ when nil
+ if STDIN.tty? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline?
+ # Both of multiline mode and singleline mode aren't specified.
+ @io = ReidlineInputMethod.new
+ else
+ @io = nil
+ end
+ when false
+ @io = nil
+ when true
+ @io = ReidlineInputMethod.new
+ end
+ unless @io
+ case use_singleline?
+ when nil
+ if (defined?(ReadlineInputMethod) && STDIN.tty? &&
+ IRB.conf[:PROMPT_MODE] != :INF_RUBY)
+ @io = ReadlineInputMethod.new
+ else
+ @io = nil
+ end
+ when false
+ @io = nil
+ when true
+ if defined?(ReadlineInputMethod)
+ @io = ReadlineInputMethod.new
+ else
+ @io = nil
+ end
+ else
+ @io = nil
+ end
+ end
+ @io = StdioInputMethod.new unless @io
when String
- @io = FileInputMethod.new(input_method)
- @irb_name = File.basename(input_method)
- @irb_path = input_method
+ @io = FileInputMethod.new(input_method)
+ @irb_name = File.basename(input_method)
+ @irb_path = input_method
else
- @io = input_method
+ @io = input_method
end
self.save_history = IRB.conf[:SAVE_HISTORY] if IRB.conf[:SAVE_HISTORY]
- if output_method
- @output_method = output_method
- else
- @output_method = StdioOutputMethod.new
- end
+ @extra_doc_dirs = IRB.conf[:EXTRA_DOC_DIRS]
- @verbose = IRB.conf[:VERBOSE]
@echo = IRB.conf[:ECHO]
if @echo.nil?
- @echo = true
+ @echo = true
+ end
+
+ @echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT]
+ if @echo_on_assignment.nil?
+ @echo_on_assignment = :truncate
+ end
+
+ @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT]
+ if @newline_before_multiline_output.nil?
+ @newline_before_multiline_output = true
end
- @debug_level = IRB.conf[:DEBUG_LEVEL]
end
+ # The top-level workspace, see WorkSpace#main
def main
@workspace.main
end
+ # The toplevel workspace, see #home_workspace
attr_reader :workspace_home
+ # WorkSpace in the current context.
attr_accessor :workspace
+ # The current thread in this context.
attr_reader :thread
+ # The current input method.
+ #
+ # Can be either StdioInputMethod, ReadlineInputMethod,
+ # ReidlineInputMethod, FileInputMethod or other specified when the
+ # context is created. See ::new for more # information on +input_method+.
attr_accessor :io
-
+
+ # Current irb session.
attr_accessor :irb
+ # A copy of the default <code>IRB.conf[:AP_NAME]</code>
attr_accessor :ap_name
+ # A copy of the default <code>IRB.conf[:RC]</code>
attr_accessor :rc
+ # A copy of the default <code>IRB.conf[:LOAD_MODULES]</code>
attr_accessor :load_modules
+ # Can be either name from <code>IRB.conf[:IRB_NAME]</code>, or the number of
+ # the current job set by JobManager, such as <code>irb#2</code>
attr_accessor :irb_name
+ # Can be either the #irb_name surrounded by parenthesis, or the
+ # +input_method+ passed to Context.new
attr_accessor :irb_path
- attr_reader :use_readline
+ # Whether multiline editor mode is enabled or not.
+ #
+ # A copy of the default <code>IRB.conf[:USE_MULTILINE]</code>
+ attr_reader :use_multiline
+ # Whether singleline editor mode is enabled or not.
+ #
+ # A copy of the default <code>IRB.conf[:USE_SINGLELINE]</code>
+ attr_reader :use_singleline
+ # Whether colorization is enabled or not.
+ #
+ # A copy of the default <code>IRB.conf[:USE_COLORIZE]</code>
+ attr_reader :use_colorize
+ # A copy of the default <code>IRB.conf[:USE_AUTOCOMPLETE]</code>
+ attr_reader :use_autocomplete
+ # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code>
attr_reader :inspect_mode
+ # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code>
attr_reader :prompt_mode
+ # Standard IRB prompt.
+ #
+ # See IRB@Customizing+the+IRB+Prompt for more information.
attr_accessor :prompt_i
+ # IRB prompt for continuated strings.
+ #
+ # See IRB@Customizing+the+IRB+Prompt for more information.
attr_accessor :prompt_s
+ # IRB prompt for continuated statement. (e.g. immediately after an +if+)
+ #
+ # See IRB@Customizing+the+IRB+Prompt for more information.
attr_accessor :prompt_c
+ # See IRB@Customizing+the+IRB+Prompt for more information.
attr_accessor :prompt_n
+ # Can be either the default <code>IRB.conf[:AUTO_INDENT]</code>, or the
+ # mode set by #prompt_mode=
+ #
+ # To disable auto-indentation in irb:
+ #
+ # IRB.conf[:AUTO_INDENT] = false
+ #
+ # or
+ #
+ # irb_context.auto_indent_mode = false
+ #
+ # or
+ #
+ # IRB.CurrentContext.auto_indent_mode = false
+ #
+ # See IRB@Configuration for more information.
attr_accessor :auto_indent_mode
+ # The format of the return statement, set by #prompt_mode= using the
+ # +:RETURN+ of the +mode+ passed to set the current #prompt_mode.
attr_accessor :return_format
+ # Whether <code>^C</code> (+control-c+) will be ignored or not.
+ #
+ # If set to +false+, <code>^C</code> will quit irb.
+ #
+ # If set to +true+,
+ #
+ # * during input: cancel input then return to top level.
+ # * during execute: abandon current execution.
attr_accessor :ignore_sigint
+ # Whether <code>^D</code> (+control-d+) will be ignored or not.
+ #
+ # If set to +false+, <code>^D</code> will quit irb.
attr_accessor :ignore_eof
+ # Specify the installation locations of the ri file to be displayed in the
+ # document dialog.
+ attr_accessor :extra_doc_dirs
+ # Whether to echo the return value to output or not.
+ #
+ # Uses <code>IRB.conf[:ECHO]</code> if available, or defaults to +true+.
+ #
+ # puts "hello"
+ # # hello
+ # #=> nil
+ # IRB.CurrentContext.echo = false
+ # puts "omg"
+ # # omg
attr_accessor :echo
+ # Whether to echo for assignment expressions.
+ #
+ # If set to +false+, the value of assignment will not be shown.
+ #
+ # If set to +true+, the value of assignment will be shown.
+ #
+ # If set to +:truncate+, the value of assignment will be shown and truncated.
+ #
+ # It defaults to +:truncate+.
+ #
+ # a = "omg"
+ # #=> omg
+ #
+ # a = "omg" * 10
+ # #=> omgomgomgomgomgomgomg...
+ #
+ # IRB.CurrentContext.echo_on_assignment = false
+ # a = "omg"
+ #
+ # IRB.CurrentContext.echo_on_assignment = true
+ # a = "omg" * 10
+ # #=> omgomgomgomgomgomgomgomgomgomg
+ #
+ # To set the behaviour of showing on assignment in irb:
+ #
+ # IRB.conf[:ECHO_ON_ASSIGNMENT] = :truncate or true or false
+ #
+ # or
+ #
+ # irb_context.echo_on_assignment = :truncate or true or false
+ #
+ # or
+ #
+ # IRB.CurrentContext.echo_on_assignment = :truncate or true or false
+ attr_accessor :echo_on_assignment
+ # Whether a newline is put before multiline output.
+ #
+ # Uses <code>IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT]</code> if available,
+ # or defaults to +true+.
+ #
+ # "abc\ndef"
+ # #=>
+ # abc
+ # def
+ # IRB.CurrentContext.newline_before_multiline_output = false
+ # "abc\ndef"
+ # #=> abc
+ # def
+ attr_accessor :newline_before_multiline_output
+ # Whether verbose messages are displayed or not.
+ #
+ # A copy of the default <code>IRB.conf[:VERBOSE]</code>
attr_accessor :verbose
- attr_reader :debug_level
+ # The limit of backtrace lines displayed as top +n+ and tail +n+.
+ #
+ # The default value is 16.
+ #
+ # Can also be set using the +--back-trace-limit+ command line option.
+ #
+ # See IRB@Command+line+options for more command line options.
attr_accessor :back_trace_limit
- alias use_readline? use_readline
+ # Alias for #use_multiline
+ alias use_multiline? use_multiline
+ # Alias for #use_singleline
+ alias use_singleline? use_singleline
+ # backward compatibility
+ alias use_reidline use_multiline
+ # backward compatibility
+ alias use_reidline? use_multiline
+ # backward compatibility
+ alias use_readline use_singleline
+ # backward compatibility
+ alias use_readline? use_singleline
+ # Alias for #use_colorize
+ alias use_colorize? use_colorize
+ # Alias for #use_autocomplete
+ alias use_autocomplete? use_autocomplete
+ # Alias for #rc
alias rc? rc
alias ignore_sigint? ignore_sigint
alias ignore_eof? ignore_eof
alias echo? echo
+ alias echo_on_assignment? echo_on_assignment
+ alias newline_before_multiline_output? newline_before_multiline_output
+ # Returns whether messages are displayed or not.
def verbose?
if @verbose.nil?
- if defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)
- false
- elsif !STDIN.tty? or @io.kind_of?(FileInputMethod)
- true
- else
- false
- end
+ if @io.kind_of?(ReidlineInputMethod)
+ false
+ elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)
+ false
+ elsif !STDIN.tty? or @io.kind_of?(FileInputMethod)
+ true
+ else
+ false
+ end
+ else
+ @verbose
end
end
+ # Whether #verbose? is +true+, and +input_method+ is either
+ # StdioInputMethod or ReidlineInputMethod or ReadlineInputMethod, see #io
+ # for more information.
def prompting?
verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) ||
- (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)))
+ @io.kind_of?(ReidlineInputMethod) ||
+ (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod)))
end
+ # The return value of the last statement evaluated.
attr_reader :last_value
+ # Sets the return value from the last statement evaluated in this context
+ # to #last_value.
def set_last_value(value)
@last_value = value
- @workspace.evaluate self, "_ = IRB.CurrentContext.last_value"
+ @workspace.local_variable_set :_, value
end
- attr_reader :irb_name
-
+ # Sets the +mode+ of the prompt in this context.
+ #
+ # See IRB@Customizing+the+IRB+Prompt for more information.
def prompt_mode=(mode)
@prompt_mode = mode
pconf = IRB.conf[:PROMPT][mode]
@@ -174,78 +391,124 @@ module IRB
@prompt_c = pconf[:PROMPT_C]
@prompt_n = pconf[:PROMPT_N]
@return_format = pconf[:RETURN]
+ @return_format = "%s\n" if @return_format == nil
if ai = pconf.include?(:AUTO_INDENT)
- @auto_indent_mode = ai
+ @auto_indent_mode = ai
else
- @auto_indent_mode = IRB.conf[:AUTO_INDENT]
+ @auto_indent_mode = IRB.conf[:AUTO_INDENT]
end
end
-
+
+ # Whether #inspect_mode is set or not, see #inspect_mode= for more detail.
def inspect?
@inspect_mode.nil? or @inspect_mode
end
+ # Whether #io uses a File for the +input_method+ passed when creating the
+ # current context, see ::new
def file_input?
@io.class == FileInputMethod
end
+ # Specifies the inspect mode with +opt+:
+ #
+ # +true+:: display +inspect+
+ # +false+:: display +to_s+
+ # +nil+:: inspect mode in non-math mode,
+ # non-inspect mode in math mode
+ #
+ # See IRB::Inspector for more information.
+ #
+ # Can also be set using the +--inspect+ and +--noinspect+ command line
+ # options.
+ #
+ # See IRB@Command+line+options for more command line options.
def inspect_mode=(opt)
- if opt
- @inspect_mode = opt
+
+ if i = Inspector::INSPECTORS[opt]
+ @inspect_mode = opt
+ @inspect_method = i
+ i.init
else
- @inspect_mode = !@inspect_mode
+ case opt
+ when nil
+ if Inspector.keys_with_inspector(Inspector::INSPECTORS[true]).include?(@inspect_mode)
+ self.inspect_mode = false
+ elsif Inspector.keys_with_inspector(Inspector::INSPECTORS[false]).include?(@inspect_mode)
+ self.inspect_mode = true
+ else
+ puts "Can't switch inspect mode."
+ return
+ end
+ when /^\s*\{.*\}\s*$/
+ begin
+ inspector = eval "proc#{opt}"
+ rescue Exception
+ puts "Can't switch inspect mode(#{opt})."
+ return
+ end
+ self.inspect_mode = inspector
+ when Proc
+ self.inspect_mode = IRB::Inspector(opt)
+ when Inspector
+ prefix = "usr%d"
+ i = 1
+ while Inspector::INSPECTORS[format(prefix, i)]; i += 1; end
+ @inspect_mode = format(prefix, i)
+ @inspect_method = opt
+ Inspector.def_inspector(format(prefix, i), @inspect_method)
+ else
+ puts "Can't switch inspect mode(#{opt})."
+ return
+ end
end
print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose?
@inspect_mode
end
- def use_readline=(opt)
- @use_readline = opt
- print "use readline module\n" if @use_readline
- end
-
- def debug_level=(value)
- @debug_level = value
- RubyLex.debug_level = value
- SLex.debug_level = value
- end
-
- def debug?
- @debug_level > 0
- end
-
- def evaluate(line, line_no)
+ def evaluate(line, line_no, exception: nil) # :nodoc:
@line_no = line_no
+ if exception
+ line_no -= 1
+ line = "begin ::Kernel.raise _; rescue _.class\n#{line}\n""end"
+ @workspace.local_variable_set(:_, exception)
+ end
set_last_value(@workspace.evaluate(self, line, irb_path, line_no))
-# @workspace.evaluate("_ = IRB.conf[:MAIN_CONTEXT]._")
-# @_ = @workspace.evaluate(line, irb_path, line_no)
+ end
+
+ def inspect_last_value # :nodoc:
+ @inspect_method.inspect_value(@last_value)
end
alias __exit__ exit
+ # Exits the current session, see IRB.irb_exit
def exit(ret = 0)
IRB.irb_exit(@irb, ret)
+ rescue UncaughtThrowError
+ super
end
- NOPRINTING_IVARS = ["@last_value"]
- NO_INSPECTING_IVARS = ["@irb", "@io"]
- IDNAME_IVARS = ["@prompt_mode"]
+ NOPRINTING_IVARS = ["@last_value"] # :nodoc:
+ NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc:
+ IDNAME_IVARS = ["@prompt_mode"] # :nodoc:
alias __inspect__ inspect
- def inspect
+ def inspect # :nodoc:
array = []
for ivar in instance_variables.sort{|e1, e2| e1 <=> e2}
- name = ivar.sub(/^@(.*)$/){$1}
- val = instance_eval(ivar)
- case ivar
- when *NOPRINTING_IVARS
- array.push format("conf.%s=%s", name, "...")
- when *NO_INSPECTING_IVARS
- array.push format("conf.%s=%s", name, val.to_s)
- when *IDNAME_IVARS
- array.push format("conf.%s=:%s", name, val.id2name)
- else
- array.push format("conf.%s=%s", name, val.inspect)
- end
+ ivar = ivar.to_s
+ name = ivar.sub(/^@(.*)$/, '\1')
+ val = instance_eval(ivar)
+ case ivar
+ when *NOPRINTING_IVARS
+ array.push format("conf.%s=%s", name, "...")
+ when *NO_INSPECTING_IVARS
+ array.push format("conf.%s=%s", name, val.to_s)
+ when *IDNAME_IVARS
+ array.push format("conf.%s=:%s", name, val.id2name)
+ else
+ array.push format("conf.%s=%s", name, val.inspect)
+ end
end
array.join("\n")
end
diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb
new file mode 100644
index 0000000000..3e79692de9
--- /dev/null
+++ b/lib/irb/easter-egg.rb
@@ -0,0 +1,138 @@
+require "reline"
+
+module IRB
+ class << self
+ class Vec
+ def initialize(x, y, z)
+ @x, @y, @z = x, y, z
+ end
+
+ attr_reader :x, :y, :z
+
+ def sub(other)
+ Vec.new(@x - other.x, @y - other.y, @z - other.z)
+ end
+
+ def dot(other)
+ @x*other.x + @y*other.y + @z*other.z
+ end
+
+ def cross(other)
+ ox, oy, oz = other.x, other.y, other.z
+ Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox)
+ end
+
+ def normalize
+ r = Math.sqrt(self.dot(self))
+ Vec.new(@x / r, @y / r, @z / r)
+ end
+ end
+
+ class Canvas
+ def initialize((h, w))
+ @data = (0..h-2).map { [0] * w }
+ @scale = [w / 2.0, h-2].min
+ @center = Complex(w / 2, h-2)
+ end
+
+ def line((x1, y1), (x2, y2))
+ p1 = Complex(x1, y1) / 2 * @scale + @center
+ p2 = Complex(x2, y2) / 2 * @scale + @center
+ line0(p1, p2)
+ end
+
+ private def line0(p1, p2)
+ mid = (p1 + p2) / 2
+ if (p1 - p2).abs < 1
+ x, y = mid.rect
+ @data[y / 2][x] |= (y % 2 > 1 ? 2 : 1)
+ else
+ line0(p1, mid)
+ line0(p2, mid)
+ end
+ end
+
+ def draw
+ @data.each {|row| row.fill(0) }
+ yield
+ @data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n")
+ end
+ end
+
+ class RubyModel
+ def initialize
+ @faces = init_ruby_model
+ end
+
+ def init_ruby_model
+ cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) }
+ middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) }
+ bottom_vertex = Vec.new(0, 0, -2)
+
+ faces = [cap_vertices]
+ 6.times do |j|
+ i = j-1
+ faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]]
+ faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]]
+ faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]]
+ end
+
+ faces
+ end
+
+ def render_frame(i)
+ angle = i / 10.0
+ dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize
+ dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0)
+ up = dir.cross(dir2)
+ nm = dir.cross(up)
+ @faces.each do |vertices|
+ v0, v1, v2, = vertices
+ if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0
+ points = vertices.map {|p| [nm.dot(p), up.dot(p)] }
+ (points + [points[0]]).each_cons(2) do |p1, p2|
+ yield p1, p2
+ end
+ end
+ end
+ end
+ end
+
+ private def easter_egg(type = nil)
+ type ||= [:logo, :dancing].sample
+ case type
+ when :logo
+ File.open(File.join(__dir__, 'ruby_logo.aa')) do |f|
+ require "rdoc"
+ RDoc::RI::Driver.new.page do |io|
+ IO.copy_stream(f, io)
+ end
+ end
+ when :dancing
+ begin
+ canvas = Canvas.new(Reline.get_screen_size)
+ Reline::IOGate.set_winch_handler do
+ canvas = Canvas.new(Reline.get_screen_size)
+ end
+ ruby_model = RubyModel.new
+ print "\e[?1049h"
+ 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
+ buff = canvas.draw do
+ ruby_model.render_frame(i) do |p1, p2|
+ canvas.line(p1, p2)
+ end
+ end
+ buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m"
+ print "\e[H" + buff
+ sleep 0.05
+ end
+ rescue Interrupt
+ ensure
+ print "\e[0m\e[?1049l"
+ end
+ end
+ end
+ end
+end
+
+IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__
diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb
index fff8f58fe5..4c57e44eab 100644
--- a/lib/irb/ext/change-ws.rb
+++ b/lib/irb/ext/change-ws.rb
@@ -1,62 +1,45 @@
+# frozen_string_literal: false
#
-# irb/ext/cb.rb -
-# $Release Version: 0.9.5$
+# irb/ext/cb.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-module IRB
+module IRB # :nodoc:
class Context
+ # Inherited from +TOPLEVEL_BINDING+.
def home_workspace
if defined? @home_workspace
- @home_workspace
+ @home_workspace
else
- @home_workspace = @workspace
+ @home_workspace = @workspace
end
end
+ # Changes the current workspace to given object or binding.
+ #
+ # If the optional argument is omitted, the workspace will be
+ # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main
+ # object, <code>IRB.conf[:MAIN_CONTEXT]</code> when irb was initialized.
+ #
+ # See IRB::WorkSpace.new for more information.
def change_workspace(*_main)
if _main.empty?
- @workspace = home_workspace
- return main
+ @workspace = home_workspace
+ return main
end
-
+
@workspace = WorkSpace.new(_main[0])
-
+
if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
- main.extend ExtendCommandBundle
+ main.extend ExtendCommandBundle
end
end
-
-# def change_binding(*_main)
-# back = @workspace
-# @workspace = WorkSpace.new(*_main)
-# unless _main.empty?
-# begin
-# main.extend ExtendCommandBundle
-# rescue
-# print "can't change binding to: ", main.inspect, "\n"
-# @workspace = back
-# return nil
-# end
-# end
-# @irb_level += 1
-# begin
-# catch(:SU_EXIT) do
-# @irb.eval_input
-# end
-# ensure
-# @irb_level -= 1
-# @workspace = back
-# end
-# end
-# alias change_workspace change_binding
- end
+ end
end
-
diff --git a/lib/irb/ext/history.rb b/lib/irb/ext/history.rb
index 40f8692e8b..fc304c6f6c 100644
--- a/lib/irb/ext/history.rb
+++ b/lib/irb/ext/history.rb
@@ -1,110 +1,155 @@
+# frozen_string_literal: false
#
-# history.rb -
-# $Release Version: 0.9.5$
+# history.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-module IRB
+module IRB # :nodoc:
class Context
NOPRINTING_IVARS.push "@eval_history_values"
+ # See #set_last_value
alias _set_last_value set_last_value
def set_last_value(value)
_set_last_value(value)
-# @workspace.evaluate self, "_ = IRB.CurrentContext.last_value"
- if @eval_history #and !@eval_history_values.equal?(llv)
- @eval_history_values.push @line_no, @last_value
- @workspace.evaluate self, "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
+ if defined?(@eval_history) && @eval_history
+ @eval_history_values.push @line_no, @last_value
+ @workspace.evaluate self, "__ = IRB.CurrentContext.instance_eval{@eval_history_values}"
end
@last_value
end
+ remove_method :eval_history= if method_defined?(:eval_history=)
+ # The command result history limit. This method is not available until
+ # #eval_history= was called with non-nil value (directly or via
+ # setting <code>IRB.conf[:EVAL_HISTORY]</code> in <code>.irbrc</code>).
attr_reader :eval_history
+ # Sets command result history limit. Default value is set from
+ # <code>IRB.conf[:EVAL_HISTORY]</code>.
+ #
+ # +no+ is an Integer or +nil+.
+ #
+ # Returns +no+ of history items if greater than 0.
+ #
+ # If +no+ is 0, the number of history items is unlimited.
+ #
+ # If +no+ is +nil+, execution result history isn't used (default).
+ #
+ # History values are available via <code>__</code> variable, see
+ # IRB::History.
def eval_history=(no)
if no
- if defined?(@eval_history) && @eval_history
- @eval_history_values.size(no)
- else
- @eval_history_values = History.new(no)
- IRB.conf[:__TMP__EHV__] = @eval_history_values
- @workspace.evaluate(self, "__ = IRB.conf[:__TMP__EHV__]")
- IRB.conf.delete(:__TMP_EHV__)
- end
+ if defined?(@eval_history) && @eval_history
+ @eval_history_values.size(no)
+ else
+ @eval_history_values = History.new(no)
+ IRB.conf[:__TMP__EHV__] = @eval_history_values
+ @workspace.evaluate(self, "__ = IRB.conf[:__TMP__EHV__]")
+ IRB.conf.delete(:__TMP_EHV__)
+ end
else
- @eval_history_values = nil
+ @eval_history_values = nil
end
@eval_history = no
end
end
+ # Represents history of results of previously evaluated commands.
+ #
+ # Available via <code>__</code> variable, only if <code>IRB.conf[:EVAL_HISTORY]</code>
+ # or <code>IRB::CurrentContext().eval_history</code> is non-nil integer value
+ # (by default it is +nil+).
+ #
+ # Example (in `irb`):
+ #
+ # # Initialize history
+ # IRB::CurrentContext().eval_history = 10
+ # # => 10
+ #
+ # # Perform some commands...
+ # 1 + 2
+ # # => 3
+ # puts 'x'
+ # # x
+ # # => nil
+ # raise RuntimeError
+ # # ...error raised
+ #
+ # # Inspect history (format is "<item number> <evaluated value>":
+ # __
+ # # => 1 10
+ # # 2 3
+ # # 3 nil
+ #
+ # __[1]
+ # # => 10
+ #
class History
- @RCS_ID='-$Id$-'
- def initialize(size = 16)
+ def initialize(size = 16) # :nodoc:
@size = size
@contents = []
end
- def size(size)
- if size != 0 && size < @size
- @contents = @contents[@size - size .. @size]
+ def size(size) # :nodoc:
+ if size != 0 && size < @size
+ @contents = @contents[@size - size .. @size]
end
@size = size
end
+ # Get one item of the content (both positive and negative indexes work).
def [](idx)
begin
- if idx >= 0
- @contents.find{|no, val| no == idx}[1]
- else
- @contents[idx][1]
- end
+ if idx >= 0
+ @contents.find{|no, val| no == idx}[1]
+ else
+ @contents[idx][1]
+ end
rescue NameError
- nil
+ nil
end
end
- def push(no, val)
+ def push(no, val) # :nodoc:
@contents.push [no, val]
@contents.shift if @size != 0 && @contents.size > @size
end
-
+
alias real_inspect inspect
- def inspect
+ def inspect # :nodoc:
if @contents.empty?
- return real_inspect
+ return real_inspect
end
unless (last = @contents.pop)[1].equal?(self)
- @contents.push last
- last = nil
+ @contents.push last
+ last = nil
end
str = @contents.collect{|no, val|
- if val.equal?(self)
- "#{no} ...self-history..."
- else
- "#{no} #{val.inspect}"
- end
+ if val.equal?(self)
+ "#{no} ...self-history..."
+ else
+ "#{no} #{val.inspect}"
+ end
}.join("\n")
if str == ""
- str = "Empty."
+ str = "Empty."
end
@contents.push last if last
str
end
end
end
-
-
diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb
index 837e2553ac..af028996e7 100644
--- a/lib/irb/ext/loader.rb
+++ b/lib/irb/ext/loader.rb
@@ -1,25 +1,29 @@
+# frozen_string_literal: false
#
-# loader.rb -
-# $Release Version: 0.9.5$
+# loader.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-module IRB
+module IRB # :nodoc:
+ # Raised in the event of an exception in a file loaded from an Irb session
class LoadAbort < Exception;end
+ # Provides a few commands for loading files within an irb session.
+ #
+ # See ExtendCommandBundle for more information.
module IrbLoader
- @RCS_ID='-$Id$-'
alias ruby_load load
alias ruby_require require
+ # Loads the given file similarly to Kernel#load
def irb_load(fn, priv = nil)
path = search_file_from_ruby_path(fn)
raise LoadError, "No such file to load -- #{fn}" unless path
@@ -27,94 +31,125 @@ module IRB
load_file(path, priv)
end
- def search_file_from_ruby_path(fn)
- if /^#{Regexp.quote(File::Separator)}/ =~ fn
- return fn if File.exist?(fn)
- return nil
+ if File.respond_to?(:absolute_path?)
+ def absolute_path?(path)
+ File.absolute_path?(path)
+ end
+ else
+ separator =
+ if File::ALT_SEPARATOR
+ "[#{Regexp.quote(File::SEPARATOR + File::ALT_SEPARATOR)}]"
+ else
+ File::SEPARATOR
+ end
+ ABSOLUTE_PATH_PATTERN = # :nodoc:
+ case Dir.pwd
+ when /\A\w:/, /\A#{separator}{2}/
+ /\A(?:\w:|#{separator})#{separator}/
+ else
+ /\A#{separator}/
+ end
+ def absolute_path?(path)
+ ABSOLUTE_PATH_PATTERN =~ path
+ end
+ end
+
+ def search_file_from_ruby_path(fn) # :nodoc:
+ if absolute_path?(fn)
+ return fn if File.exist?(fn)
+ return nil
end
for path in $:
- if File.exist?(f = File.join(path, fn))
- return f
- end
+ if File.exist?(f = File.join(path, fn))
+ return f
+ end
end
return nil
end
+ # Loads a given file in the current session and displays the source lines
+ #
+ # See Irb#suspend_input_method for more information.
def source_file(path)
irb.suspend_name(path, File.basename(path)) do
- irb.suspend_input_method(FileInputMethod.new(path)) do
- |back_io|
- irb.signal_status(:IN_LOAD) do
- if back_io.kind_of?(FileInputMethod)
- irb.eval_input
- else
- begin
- irb.eval_input
- rescue LoadAbort
- print "load abort!!\n"
- end
- end
- end
- end
+ FileInputMethod.open(path) do |io|
+ irb.suspend_input_method(io) do
+ |back_io|
+ irb.signal_status(:IN_LOAD) do
+ if back_io.kind_of?(FileInputMethod)
+ irb.eval_input
+ else
+ begin
+ irb.eval_input
+ rescue LoadAbort
+ print "load abort!!\n"
+ end
+ end
+ end
+ end
+ end
end
end
+ # Loads the given file in the current session's context and evaluates it.
+ #
+ # See Irb#suspend_input_method for more information.
def load_file(path, priv = nil)
irb.suspend_name(path, File.basename(path)) do
-
- if priv
- ws = WorkSpace.new(Module.new)
- else
- ws = WorkSpace.new
- end
- irb.suspend_workspace(ws) do
- irb.suspend_input_method(FileInputMethod.new(path)) do
- |back_io|
- irb.signal_status(:IN_LOAD) do
-# p irb.conf
- if back_io.kind_of?(FileInputMethod)
- irb.eval_input
- else
- begin
- irb.eval_input
- rescue LoadAbort
- print "load abort!!\n"
- end
- end
- end
- end
- end
+
+ if priv
+ ws = WorkSpace.new(Module.new)
+ else
+ ws = WorkSpace.new
+ end
+ irb.suspend_workspace(ws) do
+ FileInputMethod.open(path) do |io|
+ irb.suspend_input_method(io) do
+ |back_io|
+ irb.signal_status(:IN_LOAD) do
+ if back_io.kind_of?(FileInputMethod)
+ irb.eval_input
+ else
+ begin
+ irb.eval_input
+ rescue LoadAbort
+ print "load abort!!\n"
+ end
+ end
+ end
+ end
+ end
+ end
end
end
- def old
+ def old # :nodoc:
back_io = @io
back_path = @irb_path
back_name = @irb_name
back_scanner = @irb.scanner
begin
- @io = FileInputMethod.new(path)
- @irb_name = File.basename(path)
- @irb_path = path
- @irb.signal_status(:IN_LOAD) do
- if back_io.kind_of?(FileInputMethod)
- @irb.eval_input
- else
- begin
- @irb.eval_input
- rescue LoadAbort
- print "load abort!!\n"
- end
- end
- end
+ @io = FileInputMethod.new(path)
+ @irb_name = File.basename(path)
+ @irb_path = path
+ @irb.signal_status(:IN_LOAD) do
+ if back_io.kind_of?(FileInputMethod)
+ @irb.eval_input
+ else
+ begin
+ @irb.eval_input
+ rescue LoadAbort
+ print "load abort!!\n"
+ end
+ end
+ end
ensure
- @io = back_io
- @irb_name = back_name
- @irb_path = back_path
- @irb.scanner = back_scanner
+ @io = back_io
+ @irb_name = back_name
+ @irb_path = back_path
+ @irb.scanner = back_scanner
end
end
end
end
-
diff --git a/lib/irb/ext/math-mode.rb b/lib/irb/ext/math-mode.rb
deleted file mode 100644
index bd443b96ed..0000000000
--- a/lib/irb/ext/math-mode.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# math-mode.rb -
-# $Release Version: 0.9.5$
-# $Revision$
-# $Date$
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-# --
-#
-#
-#
-require "mathn"
-
-module IRB
- class Context
- attr_reader :math_mode
- alias math? math_mode
-
- def math_mode=(opt)
- if @math_mode == true && opt == false
- IRB.fail CantReturnToNormalMode
- return
- end
-
- @math_mode = opt
- if math_mode
- main.extend Math
- print "start math mode\n" if verbose?
- end
- end
-
- def inspect?
- @inspect_mode.nil? && !@math_mode or @inspect_mode
- end
- end
-end
-
diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb
index 4589b1d554..74de1ecde5 100644
--- a/lib/irb/ext/multi-irb.rb
+++ b/lib/irb/ext/multi-irb.rb
@@ -1,134 +1,170 @@
+# frozen_string_literal: false
#
# irb/multi-irb.rb - multiple irb module
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-IRB.fail CantShiftToMultiIrbMode unless defined?(Thread)
-require "thread"
+#
+fail CantShiftToMultiIrbMode unless defined?(Thread)
module IRB
- # job management class
class JobManager
- @RCS_ID='-$Id$-'
+ # Creates a new JobManager object
def initialize
- # @jobs = [[thread, irb],...]
@jobs = []
@current_job = nil
end
+ # The active irb session
attr_accessor :current_job
+ # The total number of irb sessions, used to set +irb_name+ of the current
+ # Context.
def n_jobs
@jobs.size
end
+ # Returns the thread for the given +key+ object, see #search for more
+ # information.
def thread(key)
- th, irb = search(key)
+ th, = search(key)
th
end
+ # Returns the irb session for the given +key+ object, see #search for more
+ # information.
def irb(key)
- th, irb = search(key)
+ _, irb = search(key)
irb
end
+ # Returns the top level thread.
def main_thread
@jobs[0][0]
end
+ # Returns the top level irb session.
def main_irb
@jobs[0][1]
end
+ # Add the given +irb+ session to the jobs Array.
def insert(irb)
@jobs.push [Thread.current, irb]
end
+ # Changes the current active irb session to the given +key+ in the jobs
+ # Array.
+ #
+ # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive.
+ #
+ # If the given irb session is already active, an IrbSwitchedToCurrentThread
+ # exception is raised.
def switch(key)
th, irb = search(key)
- IRB.fail IrbAlreadyDead unless th.alive?
- IRB.fail IrbSwitchedToCurrentThread if th == Thread.current
+ fail IrbAlreadyDead unless th.alive?
+ fail IrbSwitchedToCurrentThread if th == Thread.current
@current_job = irb
th.run
Thread.stop
@current_job = irb(Thread.current)
end
+ # Terminates the irb sessions specified by the given +keys+.
+ #
+ # Raises an IrbAlreadyDead exception if one of the given +keys+ is already
+ # terminated.
+ #
+ # See Thread#exit for more information.
def kill(*keys)
for key in keys
- th, irb = search(key)
- IRB.fail IrbAlreadyDead unless th.alive?
- th.exit
+ th, _ = search(key)
+ fail IrbAlreadyDead unless th.alive?
+ th.exit
end
- end
+ end
+ # Returns the associated job for the given +key+.
+ #
+ # If given an Integer, it will return the +key+ index for the jobs Array.
+ #
+ # When an instance of Irb is given, it will return the irb session
+ # associated with +key+.
+ #
+ # If given an instance of Thread, it will return the associated thread
+ # +key+ using Object#=== on the jobs Array.
+ #
+ # Otherwise returns the irb session with the same top-level binding as the
+ # given +key+.
+ #
+ # Raises a NoSuchJob exception if no job can be found with the given +key+.
def search(key)
- case key
- when Integer
- @jobs[key]
- when Irb
- @jobs.find{|k, v| v.equal?(key)}
- when Thread
- @jobs.assoc(key)
- else
- assoc = @jobs.find{|k, v| v.context.main.equal?(key)}
- IRB.fail NoSuchJob, key if assoc.nil?
- assoc
- end
+ job = case key
+ when Integer
+ @jobs[key]
+ when Irb
+ @jobs.find{|k, v| v.equal?(key)}
+ when Thread
+ @jobs.assoc(key)
+ else
+ @jobs.find{|k, v| v.context.main.equal?(key)}
+ end
+ fail NoSuchJob, key if job.nil?
+ job
end
+ # Deletes the job at the given +key+.
def delete(key)
case key
when Integer
- IRB.fail NoSuchJob, key unless @jobs[key]
- @jobs[key] = nil
+ fail NoSuchJob, key unless @jobs[key]
+ @jobs[key] = nil
else
- catch(:EXISTS) do
- @jobs.each_index do
- |i|
- if @jobs[i] and (@jobs[i][0] == key ||
- @jobs[i][1] == key ||
- @jobs[i][1].context.main.equal?(key))
- @jobs[i] = nil
- throw :EXISTS
- end
- end
- IRB.fail NoSuchJob, key
- end
+ catch(:EXISTS) do
+ @jobs.each_index do
+ |i|
+ if @jobs[i] and (@jobs[i][0] == key ||
+ @jobs[i][1] == key ||
+ @jobs[i][1].context.main.equal?(key))
+ @jobs[i] = nil
+ throw :EXISTS
+ end
+ end
+ fail NoSuchJob, key
+ end
end
until assoc = @jobs.pop; end unless @jobs.empty?
@jobs.push assoc
end
+ # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+.
def inspect
ary = []
@jobs.each_index do
- |i|
- th, irb = @jobs[i]
- next if th.nil?
-
- if th.alive?
- if th.stop?
- t_status = "stop"
- else
- t_status = "running"
- end
- else
- t_status = "exited"
- end
- ary.push format("#%d->%s on %s (%s: %s)",
- i,
- irb.context.irb_name,
- irb.context.main,
- th,
- t_status)
+ |i|
+ th, irb = @jobs[i]
+ next if th.nil?
+
+ if th.alive?
+ if th.stop?
+ t_status = "stop"
+ else
+ t_status = "running"
+ end
+ else
+ t_status = "exited"
+ end
+ ary.push format("#%d->%s on %s (%s: %s)",
+ i,
+ irb.context.irb_name,
+ irb.context.main,
+ th,
+ t_status)
end
ary.join("\n")
end
@@ -136,76 +172,64 @@ module IRB
@JobManager = JobManager.new
+ # The current JobManager in the session
def IRB.JobManager
@JobManager
end
+ # The current Context in this session
def IRB.CurrentContext
IRB.JobManager.irb(Thread.current).context
end
- # invoke multi-irb
+ # Creates a new IRB session, see Irb.new.
+ #
+ # The optional +file+ argument is given to Context.new, along with the
+ # workspace created with the remaining arguments, see WorkSpace.new
def IRB.irb(file = nil, *main)
workspace = WorkSpace.new(*main)
parent_thread = Thread.current
Thread.start do
begin
- irb = Irb.new(workspace, file)
- rescue
- print "Subirb can't start with context(self): ", workspace.main.inspect, "\n"
- print "return to main irb\n"
- Thread.pass
- Thread.main.wakeup
- Thread.exit
+ irb = Irb.new(workspace, file)
+ rescue
+ print "Subirb can't start with context(self): ", workspace.main.inspect, "\n"
+ print "return to main irb\n"
+ Thread.pass
+ Thread.main.wakeup
+ Thread.exit
end
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
@JobManager.insert(irb)
@JobManager.current_job = irb
begin
- system_exit = false
- catch(:IRB_EXIT) do
- irb.eval_input
- end
+ system_exit = false
+ catch(:IRB_EXIT) do
+ irb.eval_input
+ end
rescue SystemExit
- system_exit = true
- raise
- #fail
+ system_exit = true
+ raise
+ #fail
ensure
- unless system_exit
- @JobManager.delete(irb)
- if parent_thread.alive?
- @JobManager.current_job = @JobManager.irb(parent_thread)
- parent_thread.run
- else
- @JobManager.current_job = @JobManager.main_irb
- @JobManager.main_thread.run
- end
- end
+ unless system_exit
+ @JobManager.delete(irb)
+ if @JobManager.current_job == irb
+ if parent_thread.alive?
+ @JobManager.current_job = @JobManager.irb(parent_thread)
+ parent_thread.run
+ else
+ @JobManager.current_job = @JobManager.main_irb
+ @JobManager.main_thread.run
+ end
+ end
+ end
end
end
Thread.stop
@JobManager.current_job = @JobManager.irb(Thread.current)
end
-# class Context
-# def set_last_value(value)
-# @last_value = value
-# @workspace.evaluate "_ = IRB.JobManager.irb(Thread.current).context.last_value"
-# if @eval_history #and !@__.equal?(@last_value)
-# @eval_history_values.push @line_no, @last_value
-# @workspace.evaluate "__ = IRB.JobManager.irb(Thread.current).context.instance_eval{@eval_history_values}"
-# end
-# @last_value
-# end
-# end
-
-# module ExtendCommand
-# def irb_context
-# IRB.JobManager.irb(Thread.current).context
-# end
-# # alias conf irb_context
-# end
-
@CONF[:SINGLE_IRB_MODE] = false
@JobManager.insert(@CONF[:MAIN_CONTEXT].irb)
@JobManager.current_job = @CONF[:MAIN_CONTEXT].irb
@@ -213,22 +237,22 @@ module IRB
class Irb
def signal_handle
unless @context.ignore_sigint?
- print "\nabort!!\n" if @context.verbose?
- exit
+ print "\nabort!!\n" if @context.verbose?
+ exit
end
case @signal_status
when :IN_INPUT
- print "^C\n"
- IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput
+ print "^C\n"
+ IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput
when :IN_EVAL
- IRB.irb_abort(self)
+ IRB.irb_abort(self)
when :IN_LOAD
- IRB.irb_abort(self, LoadAbort)
+ IRB.irb_abort(self, LoadAbort)
when :IN_IRB
- # ignore
+ # ignore
else
- # ignore other cases as well
+ # ignore other cases as well
end
end
end
diff --git a/lib/irb/ext/save-history.rb b/lib/irb/ext/save-history.rb
index 5260bfcdd8..7acaebe36a 100644
--- a/lib/irb/ext/save-history.rb
+++ b/lib/irb/ext/save-history.rb
@@ -1,85 +1,130 @@
-#!/usr/local/bin/ruby
-#
-# save-history.rb -
-# $Release Version: 0.9.5$
+# frozen_string_literal: false
+# save-history.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
-# by Keiju ISHITSUKAkeiju@ruby-lang.org)
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-
-require "readline"
+#
module IRB
- module HistorySavingAbility
- @RCS_ID='-$Id$-'
+ module HistorySavingAbility # :nodoc:
end
class Context
- def init_save_history
+ def init_save_history# :nodoc:
unless (class<<@io;self;end).include?(HistorySavingAbility)
- @io.extend(HistorySavingAbility)
+ @io.extend(HistorySavingAbility)
end
end
+ # A copy of the default <code>IRB.conf[:SAVE_HISTORY]</code>
def save_history
IRB.conf[:SAVE_HISTORY]
end
+ remove_method(:save_history=) if method_defined?(:save_history=)
+ # Sets <code>IRB.conf[:SAVE_HISTORY]</code> to the given +val+ and calls
+ # #init_save_history with this context.
+ #
+ # Will store the number of +val+ entries of history in the #history_file
+ #
+ # Add the following to your +.irbrc+ to change the number of history
+ # entries stored to 1000:
+ #
+ # IRB.conf[:SAVE_HISTORY] = 1000
def save_history=(val)
IRB.conf[:SAVE_HISTORY] = val
if val
- main_context = IRB.conf[:MAIN_CONTEXT]
- main_context = self unless main_context
- main_context.init_save_history
+ main_context = IRB.conf[:MAIN_CONTEXT]
+ main_context = self unless main_context
+ main_context.init_save_history
end
end
+ # A copy of the default <code>IRB.conf[:HISTORY_FILE]</code>
def history_file
IRB.conf[:HISTORY_FILE]
end
+ # Set <code>IRB.conf[:HISTORY_FILE]</code> to the given +hist+.
def history_file=(hist)
IRB.conf[:HISTORY_FILE] = hist
end
end
- module HistorySavingAbility
- include Readline
-
- def HistorySavingAbility.create_finalizer
- proc do
- if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) > 0
- if hf = IRB.conf[:HISTORY_FILE]
- file = File.expand_path(hf)
- end
- file = IRB.rc_file("_history") unless file
- open(file, 'w' ) do |f|
- hist = HISTORY.to_a
- f.puts(hist[-num..-1] || hist)
- end
- end
- end
- end
-
+ module HistorySavingAbility # :nodoc:
def HistorySavingAbility.extended(obj)
- ObjectSpace.define_finalizer(obj, HistorySavingAbility.create_finalizer)
+ IRB.conf[:AT_EXIT].push proc{obj.save_history}
obj.load_history
obj
end
def load_history
- hist = IRB.conf[:HISTORY_FILE]
- hist = IRB.rc_file("_history") unless hist
- if File.exist?(hist)
- open(hist) do |f|
- f.each {|l| HISTORY << l.chomp}
- end
+ return unless self.class.const_defined?(:HISTORY)
+ history = self.class::HISTORY
+ if history_file = IRB.conf[:HISTORY_FILE]
+ history_file = File.expand_path(history_file)
+ end
+ history_file = IRB.rc_file("_history") unless history_file
+ if File.exist?(history_file)
+ open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
+ f.each { |l|
+ l = l.chomp
+ if self.class == ReidlineInputMethod and history.last&.end_with?("\\")
+ history.last.delete_suffix!("\\")
+ history.last << "\n" << l
+ else
+ history << l
+ end
+ }
+ end
+ @loaded_history_lines = history.size
+ @loaded_history_mtime = File.mtime(history_file)
+ end
+ end
+
+ def save_history
+ return unless self.class.const_defined?(:HISTORY)
+ history = self.class::HISTORY
+ if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) != 0
+ if history_file = IRB.conf[:HISTORY_FILE]
+ history_file = File.expand_path(history_file)
+ end
+ history_file = IRB.rc_file("_history") unless history_file
+
+ # Change the permission of a file that already exists[BUG #7694]
+ begin
+ if File.stat(history_file).mode & 066 != 0
+ File.chmod(0600, history_file)
+ end
+ rescue Errno::ENOENT
+ rescue Errno::EPERM
+ return
+ rescue
+ raise
+ end
+
+ if File.exist?(history_file) && @loaded_history_mtime &&
+ File.mtime(history_file) != @loaded_history_mtime
+ history = history[@loaded_history_lines..-1]
+ append_history = true
+ end
+
+ open(history_file, "#{append_history ? 'a' : 'w'}:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
+ hist = history.map{ |l| l.split("\n").join("\\\n") }
+ unless append_history
+ begin
+ hist = hist.last(num) if hist.size > num and num > 0
+ rescue RangeError # bignum too big to convert into `long'
+ # Do nothing because the bignum should be treated as inifinity
+ end
+ end
+ f.puts(hist)
+ end
end
end
end
end
-
diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb
index 805f630a4d..67ac4bb965 100644
--- a/lib/irb/ext/tracer.rb
+++ b/lib/irb/ext/tracer.rb
@@ -1,15 +1,28 @@
+# frozen_string_literal: false
#
-# irb/lib/tracer.rb -
-# $Release Version: 0.9.5$
+# irb/lib/tracer.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-require "tracer"
+#
+begin
+ require "tracer"
+rescue LoadError
+ $stderr.puts "Tracer extension of IRB is enabled but tracer gem doesn't found."
+ module IRB
+ TracerLoadError = true
+ class Context
+ def use_tracer=(opt)
+ # do nothing
+ end
+ end
+ end
+ return # This is about to disable loading below
+end
module IRB
@@ -19,22 +32,29 @@ module IRB
Tracer.add_filter {
|event, file, line, id, binding, *rests|
/^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and
- File::basename(file) != "irb.rb"
+ File::basename(file) != "irb.rb"
}
end
class Context
+ # Whether Tracer is used when evaluating statements in this context.
+ #
+ # See +lib/tracer.rb+ for more information.
attr_reader :use_tracer
alias use_tracer? use_tracer
+ # Sets whether or not to use the Tracer library when evaluating statements
+ # in this context.
+ #
+ # See +lib/tracer.rb+ for more information.
def use_tracer=(opt)
if opt
- Tracer.set_get_line_procs(@irb_path) {
- |line_no, *rests|
- @io.line(line_no)
- }
+ Tracer.set_get_line_procs(@irb_path) {
+ |line_no, *rests|
+ @io.line(line_no)
+ }
elsif !opt && @use_tracer
- Tracer.off
+ Tracer.off
end
@use_tracer=opt
end
@@ -42,20 +62,23 @@ module IRB
class WorkSpace
alias __evaluate__ evaluate
+ # Evaluate the context of this workspace and use the Tracer library to
+ # output the exact lines of code are being executed in chronological order.
+ #
+ # See +lib/tracer.rb+ for more information.
def evaluate(context, statements, file = nil, line = nil)
if context.use_tracer? && file != nil && line != nil
- Tracer.on
- begin
- __evaluate__(context, statements, file, line)
- ensure
- Tracer.off
- end
+ Tracer.on
+ begin
+ __evaluate__(context, statements, file, line)
+ ensure
+ Tracer.off
+ end
else
- __evaluate__(context, statements, file || __FILE__, line || __LINE__)
+ __evaluate__(context, statements, file || __FILE__, line || __LINE__)
end
end
end
IRB.initialize_tracer
end
-
diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb
index 1b4d480fcd..1897bc89e0 100644
--- a/lib/irb/ext/use-loader.rb
+++ b/lib/irb/ext/use-loader.rb
@@ -1,17 +1,17 @@
+# frozen_string_literal: false
#
-# use-loader.rb -
-# $Release Version: 0.9.5$
+# use-loader.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "irb/cmd/load"
-require "irb/ext/loader"
+require_relative "../cmd/load"
+require_relative "loader"
class Object
alias __original__load__IRB_use_loader__ load
@@ -20,9 +20,13 @@ end
module IRB
module ExtendCommandBundle
+ remove_method :irb_load if method_defined?(:irb_load)
+ # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load
def irb_load(*opts, &b)
ExtendCommand::Load.execute(irb_context, *opts, &b)
end
+ remove_method :irb_require if method_defined?(:irb_require)
+ # Loads the given file similarly to Kernel#require
def irb_require(*opts, &b)
ExtendCommand::Require.execute(irb_context, *opts, &b)
end
@@ -31,35 +35,41 @@ module IRB
class Context
IRB.conf[:USE_LOADER] = false
-
+
+ # Returns whether +irb+'s own file reader method is used by
+ # +load+/+require+ or not.
+ #
+ # This mode is globally affected (irb-wide).
def use_loader
IRB.conf[:USE_LOADER]
end
alias use_loader? use_loader
+ remove_method :use_loader= if method_defined?(:use_loader=)
+ # Sets <code>IRB.conf[:USE_LOADER]</code>
+ #
+ # See #use_loader for more information.
def use_loader=(opt)
if IRB.conf[:USE_LOADER] != opt
- IRB.conf[:USE_LOADER] = opt
- if opt
- if !$".include?("irb/cmd/load")
- end
- (class<<@workspace.main;self;end).instance_eval {
- alias_method :load, :irb_load
- alias_method :require, :irb_require
- }
- else
- (class<<@workspace.main;self;end).instance_eval {
- alias_method :load, :__original__load__IRB_use_loader__
- alias_method :require, :__original__require__IRB_use_loader__
- }
- end
+ IRB.conf[:USE_LOADER] = opt
+ if opt
+ if !$".include?("irb/cmd/load")
+ end
+ (class<<@workspace.main;self;end).instance_eval {
+ alias_method :load, :irb_load
+ alias_method :require, :irb_require
+ }
+ else
+ (class<<@workspace.main;self;end).instance_eval {
+ alias_method :load, :__original__load__IRB_use_loader__
+ alias_method :require, :__original__require__IRB_use_loader__
+ }
+ end
end
print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose?
opt
end
end
end
-
-
diff --git a/lib/irb/ext/workspaces.rb b/lib/irb/ext/workspaces.rb
index 79098570dc..730b58e64d 100644
--- a/lib/irb/ext/workspaces.rb
+++ b/lib/irb/ext/workspaces.rb
@@ -1,56 +1,66 @@
+# frozen_string_literal: false
#
-# push-ws.rb -
-# $Release Version: 0.9.5$
+# push-ws.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-module IRB
+module IRB # :nodoc:
class Context
+ # Size of the current WorkSpace stack
def irb_level
workspace_stack.size
end
+ # WorkSpaces in the current stack
def workspaces
if defined? @workspaces
- @workspaces
+ @workspaces
else
- @workspaces = []
+ @workspaces = []
end
end
+ # Creates a new workspace with the given object or binding, and appends it
+ # onto the current #workspaces stack.
+ #
+ # See IRB::Context#change_workspace and IRB::WorkSpace.new for more
+ # information.
def push_workspace(*_main)
if _main.empty?
- if workspaces.empty?
- print "No other workspace\n"
- return nil
- end
- ws = workspaces.pop
- workspaces.push @workspace
- @workspace = ws
- return workspaces
+ if workspaces.empty?
+ print "No other workspace\n"
+ return nil
+ end
+ ws = workspaces.pop
+ workspaces.push @workspace
+ @workspace = ws
+ return workspaces
end
workspaces.push @workspace
@workspace = WorkSpace.new(@workspace.binding, _main[0])
if !(class<<main;ancestors;end).include?(ExtendCommandBundle)
- main.extend ExtendCommandBundle
+ main.extend ExtendCommandBundle
end
end
+ # Removes the last element from the current #workspaces stack and returns
+ # it, or +nil+ if the current workspace stack is empty.
+ #
+ # Also, see #push_workspace.
def pop_workspace
if workspaces.empty?
- print "workspace stack empty\n"
- return
+ print "workspace stack empty\n"
+ return
end
@workspace = workspaces.pop
end
end
end
-
diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb
index 8994f2f8d2..339e9e6084 100644
--- a/lib/irb/extend-command.rb
+++ b/lib/irb/extend-command.rb
@@ -1,29 +1,38 @@
+# frozen_string_literal: false
#
-# irb/extend-command.rb - irb extend command
-# $Release Version: 0.9.5$
+# irb/extend-command.rb - irb extend command
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-module IRB
- #
- # IRB extended command
- #
+#
+module IRB # :nodoc:
+ # Installs the default irb extensions command bundle.
module ExtendCommandBundle
- EXCB = ExtendCommandBundle
+ EXCB = ExtendCommandBundle # :nodoc:
+ # See #install_alias_method.
NO_OVERRIDE = 0
+ # See #install_alias_method.
OVERRIDE_PRIVATE_ONLY = 0x01
+ # See #install_alias_method.
OVERRIDE_ALL = 0x02
+ # Quits the current irb context
+ #
+ # +ret+ is the optional signal or message to send to Context#exit
+ #
+ # Same as <code>IRB.CurrentContext.exit</code>.
def irb_exit(ret = 0)
irb_context.exit(ret)
end
+ # Displays current configuration.
+ #
+ # Modifying the configuration is achieved by sending a message to IRB.conf.
def irb_context
IRB.CurrentContext
end
@@ -37,188 +46,264 @@ module IRB
]
@EXTEND_COMMANDS = [
- [:irb_current_working_workspace, :CurrentWorkingWorkspace, "irb/cmd/chws",
- [:irb_print_working_workspace, OVERRIDE_ALL],
- [:irb_cwws, OVERRIDE_ALL],
- [:irb_pwws, OVERRIDE_ALL],
-# [:irb_cww, OVERRIDE_ALL],
-# [:irb_pww, OVERRIDE_ALL],
- [:cwws, NO_OVERRIDE],
- [:pwws, NO_OVERRIDE],
-# [:cww, NO_OVERRIDE],
-# [:pww, NO_OVERRIDE],
- [:irb_current_working_binding, OVERRIDE_ALL],
- [:irb_print_working_binding, OVERRIDE_ALL],
- [:irb_cwb, OVERRIDE_ALL],
- [:irb_pwb, OVERRIDE_ALL],
-# [:cwb, NO_OVERRIDE],
-# [:pwb, NO_OVERRIDE]
+ [
+ :irb_current_working_workspace, :CurrentWorkingWorkspace, "irb/cmd/chws",
+ [:irb_print_working_workspace, OVERRIDE_ALL],
+ [:irb_cwws, OVERRIDE_ALL],
+ [:irb_pwws, OVERRIDE_ALL],
+ [:cwws, NO_OVERRIDE],
+ [:pwws, NO_OVERRIDE],
+ [:irb_current_working_binding, OVERRIDE_ALL],
+ [:irb_print_working_binding, OVERRIDE_ALL],
+ [:irb_cwb, OVERRIDE_ALL],
+ [:irb_pwb, OVERRIDE_ALL],
+ ],
+ [
+ :irb_change_workspace, :ChangeWorkspace, "irb/cmd/chws",
+ [:irb_chws, OVERRIDE_ALL],
+ [:irb_cws, OVERRIDE_ALL],
+ [:chws, NO_OVERRIDE],
+ [:cws, NO_OVERRIDE],
+ [:irb_change_binding, OVERRIDE_ALL],
+ [:irb_cb, OVERRIDE_ALL],
+ [:cb, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_workspaces, :Workspaces, "irb/cmd/pushws",
+ [:workspaces, NO_OVERRIDE],
+ [:irb_bindings, OVERRIDE_ALL],
+ [:bindings, NO_OVERRIDE],
+ ],
+ [
+ :irb_push_workspace, :PushWorkspace, "irb/cmd/pushws",
+ [:irb_pushws, OVERRIDE_ALL],
+ [:pushws, NO_OVERRIDE],
+ [:irb_push_binding, OVERRIDE_ALL],
+ [:irb_pushb, OVERRIDE_ALL],
+ [:pushb, NO_OVERRIDE],
+ ],
+ [
+ :irb_pop_workspace, :PopWorkspace, "irb/cmd/pushws",
+ [:irb_popws, OVERRIDE_ALL],
+ [:popws, NO_OVERRIDE],
+ [:irb_pop_binding, OVERRIDE_ALL],
+ [:irb_popb, OVERRIDE_ALL],
+ [:popb, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_load, :Load, "irb/cmd/load"],
+ [
+ :irb_require, :Require, "irb/cmd/load"],
+ [
+ :irb_source, :Source, "irb/cmd/load",
+ [:source, NO_OVERRIDE],
+ ],
+
+ [
+ :irb, :IrbCommand, "irb/cmd/subirb"],
+ [
+ :irb_jobs, :Jobs, "irb/cmd/subirb",
+ [:jobs, NO_OVERRIDE],
+ ],
+ [
+ :irb_fg, :Foreground, "irb/cmd/subirb",
+ [:fg, NO_OVERRIDE],
+ ],
+ [
+ :irb_kill, :Kill, "irb/cmd/subirb",
+ [:kill, OVERRIDE_PRIVATE_ONLY],
+ ],
+
+ [
+ :irb_help, :Help, "irb/cmd/help",
+ [:help, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_info, :Info, "irb/cmd/info"
+ ],
+
+ [
+ :irb_ls, :Ls, "irb/cmd/ls",
+ [:ls, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_measure, :Measure, "irb/cmd/measure",
+ [:measure, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_show_source, :ShowSource, "irb/cmd/show_source",
+ [:show_source, NO_OVERRIDE],
+ ],
+
+ [
+ :irb_whereami, :Whereami, "irb/cmd/whereami",
+ [:whereami, NO_OVERRIDE],
],
- [:irb_change_workspace, :ChangeWorkspace, "irb/cmd/chws",
- [:irb_chws, OVERRIDE_ALL],
-# [:irb_chw, OVERRIDE_ALL],
- [:irb_cws, OVERRIDE_ALL],
-# [:irb_cw, OVERRIDE_ALL],
- [:chws, NO_OVERRIDE],
-# [:chw, NO_OVERRIDE],
- [:cws, NO_OVERRIDE],
-# [:cw, NO_OVERRIDE],
- [:irb_change_binding, OVERRIDE_ALL],
- [:irb_cb, OVERRIDE_ALL],
- [:cb, NO_OVERRIDE]],
-
- [:irb_workspaces, :Workspaces, "irb/cmd/pushws",
- [:workspaces, NO_OVERRIDE],
- [:irb_bindings, OVERRIDE_ALL],
- [:bindings, NO_OVERRIDE]],
- [:irb_push_workspace, :PushWorkspace, "irb/cmd/pushws",
- [:irb_pushws, OVERRIDE_ALL],
-# [:irb_pushw, OVERRIDE_ALL],
- [:pushws, NO_OVERRIDE],
-# [:pushw, NO_OVERRIDE],
- [:irb_push_binding, OVERRIDE_ALL],
- [:irb_pushb, OVERRIDE_ALL],
- [:pushb, NO_OVERRIDE]],
- [:irb_pop_workspace, :PopWorkspace, "irb/cmd/pushws",
- [:irb_popws, OVERRIDE_ALL],
-# [:irb_popw, OVERRIDE_ALL],
- [:popws, NO_OVERRIDE],
-# [:popw, NO_OVERRIDE],
- [:irb_pop_binding, OVERRIDE_ALL],
- [:irb_popb, OVERRIDE_ALL],
- [:popb, NO_OVERRIDE]],
-
- [:irb_load, :Load, "irb/cmd/load"],
- [:irb_require, :Require, "irb/cmd/load"],
- [:irb_source, :Source, "irb/cmd/load",
- [:source, NO_OVERRIDE]],
-
- [:irb, :IrbCommand, "irb/cmd/subirb"],
- [:irb_jobs, :Jobs, "irb/cmd/subirb",
- [:jobs, NO_OVERRIDE]],
- [:irb_fg, :Foreground, "irb/cmd/subirb",
- [:fg, NO_OVERRIDE]],
- [:irb_kill, :Kill, "irb/cmd/subirb",
- [:kill, OVERRIDE_PRIVATE_ONLY]],
-
- [:irb_help, :Help, "irb/cmd/help",
- [:help, NO_OVERRIDE]],
]
+ # Installs the default irb commands:
+ #
+ # +irb_current_working_workspace+:: Context#main
+ # +irb_change_workspace+:: Context#change_workspace
+ # +irb_workspaces+:: Context#workspaces
+ # +irb_push_workspace+:: Context#push_workspace
+ # +irb_pop_workspace+:: Context#pop_workspace
+ # +irb_load+:: #irb_load
+ # +irb_require+:: #irb_require
+ # +irb_source+:: IrbLoader#source_file
+ # +irb+:: IRB.irb
+ # +irb_jobs+:: JobManager
+ # +irb_fg+:: JobManager#switch
+ # +irb_kill+:: JobManager#kill
+ # +irb_help+:: IRB@Command+line+options
def self.install_extend_commands
for args in @EXTEND_COMMANDS
- def_extend_command(*args)
+ def_extend_command(*args)
end
end
- # aliases = [commans_alias, flag], ...
+ # Evaluate the given +cmd_name+ on the given +cmd_class+ Class.
+ #
+ # Will also define any given +aliases+ for the method.
+ #
+ # The optional +load_file+ parameter will be required within the method
+ # definition.
def self.def_extend_command(cmd_name, cmd_class, load_file = nil, *aliases)
case cmd_class
when Symbol
- cmd_class = cmd_class.id2name
+ cmd_class = cmd_class.id2name
when String
when Class
- cmd_class = cmd_class.name
+ cmd_class = cmd_class.name
end
if load_file
- eval %[
- def #{cmd_name}(*opts, &b)
- require "#{load_file}"
- eval %[
- def #{cmd_name}(*opts, &b)
- ExtendCommand::#{cmd_class}.execute(irb_context, *opts, &b)
- end
- ]
- send :#{cmd_name}, *opts, &b
- end
- ]
+ kwargs = ", **kwargs" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.7.0"
+ line = __LINE__; eval %[
+ def #{cmd_name}(*opts#{kwargs}, &b)
+ require "#{load_file}"
+ arity = ExtendCommand::#{cmd_class}.instance_method(:execute).arity
+ args = (1..(arity < 0 ? ~arity : arity)).map {|i| "arg" + i.to_s }
+ args << "*opts#{kwargs}" if arity < 0
+ args << "&block"
+ args = args.join(", ")
+ line = __LINE__; eval %[
+ unless singleton_class.class_variable_defined?(:@@#{cmd_name}_)
+ singleton_class.class_variable_set(:@@#{cmd_name}_, true)
+ def self.#{cmd_name}_(\#{args})
+ ExtendCommand::#{cmd_class}.execute(irb_context, \#{args})
+ end
+ end
+ ], nil, __FILE__, line
+ __send__ :#{cmd_name}_, *opts#{kwargs}, &b
+ end
+ ], nil, __FILE__, line
else
- eval %[
- def #{cmd_name}(*opts, &b)
- ExtendCommand::#{cmd_class}.execute(irb_context, *opts, &b)
- end
- ]
+ line = __LINE__; eval %[
+ def #{cmd_name}(*opts, &b)
+ ExtendCommand::#{cmd_class}.execute(irb_context, *opts, &b)
+ end
+ ], nil, __FILE__, line
end
for ali, flag in aliases
- @ALIASES.push [ali, cmd_name, flag]
+ @ALIASES.push [ali, cmd_name, flag]
end
end
- # override = {NO_OVERRIDE, OVERRIDE_PRIVATE_ONLY, OVERRIDE_ALL}
+ # Installs alias methods for the default irb commands, see
+ # ::install_extend_commands.
def install_alias_method(to, from, override = NO_OVERRIDE)
to = to.id2name unless to.kind_of?(String)
from = from.id2name unless from.kind_of?(String)
if override == OVERRIDE_ALL or
- (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or
- (override == NO_OVERRIDE) && !respond_to?(to, true)
- target = self
- (class<<self;self;end).instance_eval{
- if target.respond_to?(to, true) &&
- !target.respond_to?(EXCB.irb_original_method_name(to), true)
- alias_method(EXCB.irb_original_method_name(to), to)
- end
- alias_method to, from
- }
+ (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or
+ (override == NO_OVERRIDE) && !respond_to?(to, true)
+ target = self
+ (class << self; self; end).instance_eval{
+ if target.respond_to?(to, true) &&
+ !target.respond_to?(EXCB.irb_original_method_name(to), true)
+ alias_method(EXCB.irb_original_method_name(to), to)
+ end
+ alias_method to, from
+ }
else
- print "irb: warn: can't alias #{to} from #{from}.\n"
+ print "irb: warn: can't alias #{to} from #{from}.\n"
end
end
- def self.irb_original_method_name(method_name)
+ def self.irb_original_method_name(method_name) # :nodoc:
"irb_" + method_name + "_org"
end
+ # Installs alias methods for the default irb commands on the given object
+ # using #install_alias_method.
def self.extend_object(obj)
- unless (class<<obj;ancestors;end).include?(EXCB)
- super
- for ali, com, flg in @ALIASES
- obj.install_alias_method(ali, com, flg)
- end
+ unless (class << obj; ancestors; end).include?(EXCB)
+ super
+ for ali, com, flg in @ALIASES
+ obj.install_alias_method(ali, com, flg)
+ end
end
end
install_extend_commands
end
- # extension support for Context
+ # Extends methods for the Context module
module ContextExtender
- CE = ContextExtender
+ CE = ContextExtender # :nodoc:
@EXTEND_COMMANDS = [
[:eval_history=, "irb/ext/history.rb"],
[:use_tracer=, "irb/ext/tracer.rb"],
- [:math_mode=, "irb/ext/math-mode.rb"],
[:use_loader=, "irb/ext/use-loader.rb"],
[:save_history=, "irb/ext/save-history.rb"],
]
+ # Installs the default context extensions as irb commands:
+ #
+ # Context#eval_history=:: +irb/ext/history.rb+
+ # Context#use_tracer=:: +irb/ext/tracer.rb+
+ # Context#use_loader=:: +irb/ext/use-loader.rb+
+ # Context#save_history=:: +irb/ext/save-history.rb+
def self.install_extend_commands
for args in @EXTEND_COMMANDS
- def_extend_command(*args)
+ def_extend_command(*args)
end
end
+ # Evaluate the given +command+ from the given +load_file+ on the Context
+ # module.
+ #
+ # Will also define any given +aliases+ for the method.
def self.def_extend_command(cmd_name, load_file, *aliases)
- Context.module_eval %[
+ line = __LINE__; Context.module_eval %[
def #{cmd_name}(*opts, &b)
- Context.module_eval {remove_method(:#{cmd_name})}
- require "#{load_file}"
- send :#{cmd_name}, *opts, &b
- end
- for ali in aliases
- alias_method ali, cmd_name
- end
- ]
+ Context.module_eval {remove_method(:#{cmd_name})}
+ require "#{load_file}"
+ __send__ :#{cmd_name}, *opts, &b
+ end
+ for ali in aliases
+ alias_method ali, cmd_name
+ end
+ ], __FILE__, line
end
CE.install_extend_commands
end
+ # A convenience module for extending Ruby methods.
module MethodExtender
+ # Extends the given +base_method+ with a prefix call to the given
+ # +extend_method+.
def def_pre_proc(base_method, extend_method)
base_method = base_method.to_s
extend_method = extend_method.to_s
@@ -227,12 +312,14 @@ module IRB
module_eval %[
alias_method alias_name, base_method
def #{base_method}(*opts)
- send :#{extend_method}, *opts
- send :#{alias_name}, *opts
- end
+ __send__ :#{extend_method}, *opts
+ __send__ :#{alias_name}, *opts
+ end
]
end
+ # Extends the given +base_method+ with a postfix call to the given
+ # +extend_method+.
def def_post_proc(base_method, extend_method)
base_method = base_method.to_s
extend_method = extend_method.to_s
@@ -241,13 +328,19 @@ module IRB
module_eval %[
alias_method alias_name, base_method
def #{base_method}(*opts)
- send :#{alias_name}, *opts
- send :#{extend_method}, *opts
- end
+ __send__ :#{alias_name}, *opts
+ __send__ :#{extend_method}, *opts
+ end
]
end
- # return #{prefix}#{name}#{postfix}<num>
+ # Returns a unique method name to use as an alias for the given +name+.
+ #
+ # Usually returns <code>#{prefix}#{name}#{postfix}<num></code>, example:
+ #
+ # new_alias_name('foo') #=> __alias_of__foo__
+ # def bar; end
+ # new_alias_name('bar') #=> __alias_of__bar__2
def new_alias_name(name, prefix = "__alias_of__", postfix = "__")
base_name = "#{prefix}#{name}#{postfix}"
all_methods = instance_methods(true) + private_instance_methods(true)
@@ -255,10 +348,9 @@ module IRB
return base_name if same_methods.empty?
no = same_methods.size
while !same_methods.include?(alias_name = base_name + no)
- no += 1
+ no += 1
end
alias_name
end
end
end
-
diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb
index f0b0a9abf3..de54a98f1b 100644
--- a/lib/irb/frame.rb
+++ b/lib/irb/frame.rb
@@ -1,60 +1,79 @@
+# frozen_string_literal: false
#
-# frame.rb -
+# frame.rb -
# $Release Version: 0.9$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd)
#
# --
#
-#
#
-
-require "e2mmap"
+#
module IRB
class Frame
- extend Exception2MessageMapper
- def_exception :FrameOverflow, "frame overflow"
- def_exception :FrameUnderflow, "frame underflow"
+ class FrameOverflow < StandardError
+ def initialize
+ super("frame overflow")
+ end
+ end
+ class FrameUnderflow < StandardError
+ def initialize
+ super("frame underflow")
+ end
+ end
+ # Default number of stack frames
INIT_STACK_TIMES = 3
+ # Default number of frames offset
CALL_STACK_OFFSET = 3
+ # Creates a new stack frame
def initialize
@frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES
end
+ # Used by Kernel#set_trace_func to register each event in the call stack
def trace_func(event, file, line, id, binding)
case event
when 'call', 'class'
- @frames.push binding
+ @frames.push binding
when 'return', 'end'
- @frames.pop
+ @frames.pop
end
end
+ # Returns the +n+ number of frames on the call stack from the last frame
+ # initialized.
+ #
+ # Raises FrameUnderflow if there are no frames in the given stack range.
def top(n = 0)
bind = @frames[-(n + CALL_STACK_OFFSET)]
- Fail FrameUnderflow unless bind
+ fail FrameUnderflow unless bind
bind
end
+ # Returns the +n+ number of frames on the call stack from the first frame
+ # initialized.
+ #
+ # Raises FrameOverflow if there are no frames in the given stack range.
def bottom(n = 0)
bind = @frames[n]
- Fail FrameOverflow unless bind
+ fail FrameOverflow unless bind
bind
end
- # singleton functions
+ # Convenience method for Frame#bottom
def Frame.bottom(n = 0)
@backtrace.bottom(n)
end
+ # Convenience method for Frame#top
def Frame.top(n = 0)
@backtrace.top(n)
end
+ # Returns the binding context of the caller from the last frame initialized
def Frame.sender
eval "self", @backtrace.top
end
diff --git a/lib/irb/help.rb b/lib/irb/help.rb
index f091999bd1..3eeaf841b0 100644
--- a/lib/irb/help.rb
+++ b/lib/irb/help.rb
@@ -1,33 +1,36 @@
+# frozen_string_literal: false
#
-# irb/help.rb - print usase module
-# $Release Version: 0.9.5$
+# irb/help.rb - print usage module
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
#
# --
#
-#
#
+#
+
+require_relative 'magic-file'
module IRB
+ # Outputs the irb help message, see IRB@Command+line+options.
def IRB.print_usage
lc = IRB.conf[:LC_MESSAGES]
path = lc.find("irb/help-message")
space_line = false
- File.foreach(path) do
- |l|
- if /^\s*$/ =~ l
- lc.puts l unless space_line
- space_line = true
- next
+ IRB::MagicFile.open(path){|f|
+ f.each_line do |l|
+ if /^\s*$/ =~ l
+ lc.puts l unless space_line
+ space_line = true
+ next
+ end
+ space_line = false
+
+ l.sub!(/#.*$/, "")
+ next if /^\s*$/ =~ l
+ lc.puts l
end
- space_line = false
-
- l.sub!(/#.*$/, "")
- next if /^\s*$/ =~ l
- lc.puts l
- end
+ }
end
end
-
diff --git a/lib/irb/init.rb b/lib/irb/init.rb
index db22ca639b..d2baee2017 100644
--- a/lib/irb/init.rb
+++ b/lib/irb/init.rb
@@ -1,27 +1,27 @@
+# frozen_string_literal: false
#
# irb/init.rb - irb initialize module
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-module IRB
+module IRB # :nodoc:
# initialize config
- def IRB.setup(ap_path)
+ def IRB.setup(ap_path, argv: ::ARGV)
IRB.init_config(ap_path)
IRB.init_error
- IRB.parse_opts
+ IRB.parse_opts(argv: argv)
IRB.run_config
IRB.load_modules
unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]]
- IRB.fail(UndefinedPromptMode, @CONF[:PROMPT_MODE])
+ fail UndefinedPromptMode, @CONF[:PROMPT_MODE]
end
end
@@ -43,159 +43,288 @@ module IRB
@CONF[:LOAD_MODULES] = []
@CONF[:IRB_RC] = nil
- @CONF[:MATH_MODE] = false
- @CONF[:USE_READLINE] = false unless defined?(ReadlineInputMethod)
- @CONF[:INSPECT_MODE] = nil
+ @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod)
+ @CONF[:USE_COLORIZE] = !ENV['NO_COLOR']
+ @CONF[:USE_AUTOCOMPLETE] = true
+ @CONF[:INSPECT_MODE] = true
@CONF[:USE_TRACER] = false
@CONF[:USE_LOADER] = false
@CONF[:IGNORE_SIGINT] = true
@CONF[:IGNORE_EOF] = false
+ @CONF[:EXTRA_DOC_DIRS] = []
@CONF[:ECHO] = nil
+ @CONF[:ECHO_ON_ASSIGNMENT] = nil
@CONF[:VERBOSE] = nil
@CONF[:EVAL_HISTORY] = nil
- @CONF[:SAVE_HISTORY] = nil
+ @CONF[:SAVE_HISTORY] = 1000
@CONF[:BACK_TRACE_LIMIT] = 16
@CONF[:PROMPT] = {
:NULL => {
- :PROMPT_I => nil,
- :PROMPT_N => nil,
- :PROMPT_S => nil,
- :PROMPT_C => nil,
- :RETURN => "%s\n"
+ :PROMPT_I => nil,
+ :PROMPT_N => nil,
+ :PROMPT_S => nil,
+ :PROMPT_C => nil,
+ :RETURN => "%s\n"
},
:DEFAULT => {
- :PROMPT_I => "%N(%m):%03n:%i> ",
- :PROMPT_N => "%N(%m):%03n:%i> ",
- :PROMPT_S => "%N(%m):%03n:%i%l ",
- :PROMPT_C => "%N(%m):%03n:%i* ",
- :RETURN => "=> %s\n"
+ :PROMPT_I => "%N(%m):%03n:%i> ",
+ :PROMPT_N => "%N(%m):%03n:%i> ",
+ :PROMPT_S => "%N(%m):%03n:%i%l ",
+ :PROMPT_C => "%N(%m):%03n:%i* ",
+ :RETURN => "=> %s\n"
},
:CLASSIC => {
- :PROMPT_I => "%N(%m):%03n:%i> ",
- :PROMPT_N => "%N(%m):%03n:%i> ",
- :PROMPT_S => "%N(%m):%03n:%i%l ",
- :PROMPT_C => "%N(%m):%03n:%i* ",
- :RETURN => "%s\n"
+ :PROMPT_I => "%N(%m):%03n:%i> ",
+ :PROMPT_N => "%N(%m):%03n:%i> ",
+ :PROMPT_S => "%N(%m):%03n:%i%l ",
+ :PROMPT_C => "%N(%m):%03n:%i* ",
+ :RETURN => "%s\n"
},
:SIMPLE => {
- :PROMPT_I => ">> ",
- :PROMPT_N => ">> ",
- :PROMPT_S => nil,
- :PROMPT_C => "?> ",
- :RETURN => "=> %s\n"
+ :PROMPT_I => ">> ",
+ :PROMPT_N => ">> ",
+ :PROMPT_S => "%l> ",
+ :PROMPT_C => "?> ",
+ :RETURN => "=> %s\n"
},
:INF_RUBY => {
- :PROMPT_I => "%N(%m):%03n:%i> ",
-# :PROMPT_N => "%N(%m):%03n:%i> ",
- :PROMPT_N => nil,
- :PROMPT_S => nil,
- :PROMPT_C => nil,
- :RETURN => "%s\n",
- :AUTO_INDENT => true
+ :PROMPT_I => "%N(%m):%03n:%i> ",
+ :PROMPT_N => nil,
+ :PROMPT_S => nil,
+ :PROMPT_C => nil,
+ :RETURN => "%s\n",
+ :AUTO_INDENT => true
},
:XMP => {
- :PROMPT_I => nil,
- :PROMPT_N => nil,
- :PROMPT_S => nil,
- :PROMPT_C => nil,
- :RETURN => " ==>%s\n"
+ :PROMPT_I => nil,
+ :PROMPT_N => nil,
+ :PROMPT_S => nil,
+ :PROMPT_C => nil,
+ :RETURN => " ==>%s\n"
}
}
@CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL)
- @CONF[:AUTO_INDENT] = false
+ @CONF[:AUTO_INDENT] = true
- @CONF[:CONTEXT_MODE] = 3 # use binding in function on TOPLEVEL_BINDING
+ @CONF[:CONTEXT_MODE] = 4 # use a copy of TOPLEVEL_BINDING
@CONF[:SINGLE_IRB] = false
-# @CONF[:LC_MESSAGES] = "en"
+ @CONF[:MEASURE] = false
+ @CONF[:MEASURE_PROC] = {}
+ @CONF[:MEASURE_PROC][:TIME] = proc { |context, code, line_no, &block|
+ time = Time.now
+ result = block.()
+ now = Time.now
+ puts 'processing time: %fs' % (now - time) if IRB.conf[:MEASURE]
+ result
+ }
+ # arg can be either a symbol for the mode (:cpu, :wall, ..) or a hash for
+ # a more complete configuration.
+ # See https://github.com/tmm1/stackprof#all-options.
+ @CONF[:MEASURE_PROC][:STACKPROF] = proc { |context, code, line_no, arg, &block|
+ return block.() unless IRB.conf[:MEASURE]
+ success = false
+ begin
+ require 'stackprof'
+ success = true
+ rescue LoadError
+ puts 'Please run "gem install stackprof" before measuring by StackProf.'
+ end
+ if success
+ result = nil
+ arg = { mode: arg || :cpu } unless arg.is_a?(Hash)
+ stackprof_result = StackProf.run(**arg) do
+ result = block.()
+ end
+ case stackprof_result
+ when File
+ puts "StackProf report saved to #{stackprof_result.path}"
+ when Hash
+ StackProf::Report.new(stackprof_result).print_text
+ else
+ puts "Stackprof ran with #{arg.inspect}"
+ end
+ result
+ else
+ block.()
+ end
+ }
+ @CONF[:MEASURE_CALLBACKS] = []
+
@CONF[:LC_MESSAGES] = Locale.new
-
- @CONF[:DEBUG_LEVEL] = 1
+
+ @CONF[:AT_EXIT] = []
+ end
+
+ def IRB.set_measure_callback(type = nil, arg = nil, &block)
+ added = nil
+ if type
+ type_sym = type.upcase.to_sym
+ if IRB.conf[:MEASURE_PROC][type_sym]
+ added = [type_sym, IRB.conf[:MEASURE_PROC][type_sym], arg]
+ end
+ elsif IRB.conf[:MEASURE_PROC][:CUSTOM]
+ added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg]
+ elsif block_given?
+ added = [:BLOCK, block, arg]
+ found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] }
+ if found
+ found[1] = block
+ return added
+ else
+ IRB.conf[:MEASURE_CALLBACKS] << added
+ return added
+ end
+ else
+ added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg]
+ end
+ if added
+ found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] }
+ if found
+ # already added
+ nil
+ else
+ IRB.conf[:MEASURE_CALLBACKS] << added if added
+ added
+ end
+ else
+ nil
+ end
+ end
+
+ def IRB.unset_measure_callback(type = nil)
+ if type.nil?
+ IRB.conf[:MEASURE_CALLBACKS].clear
+ else
+ type_sym = type.upcase.to_sym
+ IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym }
+ end
end
def IRB.init_error
@CONF[:LC_MESSAGES].load("irb/error.rb")
end
- FEATURE_IOPT_CHANGE_VERSION = "1.9.0"
-
# option analyzing
- def IRB.parse_opts
+ def IRB.parse_opts(argv: ::ARGV)
load_path = []
- while opt = ARGV.shift
+ while opt = argv.shift
case opt
when "-f"
- @CONF[:RC] = false
- when "-m"
- @CONF[:MATH_MODE] = true
+ @CONF[:RC] = false
when "-d"
- $DEBUG = true
+ $DEBUG = true
+ $VERBOSE = true
+ when "-w"
+ Warning[:deprecated] = $VERBOSE = true
+ when /^-W(.+)?/
+ opt = $1 || argv.shift
+ case opt
+ when "0"
+ $VERBOSE = nil
+ when "1"
+ $VERBOSE = false
+ else
+ Warning[:deprecated] = $VERBOSE = true
+ end
when /^-r(.+)?/
- opt = $1 || ARGV.shift
- @CONF[:LOAD_MODULES].push opt if opt
+ opt = $1 || argv.shift
+ @CONF[:LOAD_MODULES].push opt if opt
when /^-I(.+)?/
- opt = $1 || ARGV.shift
- load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt
- when /^-K(.)/
- $KCODE = $1
+ opt = $1 || argv.shift
+ load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt
+ when '-U'
+ set_encoding("UTF-8", "UTF-8")
+ when /^-E(.+)?/, /^--encoding(?:=(.+))?/
+ opt = $1 || argv.shift
+ set_encoding(*opt.split(':', 2))
when "--inspect"
- @CONF[:INSPECT_MODE] = true
+ if /^-/ !~ argv.first
+ @CONF[:INSPECT_MODE] = argv.shift
+ else
+ @CONF[:INSPECT_MODE] = true
+ end
when "--noinspect"
- @CONF[:INSPECT_MODE] = false
- when "--readline"
- @CONF[:USE_READLINE] = true
- when "--noreadline"
- @CONF[:USE_READLINE] = false
+ @CONF[:INSPECT_MODE] = false
+ when "--singleline", "--readline", "--legacy"
+ @CONF[:USE_SINGLELINE] = true
+ when "--nosingleline", "--noreadline"
+ @CONF[:USE_SINGLELINE] = false
+ when "--multiline", "--reidline"
+ @CONF[:USE_MULTILINE] = true
+ when "--nomultiline", "--noreidline"
+ @CONF[:USE_MULTILINE] = false
+ when /^--extra-doc-dir(?:=(.+))?/
+ opt = $1 || argv.shift
+ @CONF[:EXTRA_DOC_DIRS] << opt
when "--echo"
- @CONF[:ECHO] = true
+ @CONF[:ECHO] = true
when "--noecho"
- @CONF[:ECHO] = false
+ @CONF[:ECHO] = false
+ when "--echo-on-assignment"
+ @CONF[:ECHO_ON_ASSIGNMENT] = true
+ when "--noecho-on-assignment"
+ @CONF[:ECHO_ON_ASSIGNMENT] = false
+ when "--truncate-echo-on-assignment"
+ @CONF[:ECHO_ON_ASSIGNMENT] = :truncate
when "--verbose"
- @CONF[:VERBOSE] = true
+ @CONF[:VERBOSE] = true
when "--noverbose"
- @CONF[:VERBOSE] = false
- when "--prompt-mode", "--prompt"
- prompt_mode = ARGV.shift.upcase.tr("-", "_").intern
- @CONF[:PROMPT_MODE] = prompt_mode
+ @CONF[:VERBOSE] = false
+ when "--colorize"
+ @CONF[:USE_COLORIZE] = true
+ when "--nocolorize"
+ @CONF[:USE_COLORIZE] = false
+ when "--autocomplete"
+ @CONF[:USE_AUTOCOMPLETE] = true
+ when "--noautocomplete"
+ @CONF[:USE_AUTOCOMPLETE] = false
+ when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/
+ opt = $1 || argv.shift
+ prompt_mode = opt.upcase.tr("-", "_").intern
+ @CONF[:PROMPT_MODE] = prompt_mode
when "--noprompt"
- @CONF[:PROMPT_MODE] = :NULL
+ @CONF[:PROMPT_MODE] = :NULL
when "--inf-ruby-mode"
- @CONF[:PROMPT_MODE] = :INF_RUBY
+ @CONF[:PROMPT_MODE] = :INF_RUBY
when "--sample-book-mode", "--simple-prompt"
- @CONF[:PROMPT_MODE] = :SIMPLE
+ @CONF[:PROMPT_MODE] = :SIMPLE
when "--tracer"
- @CONF[:USE_TRACER] = true
- when "--back-trace-limit"
- @CONF[:BACK_TRACE_LIMIT] = ARGV.shift.to_i
- when "--context-mode"
- @CONF[:CONTEXT_MODE] = ARGV.shift.to_i
+ @CONF[:USE_TRACER] = true
+ when /^--back-trace-limit(?:=(.+))?/
+ @CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i
+ when /^--context-mode(?:=(.+))?/
+ @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i
when "--single-irb"
- @CONF[:SINGLE_IRB] = true
- when "--irb_debug"
- @CONF[:DEBUG_LEVEL] = ARGV.shift.to_i
+ @CONF[:SINGLE_IRB] = true
when "-v", "--version"
- print IRB.version, "\n"
- exit 0
+ print IRB.version, "\n"
+ exit 0
when "-h", "--help"
- require "irb/help"
- IRB.print_usage
- exit 0
+ require_relative "help"
+ IRB.print_usage
+ exit 0
+ when "--"
+ if opt = argv.shift
+ @CONF[:SCRIPT] = opt
+ $0 = opt
+ end
+ break
when /^-/
- IRB.fail UnrecognizedSwitch, opt
+ fail UnrecognizedSwitch, opt
else
- @CONF[:SCRIPT] = opt
- $0 = opt
- break
+ @CONF[:SCRIPT] = opt
+ $0 = opt
+ break
end
end
- if RUBY_VERSION >= FEATURE_IOPT_CHANGE_VERSION
- load_path.collect! do |path|
- /\A\.\// =~ path ? path : File.expand_path(path)
- end
+
+ load_path.collect! do |path|
+ /\A\.\// =~ path ? path : File.expand_path(path)
end
$LOAD_PATH.unshift(*load_path)
end
@@ -204,14 +333,14 @@ module IRB
def IRB.run_config
if @CONF[:RC]
begin
- load rc_file
+ load rc_file
rescue LoadError, Errno::ENOENT
- rescue
- print "load error: #{rc_file}\n"
- print $!.class, ": ", $!, "\n"
- for err in $@[0, $@.size - 2]
- print "\t", err, "\n"
- end
+ rescue # StandardError, ScriptError
+ print "load error: #{rc_file}\n"
+ print $!.class, ": ", $!, "\n"
+ for err in $@[0, $@.size - 2]
+ print "\t", err, "\n"
+ end
end
end
end
@@ -220,40 +349,74 @@ module IRB
def IRB.rc_file(ext = IRBRC_EXT)
if !@CONF[:RC_NAME_GENERATOR]
rc_file_generators do |rcgen|
- @CONF[:RC_NAME_GENERATOR] ||= rcgen
- if File.exist?(rcgen.call(IRBRC_EXT))
- @CONF[:RC_NAME_GENERATOR] = rcgen
- break
- end
+ @CONF[:RC_NAME_GENERATOR] ||= rcgen
+ if File.exist?(rcgen.call(IRBRC_EXT))
+ @CONF[:RC_NAME_GENERATOR] = rcgen
+ break
+ end
end
end
- @CONF[:RC_NAME_GENERATOR].call ext
+ case rc_file = @CONF[:RC_NAME_GENERATOR].call(ext)
+ when String
+ return rc_file
+ else
+ fail IllegalRCNameGenerator
+ end
end
# enumerate possible rc-file base name generators
def IRB.rc_file_generators
if irbrc = ENV["IRBRC"]
- yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
+ yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc}
+ end
+ if xdg_config_home = ENV["XDG_CONFIG_HOME"]
+ irb_home = File.join(xdg_config_home, "irb")
+ unless File.exist? irb_home
+ require 'fileutils'
+ FileUtils.mkdir_p irb_home
+ end
+ yield proc{|rc| irb_home + "/irb#{rc}"}
end
if home = ENV["HOME"]
- yield proc{|rc| home+"/.irb#{rc}"}
+ yield proc{|rc| home+"/.irb#{rc}"}
end
- home = Dir.pwd
- yield proc{|rc| home+"/.irb#{rc}"}
- yield proc{|rc| home+"/irb#{rc.sub(/\A_?/, '.')}"}
- yield proc{|rc| home+"/_irb#{rc}"}
- yield proc{|rc| home+"/$irb#{rc}"}
+ current_dir = Dir.pwd
+ yield proc{|rc| current_dir+"/.config/irb/irb#{rc}"}
+ yield proc{|rc| current_dir+"/.irb#{rc}"}
+ yield proc{|rc| current_dir+"/irb#{rc.sub(/\A_?/, '.')}"}
+ yield proc{|rc| current_dir+"/_irb#{rc}"}
+ yield proc{|rc| current_dir+"/$irb#{rc}"}
end
# loading modules
def IRB.load_modules
for m in @CONF[:LOAD_MODULES]
begin
- require m
- rescue
- print $@[0], ":", $!.class, ": ", $!, "\n"
+ require m
+ rescue LoadError => err
+ warn "#{err.class}: #{err}", uplevel: 0
end
end
end
+
+ DefaultEncodings = Struct.new(:external, :internal)
+ class << IRB
+ private
+ def set_encoding(extern, intern = nil, override: true)
+ verbose, $VERBOSE = $VERBOSE, nil
+ Encoding.default_external = extern unless extern.nil? || extern.empty?
+ Encoding.default_internal = intern unless intern.nil? || intern.empty?
+ [$stdin, $stdout, $stderr].each do |io|
+ io.set_encoding(extern, intern)
+ end
+ if override
+ @CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern)
+ else
+ @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern)
+ end
+ ensure
+ $VERBOSE = verbose
+ end
+ end
end
diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb
index bfb90fa59a..64276e61be 100644
--- a/lib/irb/input-method.rb
+++ b/lib/irb/input-method.rb
@@ -1,120 +1,469 @@
+# frozen_string_literal: false
#
# irb/input-method.rb - input methods used irb
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
+#
+require_relative 'src_encoding'
+require_relative 'magic-file'
+require_relative 'completion'
+require 'io/console'
+require 'reline'
+require 'rdoc'
+
module IRB
- #
- # InputMethod
- # StdioInputMethod
- # FileInputMethod
- # (ReadlineInputMethod)
- #
- STDIN_FILE_NAME = "(line)"
+ STDIN_FILE_NAME = "(line)" # :nodoc:
class InputMethod
- @RCS_ID='-$Id$-'
+ # Creates a new input method object
def initialize(file = STDIN_FILE_NAME)
@file_name = file
end
+ # The file name of this input method, usually given during initialization.
attr_reader :file_name
+ # The irb prompt associated with this input method
attr_accessor :prompt
-
+
+ # Reads the next line from this input method.
+ #
+ # See IO#gets for more information.
def gets
- IRB.fail NotImplementedError, "gets"
+ fail NotImplementedError, "gets"
end
public :gets
- def readable_atfer_eof?
+ def winsize
+ if instance_variable_defined?(:@stdout)
+ @stdout.winsize
+ else
+ [24, 80]
+ end
+ end
+
+ # Whether this input method is still readable when there is no more data to
+ # read.
+ #
+ # See IO#eof for more information.
+ def readable_after_eof?
false
end
+
+ # For debug message
+ def inspect
+ 'Abstract InputMethod'
+ end
end
-
+
class StdioInputMethod < InputMethod
+ # Creates a new input method object
def initialize
super
@line_no = 0
@line = []
+ @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
+ @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
end
+ # Reads the next line from this input method.
+ #
+ # See IO#gets for more information.
def gets
print @prompt
- @line[@line_no += 1] = $stdin.gets
+ line = @stdin.gets
+ @line[@line_no += 1] = line
end
+ # Whether the end of this input method has been reached, returns +true+ if
+ # there is no more data to read.
+ #
+ # See IO#eof? for more information.
def eof?
- $stdin.eof?
+ if @stdin.wait_readable(0.00001)
+ c = @stdin.getc
+ result = c.nil? ? true : false
+ @stdin.ungetc(c) unless c.nil?
+ result
+ else # buffer is empty
+ false
+ end
end
- def readable_atfer_eof?
+ # Whether this input method is still readable when there is no more data to
+ # read.
+ #
+ # See IO#eof for more information.
+ def readable_after_eof?
true
end
+ # Returns the current line number for #io.
+ #
+ # #line counts the number of times #gets is called.
+ #
+ # See IO#lineno for more information.
def line(line_no)
@line[line_no]
end
+
+ # The external encoding for standard input.
+ def encoding
+ @stdin.external_encoding
+ end
+
+ # For debug message
+ def inspect
+ 'StdioInputMethod'
+ end
end
-
+
+ # Use a File for IO with irb, see InputMethod
class FileInputMethod < InputMethod
+ class << self
+ def open(file, &block)
+ begin
+ io = new(file)
+ block.call(io)
+ ensure
+ io&.close
+ end
+ end
+ end
+
+ # Creates a new input method object
def initialize(file)
super
- @io = open(file)
+ @io = IRB::MagicFile.open(file)
+ @external_encoding = @io.external_encoding
end
+ # The file name of this input method, usually given during initialization.
attr_reader :file_name
+ # Whether the end of this input method has been reached, returns +true+ if
+ # there is no more data to read.
+ #
+ # See IO#eof? for more information.
def eof?
- @io.eof?
+ @io.closed? || @io.eof?
end
+ # Reads the next line from this input method.
+ #
+ # See IO#gets for more information.
def gets
print @prompt
- l = @io.gets
-# print @prompt, l
- l
+ @io.gets
+ end
+
+ # The external encoding for standard input.
+ def encoding
+ @external_encoding
+ end
+
+ # For debug message
+ def inspect
+ 'FileInputMethod'
+ end
+
+ def close
+ @io.close
end
end
begin
- require "readline"
class ReadlineInputMethod < InputMethod
- include Readline
+ def self.initialize_readline
+ require "readline"
+ rescue LoadError
+ else
+ include ::Readline
+ end
+
+ # Creates a new input method object using Readline
def initialize
- super
+ self.class.initialize_readline
+ if Readline.respond_to?(:encoding_system_needs)
+ IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false)
+ end
+ super
+
+ @line_no = 0
+ @line = []
+ @eof = false
- @line_no = 0
- @line = []
- @eof = false
+ @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
+ @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
+
+ if Readline.respond_to?("basic_word_break_characters=")
+ Readline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS
+ end
+ Readline.completion_append_character = nil
+ Readline.completion_proc = IRB::InputCompletor::CompletionProc
end
+ # Reads the next line from this input method.
+ #
+ # See IO#gets for more information.
def gets
- if l = readline(@prompt, false)
+ Readline.input = @stdin
+ Readline.output = @stdout
+ if l = readline(@prompt, false)
HISTORY.push(l) if !l.empty?
- @line[@line_no += 1] = l + "\n"
- else
- @eof = true
- l
- end
+ @line[@line_no += 1] = l + "\n"
+ else
+ @eof = true
+ l
+ end
end
+ # Whether the end of this input method has been reached, returns +true+
+ # if there is no more data to read.
+ #
+ # See IO#eof? for more information.
def eof?
- @eof
+ @eof
end
- def readable_atfer_eof?
- true
+ # Whether this input method is still readable when there is no more data to
+ # read.
+ #
+ # See IO#eof for more information.
+ def readable_after_eof?
+ true
end
+ # Returns the current line number for #io.
+ #
+ # #line counts the number of times #gets is called.
+ #
+ # See IO#lineno for more information.
def line(line_no)
- @line[line_no]
+ @line[line_no]
+ end
+
+ # The external encoding for standard input.
+ def encoding
+ @stdin.external_encoding
+ end
+
+ # For debug message
+ def inspect
+ readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline'
+ str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}"
+ inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc')
+ str += " and #{inputrc_path}" if File.exist?(inputrc_path)
+ str
+ end
+ end
+ end
+
+ class ReidlineInputMethod < InputMethod
+ include Reline
+
+ # Creates a new input method object using Reline
+ def initialize
+ IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false)
+ super
+
+ @line_no = 0
+ @line = []
+ @eof = false
+
+ @stdin = ::IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
+ @stdout = ::IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-")
+
+ if Reline.respond_to?("basic_word_break_characters=")
+ Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS
+ end
+ Reline.completion_append_character = nil
+ Reline.completer_quote_characters = ''
+ Reline.completion_proc = IRB::InputCompletor::CompletionProc
+ Reline.output_modifier_proc =
+ if IRB.conf[:USE_COLORIZE]
+ proc do |output, complete: |
+ next unless IRB::Color.colorable?
+ IRB::Color.colorize_code(output, complete: complete)
+ end
+ else
+ proc do |output|
+ Reline::Unicode.escape_for_print(output)
+ end
+ end
+ Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc
+ Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE]
+ if IRB.conf[:USE_AUTOCOMPLETE]
+ Reline.add_dialog_proc(:show_doc, SHOW_DOC_DIALOG, Reline::DEFAULT_DIALOG_CONTEXT)
+ end
+ end
+
+ def check_termination(&block)
+ @check_termination_proc = block
+ end
+
+ def dynamic_prompt(&block)
+ @prompt_proc = block
+ end
+
+ def auto_indent(&block)
+ @auto_indent_proc = block
+ end
+
+ SHOW_DOC_DIALOG = ->() {
+ dialog.trap_key = nil
+ alt_d = [
+ [Reline::Key.new(nil, 0xE4, true)], # Normal Alt+d.
+ [27, 100], # Normal Alt+d when convert-meta isn't used.
+ [195, 164], # The "" that appears when Alt+d is pressed on xterm.
+ [226, 136, 130] # The "" that appears when Alt+d in pressed on iTerm2.
+ ]
+
+ if just_cursor_moving and completion_journey_data.nil?
+ return nil
+ end
+ cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4)
+ return nil if result.nil? or pointer.nil? or pointer < 0
+ name = result[pointer]
+ name = IRB::InputCompletor.retrieve_completion_data(name, doc_namespace: true)
+
+ options = {}
+ options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty?
+ driver = RDoc::RI::Driver.new(options)
+
+ if key.match?(dialog.name)
+ begin
+ driver.display_names([name])
+ rescue RDoc::RI::Driver::NotFoundError
+ end
+ end
+
+ begin
+ name = driver.expand_name(name)
+ rescue RDoc::RI::Driver::NotFoundError
+ return nil
+ rescue
+ return nil # unknown error
+ end
+ doc = nil
+ used_for_class = false
+ if not name =~ /#|\./
+ found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name)
+ if not found.empty?
+ doc = driver.class_document(name, found, klasses, includes, extends)
+ used_for_class = true
+ end
+ end
+ unless used_for_class
+ doc = RDoc::Markup::Document.new
+ begin
+ driver.add_method(doc, name)
+ rescue RDoc::RI::Driver::NotFoundError
+ doc = nil
+ rescue
+ return nil # unknown error
+ end
+ end
+ return nil if doc.nil?
+ width = 40
+
+ right_x = cursor_pos_to_render.x + autocomplete_dialog.width
+ if right_x + width > screen_width
+ right_width = screen_width - (right_x + 1)
+ left_x = autocomplete_dialog.column - width
+ left_x = 0 if left_x < 0
+ left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width
+ if right_width.positive? and left_width.positive?
+ if right_width >= left_width
+ width = right_width
+ x = right_x
+ else
+ width = left_width
+ x = left_x
+ end
+ elsif right_width.positive? and left_width <= 0
+ width = right_width
+ x = right_x
+ elsif right_width <= 0 and left_width.positive?
+ width = left_width
+ x = left_x
+ else # Both are negative width.
+ return nil
+ end
+ else
+ x = right_x
+ end
+ formatter = RDoc::Markup::ToAnsi.new
+ formatter.width = width
+ dialog.trap_key = alt_d
+ message = 'Press Alt+d to read the full document'
+ contents = [message] + doc.accept(formatter).split("\n")
+
+ y = cursor_pos_to_render.y
+ DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49')
+ }
+
+ # Reads the next line from this input method.
+ #
+ # See IO#gets for more information.
+ def gets
+ Reline.input = @stdin
+ Reline.output = @stdout
+ Reline.prompt_proc = @prompt_proc
+ Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc
+ if l = readmultiline(@prompt, false, &@check_termination_proc)
+ HISTORY.push(l) if !l.empty?
+ @line[@line_no += 1] = l + "\n"
+ else
+ @eof = true
+ l
+ end
+ end
+
+ # Whether the end of this input method has been reached, returns +true+
+ # if there is no more data to read.
+ #
+ # See IO#eof? for more information.
+ def eof?
+ @eof
+ end
+
+ # Whether this input method is still readable when there is no more data to
+ # read.
+ #
+ # See IO#eof for more information.
+ def readable_after_eof?
+ true
+ end
+
+ # Returns the current line number for #io.
+ #
+ # #line counts the number of times #gets is called.
+ #
+ # See IO#lineno for more information.
+ def line(line_no)
+ @line[line_no]
+ end
+
+ # The external encoding for standard input.
+ def encoding
+ @stdin.external_encoding
+ end
+
+ # For debug message
+ def inspect
+ config = Reline::Config.new
+ str = "ReidlineInputMethod with Reline #{Reline::VERSION}"
+ if config.respond_to?(:inputrc_path)
+ inputrc_path = File.expand_path(config.inputrc_path)
+ else
+ inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc')
end
+ str += " and #{inputrc_path}" if File.exist?(inputrc_path)
+ str
end
- rescue LoadError
end
end
diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb
new file mode 100644
index 0000000000..c2f3b605db
--- /dev/null
+++ b/lib/irb/inspector.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: false
+#
+# irb/inspector.rb - inspect methods
+# $Release Version: 0.9.6$
+# $Revision: 1.19 $
+# $Date: 2002/06/11 07:51:31 $
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+# --
+#
+#
+#
+
+module IRB # :nodoc:
+
+
+ # Convenience method to create a new Inspector, using the given +inspect+
+ # proc, and optional +init+ proc and passes them to Inspector.new
+ #
+ # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" })
+ # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28>
+ # irb(main):001:0> "what?" #=> omg! what?
+ #
+ def IRB::Inspector(inspect, init = nil)
+ Inspector.new(inspect, init)
+ end
+
+ # An irb inspector
+ #
+ # In order to create your own custom inspector there are two things you
+ # should be aware of:
+ #
+ # Inspector uses #inspect_value, or +inspect_proc+, for output of return values.
+ #
+ # This also allows for an optional #init+, or +init_proc+, which is called
+ # when the inspector is activated.
+ #
+ # Knowing this, you can create a rudimentary inspector as follows:
+ #
+ # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" })
+ # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28>
+ # irb(main):001:0> "what?" #=> omg! what?
+ #
+ class Inspector
+ # Default inspectors available to irb, this includes:
+ #
+ # +:pp+:: Using Kernel#pretty_inspect
+ # +:yaml+:: Using YAML.dump
+ # +:marshal+:: Using Marshal.dump
+ INSPECTORS = {}
+
+ # Determines the inspector to use where +inspector+ is one of the keys passed
+ # during inspector definition.
+ def self.keys_with_inspector(inspector)
+ INSPECTORS.select{|k,v| v == inspector}.collect{|k, v| k}
+ end
+
+ # Example
+ #
+ # Inspector.def_inspector(key, init_p=nil){|v| v.inspect}
+ # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect}
+ # Inspector.def_inspector(key, inspector)
+ # Inspector.def_inspector([key1,...], inspector)
+ def self.def_inspector(key, arg=nil, &block)
+ if block_given?
+ inspector = IRB::Inspector(block, arg)
+ else
+ inspector = arg
+ end
+
+ case key
+ when Array
+ for k in key
+ def_inspector(k, inspector)
+ end
+ when Symbol
+ INSPECTORS[key] = inspector
+ INSPECTORS[key.to_s] = inspector
+ when String
+ INSPECTORS[key] = inspector
+ INSPECTORS[key.intern] = inspector
+ else
+ INSPECTORS[key] = inspector
+ end
+ end
+
+ # Creates a new inspector object, using the given +inspect_proc+ when
+ # output return values in irb.
+ def initialize(inspect_proc, init_proc = nil)
+ @init = init_proc
+ @inspect = inspect_proc
+ end
+
+ # Proc to call when the inspector is activated, good for requiring
+ # dependent libraries.
+ def init
+ @init.call if @init
+ end
+
+ # Proc to call when the input is evaluated and output in irb.
+ def inspect_value(v)
+ @inspect.call(v)
+ rescue
+ puts "(Object doesn't support #inspect)"
+ ''
+ end
+ end
+
+ Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s}
+ Inspector.def_inspector([:p, :inspect]){|v|
+ result = v.inspect
+ if IRB.conf[:MAIN_CONTEXT]&.use_colorize? && Color.inspect_colorable?(v)
+ result = Color.colorize_code(result)
+ end
+ result
+ }
+ Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require "irb/color_printer"}){|v|
+ if IRB.conf[:MAIN_CONTEXT]&.use_colorize?
+ IRB::ColorPrinter.pp(v, '').chomp
+ else
+ v.pretty_inspect.chomp
+ end
+ }
+ Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
+ begin
+ YAML.dump(v)
+ rescue
+ puts "(can't dump yaml. use inspect)"
+ v.inspect
+ end
+ }
+
+ Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v|
+ Marshal.dump(v)
+ }
+end
diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec
new file mode 100644
index 0000000000..26d0fb018f
--- /dev/null
+++ b/lib/irb/irb.gemspec
@@ -0,0 +1,40 @@
+begin
+ require_relative "lib/irb/version"
+rescue LoadError
+ # for Ruby core repository
+ require_relative "version"
+end
+
+Gem::Specification.new do |spec|
+ spec.name = "irb"
+ spec.version = IRB::VERSION
+ spec.authors = ["Keiju ISHITSUKA"]
+ spec.email = ["keiju@ruby-lang.org"]
+
+ spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).}
+ spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).}
+ spec.homepage = "https://github.com/ruby/irb"
+ spec.licenses = ["Ruby", "BSD-2-Clause"]
+
+ spec.files = [
+ ".document",
+ "Gemfile",
+ "LICENSE.txt",
+ "README.md",
+ "Rakefile",
+ "bin/console",
+ "bin/setup",
+ "doc/irb/irb-tools.rd.ja",
+ "doc/irb/irb.rd.ja",
+ "exe/irb",
+ "irb.gemspec",
+ "man/irb.1",
+ ] + Dir.glob("lib/**/*")
+ spec.bindir = "exe"
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
+ spec.require_paths = ["lib"]
+
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5")
+
+ spec.add_dependency "reline", ">= 0.3.0"
+end
diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb
index 247596b7fe..798994e92c 100644
--- a/lib/irb/lc/error.rb
+++ b/lib/irb/lc/error.rb
@@ -1,30 +1,71 @@
+# frozen_string_literal: false
#
-# irb/lc/error.rb -
-# $Release Version: 0.9.5$
+# irb/lc/error.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-require "e2mmap"
+#
+# :stopdoc:
module IRB
-
- # exceptions
- extend Exception2MessageMapper
- def_exception :UnrecognizedSwitch, "Unrecognized switch: %s"
- def_exception :NotImplementedError, "Need to define `%s'"
- def_exception :CantReturnToNormalMode, "Can't return to normal mode."
- def_exception :IllegalParameter, "Illegal parameter(%s)."
- def_exception :IrbAlreadyDead, "Irb is already dead."
- def_exception :IrbSwitchedToCurrentThread, "Switched to current thread."
- def_exception :NoSuchJob, "No such job(%s)."
- def_exception :CantShiftToMultiIrbMode, "Can't shift to multi irb mode."
- def_exception :CantChangeBinding, "Can't change binding to (%s)."
- def_exception :UndefinedPromptMode, "Undefined prompt mode(%s)."
-
+ class UnrecognizedSwitch < StandardError
+ def initialize(val)
+ super("Unrecognized switch: #{val}")
+ end
+ end
+ class NotImplementedError < StandardError
+ def initialize(val)
+ super("Need to define `#{val}'")
+ end
+ end
+ class CantReturnToNormalMode < StandardError
+ def initialize
+ super("Can't return to normal mode.")
+ end
+ end
+ class IllegalParameter < StandardError
+ def initialize(val)
+ super("Invalid parameter(#{val}).")
+ end
+ end
+ class IrbAlreadyDead < StandardError
+ def initialize
+ super("Irb is already dead.")
+ end
+ end
+ class IrbSwitchedToCurrentThread < StandardError
+ def initialize
+ super("Switched to current thread.")
+ end
+ end
+ class NoSuchJob < StandardError
+ def initialize(val)
+ super("No such job(#{val}).")
+ end
+ end
+ class CantShiftToMultiIrbMode < StandardError
+ def initialize
+ super("Can't shift to multi irb mode.")
+ end
+ end
+ class CantChangeBinding < StandardError
+ def initialize(val)
+ super("Can't change binding to (#{val}).")
+ end
+ end
+ class UndefinedPromptMode < StandardError
+ def initialize(val)
+ super("Undefined prompt mode(#{val}).")
+ end
+ end
+ class IllegalRCGenerator < StandardError
+ def initialize
+ super("Define illegal RC_NAME_GENERATOR.")
+ end
+ end
end
-
+# :startdoc:
diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message
index 32087d113c..939dc38975 100644
--- a/lib/irb/lc/help-message
+++ b/lib/irb/lc/help-message
@@ -1,35 +1,61 @@
+# -*- coding: utf-8 -*-
#
-# irb/lc/help-message.rb -
-# $Release Version: 0.9.5$
+# irb/lc/help-message.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
Usage: irb.rb [options] [programfile] [arguments]
- -f Suppress read of ~/.irbrc
- -m Bc mode (load mathn, fraction or matrix are available)
+ -f Suppress read of ~/.irbrc
-d Set $DEBUG to true (same as `ruby -d')
-r load-module Same as `ruby -r'
-I path Specify $LOAD_PATH directory
- --inspect Use `inspect' for output (default except for bc mode)
- --noinspect Don't use inspect for output
- --readline Use Readline extension module
- --noreadline Don't use Readline extension module
- --prompt prompt-mode
- --prompt-mode prompt-mode
- Switch prompt mode. Pre-defined prompt modes are
- `default', `simple', `xmp' and `inf-ruby'
- --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
- Suppresses --readline.
- --simple-prompt Simple prompt mode
- --noprompt No prompt mode
- --tracer Display trace for each execution of commands.
+ -U Same as `ruby -U`
+ -E enc Same as `ruby -E`
+ -w Same as `ruby -w`
+ -W[level=2] Same as `ruby -W`
+ --context-mode n Set n[0-4] to method to create Binding Object,
+ when new workspace was created
+ --extra-doc-dir Add an extra doc dir for the doc dialog
+ --echo Show result (default)
+ --noecho Don't show result
+ --echo-on-assignment
+ Show result on assignment
+ --noecho-on-assignment
+ Don't show result on assignment
+ --truncate-echo-on-assignment
+ Show truncated result on assignment (default)
+ --inspect Use `inspect' for output
+ --noinspect Don't use inspect for output
+ --multiline Use multiline editor module
+ --nomultiline Don't use multiline editor module
+ --singleline Use singleline editor module
+ --nosingleline Don't use singleline editor module
+ --colorize Use colorization
+ --nocolorize Don't use colorization
+ --autocomplete Use autocompletion
+ --noautocomplete Don't use autocompletion
+ --prompt prompt-mode/--prompt-mode prompt-mode
+ Switch prompt mode. Pre-defined prompt modes are
+ `default', `simple', `xmp' and `inf-ruby'
+ --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs.
+ Suppresses --multiline and --singleline.
+ --sample-book-mode/--simple-prompt
+ Simple prompt mode
+ --noprompt No prompt mode
+ --single-irb Share self with sub-irb.
+ --tracer Display trace for each execution of commands.
--back-trace-limit n
- Display backtrace top n and tail n. The default
- value is 16.
- --irb_debug n Set internal debug level to n (not for popular use)
+ Display backtrace top n and tail n. The default
+ value is 16.
+ --verbose Show details
+ --noverbose Don't show details
-v, --version Print the version of irb
+ -h, --help Print help
+ -- Separate options of irb from the list of command-line args
+
+# vim:fileencoding=utf-8
diff --git a/lib/irb/lc/ja/encoding_aliases.rb b/lib/irb/lc/ja/encoding_aliases.rb
new file mode 100644
index 0000000000..c534bf0fef
--- /dev/null
+++ b/lib/irb/lc/ja/encoding_aliases.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: false
+# :stopdoc:
+module IRB
+ class Locale
+ @@legacy_encoding_alias_map = {
+ 'ujis' => Encoding::EUC_JP,
+ 'euc' => Encoding::EUC_JP
+ }.freeze
+ end
+end
+# :startdoc:
diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb
index 4c2fb3b839..31ebb3b5f0 100644
--- a/lib/irb/lc/ja/error.rb
+++ b/lib/irb/lc/ja/error.rb
@@ -1,27 +1,72 @@
-#
-# irb/lc/ja/error.rb -
-# $Release Version: 0.9.5$
+# -*- coding: utf-8 -*-
+# frozen_string_literal: false
+# irb/lc/ja/error.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-require "e2mmap"
+#
+# :stopdoc:
module IRB
- # exceptions
- extend Exception2MessageMapper
- def_exception :UnrecognizedSwitch, 'スイッチ(%s)が分りません'
- def_exception :NotImplementedError, '`%s\'の定義が必要です'
- def_exception :CantReturnToNormalMode, 'Normalモ・踉札匹北瓩譴泙擦鹿粡寂蝟赱齠洲粤讌将粤聨纔竇頸蜿塞跛繚瘡倚鱇辣鬪パラメ・踉札┘鶇が間違っています.'
- def_exception :IrbAlreadyDead, 'Irbは既に死んでいます.'
- def_exception :IrbSwitchedToCurrentThread, 'カレントスレッドに切り替わりました.'
- def_exception :NoSuchJob, 'そのよう瘢雹なジョブ(%s)はありません.'
- def_exception :CantShiftToMultiIrbMode, 'multi-irb modeに移れません.'
- def_exception :CantChangeBinding, 'バインディング(%s)に変更できません.'
- def_exception :UndefinedPromptMode, 'プロンプトモ・踉札┘鶇は定義されていません.'
+ class UnrecognizedSwitch < StandardError
+ def initialize(val)
+ super("(#{val})")
+ end
+ end
+ class NotImplementedError < StandardError
+ def initialize(val)
+ super("`#{val}'")
+ end
+ end
+ class CantReturnToNormalMode < StandardError
+ def initialize
+ super("Normal.")
+ end
+ end
+ class IllegalParameter < StandardError
+ def initialize(val)
+ super("(#{val}).")
+ end
+ end
+ class IrbAlreadyDead < StandardError
+ def initialize
+ super("Irb.")
+ end
+ end
+ class IrbSwitchedToCurrentThread < StandardError
+ def initialize
+ super(".")
+ end
+ end
+ class NoSuchJob < StandardError
+ def initialize(val)
+ super("(#{val}).")
+ end
+ end
+ class CantShiftToMultiIrbMode < StandardError
+ def initialize
+ super("multi-irb mode.")
+ end
+ end
+ class CantChangeBinding < StandardError
+ def initialize(val)
+ super("(#{val}).")
+ end
+ end
+ class UndefinedPromptMode < StandardError
+ def initialize(val)
+ super("(#{val}).")
+ end
+ end
+ class IllegalRCGenerator < StandardError
+ def initialize
+ super("RC_NAME_GENERATOR.")
+ end
+ end
end
+# :startdoc:
+# vim:fileencoding=utf-8
diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message
index debbfe9355..238535afb7 100644
--- a/lib/irb/lc/ja/help-message
+++ b/lib/irb/lc/ja/help-message
@@ -1,36 +1,57 @@
-#
-# irb/lc/ja/help-message.rb -
-# $Release Version: 0.9.5$
+# -*- coding: utf-8 -*-
+# irb/lc/ja/help-message.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
Usage: irb.rb [options] [programfile] [arguments]
- -f ~/.irbrc を読み込まない.
- -m bcモ・踉札ぢ分数, 行列の計算ができる)
- -d $DEBUG をtrueにする(ruby -d と同じ)
- -r load-module ruby -r と同じ.
- -I path $LOAD_PATH に path を追加する.
- --inspect 結果出力にinspectを用いる(bcモ・踉札桧奮阿魯妊侫襯蝟昭粡竚癈鷭т繻Ь濶銖鞳笏 結果出力にinspectを用いない.
- --readline readlineライブラリを利用する.
- --noreadline readlineライブラリを利用しない.
+ -f ~/.irbrc .
+ -d $DEBUG true(ruby -d )
+ -r load-module ruby -r .
+ -I path $LOAD_PATH path .
+ -U ruby -U .
+ -E enc ruby -E .
+ -w ruby -w .
+ -W[level=2] ruby -W .
+ --context-mode n Binding
+ 0 3 .
+ --echo ().
+ --noecho .
+ --inspect inspect.
+ --noinspect inspect.
+ --multiline .
+ --nomultiline .
+ --singleline .
+ --nosingleline .
+ --colorize .
+ --nocolorize .
+ --autocomplete .
+ --noautocomplete .
--prompt prompt-mode/--prompt-mode prompt-mode
- プロンプトモ・踉札匹鮴畋悗┐泙現在定義されているプ
- ロンプトモ・踉札匹粤聲蜊韭絳逅蜴罩鴣磔が
- 用意されています.
- --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表┐鮃圓覆逅特
- に指定がない限り, readlineライブラリは使わなくなる.
- --simple-prompt 非錣縫轡鵐廛襪淵廛蹈鵐廛箸鰺僂い襯癲ドです.
- --noprompt プロンプト表┐鮃圓覆錣覆蝟昭粡竚癈鷭т繻Ь鱇竇コマンド孫垰縫肇譟スを行なう瘢雹.
+ .
+ , default, simple, xmp, inf-ruby
+ .
+ --inf-ruby-mode emacsinf-ruby-mode.
+ ,
+ .
+ --sample-book-mode/--simple-prompt
+ .
+ --noprompt .
+ --single-irb irb self
+ irb .
+ --tracer .
--back-trace-limit n
- バックトレ・踉札紘拾踉雑をバックトレ・踉札垢瞭後ろ
- からnだけ行なう瘢雹. デフォルトは16
- --irb_debug n irbのデバッグデバッグレベルをnに設定する(利用しな
- い方が無難でしょう瘢雹).
- -v, --version irbのバ・踉札献腑鵑鯢拾踉雑する
+ n,
+ n. 16
+
+ --verbose .
+ --noverbose ().
+ -v, --version irb.
+ -h, --help irb .
+ -- .
+
+# vim:fileencoding=utf-8
diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb
index 5ed9f54507..bb44b41002 100644
--- a/lib/irb/locale.rb
+++ b/lib/irb/locale.rb
@@ -1,51 +1,59 @@
+# frozen_string_literal: false
#
# irb/locale.rb - internationalization module
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-
-autoload :Kconv, "kconv"
-
-module IRB
+#
+module IRB # :nodoc:
class Locale
- @RCS_ID='-$Id$-'
- JPDefaultLocale = "ja"
+ LOCALE_NAME_RE = %r[
+ (?<language>[[:alpha:]]{2,3})
+ (?:_ (?<territory>[[:alpha:]]{2,3}) )?
+ (?:\. (?<codeset>[^@]+) )?
+ (?:@ (?<modifier>.*) )?
+ ]x
LOCALE_DIR = "/lc/"
+ @@legacy_encoding_alias_map = {}.freeze
+ @@loaded = []
+
def initialize(locale = nil)
- @lang = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C"
+ @override_encoding = nil
+ @lang = @territory = @encoding_name = @modifier = nil
+ @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C"
+ if m = LOCALE_NAME_RE.match(@locale)
+ @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier]
+
+ if @encoding_name
+ begin load 'irb/encoding_aliases.rb'; rescue LoadError; end
+ if @encoding = @@legacy_encoding_alias_map[@encoding_name]
+ warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1)
+ end
+ @encoding = Encoding.find(@encoding_name) rescue nil
+ end
+ end
+ @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT)
end
- attr_reader :lang
+ attr_reader :lang, :territory, :modifier
- def lc2kconv(lang)
- case lang
- when "ja_JP.ujis", "ja_JP.euc", "ja_JP.eucJP"
- Kconv::EUC
- when "ja_JP.sjis", "ja_JP.SJIS"
- Kconv::SJIS
- when /ja_JP.utf-?8/i
- Kconv::UTF8
- end
+ def encoding
+ @override_encoding || @encoding
end
- private :lc2kconv
def String(mes)
mes = super(mes)
- case @lang
- when /^ja/
- mes = Kconv::kconv(mes, lc2kconv(@lang))
+ if encoding
+ mes.encode(encoding, undef: :replace)
else
- mes
+ mes
end
- mes
end
def format(*opts)
@@ -81,104 +89,103 @@ module IRB
case file
when /\.rb$/
- begin
- load(file, priv)
- $".push file
- return true
- rescue LoadError
- end
+ begin
+ load(file, priv)
+ $".push file
+ return true
+ rescue LoadError
+ end
when /\.(so|o|sl)$/
- return super
+ return super
end
begin
- load(f = file + ".rb")
- $".push f #"
- return true
+ load(f = file + ".rb")
+ $".push f #"
+ return true
rescue LoadError
- return ruby_require(file)
+ return ruby_require(file)
end
end
alias toplevel_load load
-
+
def load(file, priv=nil)
+ found = find(file)
+ if found
+ unless @@loaded.include?(found)
+ @@loaded << found # cache
+ return real_load(found, priv)
+ end
+ else
+ raise LoadError, "No such file to load -- #{file}"
+ end
+ end
+
+ def find(file , paths = $:)
dir = File.dirname(file)
dir = "" if dir == "."
base = File.basename(file)
- if /^ja(_JP)?$/ =~ @lang
- back, @lang = @lang, "C"
- end
- begin
- if dir[0] == ?/ #/
- lc_path = search_file(dir, base)
- return real_load(lc_path, priv) if lc_path
- end
-
- for path in $:
- lc_path = search_file(path + "/" + dir, base)
- return real_load(lc_path, priv) if lc_path
- end
- ensure
- @lang = back if back
+ if dir.start_with?('/')
+ return each_localized_path(dir, base).find{|full_path| File.readable? full_path}
+ else
+ return search_file(paths, dir, base)
end
- raise LoadError, "No such file to load -- #{file}"
- end
+ end
+ private
def real_load(path, priv)
- src = self.String(File.read(path))
+ src = MagicFile.open(path){|f| f.read}
if priv
- eval("self", TOPLEVEL_BINDING).extend(Module.new {eval(src, nil, path)})
+ eval("self", TOPLEVEL_BINDING).extend(Module.new {eval(src, nil, path)})
else
- eval(src, TOPLEVEL_BINDING, path)
+ eval(src, TOPLEVEL_BINDING, path)
end
end
- private :real_load
- def find(file , paths = $:)
- dir = File.dirname(file)
- dir = "" if dir == "."
- base = File.basename(file)
- if dir[0] == ?/ #/
- return lc_path = search_file(dir, base)
- else
- for path in $:
- if lc_path = search_file(path + "/" + dir, base)
- return lc_path
- end
- end
+ # @param paths load paths in which IRB find a localized file.
+ # @param dir directory
+ # @param file basename to be localized
+ #
+ # typically, for the parameters and a <path> in paths, it searches
+ # <path>/<dir>/<locale>/<file>
+ def search_file(lib_paths, dir, file)
+ each_localized_path(dir, file) do |lc_path|
+ lib_paths.each do |libpath|
+ full_path = File.join(libpath, lc_path)
+ return full_path if File.readable?(full_path)
+ end
+ redo if defined?(Gem) and Gem.try_activate(lc_path)
end
nil
end
- def search_file(path, file)
- if File.exist?(p1 = path + lc_path(file, "C"))
- if File.exist?(p2 = path + lc_path(file))
- return p2
- else
- end
- return p1
- else
+ def each_localized_path(dir, file)
+ return enum_for(:each_localized_path) unless block_given?
+ each_sublocale do |lc|
+ yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file)
end
- nil
end
- private :search_file
- def lc_path(file = "", lc = @lang)
- case lc
- when "C"
- LOCALE_DIR + file
- when /^ja/
- LOCALE_DIR + "ja/" + file
- else
- LOCALE_DIR + @lang + "/" + file
+ def each_sublocale
+ if @lang
+ if @territory
+ if @encoding_name
+ yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier
+ yield "#{@lang}_#{@territory}.#{@encoding_name}"
+ end
+ yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier
+ yield "#{@lang}_#{@territory}"
+ end
+ if @encoding_name
+ yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier
+ yield "#{@lang}.#{@encoding_name}"
+ end
+ yield "#{@lang}@#{@modifier}" if @modifier
+ yield "#{@lang}"
end
+ yield nil
end
- private :lc_path
end
end
-
-
-
-
diff --git a/lib/irb/magic-file.rb b/lib/irb/magic-file.rb
new file mode 100644
index 0000000000..34e06d64b3
--- /dev/null
+++ b/lib/irb/magic-file.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: false
+module IRB
+ class << (MagicFile = Object.new)
+ # see parser_magic_comment in parse.y
+ ENCODING_SPEC_RE = %r"coding\s*[=:]\s*([[:alnum:]\-_]+)"
+
+ def open(path)
+ io = File.open(path, 'rb')
+ line = io.gets
+ line = io.gets if line[0,2] == "#!"
+ encoding = detect_encoding(line)
+ internal_encoding = encoding
+ encoding ||= IRB.default_src_encoding
+ io.rewind
+ io.set_encoding(encoding, internal_encoding)
+
+ if block_given?
+ begin
+ return (yield io)
+ ensure
+ io.close
+ end
+ else
+ return io
+ end
+ end
+
+ private
+ def detect_encoding(line)
+ return unless line[0] == ?#
+ line = line[1..-1]
+ line = $1 if line[/-\*-\s*(.*?)\s*-*-$/]
+ return nil unless ENCODING_SPEC_RE =~ line
+ encoding = $1
+ return encoding.sub(/-(?:mac|dos|unix)/i, '')
+ end
+ end
+end
diff --git a/lib/irb/notifier.rb b/lib/irb/notifier.rb
index c8e66fa859..d0e413dd68 100644
--- a/lib/irb/notifier.rb
+++ b/lib/irb/notifier.rb
@@ -1,145 +1,236 @@
+# frozen_string_literal: false
#
-# notifier.rb - optput methods used by irb
-# $Release Version: 0.9.5$
+# notifier.rb - output methods used by irb
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "e2mmap"
-require "irb/output-method"
+require_relative "output-method"
module IRB
+ # An output formatter used internally by the lexer.
module Notifier
- extend Exception2MessageMapper
- def_exception :ErrUndefinedNotifier,
- "undefined notifier level: %d is specified"
- def_exception :ErrUnrecognizedLevel,
- "unrecognized notifier level: %s is specified"
+ class ErrUndefinedNotifier < StandardError
+ def initialize(val)
+ super("undefined notifier level: #{val} is specified")
+ end
+ end
+ class ErrUnrecognizedLevel < StandardError
+ def initialize(val)
+ super("unrecognized notifier level: #{val} is specified")
+ end
+ end
+ # Define a new Notifier output source, returning a new CompositeNotifier
+ # with the given +prefix+ and +output_method+.
+ #
+ # The optional +prefix+ will be appended to all objects being inspected
+ # during output, using the given +output_method+ as the output source. If
+ # no +output_method+ is given, StdioOutputMethod will be used, and all
+ # expressions will be sent directly to STDOUT without any additional
+ # formatting.
def def_notifier(prefix = "", output_method = StdioOutputMethod.new)
CompositeNotifier.new(prefix, output_method)
end
module_function :def_notifier
-
- class AbstructNotifier
+
+ # An abstract class, or superclass, for CompositeNotifier and
+ # LeveledNotifier to inherit. It provides several wrapper methods for the
+ # OutputMethod object used by the Notifier.
+ class AbstractNotifier
+ # Creates a new Notifier object
def initialize(prefix, base_notifier)
- @prefix = prefix
- @base_notifier = base_notifier
+ @prefix = prefix
+ @base_notifier = base_notifier
end
+ # The +prefix+ for this Notifier, which is appended to all objects being
+ # inspected during output.
attr_reader :prefix
+ # A wrapper method used to determine whether notifications are enabled.
+ #
+ # Defaults to +true+.
def notify?
- true
+ true
end
+ # See OutputMethod#print for more detail.
def print(*opts)
- @base_notifier.print prefix, *opts if notify?
+ @base_notifier.print prefix, *opts if notify?
end
+ # See OutputMethod#printn for more detail.
def printn(*opts)
- @base_notifier.printn prefix, *opts if notify?
+ @base_notifier.printn prefix, *opts if notify?
end
+ # See OutputMethod#printf for more detail.
def printf(format, *opts)
- @base_notifier.printf(prefix + format, *opts) if notify?
+ @base_notifier.printf(prefix + format, *opts) if notify?
end
+ # See OutputMethod#puts for more detail.
def puts(*objs)
- if notify?
- @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s})
- end
+ if notify?
+ @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s})
+ end
end
+ # Same as #ppx, except it uses the #prefix given during object
+ # initialization.
+ # See OutputMethod#ppx for more detail.
def pp(*objs)
- if notify?
- @base_notifier.ppx @prefix, *objs
- end
+ if notify?
+ @base_notifier.ppx @prefix, *objs
+ end
end
+ # Same as #pp, except it concatenates the given +prefix+ with the #prefix
+ # given during object initialization.
+ #
+ # See OutputMethod#ppx for more detail.
def ppx(prefix, *objs)
- if notify?
- @base_notifier.ppx @prefix+prefix, *objs
- end
+ if notify?
+ @base_notifier.ppx @prefix+prefix, *objs
+ end
end
+ # Execute the given block if notifications are enabled.
def exec_if
- yield(@base_notifier) if notify?
+ yield(@base_notifier) if notify?
end
end
- class CompositeNotifier<AbstructNotifier
+ # A class that can be used to create a group of notifier objects with the
+ # intent of representing a leveled notification system for irb.
+ #
+ # This class will allow you to generate other notifiers, and assign them
+ # the appropriate level for output.
+ #
+ # The Notifier class provides a class-method Notifier.def_notifier to
+ # create a new composite notifier. Using the first composite notifier
+ # object you create, sibling notifiers can be initialized with
+ # #def_notifier.
+ class CompositeNotifier < AbstractNotifier
+ # Create a new composite notifier object with the given +prefix+, and
+ # +base_notifier+ to use for output.
def initialize(prefix, base_notifier)
- super
+ super
- @notifiers = [D_NOMSG]
- @level_notifier = D_NOMSG
+ @notifiers = [D_NOMSG]
+ @level_notifier = D_NOMSG
end
+ # List of notifiers in the group
attr_reader :notifiers
+ # Creates a new LeveledNotifier in the composite #notifiers group.
+ #
+ # The given +prefix+ will be assigned to the notifier, and +level+ will
+ # be used as the index of the #notifiers Array.
+ #
+ # This method returns the newly created instance.
def def_notifier(level, prefix = "")
- notifier = LeveledNotifier.new(self, level, prefix)
- @notifiers[level] = notifier
- notifier
+ notifier = LeveledNotifier.new(self, level, prefix)
+ @notifiers[level] = notifier
+ notifier
end
+ # Returns the leveled notifier for this object
attr_reader :level_notifier
alias level level_notifier
+ # Sets the leveled notifier for this object.
+ #
+ # When the given +value+ is an instance of AbstractNotifier,
+ # #level_notifier is set to the given object.
+ #
+ # When an Integer is given, #level_notifier is set to the notifier at the
+ # index +value+ in the #notifiers Array.
+ #
+ # If no notifier exists at the index +value+ in the #notifiers Array, an
+ # ErrUndefinedNotifier exception is raised.
+ #
+ # An ErrUnrecognizedLevel exception is raised if the given +value+ is not
+ # found in the existing #notifiers Array, or an instance of
+ # AbstractNotifier
def level_notifier=(value)
- case value
- when AbstructNotifier
- @level_notifier = value
- when Integer
- l = @notifiers[value]
- Notifier.Raise ErrUndefinedNotifer, value unless l
- @level_notifier = l
- else
- Notifier.Raise ErrUnrecognizedLevel, value unless l
- end
+ case value
+ when AbstractNotifier
+ @level_notifier = value
+ when Integer
+ l = @notifiers[value]
+ raise ErrUndefinedNotifier, value unless l
+ @level_notifier = l
+ else
+ raise ErrUnrecognizedLevel, value unless l
+ end
end
alias level= level_notifier=
end
- class LeveledNotifier<AbstructNotifier
+ # A leveled notifier is comparable to the composite group from
+ # CompositeNotifier#notifiers.
+ class LeveledNotifier < AbstractNotifier
include Comparable
+ # Create a new leveled notifier with the given +base+, and +prefix+ to
+ # send to AbstractNotifier.new
+ #
+ # The given +level+ is used to compare other leveled notifiers in the
+ # CompositeNotifier group to determine whether or not to output
+ # notifications.
def initialize(base, level, prefix)
- super(prefix, base)
-
- @level = level
+ super(prefix, base)
+
+ @level = level
end
+ # The current level of this notifier object
attr_reader :level
+ # Compares the level of this notifier object with the given +other+
+ # notifier.
+ #
+ # See the Comparable module for more information.
def <=>(other)
- @level <=> other.level
+ @level <=> other.level
end
-
+
+ # Whether to output messages to the output method, depending on the level
+ # of this notifier object.
def notify?
- @base_notifier.level >= self
+ @base_notifier.level >= self
end
end
- class NoMsgNotifier<LeveledNotifier
+ # NoMsgNotifier is a LeveledNotifier that's used as the default notifier
+ # when creating a new CompositeNotifier.
+ #
+ # This notifier is used as the +zero+ index, or level +0+, for
+ # CompositeNotifier#notifiers, and will not output messages of any sort.
+ class NoMsgNotifier < LeveledNotifier
+ # Creates a new notifier that should not be used to output messages.
def initialize
- @base_notifier = nil
- @level = 0
- @prefix = ""
+ @base_notifier = nil
+ @level = 0
+ @prefix = ""
end
+ # Ensures notifications are ignored, see AbstractNotifier#notify? for
+ # more information.
def notify?
- false
+ false
end
end
- D_NOMSG = NoMsgNotifier.new
+ D_NOMSG = NoMsgNotifier.new # :nodoc:
end
end
diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb
index b9a3a8851e..3fda708cb0 100644
--- a/lib/irb/output-method.rb
+++ b/lib/irb/output-method.rb
@@ -1,83 +1,90 @@
+# frozen_string_literal: false
#
-# output-method.rb - optput methods used by irb
-# $Release Version: 0.9.5$
+# output-method.rb - output methods used by irb
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-
-require "e2mmap"
+#
module IRB
- # OutputMethod
- # StdioOutputMethod
-
+ # An abstract output class for IO in irb. This is mainly used internally by
+ # IRB::Notifier. You can define your own output method to use with Irb.new,
+ # or Context.new
class OutputMethod
- @RCS_ID='-$Id$-'
+ class NotImplementedError < StandardError
+ def initialize(val)
+ super("Need to define `#{val}'")
+ end
+ end
+ # Open this method to implement your own output method, raises a
+ # NotImplementedError if you don't define #print in your own class.
def print(*opts)
- IRB.fail NotImplementError, "print"
+ raise NotImplementedError, "print"
end
+ # Prints the given +opts+, with a newline delimiter.
def printn(*opts)
print opts.join(" "), "\n"
end
- # extend printf
+ # Extends IO#printf to format the given +opts+ for Kernel#sprintf using
+ # #parse_printf_format
def printf(format, *opts)
if /(%*)%I/ =~ format
- format, opts = parse_printf_format(format, opts)
+ format, opts = parse_printf_format(format, opts)
end
print sprintf(format, *opts)
end
- # %
- # <> [#0- +]
- # <> (\*|\*[1-9][0-9]*\$|[1-9][0-9]*)
- # <>.(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)?
- # #<>(hh|h|l|ll|L|q|j|z|t)
- # <>[diouxXeEfgGcsb%]
+ # Returns an array of the given +format+ and +opts+ to be used by
+ # Kernel#sprintf, if there was a successful Regexp match in the given
+ # +format+ from #printf
+ #
+ # %
+ # <flag> [#0- +]
+ # <minimum field width> (\*|\*[1-9][0-9]*\$|[1-9][0-9]*)
+ # <precision>.(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)?
+ # #<length modifier>(hh|h|l|ll|L|q|j|z|t)
+ # <conversion specifier>[diouxXeEfgGcsb%]
def parse_printf_format(format, opts)
return format, opts if $1.size % 2 == 1
end
- def foo(format)
- pos = 0
- inspects = []
- format.scan(/%[#0\-+ ]?(\*(?=[^0-9])|\*[1-9][0-9]*\$|[1-9][0-9]*(?=[^0-9]))?(\.(\*(?=[^0-9])|\*[1-9][0-9]*\$|[1-9][0-9]*(?=[^0-9])))?(([1-9][0-9]*\$)*)([diouxXeEfgGcsb%])/) {|f, p, pp, pos, new_pos, c|
- puts [f, p, pp, pos, new_pos, c].join("!")
- pos = new_pos if new_pos
- if c == "I"
- inspects.push pos.to_i
- (f||"")+(p||"")+(pp||"")+(pos||"")+"s"
- else
- $&
- end
- }
- end
-
+ # Calls #print on each element in the given +objs+, followed by a newline
+ # character.
def puts(*objs)
for obj in objs
- print(*obj)
- print "\n"
+ print(*obj)
+ print "\n"
end
end
+ # Prints the given +objs+ calling Object#inspect on each.
+ #
+ # See #puts for more detail.
def pp(*objs)
puts(*objs.collect{|obj| obj.inspect})
end
+ # Prints the given +objs+ calling Object#inspect on each and appending the
+ # given +prefix+.
+ #
+ # See #puts for more detail.
def ppx(prefix, *objs)
puts(*objs.collect{|obj| prefix+obj.inspect})
end
end
- class StdioOutputMethod<OutputMethod
+ # A standard output printer
+ class StdioOutputMethod < OutputMethod
+ # Prints the given +opts+ to standard output, see IO#print for more
+ # information.
def print(*opts)
STDOUT.print(*opts)
end
diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb
index ab584d5253..29862f5507 100644
--- a/lib/irb/ruby-lex.rb
+++ b/lib/irb/ruby-lex.rb
@@ -1,82 +1,114 @@
+# frozen_string_literal: false
#
-# irb/ruby-lex.rb - ruby lexcal analizer
-# $Release Version: 0.9.5$
+# irb/ruby-lex.rb - ruby lexcal analyzer
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
-require "e2mmap"
-require "irb/slex"
-require "irb/ruby-token"
+require "ripper"
+require "jruby" if RUBY_ENGINE == "jruby"
+# :stopdoc:
class RubyLex
- @RCS_ID='-$Id$-'
-
- extend Exception2MessageMapper
- def_exception(:AlreadyDefinedToken, "Already defined token(%s)")
- def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')")
- def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')")
- def_exception(:TkReading2TokenDuplicateError,
- "key duplicate(token_n='%s', key='%s')")
- def_exception(:SyntaxError, "%s")
- def_exception(:TerminateLineInput, "Terminate Line Input")
-
- include RubyToken
-
- class << self
- attr_accessor :debug_level
- def debug?
- @debug_level > 0
+ class TerminateLineInput < StandardError
+ def initialize
+ super("Terminate Line Input")
end
end
- @debug_level = 0
def initialize
- lex_init
- set_input(STDIN)
-
- @seek = 0
@exp_line_no = @line_no = 1
- @base_char_no = 0
- @char_no = 0
- @rests = []
- @readed = []
- @here_readed = []
-
@indent = 0
- @indent_stack = []
- @lex_state = EXPR_BEG
- @space_seen = false
- @here_header = false
-
@continue = false
@line = ""
-
- @skip_space = false
- @readed_auto_clean_up = false
- @exception_on_syntax_error = true
-
@prompt = nil
end
- attr_accessor :skip_space
- attr_accessor :readed_auto_clean_up
- attr_accessor :exception_on_syntax_error
-
- attr_reader :seek
- attr_reader :char_no
- attr_reader :line_no
- attr_reader :indent
+ def self.compile_with_errors_suppressed(code, line_no: 1)
+ begin
+ result = yield code, line_no
+ rescue ArgumentError
+ # Ruby can issue an error for the code if there is an
+ # incomplete magic comment for encoding in it. Force an
+ # expression with a new line before the code in this
+ # case to prevent magic comment handling. To make sure
+ # line numbers in the lexed code remain the same,
+ # decrease the line number by one.
+ code = ";\n#{code}"
+ line_no -= 1
+ result = yield code, line_no
+ end
+ result
+ end
# io functions
- def set_input(io, p = nil, &block)
+ def set_input(io, p = nil, context: nil, &block)
@io = io
+ if @io.respond_to?(:check_termination)
+ @io.check_termination do |code|
+ if Reline::IOGate.in_pasting?
+ lex = RubyLex.new
+ rest = lex.check_termination_in_prev_line(code, context: context)
+ if rest
+ Reline.delete_text
+ rest.bytes.reverse_each do |c|
+ Reline.ungetc(c)
+ end
+ true
+ else
+ false
+ end
+ else
+ code.gsub!(/\s*\z/, '').concat("\n")
+ ltype, indent, continue, code_block_open = check_state(code, context: context)
+ if ltype or indent > 0 or continue or code_block_open
+ false
+ else
+ true
+ end
+ end
+ end
+ end
+ if @io.respond_to?(:dynamic_prompt)
+ @io.dynamic_prompt do |lines|
+ lines << '' if lines.empty?
+ result = []
+ tokens = self.class.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, context: context)
+ code = String.new
+ partial_tokens = []
+ unprocessed_tokens = []
+ line_num_offset = 0
+ tokens.each do |t|
+ partial_tokens << t
+ unprocessed_tokens << t
+ if t.tok.include?("\n")
+ t_str = t.tok
+ t_str.each_line("\n") do |s|
+ code << s << "\n"
+ ltype, indent, continue, code_block_open = check_state(code, partial_tokens, context: context)
+ result << @prompt.call(ltype, indent, continue || code_block_open, @line_no + line_num_offset)
+ line_num_offset += 1
+ end
+ unprocessed_tokens = []
+ else
+ code << t.tok
+ end
+ end
+
+ unless unprocessed_tokens.empty?
+ ltype, indent, continue, code_block_open = check_state(code, unprocessed_tokens, context: context)
+ result << @prompt.call(ltype, indent, continue || code_block_open, @line_no + line_num_offset)
+ end
+ result
+ end
+ end
+
if p.respond_to?(:call)
@input = p
elsif block_given?
@@ -86,120 +118,115 @@ class RubyLex
end
end
- def get_readed
- if idx = @readed.reverse.index("\n")
- @base_char_no = idx
- else
- @base_char_no += @readed.size
- end
-
- readed = @readed.join("")
- @readed = []
- readed
- end
-
- def getc
- while @rests.empty?
-# return nil unless buf_input
- @rests.push nil unless buf_input
- end
- c = @rests.shift
- if @here_header
- @here_readed.push c
- else
- @readed.push c
- end
- @seek += 1
- if c == "\n"
- @line_no += 1
- @char_no = 0
+ def set_prompt(p = nil, &block)
+ p = block if block_given?
+ if p.respond_to?(:call)
+ @prompt = p
else
- @char_no += 1
- end
- c
- end
-
- def gets
- l = ""
- while c = getc
- l.concat(c)
- break if c == "\n"
+ @prompt = Proc.new{print p}
end
- return nil if l == "" and c.nil?
- l
end
- def eof?
- @io.eof?
- end
-
- def getc_of_rests
- if @rests.empty?
- nil
- else
- getc
- end
- end
+ ERROR_TOKENS = [
+ :on_parse_error,
+ :compile_error,
+ :on_assign_error,
+ :on_alias_error,
+ :on_class_name_error,
+ :on_param_error
+ ]
- def ungetc(c = nil)
- if @here_readed.empty?
- c2 = @readed.pop
- else
- c2 = @here_readed.pop
- end
- c = c2 unless c
- @rests.unshift c #c =
- @seek -= 1
- if c == "\n"
- @line_no -= 1
- if idx = @readed.reverse.index("\n")
- @char_no = @readed.size - idx
+ def self.ripper_lex_without_warning(code, context: nil)
+ verbose, $VERBOSE = $VERBOSE, nil
+ if context
+ lvars = context&.workspace&.binding&.local_variables
+ if lvars && !lvars.empty?
+ code = "#{lvars.join('=')}=nil\n#{code}"
+ line_no = 0
else
- @char_no = @base_char_no + @readed.size
+ line_no = 1
+ end
+ end
+ tokens = nil
+ compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
+ lexer = Ripper::Lexer.new(inner_code, '-', line_no)
+ if lexer.respond_to?(:scan) # Ruby 2.7+
+ tokens = []
+ pos_to_index = {}
+ lexer.scan.each do |t|
+ next if t.pos.first == 0
+ if pos_to_index.has_key?(t.pos)
+ index = pos_to_index[t.pos]
+ found_tk = tokens[index]
+ if ERROR_TOKENS.include?(found_tk.event) && !ERROR_TOKENS.include?(t.event)
+ tokens[index] = t
+ end
+ else
+ pos_to_index[t.pos] = tokens.size
+ tokens << t
+ end
+ end
+ else
+ tokens = lexer.parse.reject { |it| it.pos.first == 0 }
end
- else
- @char_no -= 1
- end
- end
-
- def peek_equal?(str)
- chrs = str.split(//)
- until @rests.size >= chrs.size
- return false unless buf_input
end
- @rests[0, chrs.size] == chrs
+ tokens
+ ensure
+ $VERBOSE = verbose
end
- def peek_match?(regexp)
- while @rests.empty?
- return false unless buf_input
- end
- regexp =~ @rests.join("")
+ def find_prev_spaces(line_index)
+ return 0 if @tokens.size == 0
+ md = @tokens[0].tok.match(/(\A +)/)
+ prev_spaces = md.nil? ? 0 : md[1].count(' ')
+ line_count = 0
+ @tokens.each_with_index do |t, i|
+ if t.tok.include?("\n")
+ line_count += t.tok.count("\n")
+ if line_count >= line_index
+ return prev_spaces
+ end
+ if (@tokens.size - 1) > i
+ md = @tokens[i + 1].tok.match(/(\A +)/)
+ prev_spaces = md.nil? ? 0 : md[1].count(' ')
+ end
+ end
+ end
+ prev_spaces
end
- def peek(i = 0)
- while @rests.size <= i
- return nil unless buf_input
+ def set_auto_indent(context)
+ if @io.respond_to?(:auto_indent) and context.auto_indent_mode
+ @io.auto_indent do |lines, line_index, byte_pointer, is_newline|
+ if is_newline
+ @tokens = self.class.ripper_lex_without_warning(lines[0..line_index].join("\n"), context: context)
+ prev_spaces = find_prev_spaces(line_index)
+ depth_difference = check_newline_depth_difference
+ depth_difference = 0 if depth_difference < 0
+ prev_spaces + depth_difference * 2
+ else
+ code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join
+ last_line = lines[line_index]&.byteslice(0, byte_pointer)
+ code += last_line if last_line
+ @tokens = self.class.ripper_lex_without_warning(code, context: context)
+ corresponding_token_depth = check_corresponding_token_depth(lines, line_index)
+ if corresponding_token_depth
+ corresponding_token_depth
+ else
+ nil
+ end
+ end
+ end
end
- @rests[i]
end
- def buf_input
- prompt
- line = @input.call
- return nil unless line
- @rests.concat line.split(//)
- true
- end
- private :buf_input
-
- def set_prompt(p = nil, &block)
- p = block if block_given?
- if p.respond_to?(:call)
- @prompt = p
- else
- @prompt = Proc.new{print p}
- end
+ def check_state(code, tokens = nil, context: nil)
+ tokens = self.class.ripper_lex_without_warning(code, context: context) unless tokens
+ ltype = process_literal_type(tokens)
+ indent = process_nesting_level(tokens)
+ continue = process_continue(tokens)
+ code_block_open = check_code_block(code, tokens)
+ [ltype, indent, continue, code_block_open]
end
def prompt
@@ -210,940 +237,625 @@ class RubyLex
def initialize_input
@ltype = nil
- @quoted = nil
@indent = 0
- @indent_stack = []
- @lex_state = EXPR_BEG
- @space_seen = false
- @here_header = false
-
@continue = false
- prompt
-
@line = ""
@exp_line_no = @line_no
+ @code_block_open = false
end
-
+
def each_top_level_statement
initialize_input
catch(:TERM_INPUT) do
loop do
- begin
- @continue = false
- prompt
- unless l = lex
- throw :TERM_INPUT if @line == ''
- else
- #p l
- @line.concat l
- if @ltype or @continue or @indent > 0
- next
- end
- end
- if @line != "\n"
- yield @line, @exp_line_no
- end
- break unless l
- @line = ''
- @exp_line_no = @line_no
-
- @indent = 0
- @indent_stack = []
- prompt
- rescue TerminateLineInput
- initialize_input
- prompt
- get_readed
- end
+ begin
+ prompt
+ unless l = lex
+ throw :TERM_INPUT if @line == ''
+ else
+ @line_no += l.count("\n")
+ if l == "\n"
+ @exp_line_no += 1
+ next
+ end
+ @line.concat l
+ if @code_block_open or @ltype or @continue or @indent > 0
+ next
+ end
+ end
+ if @line != "\n"
+ @line.force_encoding(@io.encoding)
+ yield @line, @exp_line_no
+ end
+ raise TerminateLineInput if @io.eof?
+ @line = ''
+ @exp_line_no = @line_no
+
+ @indent = 0
+ rescue TerminateLineInput
+ initialize_input
+ prompt
+ end
end
end
end
def lex
- until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) &&
- !@continue or
- tk.nil?)
- #p tk
- #p @lex_state
- #p self
- end
- line = get_readed
- # print self.inspect
- if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil?
- nil
- else
- line
- end
+ line = @input.call
+ if @io.respond_to?(:check_termination)
+ return line # multiline
+ end
+ code = @line + (line.nil? ? '' : line)
+ code.gsub!(/\s*\z/, '').concat("\n")
+ @tokens = self.class.ripper_lex_without_warning(code)
+ @continue = process_continue
+ @code_block_open = check_code_block(code)
+ @indent = process_nesting_level
+ @ltype = process_literal_type
+ line
end
- def token
- # require "tracer"
- # Tracer.on
- @prev_seek = @seek
- @prev_line_no = @line_no
- @prev_char_no = @char_no
- begin
- begin
- tk = @OP.match(self)
- @space_seen = tk.kind_of?(TkSPACE)
- rescue SyntaxError
- raise if @exception_on_syntax_error
- tk = TkError.new(@seek, @line_no, @char_no)
- end
- end while @skip_space and tk.kind_of?(TkSPACE)
- if @readed_auto_clean_up
- get_readed
- end
- # Tracer.off
- tk
+ def process_continue(tokens = @tokens)
+ # last token is always newline
+ if tokens.size >= 2 and tokens[-2].event == :on_regexp_end
+ # end of regexp literal
+ return false
+ elsif tokens.size >= 2 and tokens[-2].event == :on_semicolon
+ return false
+ elsif tokens.size >= 2 and tokens[-2].event == :on_kw and ['begin', 'else', 'ensure'].include?(tokens[-2].tok)
+ return false
+ elsif !tokens.empty? and tokens.last.tok == "\\\n"
+ return true
+ elsif tokens.size >= 1 and tokens[-1].event == :on_heredoc_end # "EOH\n"
+ return false
+ elsif tokens.size >= 2 and defined?(Ripper::EXPR_BEG) and tokens[-2].state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_FNAME) and tokens[-2].tok !~ /\A\.\.\.?\z/
+ # end of literal except for regexp
+ # endless range at end of line is not a continue
+ return true
+ end
+ false
end
-
- ENINDENT_CLAUSE = [
- "case", "class", "def", "do", "for", "if",
- "module", "unless", "until", "while", "begin" #, "when"
- ]
- DEINDENT_CLAUSE = ["end" #, "when"
- ]
-
- PERCENT_LTYPE = {
- "q" => "\'",
- "Q" => "\"",
- "x" => "\`",
- "r" => "/",
- "w" => "]",
- "W" => "]",
- "s" => ":"
- }
-
- PERCENT_PAREN = {
- "{" => "}",
- "[" => "]",
- "<" => ">",
- "(" => ")"
- }
-
- Ltype2Token = {
- "\'" => TkSTRING,
- "\"" => TkSTRING,
- "\`" => TkXSTRING,
- "/" => TkREGEXP,
- "]" => TkDSTRING,
- ":" => TkSYMBOL
- }
- DLtype2Token = {
- "\"" => TkDSTRING,
- "\`" => TkDXSTRING,
- "/" => TkDREGEXP,
- }
-
- def lex_init()
- @OP = IRB::SLex.new
- @OP.def_rules("\0", "\004", "\032") do |op, io|
- Token(TkEND_OF_SCRIPT)
- end
-
- @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io|
- @space_seen = true
- while getc =~ /[ \t\f\r\13]/; end
- ungetc
- Token(TkSPACE)
- end
-
- @OP.def_rule("#") do |op, io|
- identify_comment
- end
-
- @OP.def_rule("=begin",
- proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do
- |op, io|
- @ltype = "="
- until getc == "\n"; end
- until peek_equal?("=end") && peek(4) =~ /\s/
- until getc == "\n"; end
- end
- gets
- @ltype = nil
- Token(TkRD_COMMENT)
- end
- @OP.def_rule("\n") do |op, io|
- print "\\n\n" if RubyLex.debug?
- case @lex_state
- when EXPR_BEG, EXPR_FNAME, EXPR_DOT
- @continue = true
+ def check_code_block(code, tokens = @tokens)
+ return true if tokens.empty?
+ if tokens.last.event == :on_heredoc_beg
+ return true
+ end
+
+ begin # check if parser error are available
+ verbose, $VERBOSE = $VERBOSE, nil
+ case RUBY_ENGINE
+ when 'ruby'
+ self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
+ RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no)
+ end
+ when 'jruby'
+ JRuby.compile_ir(code)
else
- @continue = false
- @lex_state = EXPR_BEG
- until (@indent_stack.empty? ||
- [TkLPAREN, TkLBRACK, TkLBRACE,
- TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
- @indent_stack.pop
- end
- end
- @here_header = false
- @here_readed = []
- Token(TkNL)
- end
-
- @OP.def_rules("*", "**",
- "=", "==", "===",
- "=~", "<=>",
- "<", "<=",
- ">", ">=", ">>") do
- |op, io|
- case @lex_state
- when EXPR_FNAME, EXPR_DOT
- @lex_state = EXPR_ARG
- else
- @lex_state = EXPR_BEG
- end
- Token(op)
- end
-
- @OP.def_rules("!", "!=", "!~") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op)
- end
-
- @OP.def_rules("<<") do
- |op, io|
- tk = nil
- if @lex_state != EXPR_END && @lex_state != EXPR_CLASS &&
- (@lex_state != EXPR_ARG || @space_seen)
- c = peek(0)
- if /\S/ =~ c && (/["'`]/ =~ c || /[\w_]/ =~ c || c == "-")
- tk = identify_here_document
- end
- end
- unless tk
- tk = Token(op)
- case @lex_state
- when EXPR_FNAME, EXPR_DOT
- @lex_state = EXPR_ARG
- else
- @lex_state = EXPR_BEG
- end
- end
- tk
- end
-
- @OP.def_rules("'", '"') do
- |op, io|
- identify_string(op)
- end
-
- @OP.def_rules("`") do
- |op, io|
- if @lex_state == EXPR_FNAME
- @lex_state = EXPR_END
- Token(op)
- else
- identify_string(op)
- end
- end
-
- @OP.def_rules('?') do
- |op, io|
- if @lex_state == EXPR_END
- @lex_state = EXPR_BEG
- Token(TkQUESTION)
- else
- ch = getc
- if @lex_state == EXPR_ARG && ch =~ /\s/
- ungetc
- @lex_state = EXPR_BEG;
- Token(TkQUESTION)
- else
- if (ch == '\\')
- read_escape
- end
- @lex_state = EXPR_END
- Token(TkINTEGER)
- end
- end
- end
-
- @OP.def_rules("&", "&&", "|", "||") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op)
- end
-
- @OP.def_rules("+=", "-=", "*=", "**=",
- "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do
- |op, io|
- @lex_state = EXPR_BEG
- op =~ /^(.*)=$/
- Token(TkOPASGN, $1)
- end
-
- @OP.def_rule("+@", proc{|op, io| @lex_state == EXPR_FNAME}) do
- |op, io|
- @lex_state = EXPR_ARG
- Token(op)
- end
-
- @OP.def_rule("-@", proc{|op, io| @lex_state == EXPR_FNAME}) do
- |op, io|
- @lex_state = EXPR_ARG
- Token(op)
- end
-
- @OP.def_rules("+", "-") do
- |op, io|
- catch(:RET) do
- if @lex_state == EXPR_ARG
- if @space_seen and peek(0) =~ /[0-9]/
- throw :RET, identify_number
- else
- @lex_state = EXPR_BEG
- end
- elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/
- throw :RET, identify_number
- else
- @lex_state = EXPR_BEG
- end
- Token(op)
+ catch(:valid) do
+ eval("BEGIN { throw :valid, true }\n#{code}")
+ false
+ end
+ end
+ rescue EncodingError
+ # This is for a hash with invalid encoding symbol, {"\xAE": 1}
+ rescue SyntaxError => e
+ case e.message
+ when /unterminated (?:string|regexp) meets end of file/
+ # "unterminated regexp meets end of file"
+ #
+ # example:
+ # /
+ #
+ # "unterminated string meets end of file"
+ #
+ # example:
+ # '
+ return true
+ when /syntax error, unexpected end-of-input/
+ # "syntax error, unexpected end-of-input, expecting keyword_end"
+ #
+ # example:
+ # if true
+ # hoge
+ # if false
+ # fuga
+ # end
+ return true
+ when /syntax error, unexpected keyword_end/
+ # "syntax error, unexpected keyword_end"
+ #
+ # example:
+ # if (
+ # end
+ #
+ # example:
+ # end
+ return false
+ when /syntax error, unexpected '\.'/
+ # "syntax error, unexpected '.'"
+ #
+ # example:
+ # .
+ return false
+ when /unexpected tREGEXP_BEG/
+ # "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('"
+ #
+ # example:
+ # method / f /
+ return false
end
+ ensure
+ $VERBOSE = verbose
end
- @OP.def_rule(".") do
- |op, io|
- @lex_state = EXPR_BEG
- if peek(0) =~ /[0-9]/
- ungetc
- identify_number
- else
- # for "obj.if" etc.
- @lex_state = EXPR_DOT
- Token(TkDOT)
+ if defined?(Ripper::EXPR_BEG)
+ last_lex_state = tokens.last.state
+ if last_lex_state.allbits?(Ripper::EXPR_BEG)
+ return false
+ elsif last_lex_state.allbits?(Ripper::EXPR_DOT)
+ return true
+ elsif last_lex_state.allbits?(Ripper::EXPR_CLASS)
+ return true
+ elsif last_lex_state.allbits?(Ripper::EXPR_FNAME)
+ return true
+ elsif last_lex_state.allbits?(Ripper::EXPR_VALUE)
+ return true
+ elsif last_lex_state.allbits?(Ripper::EXPR_ARG)
+ return false
end
end
- @OP.def_rules("..", "...") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op)
- end
-
- lex_int2
+ false
end
-
- def lex_int2
- @OP.def_rules("]", "}", ")") do
- |op, io|
- @lex_state = EXPR_END
- @indent -= 1
- @indent_stack.pop
- Token(op)
- end
-
- @OP.def_rule(":") do
- |op, io|
- if @lex_state == EXPR_END || peek(0) =~ /\s/
- @lex_state = EXPR_BEG
- Token(TkCOLON)
- else
- @lex_state = EXPR_FNAME;
- Token(TkSYMBEG)
- end
- end
-
- @OP.def_rule("::") do
- |op, io|
-# p @lex_state.id2name, @space_seen
- if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen
- @lex_state = EXPR_BEG
- Token(TkCOLON3)
- else
- @lex_state = EXPR_DOT
- Token(TkCOLON2)
- end
- end
-
- @OP.def_rule("/") do
- |op, io|
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- identify_string(op)
- elsif peek(0) == '='
- getc
- @lex_state = EXPR_BEG
- Token(TkOPASGN, "/") #/)
- elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
- identify_string(op)
- else
- @lex_state = EXPR_BEG
- Token("/") #/)
- end
- end
-
- @OP.def_rules("^") do
- |op, io|
- @lex_state = EXPR_BEG
- Token("^")
- end
-
- # @OP.def_rules("^=") do
- # @lex_state = EXPR_BEG
- # Token(OP_ASGN, :^)
- # end
-
- @OP.def_rules(",") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op)
- end
-
- @OP.def_rules(";") do
- |op, io|
- @lex_state = EXPR_BEG
- until (@indent_stack.empty? ||
- [TkLPAREN, TkLBRACK, TkLBRACE,
- TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
- @indent_stack.pop
- end
- Token(op)
- end
-
- @OP.def_rule("~") do
- |op, io|
- @lex_state = EXPR_BEG
- Token("~")
- end
- @OP.def_rule("~@", proc{|op, io| @lex_state == EXPR_FNAME}) do
- |op, io|
- @lex_state = EXPR_BEG
- Token("~")
- end
-
- @OP.def_rule("(") do
- |op, io|
- @indent += 1
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- @lex_state = EXPR_BEG
- tk_c = TkfLPAREN
+ def process_nesting_level(tokens = @tokens)
+ indent = 0
+ in_oneliner_def = nil
+ tokens.each_with_index { |t, index|
+ # detecting one-liner method definition
+ if in_oneliner_def.nil?
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ in_oneliner_def = :ENDFN
+ end
else
- @lex_state = EXPR_BEG
- tk_c = TkLPAREN
- end
- @indent_stack.push tk_c
- tk = Token(tk_c)
- end
-
- @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do
- |op, io|
- @lex_state = EXPR_ARG
- Token("[]")
- end
-
- @OP.def_rule("[]=", proc{|op, io| @lex_state == EXPR_FNAME}) do
- |op, io|
- @lex_state = EXPR_ARG
- Token("[]=")
- end
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ # continuing
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
+ in_oneliner_def = :BODY
+ end
+ else
+ if in_oneliner_def == :BODY
+ # one-liner method definition
+ indent -= 1
+ end
+ in_oneliner_def = nil
+ end
+ end
+
+ case t.event
+ when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
+ indent += 1
+ when :on_rbracket, :on_rbrace, :on_rparen
+ indent -= 1
+ when :on_kw
+ next if index > 0 and tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
+ when 'do'
+ syntax_of_do = take_corresponding_syntax_to_kw_do(tokens, index)
+ indent += 1 if syntax_of_do == :method_calling
+ when 'def', 'case', 'for', 'begin', 'class', 'module'
+ indent += 1
+ when 'if', 'unless', 'while', 'until'
+ # postfix if/unless/while/until must be Ripper::EXPR_LABEL
+ indent += 1 unless t.state.allbits?(Ripper::EXPR_LABEL)
+ when 'end'
+ indent -= 1
+ end
+ end
+ # percent literals are not indented
+ }
+ indent
+ end
- @OP.def_rule("[") do
- |op, io|
- @indent += 1
- if @lex_state == EXPR_FNAME
- tk_c = TkfLBRACK
- else
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- tk_c = TkLBRACK
- elsif @lex_state == EXPR_ARG && @space_seen
- tk_c = TkLBRACK
- else
- tk_c = TkfLBRACK
- end
- @lex_state = EXPR_BEG
- end
- @indent_stack.push tk_c
- Token(tk_c)
- end
+ def is_method_calling?(tokens, index)
+ tk = tokens[index]
+ if tk.state.anybits?(Ripper::EXPR_CMDARG) and tk.event == :on_ident
+ # The target method call to pass the block with "do".
+ return true
+ elsif tk.state.anybits?(Ripper::EXPR_ARG) and tk.event == :on_ident
+ non_sp_index = tokens[0..(index - 1)].rindex{ |t| t.event != :on_sp }
+ if non_sp_index
+ prev_tk = tokens[non_sp_index]
+ if prev_tk.state.anybits?(Ripper::EXPR_DOT) and prev_tk.event == :on_period
+ # The target method call with receiver to pass the block with "do".
+ return true
+ end
+ end
+ end
+ false
+ end
- @OP.def_rule("{") do
- |op, io|
- @indent += 1
- if @lex_state != EXPR_END && @lex_state != EXPR_ARG
- tk_c = TkLBRACE
- else
- tk_c = TkfLBRACE
- end
- @lex_state = EXPR_BEG
- @indent_stack.push tk_c
- Token(tk_c)
- end
+ def take_corresponding_syntax_to_kw_do(tokens, index)
+ syntax_of_do = nil
+ # Finding a syntax corresponding to "do".
+ index.downto(0) do |i|
+ tk = tokens[i]
+ # In "continue", the token isn't the corresponding syntax to "do".
+ non_sp_index = tokens[0..(i - 1)].rindex{ |t| t.event != :on_sp }
+ first_in_fomula = false
+ if non_sp_index.nil?
+ first_in_fomula = true
+ elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index].event)
+ first_in_fomula = true
+ end
+ if is_method_calling?(tokens, i)
+ syntax_of_do = :method_calling
+ break if first_in_fomula
+ elsif tk.event == :on_kw && %w{while until for}.include?(tk.tok)
+ # A loop syntax in front of "do" found.
+ #
+ # while cond do # also "until" or "for"
+ # end
+ #
+ # This "do" doesn't increment indent because the loop syntax already
+ # incremented.
+ syntax_of_do = :loop_syntax
+ break if first_in_fomula
+ end
+ end
+ syntax_of_do
+ end
- @OP.def_rule('\\') do
- |op, io|
- if getc == "\n"
- @space_seen = true
- @continue = true
- Token(TkSPACE)
- else
- ungetc
- Token("\\")
- end
- end
+ def is_the_in_correspond_to_a_for(tokens, index)
+ syntax_of_in = nil
+ # Finding a syntax corresponding to "do".
+ index.downto(0) do |i|
+ tk = tokens[i]
+ # In "continue", the token isn't the corresponding syntax to "do".
+ non_sp_index = tokens[0..(i - 1)].rindex{ |t| t.event != :on_sp }
+ first_in_fomula = false
+ if non_sp_index.nil?
+ first_in_fomula = true
+ elsif [:on_ignored_nl, :on_nl, :on_comment].include?(tokens[non_sp_index].event)
+ first_in_fomula = true
+ end
+ if tk.event == :on_kw && tk.tok == 'for'
+ # A loop syntax in front of "do" found.
+ #
+ # while cond do # also "until" or "for"
+ # end
+ #
+ # This "do" doesn't increment indent because the loop syntax already
+ # incremented.
+ syntax_of_in = :for
+ end
+ break if first_in_fomula
+ end
+ syntax_of_in
+ end
- @OP.def_rule('%') do
- |op, io|
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- identify_quotation
- elsif peek(0) == '='
- getc
- Token(TkOPASGN, :%)
- elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
- identify_quotation
+ def check_newline_depth_difference
+ depth_difference = 0
+ open_brace_on_line = 0
+ in_oneliner_def = nil
+ @tokens.each_with_index do |t, index|
+ # detecting one-liner method definition
+ if in_oneliner_def.nil?
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ in_oneliner_def = :ENDFN
+ end
else
- @lex_state = EXPR_BEG
- Token("%") #))
- end
- end
-
- @OP.def_rule('$') do
- |op, io|
- identify_gvar
- end
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ # continuing
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
+ in_oneliner_def = :BODY
+ end
+ else
+ if in_oneliner_def == :BODY
+ # one-liner method definition
+ depth_difference -= 1
+ end
+ in_oneliner_def = nil
+ end
+ end
+
+ case t.event
+ when :on_ignored_nl, :on_nl, :on_comment
+ if index != (@tokens.size - 1) and in_oneliner_def != :BODY
+ depth_difference = 0
+ open_brace_on_line = 0
+ end
+ next
+ when :on_sp
+ next
+ end
+
+ case t.event
+ when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
+ depth_difference += 1
+ open_brace_on_line += 1
+ when :on_rbracket, :on_rbrace, :on_rparen
+ depth_difference -= 1 if open_brace_on_line > 0
+ when :on_kw
+ next if index > 0 and @tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
+ when 'do'
+ syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
+ depth_difference += 1 if syntax_of_do == :method_calling
+ when 'def', 'case', 'for', 'begin', 'class', 'module'
+ depth_difference += 1
+ when 'if', 'unless', 'while', 'until', 'rescue'
+ # postfix if/unless/while/until/rescue must be Ripper::EXPR_LABEL
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
+ depth_difference += 1
+ end
+ when 'else', 'elsif', 'ensure', 'when'
+ depth_difference += 1
+ when 'in'
+ unless is_the_in_correspond_to_a_for(@tokens, index)
+ depth_difference += 1
+ end
+ when 'end'
+ depth_difference -= 1
+ end
+ end
+ end
+ depth_difference
+ end
- @OP.def_rule('@') do
- |op, io|
- if peek(0) =~ /[\w_@]/
- ungetc
- identify_identifier
+ def check_corresponding_token_depth(lines, line_index)
+ corresponding_token_depth = nil
+ is_first_spaces_of_line = true
+ is_first_printable_of_line = true
+ spaces_of_nest = []
+ spaces_at_line_head = 0
+ open_brace_on_line = 0
+ in_oneliner_def = nil
+
+ if heredoc_scope?
+ return lines[line_index][/^ */].length
+ end
+
+ @tokens.each_with_index do |t, index|
+ # detecting one-liner method definition
+ if in_oneliner_def.nil?
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ in_oneliner_def = :ENDFN
+ end
else
- Token("@")
- end
- end
-
- # @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do
- # |op, io|
- # @indent += 1
- # @lex_state = EXPR_FNAME
- # # @lex_state = EXPR_END
- # # until @rests[0] == "\n" or @rests[0] == ";"
- # # rests.shift
- # # end
- # end
-
- @OP.def_rule("") do
- |op, io|
- printf "MATCH: start %s: %s\n", op, io.inspect if RubyLex.debug?
- if peek(0) =~ /[0-9]/
- t = identify_number
- elsif peek(0) =~ /[\w_]/
- t = identify_identifier
- end
- printf "MATCH: end %s: %s\n", op, io.inspect if RubyLex.debug?
- t
- end
-
- p @OP if RubyLex.debug?
- end
-
- def identify_gvar
- @lex_state = EXPR_END
-
- case ch = getc
- when /[~_*$?!@\/\\;,=:<>".]/ #"
- Token(TkGVAR, "$" + ch)
- when "-"
- Token(TkGVAR, "$-" + getc)
- when "&", "`", "'", "+"
- Token(TkBACK_REF, "$"+ch)
- when /[1-9]/
- while getc =~ /[0-9]/; end
- ungetc
- Token(TkNTH_REF)
- when /\w/
- ungetc
- ungetc
- identify_identifier
- else
- ungetc
- Token("$")
- end
+ if t.state.allbits?(Ripper::EXPR_ENDFN)
+ # continuing
+ elsif t.state.allbits?(Ripper::EXPR_BEG)
+ if t.tok == '='
+ in_oneliner_def = :BODY
+ end
+ else
+ if in_oneliner_def == :BODY
+ # one-liner method definition
+ if is_first_printable_of_line
+ corresponding_token_depth = spaces_of_nest.pop
+ else
+ spaces_of_nest.pop
+ corresponding_token_depth = nil
+ end
+ end
+ in_oneliner_def = nil
+ end
+ end
+
+ case t.event
+ when :on_ignored_nl, :on_nl, :on_comment
+ if in_oneliner_def != :BODY
+ corresponding_token_depth = nil
+ spaces_at_line_head = 0
+ is_first_spaces_of_line = true
+ is_first_printable_of_line = true
+ open_brace_on_line = 0
+ end
+ next
+ when :on_sp
+ spaces_at_line_head = t.tok.count(' ') if is_first_spaces_of_line
+ is_first_spaces_of_line = false
+ next
+ end
+
+ case t.event
+ when :on_lbracket, :on_lbrace, :on_lparen, :on_tlambeg
+ spaces_of_nest.push(spaces_at_line_head + open_brace_on_line * 2)
+ open_brace_on_line += 1
+ when :on_rbracket, :on_rbrace, :on_rparen
+ if is_first_printable_of_line
+ corresponding_token_depth = spaces_of_nest.pop
+ else
+ spaces_of_nest.pop
+ corresponding_token_depth = nil
+ end
+ open_brace_on_line -= 1
+ when :on_kw
+ next if index > 0 and @tokens[index - 1].state.allbits?(Ripper::EXPR_FNAME)
+ case t.tok
+ when 'do'
+ syntax_of_do = take_corresponding_syntax_to_kw_do(@tokens, index)
+ if syntax_of_do == :method_calling
+ spaces_of_nest.push(spaces_at_line_head)
+ end
+ when 'def', 'case', 'for', 'begin', 'class', 'module'
+ spaces_of_nest.push(spaces_at_line_head)
+ when 'rescue'
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
+ corresponding_token_depth = spaces_of_nest.last
+ end
+ when 'if', 'unless', 'while', 'until'
+ # postfix if/unless/while/until must be Ripper::EXPR_LABEL
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
+ spaces_of_nest.push(spaces_at_line_head)
+ end
+ when 'else', 'elsif', 'ensure', 'when'
+ corresponding_token_depth = spaces_of_nest.last
+ when 'in'
+ if in_keyword_case_scope?
+ corresponding_token_depth = spaces_of_nest.last
+ end
+ when 'end'
+ if is_first_printable_of_line
+ corresponding_token_depth = spaces_of_nest.pop
+ else
+ spaces_of_nest.pop
+ corresponding_token_depth = nil
+ end
+ end
+ end
+ is_first_spaces_of_line = false
+ is_first_printable_of_line = false
+ end
+ corresponding_token_depth
end
-
- def identify_identifier
- token = ""
- if peek(0) =~ /[$@]/
- token.concat(c = getc)
- if c == "@" and peek(0) == "@"
- token.concat getc
- end
- end
-
- while (ch = getc) =~ /\w|_/
- print ":", ch, ":" if RubyLex.debug?
- token.concat ch
- end
- ungetc
-
- if (ch == "!" || ch == "?") && token[0,1] =~ /\w/ && peek(0) != "="
- token.concat getc
- end
-
- # almost fix token
-
- case token
- when /^\$/
- return Token(TkGVAR, token)
- when /^\@\@/
- @lex_state = EXPR_END
- # p Token(TkCVAR, token)
- return Token(TkCVAR, token)
- when /^\@/
- @lex_state = EXPR_END
- return Token(TkIVAR, token)
- end
-
- if @lex_state != EXPR_DOT
- print token, "\n" if RubyLex.debug?
-
- token_c, *trans = TkReading2Token[token]
- if token_c
- # reserved word?
-
- if (@lex_state != EXPR_BEG &&
- @lex_state != EXPR_FNAME &&
- trans[1])
- # modifiers
- token_c = TkSymbol2Token[trans[1]]
- @lex_state = trans[0]
- else
- if @lex_state != EXPR_FNAME
- if ENINDENT_CLAUSE.include?(token)
- # check for ``class = val'' etc.
- valid = true
- case token
- when "class"
- valid = false unless peek_match?(/^\s*(<<|\w|::)/)
- when "def"
- valid = false if peek_match?(/^\s*(([+-\/*&\|^]|<<|>>|\|\||\&\&)=|\&\&|\|\|)/)
- when "do"
- valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&)/)
- when *ENINDENT_CLAUSE
- valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&|\|)/)
- else
- # no nothing
- end
- if valid
- if token == "do"
- if ![TkFOR, TkWHILE, TkUNTIL].include?(@indent_stack.last)
- @indent += 1
- @indent_stack.push token_c
- end
- else
- @indent += 1
- @indent_stack.push token_c
- end
-# p @indent_stack
- end
-
- elsif DEINDENT_CLAUSE.include?(token)
- @indent -= 1
- @indent_stack.pop
- end
- @lex_state = trans[0]
- else
- @lex_state = EXPR_END
- end
- end
- return Token(token_c, token)
- end
- end
-
- if @lex_state == EXPR_FNAME
- @lex_state = EXPR_END
- if peek(0) == '='
- token.concat getc
- end
- elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT
- @lex_state = EXPR_ARG
- else
- @lex_state = EXPR_END
- end
- if token[0, 1] =~ /[A-Z]/
- return Token(TkCONSTANT, token)
- elsif token[token.size - 1, 1] =~ /[!?]/
- return Token(TkFID, token)
- else
- return Token(TkIDENTIFIER, token)
- end
+ def check_string_literal(tokens)
+ i = 0
+ start_token = []
+ end_type = []
+ while i < tokens.size
+ t = tokens[i]
+ case t.event
+ when *end_type.last
+ start_token.pop
+ end_type.pop
+ when :on_tstring_beg
+ start_token << t
+ end_type << [:on_tstring_end, :on_label_end]
+ when :on_regexp_beg
+ start_token << t
+ end_type << :on_regexp_end
+ when :on_symbeg
+ acceptable_single_tokens = %i{on_ident on_const on_op on_cvar on_ivar on_gvar on_kw on_int on_backtick}
+ if (i + 1) < tokens.size
+ if acceptable_single_tokens.all?{ |st| tokens[i + 1].event != st }
+ start_token << t
+ end_type << :on_tstring_end
+ else
+ i += 1
+ end
+ end
+ when :on_backtick
+ start_token << t
+ end_type << :on_tstring_end
+ when :on_qwords_beg, :on_words_beg, :on_qsymbols_beg, :on_symbols_beg
+ start_token << t
+ end_type << :on_tstring_end
+ when :on_heredoc_beg
+ start_token << t
+ end_type << :on_heredoc_end
+ end
+ i += 1
+ end
+ start_token.last.nil? ? nil : start_token.last
end
- def identify_here_document
- ch = getc
-# if lt = PERCENT_LTYPE[ch]
- if ch == "-"
- ch = getc
- indent = true
- end
- if /['"`]/ =~ ch
- lt = ch
- quoted = ""
- while (c = getc) && c != lt
- quoted.concat c
+ def process_literal_type(tokens = @tokens)
+ start_token = check_string_literal(tokens)
+ return nil if start_token == ""
+
+ case start_token&.event
+ when :on_tstring_beg
+ case start_token&.tok
+ when ?" then ?"
+ when /^%.$/ then ?"
+ when /^%Q.$/ then ?"
+ when ?' then ?'
+ when /^%q.$/ then ?'
+ end
+ when :on_regexp_beg then ?/
+ when :on_symbeg then ?:
+ when :on_backtick then ?`
+ when :on_qwords_beg then ?]
+ when :on_words_beg then ?]
+ when :on_qsymbols_beg then ?]
+ when :on_symbols_beg then ?]
+ when :on_heredoc_beg
+ start_token&.tok =~ /<<[-~]?(['"`])[_a-zA-Z0-9]+\1/
+ case $1
+ when ?" then ?"
+ when ?' then ?'
+ when ?` then ?`
+ else ?"
end
else
- lt = '"'
- quoted = ch.dup
- while (c = getc) && c =~ /\w/
- quoted.concat c
- end
- ungetc
- end
-
- ltback, @ltype = @ltype, lt
- reserve = []
- while ch = getc
- reserve.push ch
- if ch == "\\"
- reserve.push ch = getc
- elsif ch == "\n"
- break
- end
- end
-
- @here_header = false
- while l = gets
- l = l.sub(/(:?\r)?\n\z/, '')
- if (indent ? l.strip : l) == quoted
- break
- end
- end
-
- @here_header = true
- @here_readed.concat reserve
- while ch = reserve.pop
- ungetc ch
- end
-
- @ltype = ltback
- @lex_state = EXPR_END
- Token(Ltype2Token[lt])
- end
-
- def identify_quotation
- ch = getc
- if lt = PERCENT_LTYPE[ch]
- ch = getc
- elsif ch =~ /\W/
- lt = "\""
- else
- RubyLex.fail SyntaxError, "unknown type of %string"
+ nil
end
-# if ch !~ /\W/
-# ungetc
-# next
-# end
- #@ltype = lt
- @quoted = ch unless @quoted = PERCENT_PAREN[ch]
- identify_string(lt, @quoted)
end
- def identify_number
- @lex_state = EXPR_END
-
- if peek(0) == "0" && peek(1) !~ /[.eE]/
- getc
- case peek(0)
- when /[xX]/
- ch = getc
- match = /[0-9a-fA-F_]/
- when /[bB]/
- ch = getc
- match = /[01_]/
- when /[oO]/
- ch = getc
- match = /[0-7_]/
- when /[dD]/
- ch = getc
- match = /[0-9_]/
- when /[0-7]/
- match = /[0-7_]/
- when /[89]/
- RubyLex.fail SyntaxError, "Illegal octal digit"
- else
- return Token(TkINTEGER)
- end
-
- len0 = true
- non_digit = false
- while ch = getc
- if match =~ ch
- if ch == "_"
- if non_digit
- RubyLex.fail SyntaxError, "trailing `#{ch}' in number"
- else
- non_digit = ch
- end
- else
- non_digit = false
- len0 = false
- end
- else
- ungetc
- if len0
- RubyLex.fail SyntaxError, "numeric literal without digits"
- end
- if non_digit
- RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
- end
- break
- end
- end
- return Token(TkINTEGER)
- end
-
- type = TkINTEGER
- allow_point = true
- allow_e = true
- non_digit = false
- while ch = getc
- case ch
- when /[0-9]/
- non_digit = false
- when "_"
- non_digit = ch
- when allow_point && "."
- if non_digit
- RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
- end
- type = TkFLOAT
- if peek(0) !~ /[0-9]/
- type = TkINTEGER
- ungetc
- break
- end
- allow_point = false
- when allow_e && "e", allow_e && "E"
- if non_digit
- RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
- end
- type = TkFLOAT
- if peek(0) =~ /[+-]/
- getc
- end
- allow_e = false
- allow_point = false
- non_digit = ch
+ def check_termination_in_prev_line(code, context: nil)
+ tokens = self.class.ripper_lex_without_warning(code, context: context)
+ past_first_newline = false
+ index = tokens.rindex do |t|
+ # traverse first token before last line
+ if past_first_newline
+ if t.tok.include?("\n")
+ true
+ end
+ elsif t.tok.include?("\n")
+ past_first_newline = true
+ false
else
- if non_digit
- RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number"
- end
- ungetc
- break
+ false
end
end
- Token(type)
- end
-
- def identify_string(ltype, quoted = ltype)
- @ltype = ltype
- @quoted = quoted
- subtype = nil
- begin
- nest = 0
- while ch = getc
- if @quoted == ch and nest == 0
- break
- elsif @ltype != "'" && @ltype != "]" && @ltype != ":" and ch == "#"
- subtype = true
- elsif ch == '\\' #'
- read_escape
- end
- if PERCENT_PAREN.values.include?(@quoted)
- if PERCENT_PAREN[ch] == @quoted
- nest += 1
- elsif ch == @quoted
- nest -= 1
- end
- end
- end
- if @ltype == "/"
- if peek(0) =~ /i|m|x|o|e|s|u|n/
- getc
- end
+
+ if index
+ first_token = nil
+ last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
+ last_line_tokens.each do |t|
+ unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
+ first_token = t
+ break
+ end
end
- if subtype
- Token(DLtype2Token[ltype])
+
+ if first_token.nil?
+ return false
+ elsif first_token && first_token.state == Ripper::EXPR_DOT
+ return false
else
- Token(Ltype2Token[ltype])
- end
- ensure
- @ltype = nil
- @quoted = nil
- @lex_state = EXPR_END
- end
+ tokens_without_last_line = tokens[0..index]
+ ltype = process_literal_type(tokens_without_last_line)
+ indent = process_nesting_level(tokens_without_last_line)
+ continue = process_continue(tokens_without_last_line)
+ code_block_open = check_code_block(tokens_without_last_line.map(&:tok).join(''), tokens_without_last_line)
+ if ltype or indent > 0 or continue or code_block_open
+ return false
+ else
+ return last_line_tokens.map(&:tok).join('')
+ end
+ end
+ end
+ false
end
-
- def identify_comment
- @ltype = "#"
- while ch = getc
-# if ch == "\\" #"
-# read_escape
-# end
- if ch == "\n"
- @ltype = nil
- ungetc
- break
- end
- end
- return Token(TkCOMMENT)
- end
-
- def read_escape
- case ch = getc
- when "\n", "\r", "\f"
- when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #"
- when /[0-7]/
- ungetc ch
- 3.times do
- case ch = getc
- when /[0-7]/
- when nil
- break
- else
- ungetc
- break
- end
- end
-
- when "x"
- 2.times do
- case ch = getc
- when /[0-9a-fA-F]/
- when nil
- break
- else
- ungetc
- break
- end
- end
+ private
- when "M"
- if (ch = getc) != '-'
- ungetc
- else
- if (ch = getc) == "\\" #"
- read_escape
- end
- end
+ def heredoc_scope?
+ heredoc_tokens = @tokens.select { |t| [:on_heredoc_beg, :on_heredoc_end].include?(t.event) }
+ heredoc_tokens[-1]&.event == :on_heredoc_beg
+ end
- when "C", "c" #, "^"
- if ch == "C" and (ch = getc) != "-"
- ungetc
- elsif (ch = getc) == "\\" #"
- read_escape
+ def in_keyword_case_scope?
+ kw_tokens = @tokens.select { |t| t.event == :on_kw && ['case', 'for', 'end'].include?(t.tok) }
+ counter = 0
+ kw_tokens.reverse.each do |t|
+ if t.tok == 'case'
+ return true if counter.zero?
+ counter += 1
+ elsif t.tok == 'for'
+ counter += 1
+ elsif t.tok == 'end'
+ counter -= 1
end
- else
- # other characters
end
+ false
end
end
+# :startdoc:
diff --git a/lib/irb/ruby-token.rb b/lib/irb/ruby-token.rb
deleted file mode 100644
index 525d4df14c..0000000000
--- a/lib/irb/ruby-token.rb
+++ /dev/null
@@ -1,273 +0,0 @@
-#
-# irb/ruby-token.rb - ruby tokens
-# $Release Version: 0.9.5$
-# $Revision$
-# $Date$
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-# --
-#
-#
-#
-module RubyToken
- EXPR_BEG = :EXPR_BEG
- EXPR_MID = :EXPR_MID
- EXPR_END = :EXPR_END
- EXPR_ARG = :EXPR_ARG
- EXPR_FNAME = :EXPR_FNAME
- EXPR_DOT = :EXPR_DOT
- EXPR_CLASS = :EXPR_CLASS
-
- # for ruby 1.4X
- if !defined?(Symbol)
- Symbol = Integer
- end
-
- class Token
- def initialize(seek, line_no, char_no)
- @seek = seek
- @line_no = line_no
- @char_no = char_no
- end
- attr :seek
- attr :line_no
- attr :char_no
- end
-
- class TkNode < Token
- def initialize(seek, line_no, char_no)
- super
- end
- attr :node
- end
-
- class TkId < Token
- def initialize(seek, line_no, char_no, name)
- super(seek, line_no, char_no)
- @name = name
- end
- attr :name
- end
-
- class TkVal < Token
- def initialize(seek, line_no, char_no, value = nil)
- super(seek, line_no, char_no)
- @value = value
- end
- attr :value
- end
-
- class TkOp < Token
- attr :name, true
- end
-
- class TkOPASGN < TkOp
- def initialize(seek, line_no, char_no, op)
- super(seek, line_no, char_no)
- op = TkReading2Token[op][0] unless op.kind_of?(Symbol)
- @op = op
- end
- attr :op
- end
-
- class TkUnknownChar < Token
- def initialize(seek, line_no, char_no, id)
- super(seek, line_no, char_no)
- @name = name
- end
- attr :name
- end
-
- class TkError < Token
- end
-
- def Token(token, value = nil)
- case token
- when String
- if (tk = TkReading2Token[token]).nil?
- IRB.fail TkReading2TokenNoKey, token
- end
- tk = Token(tk[0], value)
- if tk.kind_of?(TkOp)
- tk.name = token
- end
- return tk
- when Symbol
- if (tk = TkSymbol2Token[token]).nil?
- IRB.fail TkSymbol2TokenNoKey, token
- end
- return Token(tk[0], value)
- else
- if (token.ancestors & [TkId, TkVal, TkOPASGN, TkUnknownChar]).empty?
- token.new(@prev_seek, @prev_line_no, @prev_char_no)
- else
- token.new(@prev_seek, @prev_line_no, @prev_char_no, value)
- end
- end
- end
-
- TokenDefinitions = [
- [:TkCLASS, TkId, "class", EXPR_CLASS],
- [:TkMODULE, TkId, "module", EXPR_BEG],
- [:TkDEF, TkId, "def", EXPR_FNAME],
- [:TkUNDEF, TkId, "undef", EXPR_FNAME],
- [:TkBEGIN, TkId, "begin", EXPR_BEG],
- [:TkRESCUE, TkId, "rescue", EXPR_MID],
- [:TkENSURE, TkId, "ensure", EXPR_BEG],
- [:TkEND, TkId, "end", EXPR_END],
- [:TkIF, TkId, "if", EXPR_BEG, :TkIF_MOD],
- [:TkUNLESS, TkId, "unless", EXPR_BEG, :TkUNLESS_MOD],
- [:TkTHEN, TkId, "then", EXPR_BEG],
- [:TkELSIF, TkId, "elsif", EXPR_BEG],
- [:TkELSE, TkId, "else", EXPR_BEG],
- [:TkCASE, TkId, "case", EXPR_BEG],
- [:TkWHEN, TkId, "when", EXPR_BEG],
- [:TkWHILE, TkId, "while", EXPR_BEG, :TkWHILE_MOD],
- [:TkUNTIL, TkId, "until", EXPR_BEG, :TkUNTIL_MOD],
- [:TkFOR, TkId, "for", EXPR_BEG],
- [:TkBREAK, TkId, "break", EXPR_END],
- [:TkNEXT, TkId, "next", EXPR_END],
- [:TkREDO, TkId, "redo", EXPR_END],
- [:TkRETRY, TkId, "retry", EXPR_END],
- [:TkIN, TkId, "in", EXPR_BEG],
- [:TkDO, TkId, "do", EXPR_BEG],
- [:TkRETURN, TkId, "return", EXPR_MID],
- [:TkYIELD, TkId, "yield", EXPR_END],
- [:TkSUPER, TkId, "super", EXPR_END],
- [:TkSELF, TkId, "self", EXPR_END],
- [:TkNIL, TkId, "nil", EXPR_END],
- [:TkTRUE, TkId, "true", EXPR_END],
- [:TkFALSE, TkId, "false", EXPR_END],
- [:TkAND, TkId, "and", EXPR_BEG],
- [:TkOR, TkId, "or", EXPR_BEG],
- [:TkNOT, TkId, "not", EXPR_BEG],
- [:TkIF_MOD, TkId],
- [:TkUNLESS_MOD, TkId],
- [:TkWHILE_MOD, TkId],
- [:TkUNTIL_MOD, TkId],
- [:TkALIAS, TkId, "alias", EXPR_FNAME],
- [:TkDEFINED, TkId, "defined?", EXPR_END],
- [:TklBEGIN, TkId, "BEGIN", EXPR_END],
- [:TklEND, TkId, "END", EXPR_END],
- [:Tk__LINE__, TkId, "__LINE__", EXPR_END],
- [:Tk__FILE__, TkId, "__FILE__", EXPR_END],
-
- [:TkIDENTIFIER, TkId],
- [:TkFID, TkId],
- [:TkGVAR, TkId],
- [:TkCVAR, TkId],
- [:TkIVAR, TkId],
- [:TkCONSTANT, TkId],
-
- [:TkINTEGER, TkVal],
- [:TkFLOAT, TkVal],
- [:TkSTRING, TkVal],
- [:TkXSTRING, TkVal],
- [:TkREGEXP, TkVal],
- [:TkSYMBOL, TkVal],
-
- [:TkDSTRING, TkNode],
- [:TkDXSTRING, TkNode],
- [:TkDREGEXP, TkNode],
- [:TkNTH_REF, TkNode],
- [:TkBACK_REF, TkNode],
-
- [:TkUPLUS, TkOp, "+@"],
- [:TkUMINUS, TkOp, "-@"],
- [:TkPOW, TkOp, "**"],
- [:TkCMP, TkOp, "<=>"],
- [:TkEQ, TkOp, "=="],
- [:TkEQQ, TkOp, "==="],
- [:TkNEQ, TkOp, "!="],
- [:TkGEQ, TkOp, ">="],
- [:TkLEQ, TkOp, "<="],
- [:TkANDOP, TkOp, "&&"],
- [:TkOROP, TkOp, "||"],
- [:TkMATCH, TkOp, "=~"],
- [:TkNMATCH, TkOp, "!~"],
- [:TkDOT2, TkOp, ".."],
- [:TkDOT3, TkOp, "..."],
- [:TkAREF, TkOp, "[]"],
- [:TkASET, TkOp, "[]="],
- [:TkLSHFT, TkOp, "<<"],
- [:TkRSHFT, TkOp, ">>"],
- [:TkCOLON2, TkOp],
- [:TkCOLON3, TkOp],
-# [:OPASGN, TkOp], # +=, -= etc. #
- [:TkASSOC, TkOp, "=>"],
- [:TkQUESTION, TkOp, "?"], #?
- [:TkCOLON, TkOp, ":"], #:
-
- [:TkfLPAREN], # func( #
- [:TkfLBRACK], # func[ #
- [:TkfLBRACE], # func{ #
- [:TkSTAR], # *arg
- [:TkAMPER], # &arg #
- [:TkSYMBEG], # :SYMBOL
-
- [:TkGT, TkOp, ">"],
- [:TkLT, TkOp, "<"],
- [:TkPLUS, TkOp, "+"],
- [:TkMINUS, TkOp, "-"],
- [:TkMULT, TkOp, "*"],
- [:TkDIV, TkOp, "/"],
- [:TkMOD, TkOp, "%"],
- [:TkBITOR, TkOp, "|"],
- [:TkBITXOR, TkOp, "^"],
- [:TkBITAND, TkOp, "&"],
- [:TkBITNOT, TkOp, "~"],
- [:TkNOTOP, TkOp, "!"],
-
- [:TkBACKQUOTE, TkOp, "`"],
-
- [:TkASSIGN, Token, "="],
- [:TkDOT, Token, "."],
- [:TkLPAREN, Token, "("], #(exp)
- [:TkLBRACK, Token, "["], #[arry]
- [:TkLBRACE, Token, "{"], #{hash}
- [:TkRPAREN, Token, ")"],
- [:TkRBRACK, Token, "]"],
- [:TkRBRACE, Token, "}"],
- [:TkCOMMA, Token, ","],
- [:TkSEMICOLON, Token, ";"],
-
- [:TkCOMMENT],
- [:TkRD_COMMENT],
- [:TkSPACE],
- [:TkNL],
- [:TkEND_OF_SCRIPT],
-
- [:TkBACKSLASH, TkUnknownChar, "\\"],
- [:TkAT, TkUnknownChar, "@"],
- [:TkDOLLAR, TkUnknownChar, "$"],
- ]
-
- # {reading => token_class}
- # {reading => [token_class, *opt]}
- TkReading2Token = {}
- TkSymbol2Token = {}
-
- def RubyToken.def_token(token_n, super_token = Token, reading = nil, *opts)
- token_n = token_n.id2name if token_n.kind_of?(Symbol)
- if RubyToken.const_defined?(token_n)
- IRB.fail AlreadyDefinedToken, token_n
- end
- token_c = eval("class #{token_n} < #{super_token}; end; #{token_n}")
-
- if reading
- if TkReading2Token[reading]
- IRB.fail TkReading2TokenDuplicateError, token_n, reading
- end
- if opts.empty?
- TkReading2Token[reading] = [token_c]
- else
- TkReading2Token[reading] = [token_c].concat(opts)
- end
- end
- TkSymbol2Token[token_n.intern] = token_c
- end
-
- for defs in TokenDefinitions
- def_token(*defs)
- end
-end
diff --git a/lib/irb/ruby_logo.aa b/lib/irb/ruby_logo.aa
new file mode 100644
index 0000000000..a34a3e2f28
--- /dev/null
+++ b/lib/irb/ruby_logo.aa
@@ -0,0 +1,37 @@
+
+ -+smJYYN?mm-
+ HB"BBYT TQg NggT
+ 9Q+g Nm,T 8g NJW
+ YS+ N2NJ"Sg N?
+ BQg #( gT Nggggk J
+ 5j NJ NJ NNge
+ #Q #JJ NgT N(
+ @j bj mT J
+ Bj @/d NJ (
+ #q #(( NgT #J
+ 5d #(t mT $d
+ #q @(@J NJB;
+ @( 5d ? HHH H HQmgggggggmN qD
+ 5d #uN 2QdH E O
+ 5 5JSd Nd NJH @d j
+ Fd @J4d s NQH #d (
+ #( #o6d Nd NgH #d #d
+ 4 B&Od v NgT #d F
+ #( 9JGd NH NgUd F
+ #d #GJQ d NP $
+ #J #U+#Q N Q # j
+ j /W BQ+ BQ d NJ NJ
+ - NjJH HBIjTQggPJQgW N W k #J
+ #J b HYWgggN j s Nag d NN b #d
+ #J 5- D s Ngg N d Nd F
+ Fd BKH2 #+ s NNgg J Q J ]
+ F H @ J N y K(d P I
+ F4 E N? #d y #Q NJ E j
+ F W Nd q m Bg NxW N(H-
+ F d b @ m Hd gW vKJ
+ NJ d K d s Bg aT FDd
+ b # d N m BQ mV N>
+ e5 Nd #d NggggggQWH HHHH NJ -
+ m7 NW H N HSVO1z=?11-
+ NgTH bB kH WBHWWHBHWmQgg&gggggNNN
+ NNggggggNN
diff --git a/lib/irb/slex.rb b/lib/irb/slex.rb
deleted file mode 100644
index a6ea6fb473..0000000000
--- a/lib/irb/slex.rb
+++ /dev/null
@@ -1,285 +0,0 @@
-#
-# irb/slex.rb - symple lex analizer
-# $Release Version: 0.9.5$
-# $Revision$
-# $Date$
-# by Keiju ISHITSUKA(keiju@ruby-lang.org)
-#
-# --
-#
-#
-#
-
-require "e2mmap"
-require "irb/notifier"
-
-module IRB
- class SLex
- @RCS_ID='-$Id$-'
-
- extend Exception2MessageMapper
- def_exception :ErrNodeNothing, "node nothing"
- def_exception :ErrNodeAlreadyExists, "node already exists"
-
- DOUT = Notifier::def_notifier("SLex::")
- D_WARN = DOUT::def_notifier(1, "Warn: ")
- D_DEBUG = DOUT::def_notifier(2, "Debug: ")
- D_DETAIL = DOUT::def_notifier(4, "Detail: ")
-
- DOUT.level = Notifier::D_NOMSG
-
- def initialize
- @head = Node.new("")
- end
-
- def def_rule(token, preproc = nil, postproc = nil, &block)
- D_DETAIL.pp token
-
- postproc = block if block_given?
- node = create(token, preproc, postproc)
- end
-
- def def_rules(*tokens, &block)
- if block_given?
- p = block
- end
- for token in tokens
- def_rule(token, nil, p)
- end
- end
-
- def preproc(token, proc)
- node = search(token)
- node.preproc=proc
- end
-
- #要チェック?
- def postproc(token)
- node = search(token, proc)
- node.postproc=proc
- end
-
- def search(token)
- @head.search(token.split(//))
- end
-
- def create(token, preproc = nil, postproc = nil)
- @head.create_subnode(token.split(//), preproc, postproc)
- end
-
- def match(token)
- case token
- when Array
- when String
- return match(token.split(//))
- else
- return @head.match_io(token)
- end
- ret = @head.match(token)
- D_DETAIL.exec_if{D_DEATIL.printf "match end: %s:%s\n", ret, token.inspect}
- ret
- end
-
- def inspect
- format("<SLex: @head = %s>", @head.inspect)
- end
-
- #----------------------------------------------------------------------
- #
- # class Node -
- #
- #----------------------------------------------------------------------
- class Node
- # if postproc is nil, this node is an abstract node.
- # if postproc is non-nil, this node is a real node.
- def initialize(preproc = nil, postproc = nil)
- @Tree = {}
- @preproc = preproc
- @postproc = postproc
- end
-
- attr_accessor :preproc
- attr_accessor :postproc
-
- def search(chrs, opt = nil)
- return self if chrs.empty?
- ch = chrs.shift
- if node = @Tree[ch]
- node.search(chrs, opt)
- else
- if opt
- chrs.unshift ch
- self.create_subnode(chrs)
- else
- SLex.fail ErrNodeNothing
- end
- end
- end
-
- def create_subnode(chrs, preproc = nil, postproc = nil)
- if chrs.empty?
- if @postproc
- D_DETAIL.pp node
- SLex.fail ErrNodeAlreadyExists
- else
- D_DEBUG.puts "change abstract node to real node."
- @preproc = preproc
- @postproc = postproc
- end
- return self
- end
-
- ch = chrs.shift
- if node = @Tree[ch]
- if chrs.empty?
- if node.postproc
- DebugLogger.pp node
- DebugLogger.pp self
- DebugLogger.pp ch
- DebugLogger.pp chrs
- SLex.fail ErrNodeAlreadyExists
- else
- D_WARN.puts "change abstract node to real node"
- node.preproc = preproc
- node.postproc = postproc
- end
- else
- node.create_subnode(chrs, preproc, postproc)
- end
- else
- if chrs.empty?
- node = Node.new(preproc, postproc)
- else
- node = Node.new
- node.create_subnode(chrs, preproc, postproc)
- end
- @Tree[ch] = node
- end
- node
- end
-
- #
- # chrs: String
- # character array
- # io must have getc()/ungetc(); and ungetc() must be
- # able to be called arbitrary number of times.
- #
- def match(chrs, op = "")
- D_DETAIL.print "match>: ", chrs, "op:", op, "\n"
- if chrs.empty?
- if @preproc.nil? || @preproc.call(op, chrs)
- DOUT.printf(D_DETAIL, "op1: %s\n", op)
- @postproc.call(op, chrs)
- else
- nil
- end
- else
- ch = chrs.shift
- if node = @Tree[ch]
- if ret = node.match(chrs, op+ch)
- return ret
- else
- chrs.unshift ch
- if @postproc and @preproc.nil? || @preproc.call(op, chrs)
- DOUT.printf(D_DETAIL, "op2: %s\n", op.inspect)
- ret = @postproc.call(op, chrs)
- return ret
- else
- return nil
- end
- end
- else
- chrs.unshift ch
- if @postproc and @preproc.nil? || @preproc.call(op, chrs)
- DOUT.printf(D_DETAIL, "op3: %s\n", op)
- @postproc.call(op, chrs)
- return ""
- else
- return nil
- end
- end
- end
- end
-
- def match_io(io, op = "")
- if op == ""
- ch = io.getc
- if ch == nil
- return nil
- end
- else
- ch = io.getc_of_rests
- end
- if ch.nil?
- if @preproc.nil? || @preproc.call(op, io)
- D_DETAIL.printf("op1: %s\n", op)
- @postproc.call(op, io)
- else
- nil
- end
- else
- if node = @Tree[ch]
- if ret = node.match_io(io, op+ch)
- ret
- else
- io.ungetc ch
- if @postproc and @preproc.nil? || @preproc.call(op, io)
- DOUT.exec_if{D_DETAIL.printf "op2: %s\n", op.inspect}
- @postproc.call(op, io)
- else
- nil
- end
- end
- else
- io.ungetc ch
- if @postproc and @preproc.nil? || @preproc.call(op, io)
- D_DETAIL.printf("op3: %s\n", op)
- @postproc.call(op, io)
- else
- nil
- end
- end
- end
- end
- end
- end
-end
-
-SLex=IRB::SLex
-
-if $0 == __FILE__
- # Tracer.on
- case $1
- when "1"
- tr = SLex.new
- print "0: ", tr.inspect, "\n"
- tr.def_rule("=") {print "=\n"}
- print "1: ", tr.inspect, "\n"
- tr.def_rule("==") {print "==\n"}
- print "2: ", tr.inspect, "\n"
-
- print "case 1:\n"
- print tr.match("="), "\n"
- print "case 2:\n"
- print tr.match("=="), "\n"
- print "case 3:\n"
- print tr.match("=>"), "\n"
-
- when "2"
- tr = SLex.new
- print "0: ", tr.inspect, "\n"
- tr.def_rule("=") {print "=\n"}
- print "1: ", tr.inspect, "\n"
- tr.def_rule("==", proc{false}) {print "==\n"}
- print "2: ", tr.inspect, "\n"
-
- print "case 1:\n"
- print tr.match("="), "\n"
- print "case 2:\n"
- print tr.match("=="), "\n"
- print "case 3:\n"
- print tr.match("=>"), "\n"
- end
- exit
-end
-
diff --git a/lib/irb/src_encoding.rb b/lib/irb/src_encoding.rb
new file mode 100644
index 0000000000..99aea2b43e
--- /dev/null
+++ b/lib/irb/src_encoding.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: false
+# DO NOT WRITE ANY MAGIC COMMENT HERE.
+module IRB
+ def self.default_src_encoding
+ return __ENCODING__
+ end
+end
diff --git a/lib/irb/version.rb b/lib/irb/version.rb
index 28b079740a..481d14ffd2 100644
--- a/lib/irb/version.rb
+++ b/lib/irb/version.rb
@@ -1,16 +1,17 @@
+# frozen_string_literal: false
#
# irb/version.rb - irb version definition file
-# $Release Version: 0.9.5$
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
#
# --
#
-#
+#
#
-module IRB
- @RELEASE_VERSION = "0.9.5"
- @LAST_UPDATE_DATE = "05/04/13"
+module IRB # :nodoc:
+ VERSION = "1.4.1"
+ @RELEASE_VERSION = VERSION
+ @LAST_UPDATE_DATE = "2021-12-25"
end
diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb
index 7d1794cd7b..2c4c40f348 100644
--- a/lib/irb/workspace.rb
+++ b/lib/irb/workspace.rb
@@ -1,106 +1,186 @@
+# frozen_string_literal: false
#
-# irb/workspace-binding.rb -
-# $Release Version: 0.9.5$
+# irb/workspace-binding.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
#
-module IRB
+#
+
+require "delegate"
+
+IRB::TOPLEVEL_BINDING = binding
+module IRB # :nodoc:
class WorkSpace
- # create new workspace. set self to main if specified, otherwise
+ # Creates a new workspace.
+ #
+ # set self to main if specified, otherwise
# inherit main from TOPLEVEL_BINDING.
def initialize(*main)
if main[0].kind_of?(Binding)
- @binding = main.shift
+ @binding = main.shift
elsif IRB.conf[:SINGLE_IRB]
- @binding = TOPLEVEL_BINDING
+ @binding = TOPLEVEL_BINDING
else
- case IRB.conf[:CONTEXT_MODE]
- when 0 # binding in proc on TOPLEVEL_BINDING
- @binding = eval("proc{binding}.call",
- TOPLEVEL_BINDING,
- __FILE__,
- __LINE__)
- when 1 # binding in loaded file
- require "tempfile"
- f = Tempfile.open("irb-binding")
- f.print <<EOF
- $binding = binding
+ case IRB.conf[:CONTEXT_MODE]
+ when 0 # binding in proc on TOPLEVEL_BINDING
+ @binding = eval("proc{binding}.call",
+ TOPLEVEL_BINDING,
+ __FILE__,
+ __LINE__)
+ when 1 # binding in loaded file
+ require "tempfile"
+ f = Tempfile.open("irb-binding")
+ f.print <<EOF
+ $binding = binding
EOF
- f.close
- load f.path
- @binding = $binding
-
- when 2 # binding in loaded file(thread use)
- unless defined? BINDING_QUEUE
- require "thread"
-
- IRB.const_set("BINDING_QUEUE", SizedQueue.new(1))
- Thread.abort_on_exception = true
- Thread.start do
- eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__
- end
- Thread.pass
- end
- @binding = BINDING_QUEUE.pop
-
- when 3 # binging in function on TOPLEVEL_BINDING(default)
- @binding = eval("def irb_binding; binding; end; irb_binding",
- TOPLEVEL_BINDING,
- __FILE__,
- __LINE__ - 3)
- end
+ f.close
+ load f.path
+ @binding = $binding
+
+ when 2 # binding in loaded file(thread use)
+ unless defined? BINDING_QUEUE
+ IRB.const_set(:BINDING_QUEUE, Thread::SizedQueue.new(1))
+ Thread.abort_on_exception = true
+ Thread.start do
+ eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__
+ end
+ Thread.pass
+ end
+ @binding = BINDING_QUEUE.pop
+
+ when 3 # binding in function on TOPLEVEL_BINDING
+ @binding = eval("self.class.remove_method(:irb_binding) if defined?(irb_binding); private; def irb_binding; binding; end; irb_binding",
+ TOPLEVEL_BINDING,
+ __FILE__,
+ __LINE__ - 3)
+ when 4 # binding is a copy of TOPLEVEL_BINDING (default)
+ # Note that this will typically be IRB::TOPLEVEL_BINDING
+ # This is to avoid RubyGems' local variables (see issue #17623)
+ @binding = TOPLEVEL_BINDING.dup
+ end
end
+
if main.empty?
- @main = eval("self", @binding)
+ @main = eval("self", @binding)
else
- @main = main[0]
- IRB.conf[:__MAIN__] = @main
- case @main
- when Module
- @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
- else
- begin
- @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
- rescue TypeError
- IRB.fail CantChangeBinding, @main.inspect
- end
- end
+ @main = main[0]
+ end
+ IRB.conf[:__MAIN__] = @main
+
+ unless main.empty?
+ case @main
+ when Module
+ @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
+ else
+ begin
+ @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
+ rescue TypeError
+ fail CantChangeBinding, @main.inspect
+ end
+ end
end
- eval("_=nil", @binding)
+
+ case @main
+ when Object
+ use_delegator = @main.frozen?
+ else
+ use_delegator = true
+ end
+
+ if use_delegator
+ @main = SimpleDelegator.new(@main)
+ IRB.conf[:__MAIN__] = @main
+ @main.singleton_class.class_eval do
+ private
+ define_method(:exit) do |*a, &b|
+ # Do nothing, will be overridden
+ end
+ define_method(:binding, Kernel.instance_method(:binding))
+ define_method(:local_variables, Kernel.instance_method(:local_variables))
+ end
+ @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location)
+ end
+
+ @binding.local_variable_set(:_, nil)
end
+ # The Binding of this workspace
attr_reader :binding
+ # The top-level workspace of this context, also available as
+ # <code>IRB.conf[:__MAIN__]</code>
attr_reader :main
+ # Evaluate the given +statements+ within the context of this workspace.
def evaluate(context, statements, file = __FILE__, line = __LINE__)
eval(statements, @binding, file, line)
end
-
+
+ def local_variable_set(name, value)
+ @binding.local_variable_set(name, value)
+ end
+
+ def local_variable_get(name)
+ @binding.local_variable_get(name)
+ end
+
# error message manipulator
def filter_backtrace(bt)
+ return nil if bt =~ /\/irb\/.*\.rb/
+ return nil if bt =~ /\/irb\.rb/
+ return nil if bt =~ /tool\/lib\/.*\.rb|runner\.rb/ # for tests in Ruby repository
case IRB.conf[:CONTEXT_MODE]
- when 0
- return nil if bt =~ /\(irb_local_binding\)/
when 1
- if(bt =~ %r!/tmp/irb-binding! or
- bt =~ %r!irb/.*\.rb! or
- bt =~ /irb\.rb/)
- return nil
- end
- when 2
- return nil if bt =~ /irb\/.*\.rb/
+ return nil if bt =~ %r!/tmp/irb-binding!
when 3
- return nil if bt =~ /irb\/.*\.rb/
- bt.sub!(/:\s*in `irb_binding'/){""}
+ bt = bt.sub(/:\s*in `irb_binding'/, '')
end
bt
end
+ def code_around_binding
+ if @binding.respond_to?(:source_location)
+ file, pos = @binding.source_location
+ else
+ file, pos = @binding.eval('[__FILE__, __LINE__]')
+ end
+
+ if defined?(::SCRIPT_LINES__[file]) && lines = ::SCRIPT_LINES__[file]
+ code = ::SCRIPT_LINES__[file].join('')
+ else
+ begin
+ code = File.read(file)
+ rescue SystemCallError
+ return
+ end
+ end
+
+ # NOT using #use_colorize? of IRB.conf[:MAIN_CONTEXT] because this method may be called before IRB::Irb#run
+ use_colorize = IRB.conf.fetch(:USE_COLORIZE, true)
+ if use_colorize
+ lines = Color.colorize_code(code).lines
+ else
+ lines = code.lines
+ end
+ pos -= 1
+
+ start_pos = [pos - 5, 0].max
+ end_pos = [pos + 5, lines.size - 1].min
+
+ if use_colorize
+ fmt = " %2s #{Color.colorize("%#{end_pos.to_s.length}d", [:BLUE, :BOLD])}: %s"
+ else
+ fmt = " %2s %#{end_pos.to_s.length}d: %s"
+ end
+ body = (start_pos..end_pos).map do |current_pos|
+ sprintf(fmt, pos == current_pos ? '=>' : '', current_pos + 1, lines[current_pos])
+ end.join("")
+ "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear if use_colorize}\n"
+ end
+
def IRB.delete_caller
end
end
diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb
index afd49d23e1..eb173fddca 100644
--- a/lib/irb/ws-for-case-2.rb
+++ b/lib/irb/ws-for-case-2.rb
@@ -1,15 +1,15 @@
+# frozen_string_literal: false
#
-# irb/ws-for-case-2.rb -
-# $Release Version: 0.9.5$
+# irb/ws-for-case-2.rb -
+# $Release Version: 0.9.6$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
-#
+#
#
while true
- IRB::BINDING_QUEUE.push b = binding
+ IRB::BINDING_QUEUE.push _ = binding
end
diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb
index 4bcc2ca22f..88cbd88525 100644
--- a/lib/irb/xmp.rb
+++ b/lib/irb/xmp.rb
@@ -1,25 +1,59 @@
+# frozen_string_literal: false
#
# xmp.rb - irb version of gotoken xmp
# $Release Version: 0.9$
# $Revision$
-# $Date$
# by Keiju ISHITSUKA(Nippon Rational Inc.)
#
# --
#
-#
+#
#
-require "irb"
-require "irb/frame"
+require_relative "../irb"
+require_relative "frame"
+# An example printer for irb.
+#
+# It's much like the standard library PrettyPrint, that shows the value of each
+# expression as it runs.
+#
+# In order to use this library, you must first require it:
+#
+# require 'irb/xmp'
+#
+# Now, you can take advantage of the Object#xmp convenience method.
+#
+# xmp <<END
+# foo = "bar"
+# baz = 42
+# END
+# #=> foo = "bar"
+# #==>"bar"
+# #=> baz = 42
+# #==>42
+#
+# You can also create an XMP object, with an optional binding to print
+# expressions in the given binding:
+#
+# ctx = binding
+# x = XMP.new ctx
+# x.puts
+# #=> today = "a good day"
+# #==>"a good day"
+# ctx.eval 'today # is what?'
+# #=> "a good day"
class XMP
- @RCS_ID='-$Id$-'
+ # Creates a new XMP object.
+ #
+ # The top-level binding or, optional +bind+ parameter will be used when
+ # creating the workspace. See WorkSpace.new for more information.
+ #
+ # This uses the +:XMP+ prompt mode, see IRB@Customizing+the+IRB+Prompt for
+ # full detail.
def initialize(bind = nil)
IRB.init_config(nil)
- #IRB.parse_opts
- #IRB.load_modules
IRB.conf[:PROMPT_MODE] = :XMP
@@ -29,55 +63,105 @@ class XMP
@irb = IRB::Irb.new(ws, @io)
@irb.context.ignore_sigint = false
-# IRB.conf[:IRB_RC].call(@irb.context) if IRB.conf[:IRB_RC]
IRB.conf[:MAIN_CONTEXT] = @irb.context
end
+ # Evaluates the given +exps+, for example:
+ #
+ # require 'irb/xmp'
+ # x = XMP.new
+ #
+ # x.puts '{:a => 1, :b => 2, :c => 3}'
+ # #=> {:a => 1, :b => 2, :c => 3}
+ # # ==>{:a=>1, :b=>2, :c=>3}
+ # x.puts 'foo = "bar"'
+ # # => foo = "bar"
+ # # ==>"bar"
def puts(exps)
@io.puts exps
if @irb.context.ignore_sigint
begin
- trap_proc_b = trap("SIGINT"){@irb.signal_handle}
- catch(:IRB_EXIT) do
- @irb.eval_input
- end
+ trap_proc_b = trap("SIGINT"){@irb.signal_handle}
+ catch(:IRB_EXIT) do
+ @irb.eval_input
+ end
ensure
- trap("SIGINT", trap_proc_b)
+ trap("SIGINT", trap_proc_b)
end
else
catch(:IRB_EXIT) do
- @irb.eval_input
+ @irb.eval_input
end
end
end
+ # A custom InputMethod class used by XMP for evaluating string io.
class StringInputMethod < IRB::InputMethod
+ # Creates a new StringInputMethod object
def initialize
super
@exps = []
end
+ # Whether there are any expressions left in this printer.
def eof?
@exps.empty?
end
+ # Reads the next expression from this printer.
+ #
+ # See IO#gets for more information.
def gets
while l = @exps.shift
- next if /^\s+$/ =~ l
- l.concat "\n"
- print @prompt, l
- break
+ next if /^\s+$/ =~ l
+ l.concat "\n"
+ print @prompt, l
+ break
end
l
end
+ # Concatenates all expressions in this printer, separated by newlines.
+ #
+ # An Encoding::CompatibilityError is raised of the given +exps+'s encoding
+ # doesn't match the previous expression evaluated.
def puts(exps)
+ if @encoding and exps.encoding != @encoding
+ enc = Encoding.compatible?(@exps.join("\n"), exps)
+ if enc.nil?
+ raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one"
+ else
+ @encoding = enc
+ end
+ else
+ @encoding = exps.encoding
+ end
@exps.concat exps.split(/\n/)
end
+
+ # Returns the encoding of last expression printed by #puts.
+ attr_reader :encoding
end
end
+# A convenience method that's only available when the you require the IRB::XMP standard library.
+#
+# Creates a new XMP object, using the given expressions as the +exps+
+# parameter, and optional binding as +bind+ or uses the top-level binding. Then
+# evaluates the given expressions using the +:XMP+ prompt mode.
+#
+# For example:
+#
+# require 'irb/xmp'
+# ctx = binding
+# xmp 'foo = "bar"', ctx
+# #=> foo = "bar"
+# #==>"bar"
+# ctx.eval 'foo'
+# #=> "bar"
+#
+# See XMP.new for more information.
def xmp(exps, bind = nil)
bind = IRB::Frame.top(1) unless bind
xmp = XMP.new(bind)