1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/irb/main.rb

868 lines
18 KiB
Ruby
Raw Normal View History

#
# main.rb - irb main module
# $Release Version: 0.6 $
# $Revision$
# $Date$
# by Keiju ISHITSUKA(Nippon Rational Inc.)
#
# --
#
#
#
require "e2mmap"
require "irb/ruby-lex"
require "irb/input-method"
require "irb/workspace-binding"
STDOUT.sync = true
module IRB
@RCS_ID='-$Id$-'
# exceptions
extend Exception2MessageMapper
def_exception :UnrecognizedSwitch, "Unrecognized switch: %s"
def_exception :NotImplementError, "Need to define `%s'"
def_exception :CantRetuenNormalMode, "Can't return normal mode."
def_exception :IllegalParameter, "Illegal parameter(%s)."
def_exception :IrbAlreadyDead, "Irb is already dead."
def_exception :IrbSwitchToCurrentThread, "Change to current thread."
def_exception :NoSuchJob, "No such job(%s)."
def_exception :CanNotGoMultiIrbMode, "Can't go multi irb mode."
def_exception :CanNotChangeBinding, "Can't change binding to (%s)."
def_exception :UndefinedPromptMode, "Undefined prompt mode(%s)."
class Abort < Exception;end
# initialize IRB and start TOP_LEVEL irb
def IRB.start(ap_path = nil)
$0 = File::basename(ap_path, ".rb") if ap_path
IRB.initialize(ap_path)
IRB.parse_opts
IRB.load_modules
bind = workspace_binding
main = eval("self", bind)
if @CONF[:SCRIPT]
irb = Irb.new(main, bind, @CONF[:SCRIPT])
else
irb = Irb.new(main, bind)
end
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
@CONF[:MAIN_CONTEXT] = irb.context
trap("SIGINT") do
irb.signal_handle
end
catch(:IRB_EXIT) do
irb.eval_input
end
print "\n"
end
# initialize config
def IRB.initialize(ap_path)
IRB.init_config(ap_path)
IRB.run_config
end
#
# @CONF functions
#
@CONF = {}
# @CONF default setting
def IRB.init_config(ap_path)
# class instance variables
@TRACER_INITIALIZED = false
@MATHN_INITIALIZED = false
# default configurations
unless ap_path and @CONF[:AP_NAME]
ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb")
end
@CONF[:AP_NAME] = File::basename(ap_path, ".rb")
@CONF[:IRB_NAME] = "irb"
@CONF[:IRB_LIB_PATH] = File.dirname(__FILE__)
@CONF[:RC] = true
@CONF[:LOAD_MODULES] = []
@CONF[:IRB_RC] = nil
@CONF[:MATH_MODE] = false
@CONF[:USE_READLINE] = false unless defined?(ReadlineInputMethod)
@CONF[:INSPECT_MODE] = nil
@CONF[:USE_TRACER] = false
@CONF[:USE_LOADER] = false
@CONF[:IGNORE_SIGINT] = true
@CONF[:IGNORE_EOF] = false
@CONF[:BACK_TRACE_LIMIT] = 16
@CONF[:PROMPT] = {
:NULL => {
:PROMPT_I => nil,
:PROMPT_S => nil,
:PROMPT_C => nil,
:RETURN => "%s\n"
},
:DEFAULT => {
:PROMPT_I => "%N(%m):%03n:%i> ",
:PROMPT_S => "%N(%m):%03n:%i%l ",
:PROMPT_C => "%N(%m):%03n:%i* ",
:RETURN => "%s\n"
},
:SIMPLE => {
:PROMPT_I => ">> ",
:PROMPT_S => nil,
:PROMPT_C => "?> ",
:RETURN => "=> %s\n"
},
:INF_RUBY => {
:PROMPT_I => "%N(%m):%03n:%i> ",
:PROMPT_S => nil,
:PROMPT_C => nil,
:RETURN => "%s\n",
:AUTO_INDENT => true
},
:XMP => {
:PROMPT_I => nil,
:PROMPT_S => nil,
:PROMPT_C => nil,
:RETURN => " ==>%s\n"
}
}
@CONF[:PROMPT_MODE] = :DEFAULT
@CONF[:AUTO_INDENT] = false
@CONF[:CONTEXT_MODE] = 3
@CONF[:SINGLE_IRB] = false
@CONF[:DEBUG_LEVEL] = 1
@CONF[:VERBOSE] = true
end
# IRB version method
def IRB.version
if v = @CONF[:VERSION] then return v end
require "irb/version"
rv = @RELEASE_VERSION.sub(/\.0/, "")
@CONF[:VERSION] = format("irb %s(%s)", rv, @LAST_UPDATE_DATE)
end
def IRB.conf
@CONF
end
# option analyzing
def IRB.parse_opts
while opt = ARGV.shift
case opt
when "-f"
opt = ARGV.shift
@CONF[:RC] = false
when "-m"
@CONF[:MATH_MODE] = true
when "-d"
$DEBUG = true
when "-r"
opt = ARGV.shift
@CONF[:LOAD_MODULES].push opt if opt
when "--inspect"
@CONF[:INSPECT_MODE] = true
when "--noinspect"
@CONF[:INSPECT_MODE] = false
when "--readline"
@CONF[:USE_READLINE] = true
when "--noreadline"
@CONF[:USE_READLINE] = false
when "--prompt-mode", "--prompt"
prompt_mode = ARGV.shift.upcase.tr("-", "_").intern
IRB.fail(UndefinedPromptMode,
prompt_mode.id2name) unless @CONF[:PROMPT][prompt_mode]
@CONF[:PROMPT_MODE] = prompt_mode
when "--noprompt"
@CONF[:PROMPT_MODE] = :NULL
when "--inf-ruby-mode"
@CONF[:PROMPT_MODE] = :INF_RUBY
when "--sample-book-mode", "--simple-prompt"
@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
when "--single-irb"
@CONF[:SINGLE_IRB] = true
when "--irb_debug"
@CONF[:DEBUG_LEVEL] = ARGV.shift.to_i
when "-v", "--version"
print IRB.version, "\n"
exit(0)
when /^-/
IRB.fail UnrecognizedSwitch, opt
else
@CONF[:USE_READLINE] = false
@CONF[:SCRIPT] = opt
$0 = opt
break
end
end
end
# running config
def IRB.run_config
if @CONF[:RC]
rcs = []
rcs.push File.expand_path("~/.irbrc") if ENV.key?("HOME")
rcs.push ".irbrc"
rcs.push "irb.rc"
rcs.push "_irbrc"
rcs.push "$irbrc"
catch(:EXIT) do
for rc in rcs
begin
load rc
throw :EXIT
rescue LoadError, Errno::ENOENT
rescue
print "load error: #{rc}\n"
print $!.type, ": ", $!, "\n"
for err in $@[0, $@.size - 2]
print "\t", err, "\n"
end
throw :EXIT
end
end
end
end
end
# loading modules
def IRB.load_modules
for m in @CONF[:LOAD_MODULES]
begin
require m
rescue
print $@[0], ":", $!.type, ": ", $!, "\n"
end
end
end
# initialize tracing function
def IRB.initialize_tracer
unless @TRACER_INITIALIZED
require("tracer")
Tracer.verbose = false
Tracer.add_filter {
|event, file, line, id, binding|
File::dirname(file) != @CONF[:IRB_LIB_PATH]
}
@TRACER_INITIALIZED = true
end
end
# initialize mathn function
def IRB.initialize_mathn
unless @MATHN_INITIALIZED
require "mathn"
@MATHN_INITIALIZED = true
end
end
# initialize loader function
def IRB.initialize_loader
unless @LOADER_INITIALIZED
require "irb/loader"
@LOADER_INITIALIZED = true
end
end
def IRB.irb_exit(irb, ret)
throw :IRB_EXIT, ret
end
def IRB.irb_abort(irb, exception = Abort)
if defined? Thread
irb.context.thread.raise exception, "abort then interrupt!!"
else
raise exception, "abort then interrupt!!"
end
end
#
# irb interpriter main routine
#
class Irb
def initialize(main, bind, input_method = nil)
@context = Context.new(self, main, bind, input_method)
main.extend ExtendCommand
@signal_status = :IN_IRB
@scanner = RubyLex.new
@scanner.exception_on_syntax_error = false
end
attr :context
attr :scanner, true
def eval_input
# @scanner = RubyLex.new
@scanner.set_input(@context.io) do
signal_status(:IN_INPUT) do
unless l = @context.io.gets
if @context.ignore_eof? and @context.io.readable_atfer_eof?
l = "\n"
if @context.verbose?
printf "Use \"exit\" to leave %s\n", @context.ap_name
end
end
end
l
end
end
@scanner.set_prompt do
|ltype, indent, continue, line_no|
if ltype
f = @context.prompt_s
elsif continue
f = @context.prompt_c
else @context.prompt_i
f = @context.prompt_i
end
f = "" unless f
@context.io.prompt = p = prompt(f, ltype, indent, line_no)
if @context.auto_indent_mode
unless ltype
ind = prompt(@context.prompt_i, ltype, indent, line_no).size +
indent * 2 - p.size
ind += 2 if continue
@context.io.prompt = p + " " * ind if ind > 0
end
end
end
@scanner.each_top_level_statement do
|line, line_no|
signal_status(:IN_EVAL) do
begin
trace_in do
@context._ = eval(line, @context.bind, @context.irb_path, line_no)
# @context._ = irb_eval(line, @context.bind, @context.irb_path, line_no)
end
if @context.inspect?
printf @context.return_format, @context._.inspect
else
printf @context.return_format, @context._
end
rescue StandardError, ScriptError, Abort
$! = RuntimeError.new("unknown exception raised") unless $!
print $!.type, ": ", $!, "\n"
if $@[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && $!.type.to_s !~ /^IRB/
irb_bug = true
else
irb_bug = false
end
messages = []
lasts = []
levels = 0
for m in $@
if m !~ /irb2?(\/.*|-.*|\.rb)?:/ or irb_bug
if messages.size < @context.back_trace_limit
messages.push m
else
lasts.push m
if lasts.size > @context.back_trace_limit
lasts.shift
levels += 1
end
end
end
end
print messages.join("\n"), "\n"
unless lasts.empty?
printf "... %d levels...\n", levels if levels > 0
print lasts.join("\n")
end
print "Maybe IRB bug!!\n" if irb_bug
end
end
end
end
# def irb_eval(line, bind, path, line_no)
# id, str = catch(:IRB_TOPLEVEL_EVAL){
# return eval(line, bind, path, line_no)
# }
# case id
# when :EVAL_TOPLEVEL
# eval(str, bind, "(irb_internal)", 1)
# when :EVAL_CONTEXT
# @context.instance_eval(str)
# else
# IRB.fail IllegalParameter
# end
# end
def signal_handle
unless @context.ignore_sigint?
print "\nabort!!\n" if @context.verbose?
exit
end
case @signal_status
when :IN_INPUT
print "^C\n"
@scanner.initialize_input
print @context.io.prompt
when :IN_EVAL
IRB.irb_abort(self)
when :IN_LOAD
IRB.irb_abort(self, LoadAbort)
when :IN_IRB
# ignore
else
# ignore
end
end
def signal_status(status)
return yield if @signal_status == :IN_LOAD
signal_status_back = @signal_status
@signal_status = status
begin
yield
ensure
@signal_status = signal_status_back
end
end
def trace_in
Tracer.on if @context.use_tracer?
begin
yield
ensure
Tracer.off if @context.use_tracer?
end
end
def prompt(prompt, ltype, indent, line_no)
p = prompt.dup
p.gsub!(/%([0-9]+)?([a-zA-Z])/) do
case $2
when "N"
@context.irb_name
when "m"
@context.main.to_s
when "M"
@context.main.inspect
when "l"
ltype
when "i"
if $1
format("%" + $1 + "d", indent)
else
indent.to_s
end
when "n"
if $1
format("%" + $1 + "d", line_no)
else
line_no.to_s
end
when "%"
"%"
end
end
p
end
def inspect
ary = []
for iv in instance_variables
case iv
when "@signal_status"
ary.push format("%s=:%s", iv, @signal_status.id2name)
when "@context"
ary.push format("%s=%s", iv, eval(iv).__to_s__)
else
ary.push format("%s=%s", iv, eval(iv))
end
end
format("#<%s: %s>", type, ary.join(", "))
end
end
#
# irb context
#
class Context
#
# Arguments:
# input_method: nil -- stdin or readline
# String -- File
# other -- using this as InputMethod
#
def initialize(irb, main, bind, input_method = nil)
@irb = irb
@main = main
@bind = bind
@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]
self.math_mode = IRB.conf[:MATH_MODE]
@use_readline = IRB.conf[:USE_READLINE]
@inspect_mode = IRB.conf[:INSPECT_MODE]
@use_tracer = IRB.conf[:USE_TRACER]
# @use_loader = IRB.conf[:USE_LOADER]
self.prompt_mode = IRB.conf[:PROMPT_MODE]
@ignore_sigint = IRB.conf[:IGNORE_SIGINT]
@ignore_eof = IRB.conf[:IGNORE_EOF]
@back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT]
debug_level = IRB.conf[:DEBUG_LEVEL]
@verbose = IRB.conf[:VERBOSE]
@tracer_initialized = false
if IRB.conf[:SINGLE_IRB] or !defined?(JobManager)
@irb_name = IRB.conf[:IRB_NAME]
else
@irb_name = "irb#"+IRB.JobManager.n_jobs.to_s
end
@irb_path = "(" + @irb_name + ")"
case input_method
when nil
if (use_readline.nil? && IRB.conf[:PROMPT_MODE] != :INF_RUBY ||
use_readline?)
@io = ReadlineInputMethod.new
else
@io = StdioInputMethod.new
end
when String
@io = FileInputMethod.new(input_method)
@irb_name = File.basename(input_method)
@irb_path = input_method
else
@io = input_method
end
end
attr :bind, true
attr :main, true
attr :thread
attr :io, true
attr :_
attr :irb
attr :ap_name
attr :rc
attr :load_modules
attr :irb_name
attr :irb_path
attr :math_mode, true
attr :use_readline, true
attr :inspect_mode
attr :use_tracer
# attr :use_loader
attr :debug_level
attr :verbose, true
attr :prompt_mode
attr :prompt_i, true
attr :prompt_s, true
attr :prompt_c, true
attr :auto_indent_mode, true
attr :return_format, true
attr :ignore_sigint, true
attr :ignore_eof, true
attr :back_trace_limit
# alias use_loader? use_loader
alias use_tracer? use_tracer
alias use_readline? use_readline
alias rc? rc
alias math? math_mode
alias verbose? verbose
alias ignore_sigint? ignore_sigint
alias ignore_eof? ignore_eof
def _=(value)
@_ = value
eval "_ = IRB.conf[:MAIN_CONTEXT]._", @bind
end
def irb_name
if @irb_level == 0
@irb_name
elsif @irb_name =~ /#[0-9]*$/
@irb_name + "." + @irb_level.to_s
else
@irb_name + "#0." + @irb_level.to_s
end
end
def prompt_mode=(mode)
@prompt_mode = mode
pconf = IRB.conf[:PROMPT][mode]
@prompt_i = pconf[:PROMPT_I]
@prompt_s = pconf[:PROMPT_S]
@prompt_c = pconf[:PROMPT_C]
@return_format = pconf[:RETURN]
if ai = pconf.include?(:AUTO_INDENT)
@auto_indent_mode = ai
else
@auto_indent_mode = IRB.conf[:AUTO_INDENT]
end
end
def inspect?
@inspect_mode.nil? && !@math_mode or @inspect_mode
end
def file_input?
@io.type == FileInputMethod
end
def use_tracer=(opt)
if opt
IRB.initialize_tracer
unless @tracer_initialized
Tracer.set_get_line_procs(@irb_path) {
|line_no|
@io.line(line_no)
}
@tracer_initialized = true
end
elsif !opt && @use_tracer
Tracer.off
end
@use_tracer=opt
end
def use_loader
IRB.conf[:USE_LOADER]
end
def use_loader=(opt)
IRB.conf[:USE_LOADER] = opt
if opt
IRB.initialize_loader
end
print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose?
opt
end
def inspect_mode=(opt)
if opt
@inspect_mode = opt
else
@inspect_mode = !@inspect_mode
end
print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose?
@inspect_mode
end
def math_mode=(opt)
if @math_mode == true && opt == false
IRB.fail CantRetuenNormalMode
return
end
@math_mode = opt
if math_mode
IRB.initialize_mathn
@main.instance_eval("include Math")
print "start math mode\n" if verbose?
end
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 change_binding(*main)
back = [@bind, @main]
@bind = IRB.workspace_binding(*main)
unless main.empty?
@main = eval("self", @bind)
begin
@main.extend ExtendCommand
rescue
print "can't change binding to: ", @main.inspect, "\n"
@bind, @main = back
return nil
end
end
@irb_level += 1
begin
catch(:SU_EXIT) do
@irb.eval_input
end
ensure
@irb_level -= 1
@bind, @main = back
end
end
alias __exit__ exit
def exit(ret = 0)
if @irb_level == 0
IRB.irb_exit(@irb, ret)
else
throw :SU_EXIT, ret
end
end
NOPRINTING_IVARS = ["@_"]
NO_INSPECTING_IVARS = ["@irb", "@io"]
IDNAME_IVARS = ["@prompt_mode"]
alias __inspect__ inspect
def inspect
array = []
for ivar in instance_variables.sort{|e1, e2| e1 <=> e2}
name = ivar.sub(/^@(.*)$/){$1}
val = instance_eval(ivar)
case ivar
when *NOPRINTING_IVARS
next
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
alias __to_s__ to_s
alias to_s inspect
end
#
# IRB extended command
#
module Loader; end
module ExtendCommand
include Loader
alias irb_exit_org exit
def irb_exit(ret = 0)
irb_context.exit(ret)
end
alias exit irb_exit
alias quit irb_exit
alias irb_fork fork
def fork(&block)
unless irb_fork
eval "alias exit irb_exit_org"
instance_eval "alias exit irb_exit_org"
if iterator?
yield
exit
end
end
end
def irb_change_binding(*main)
irb_context.change_binding(*main)
end
alias cb irb_change_binding
def irb_source(file)
irb_context.source(file)
end
alias source irb_source
def irb(*obj)
require "irb/multi-irb"
IRB.irb(nil, *obj)
end
def irb_context
IRB.conf[:MAIN_CONTEXT]
end
alias conf irb_context
def irb_jobs
require "irb/multi-irb"
IRB.JobManager
end
alias jobs irb_jobs
def irb_fg(key)
require "irb/multi-irb"
IRB.JobManager.switch(key)
end
alias fg irb_fg
def irb_kill(*keys)
require "irb/multi-irb"
IRB.JobManager.kill(*keys)
end
alias kill irb_kill
end
# Singleton method
def @CONF.inspect
IRB.version unless self[:VERSION]
array = []
for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name}
case k
when :MAIN_CONTEXT
next
when :PROMPT
s = v.collect{
|kk, vv|
ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"}
format(":%s=>{%s}", kk.id2name, ss.join(", "))
}
array.push format("CONF[:%s]={%s}", k.id2name, s.join(", "))
else
array.push format("CONF[:%s]=%s", k.id2name, v.inspect)
end
end
array.join("\n")
end
end