diff options
author | matz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2000-05-12 09:07:57 +0000 |
---|---|---|
committer | matz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2000-05-12 09:07:57 +0000 |
commit | 9da4f78db46764be6dae5e7e83ff48cbecb3fb23 (patch) | |
tree | c0805e6c95d6396e28e6129d88905c4dee085f4e /lib/irb | |
parent | 014f2164ed7031a1c31604b290d2ab0cf1deacdc (diff) |
2000-05-12
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@687 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/irb')
-rw-r--r-- | lib/irb/completion.rb | 47 | ||||
-rw-r--r-- | lib/irb/frame.rb | 67 | ||||
-rw-r--r-- | lib/irb/input-method.rb | 118 | ||||
-rw-r--r-- | lib/irb/loader.rb | 118 | ||||
-rw-r--r-- | lib/irb/main.rb | 867 | ||||
-rw-r--r-- | lib/irb/multi-irb.rb | 212 | ||||
-rw-r--r-- | lib/irb/ruby-lex.rb | 955 | ||||
-rw-r--r-- | lib/irb/ruby-token.rb | 266 | ||||
-rw-r--r-- | lib/irb/slex.rb | 279 | ||||
-rw-r--r-- | lib/irb/version.rb | 16 | ||||
-rw-r--r-- | lib/irb/workspace-binding-2.rb | 15 | ||||
-rw-r--r-- | lib/irb/workspace-binding.rb | 77 | ||||
-rw-r--r-- | lib/irb/xmp.rb | 84 |
13 files changed, 3121 insertions, 0 deletions
diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb new file mode 100644 index 0000000000..cbd4012773 --- /dev/null +++ b/lib/irb/completion.rb @@ -0,0 +1,47 @@ + +require "readline" + +module IRB + module InputCompletion + 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" + ] + + CompletionProc = proc { |input| + case input + when /^([^.]+)\.([^.]*)$/ + receiver = $1 + message = $2 + if eval("(local_variables|#{receiver}.type.constants).include?('#{receiver}')", + IRB.conf[:MAIN_CONTEXT].bind) + candidates = eval("#{receiver}.methods", IRB.conf[:MAIN_CONTEXT].bind) + else + candidates = [] + end + candidates.grep(/^#{Regexp.quote(message)}/).collect{|e| receiver + "." + e} + else + candidates = eval("methods | private_methods | local_variables | type.constants", + IRB.conf[:MAIN_CONTEXT].bind) + (candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/) + end + } + end +end + +Readline.completion_proc = IRB::InputCompletion::CompletionProc diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb new file mode 100644 index 0000000000..a2d7de3778 --- /dev/null +++ b/lib/irb/frame.rb @@ -0,0 +1,67 @@ +# +# frame.rb - +# $Release Version: 0.6$ +# $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" + + INIT_STACK_TIMES = 3 + CALL_STACK_OFFSET = 3 + + def initialize + @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES + end + + def trace_func(event, file, line, id, binding) + case event + when 'call', 'class' + @frames.push binding + when 'return', 'end' + @frames.pop + end + end + + def top(n = 0) + bind = @frames[-(n + CALL_STACK_OFFSET)] + Fail FrameUnderflow unless bind + bind + end + + def bottom(n = 0) + bind = @frames[n] + Fail FrameOverflow unless bind + bind + end + + # singleton functions + def Frame.bottom(n = 0) + @backtrace.bottom(n) + end + + def Frame.top(n = 0) + @backtrace.top(n) + end + + def Frame.sender + eval "self", @backtrace.top + end + + @backtrace = Frame.new + set_trace_func proc{|event, file, line, id, binding| + @backtrace.trace_func(event, file, line, id, binding) + } + end +end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb new file mode 100644 index 0000000000..19df0eb073 --- /dev/null +++ b/lib/irb/input-method.rb @@ -0,0 +1,118 @@ +# +# input-method.rb - input methods using irb +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# +module IRB + # + # InputMethod + # StdioInputMethod + # FileInputMethod + # (ReadlineInputMethod) + # + STDIN_FILE_NAME = "(line)" + class InputMethod + @RCS_ID='-$Id$-' + + def initialize(file = STDIN_FILE_NAME) + @file_name = file + end + attr :file_name + + attr :prompt, true + + def gets + IRB.fail NotImplementError, "gets" + end + public :gets + + def readable_atfer_eof? + false + end + end + + class StdioInputMethod < InputMethod + def initialize + super + @line_no = 0 + @line = [] + end + + def gets + print @prompt + @line[@line_no += 1] = $stdin.gets + end + + def eof? + $stdin.eof? + end + + def readable_atfer_eof? + true + end + + def line(line_no) + @line[line_no] + end + end + + class FileInputMethod < InputMethod + def initialize(file) + super + @io = open(file) + end + attr :file_name + + def eof? + @io.eof? + end + + def gets + l = @io.gets + print @prompt, l + l + end + end + + begin + require "readline" + class ReadlineInputMethod < InputMethod + include Readline + def initialize + super + + @line_no = 0 + @line = [] + @eof = false + end + + def gets + if l = readline(@prompt, true) + @line[@line_no += 1] = l + "\n" + else + @eof = true + l + end + end + + def eof? + @eof + end + + def readable_atfer_eof? + true + end + + def line(line_no) + @line[line_no] + end + end + rescue LoadError + end +end diff --git a/lib/irb/loader.rb b/lib/irb/loader.rb new file mode 100644 index 0000000000..83b10a55a0 --- /dev/null +++ b/lib/irb/loader.rb @@ -0,0 +1,118 @@ +# +# irb-loader.rb - +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +module IRB + class LoadAbort < GlobalExit;end + + module Loader + @RCS_ID='-$Id$-' + + alias ruby_load load + alias ruby_require require + + def irb_load(file_name) + return ruby_load(file_name) unless IRB.conf[:USE_LOADER] + + load_sub(file_name) + return true + end + + def irb_require(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} + + case file_name + when /\.rb$/ + begin + load_sub(file_name) + $".push file_name + return true + rescue LoadError + end + when /\.(so|o|sl)$/ + return ruby_require(file_name) + end + + begin + load_sub(f = file_name + ".rb") + $".push f + return true + rescue LoadError + return ruby_require(file_name) + end + end + + def load_sub(fn) + if fn =~ /^#{Regexp.quote(File::Separator)}/ + return false unless File.exist?(fn) + return irb_context.load_file(fn) + end + + for path in $: + if File.exist?(f = File.join(path, fn)) + return irb_context.load_file(f) + end + end + raise LoadError, "No such file to load -- #{file_name}" + end + + alias load irb_load + alias require irb_require + end + +# class Context +# def load_from(file_name) +# io = FileInputMethod.new(file_name) +# @irb.signal_status(:IN_LOAD) do +# switch_io(io, file_name) do +# eval_input +# end +# end +# end +# end + + class Context + def load_file(path) + 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 + ensure + @io = back_io + @irb_name = back_name + @irb_path = back_path + @irb.scanner = back_scanner + end + end + end + + module ExtendCommand + include Loader + end +end diff --git a/lib/irb/main.rb b/lib/irb/main.rb new file mode 100644 index 0000000000..4c7dac240b --- /dev/null +++ b/lib/irb/main.rb @@ -0,0 +1,867 @@ +# +# 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, [email protected] - 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 diff --git a/lib/irb/multi-irb.rb b/lib/irb/multi-irb.rb new file mode 100644 index 0000000000..39dbcbae3c --- /dev/null +++ b/lib/irb/multi-irb.rb @@ -0,0 +1,212 @@ +# +# multi-irb.rb - multiple irb module +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# +IRB.fail CanNotGoMultiIrbMode unless defined?(Thread) +require "thread" + +module IRB + # job management class + class JobManager + @RCS_ID='-$Id$-' + + def initialize + # @jobs = [[thread, irb],...] + @jobs = [] + @current_job = nil + end + + attr :current_job, true + + def n_jobs + @jobs.size + end + + def thread(key) + th, irb = search(key) + irb + end + + def irb(key) + th, irb = search(key) + irb + end + + def main_thread + @jobs[0][0] + end + + def main_irb + @jobs[0][1] + end + + def insert(irb) + @jobs.push [Thread.current, irb] + end + + def switch(key) + th, irb = search(key) + IRB.fail IrbAlreadyDead unless th.alive? + IRB.fail IrbSwitchToCurrentThread if th == Thread.current + @current_job = irb + th.run + Thread.stop + @current_job = irb(Thread.current) + end + + def kill(*keys) + for key in keys + th, irb = search(key) + IRB.fail IrbAlreadyDead unless th.alive? + th.exit + end + end + + def search(key) + case key + when Integer + @jobs[key] + when Irb + @jobs.find{|k, v| v.equal?(irb)} + 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 + end + + def delete(key) + case key + when Integer + IRB.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 + end + until assoc = @jobs.pop; end unless @jobs.empty? + @jobs.push assoc + end + + 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) + end + ary.join("\n") + end + end + + @JobManager = JobManager.new + + def IRB.JobManager + @JobManager + end + + # invoke multiple irb + def IRB.irb(file = nil, *main) + workspace = IRB.workspace_binding(*main) + if main.empty? + main = eval("self", workspace) + else + main = main[0] + end + parent_thread = Thread.current + Thread.start do + begin + irb = Irb.new(main, workspace, file) + rescue + print "Subirb can't start with context(self): ", 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) + begin + system_exit = false + catch(:IRB_EXIT) do + irb.eval_input + end + rescue SystemExit + 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 + end + end + Thread.stop + @JobManager.current_job = @JobManager.irb(Thread.current) + end + + class Context + def _=(value) + @_ = value + eval "_ = IRB.JobManager.irb(Thread.current).context._", @bind + 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 + + trap("SIGINT") do + @JobManager.current_job.signal_handle + end + +end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb new file mode 100644 index 0000000000..e66ba8879e --- /dev/null +++ b/lib/irb/ruby-lex.rb @@ -0,0 +1,955 @@ +# +# ruby-lex.rb - ruby lexcal analizer +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" +require "irb/slex" +require "irb/ruby-token" + +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") + + include RubyToken + + class << self + attr :debug_level, TRUE + def debug? + @debug_level > 0 + 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 + + @skip_space = false + @readed_auto_clean_up = false + @exception_on_syntax_error = true + end + + attr :skip_space, true + attr :readed_auto_clean_up, true + attr :exception_on_syntax_error, true + + attr :seek + attr :char_no + attr :line_no + attr :indent + + # io functions + def set_input(io, p = nil) + @io = io + if p.kind_of?(Proc) + @input = p + elsif iterator? + @input = proc + else + @input = proc{@io.gets} + 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 + 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 + else + @char_no += 1 + end + c + end + + def gets + l = "" + while c = getc + l.concat c + break if c == "\n" + end + l + end + + def eof? + @io.eof? + end + + def getc_of_rests + if @rests.empty? + nil + else + getc + end + end + + 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 + else + @char_no = @base_char_no + @readed.size + 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 + end + + def peek_match?(regexp) + while @rests.empty? + return false unless buf_input + end + regexp =~ @rests.join("") + end + + def peek(i = 0) + while @rests.size <= i + return nil unless buf_input + 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 = proc) + if p.kind_of?(Proc) + @prompt = p + else + @prompt = proc{print p} + end + end + + def prompt + if @prompt + @prompt.call(@ltype, @indent, @continue, @line_no) + end + end + + def initialize_input + @ltype = nil + @quoted = nil + @indent = 0 + @lex_state = EXPR_BEG + @space_seen = false + @here_header = false + + prompt + @continue = FALSE + + @line = "" + @exp_line_no = @line_no + end + + def each_top_level_statement + initialize_input + loop do + @continue = FALSE + prompt + unless l = lex + break 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 + prompt + end + end + + def lex + until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) && + !@continue or + tk.nil?) + # p tk + # p self + end + line = get_readed + # print self.inspect + if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil? + nil + else + line + end + 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 + abort 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 + 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" => "]" + } + + PERCENT_PAREN = { + "{" => "}", + "[" => "]", + "<" => ">", + "(" => ")" + } + + Ltype2Token = { + "\'" => TkSTRING, + "\"" => TkSTRING, + "\`" => TkXSTRING, + "\/" => TkREGEXP, + "]" => TkDSTRING + } + DLtype2Token = { + "\"" => TkDSTRING, + "\`" => TkDXSTRING, + "\/" => TkDREGEXP, + } + + def lex_init() + @OP = SLex.new + @OP.def_rules("\0", "\004", "\032") do + Token(TkEND_OF_SCRIPT) + end + + @OP.def_rules(" ", "\t", "\f", "\r", "\13") do + @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{@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 + getc; getc; getc; getc + @ltype = nil + Token(TkRD_COMMENT) + end + + @OP.def_rule("\n") do + print "\\n\n" if RubyLex.debug? + case @lex_state + when EXPR_BEG, EXPR_FNAME, EXPR_DOT + @continue = TRUE + else + @continue = FALSE + @lex_state = EXPR_BEG + end + @here_header = false + @here_readed = [] + Token(TkNL) + end + + @OP.def_rules("*", "**", + "!", "!=", "!~", + "=", "==", "===", + "=~", "<=>", + "<", "<=", + ">", ">=", ">>") do + |op, io| + @lex_state = EXPR_BEG + Token(op) + end + + @OP.def_rules("<<") do + |op, io| + if @lex_state != EXPR_END && @lex_state != EXPR_CLASS && + (@lex_state != EXPR_ARG || @space_seen) + c = peek(0) + if /\S/ =~ c && (/["'`]/ =~ c || /[\w_]/ =~ c) + tk = identify_here_document; + end + else + tk = Token(op) + end + tk + end + + @OP.def_rules("'", '"') do + |op, io| + identify_string(op) + end + + @OP.def_rules("`") do + |op, io| + if @lex_state == EXPR_FNAME + 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{@lex_state == EXPR_FNAME}) do + Token(TkUPLUS) + end + + @OP.def_rule("-@", proc{@lex_state == EXPR_FNAME}) do + Token(TkUMINUS) + 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) + end + end + + @OP.def_rule(".") do + @lex_state = EXPR_BEG + if peek(0) =~ /[0-9]/ + ungetc + identify_number + else + # for obj.if + @lex_state = EXPR_DOT + Token(TkDOT) + end + end + + @OP.def_rules("..", "...") do + |op, io| + @lex_state = EXPR_BEG + Token(op) + end + + lex_int2 + end + + def lex_int2 + @OP.def_rules("]", "}", ")") do + |op, io| + @lex_state = EXPR_END + @indent -= 1 + Token(op) + end + + @OP.def_rule(":") do + 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 +# 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 + @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_rule("~") do + @lex_state = EXPR_BEG + Token("~") + end + + @OP.def_rule("~@", proc{@lex_state = EXPR_FNAME}) do + @lex_state = EXPR_BEG + Token("~") + end + + @OP.def_rule("(") do + @indent += 1 + if @lex_state == EXPR_BEG || @lex_state == EXPR_MID + @lex_state = EXPR_BEG + Token(TkfLPAREN) + else + @lex_state = EXPR_BEG + Token(TkLPAREN) + end + end + + @OP.def_rule("[]", proc{@lex_state == EXPR_FNAME}) do + Token("[]") + end + + @OP.def_rule("[]=", proc{@lex_state == EXPR_FNAME}) do + Token("[]=") + end + + @OP.def_rule("[") do + @indent += 1 + if @lex_state == EXPR_FNAME + Token(TkfLBRACK) + else + if @lex_state == EXPR_BEG || @lex_state == EXPR_MID + t = Token(TkLBRACK) + elsif @lex_state == EXPR_ARG && @space_seen + t = Token(TkLBRACK) + else + t = Token(TkfLBRACK) + end + @lex_state = EXPR_BEG + t + end + end + + @OP.def_rule("{") do + @indent += 1 + if @lex_state != EXPR_END && @lex_state != EXPR_ARG + t = Token(TkLBRACE) + else + t = Token(TkfLBRACE) + end + @lex_state = EXPR_BEG + t + end + + @OP.def_rule('\\') do + if getc == "\n" + @space_seen = true + @continue = true + Token(TkSPACE) + else + ungetc + Token("\\") + end + end + + @OP.def_rule('%') do + |op, io| + if @lex_state == EXPR_BEG || @lex_state == EXPR_MID + identify_quotation + elsif peek(0) == '=' + getc + Token(OP_ASGIN, "%") + elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ + identify_quotation + else + @lex_state = EXPR_BEG + Token("%") #)) + end + end + + @OP.def_rule('$') do + identify_gvar + end + + @OP.def_rule('@') do + if peek(0) =~ /[\w_]/ + ungetc + identify_identifier + 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 + end + + def identify_identifier + token = "" + token.concat getc if peek(0) =~ /[$@]/ + while (ch = getc) =~ /\w|_/ + print ":", ch, ":" if RubyLex.debug? + token.concat ch + end + ungetc + + if ch == "!" or ch == "?" + token.concat getc + end + # fix token + + case token + when /^\$/ + return Token(TkGVAR, 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) + @indent += 1 + elsif DEINDENT_CLAUSE.include?(token) + @indent -= 1 + 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 + end + + def identify_here_document + ch = getc + if lt = PERCENT_LTYPE[ch] + quoted = "" + while (c = getc) && c != lt + quoted.concat c + 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.chomp) && l != quoted + 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" + 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 ch = getc + if peek(0) == "x" + ch = getc + match = /[0-9a-f_]/ + else + match = /[0-7_]/ + end + while ch = getc + if ch !~ match + ungetc + break + end + end + return Token(TkINTEGER) + end + + type = TkINTEGER + allow_point = TRUE + allow_e = TRUE + while ch = getc + case ch + when /[0-9_]/ + when allow_point && "." + type = TkFLOAT + if peek(0) !~ /[0-9]/ + ungetc + break + end + allow_point = false + when allow_e && "e", allow_e && "E" + type = TkFLOAT + if peek(0) =~ /[+-]/ + getc + end + allow_e = false + allow_point = false + else + ungetc + break + end + end + Token(type) + end + + def identify_string(ltype, quoted = ltype) + @ltype = ltype + @quoted = quoted + subtype = nil + begin + while ch = getc + if @quoted == ch + break + elsif @ltype != "'" && @ltype != "]" and ch == "#" + subtype = true + elsif ch == '\\' #' + read_escape + end + end + if @ltype == "/" + if peek(0) =~ /i|o|n|e|s/ + getc + end + end + if subtype + Token(DLtype2Token[ltype]) + else + Token(Ltype2Token[ltype]) + end + ensure + @ltype = nil + @quoted = nil + @lex_state = EXPR_END + end + 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" #" + 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 + + when "M" + if (ch = getc) != '-' + ungetc + else + if (ch = getc) == "\\" #" + read_escape(chrs) + end + end + + when "C", "c", "^" + if ch == "C" and (ch = getc) != "-" + ungetc + elsif (ch = getc) == "\\" #" + read_escape(chrs) + end + else + # other characters + end + end +end diff --git a/lib/irb/ruby-token.rb b/lib/irb/ruby-token.rb new file mode 100644 index 0000000000..1532dc78eb --- /dev/null +++ b/lib/irb/ruby-token.rb @@ -0,0 +1,266 @@ +# +# ruby-token.rb - ruby tokens +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# +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 + + 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] 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], + [:TkIVAR, TkId], + [:TkCONSTANT, TkId], + + [:TkINTEGER, TkVal], + [:TkFLOAT, TkVal], + [:TkSTRING, TkVal], + [:TkXSTRING, TkVal], + [:TkREGEXP, 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, "`"], + + [:TkASSGIN, 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 unless token_n.kind_of?(String) + 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/slex.rb b/lib/irb/slex.rb new file mode 100644 index 0000000000..85aa92bd73 --- /dev/null +++ b/lib/irb/slex.rb @@ -0,0 +1,279 @@ +# +# irb-slex.rb - symple lex analizer +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" + +class SLex + @RCS_ID='-$Id$-' + + extend Exception2MessageMapper + def_exception :ErrNodeNothing, "node nothing" + def_exception :ErrNodeAlreadyExists, "node already exists" + + class << self + attr :debug_level, TRUE + def debug? + debug_level > 0 + end + end + @debug_level = 0 + + def initialize + @head = Node.new("") + end + + def def_rule(token, preproc = nil, postproc = nil) + # print node.inspect, "\n" if SLex.debug? + postproc = proc if iterator? + node = create(token, preproc, postproc) + end + + def def_rules(*tokens) + if iterator? + p = proc + end + for token in tokens + def_rule(token, nil, p) + end + end + + def preporc(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 + token = token.split(//) + match(token.split(//)) + else + return @head.match_io(token) + end + ret = @head.match(token) + printf "match end: %s:%s", ret, token.inspect if SLex.debug? + ret + end + + def inspect + format("<SLex: @head = %s>", @head.inspect) + end + + #---------------------------------------------------------------------- + # + # class Node - + # + #---------------------------------------------------------------------- + class Node + # if postproc no exist, this node is abstract node. + # if postproc isn't nil, this node is real node. + def initialize(preproc = nil, postproc = nil) + @Tree = {} + @preproc = preproc + @postproc = postproc + end + + attr :preproc, TRUE + attr :postproc, TRUE + + 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 + p node + SLex.fail ErrNodeAlreadyExists + else + print "Warn: change abstruct node to real node\n" if SLex.debug? + @preproc = preproc + @postproc = postproc + end + return self + end + + ch = chrs.shift + if node = @Tree[ch] + if chrs.empty? + if node.postproc + p node + p self + p ch + p chrs + SLex.fail ErrNodeAlreadyExists + else + print "Warn: change abstruct node to real node\n" if SLex.debug? + 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 It must have getc()/ungetc(), and ungetc() can be + # called any number of times. + # + def match(chrs, op = "") + print "match>: ", chrs, "op:", op, "\n" if SLex.debug? + if chrs.empty? + if @preproc.nil? || @preproc.call(op, chrs) + printf "op1: %s\n", op if SLex.debug? + @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) + printf "op2: %s\n", op.inspect if SLex.debug? + 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) + printf "op3: %s\n", op if SLex.debug? + @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) + printf "op1: %s\n", op if SLex.debug? + @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) + printf "op2: %s\n", op.inspect if SLex.debug? + @postproc.call(op, io) + else + nil + end + end + else + io.ungetc ch + if @postproc and @preproc.nil? || @preproc.call(op, io) + printf "op3: %s\n", op if SLex.debug? + @postproc.call(op, io) + else + nil + end + end + end + end + end +end + +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/version.rb b/lib/irb/version.rb new file mode 100644 index 0000000000..7179d1c163 --- /dev/null +++ b/lib/irb/version.rb @@ -0,0 +1,16 @@ +# +# version.rb - irb version definition file +# $Release Version: 0.6.1$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +module IRB + @RELEASE_VERSION = "0.6.1" + @LAST_UPDATE_DATE = "99/09/16" +end diff --git a/lib/irb/workspace-binding-2.rb b/lib/irb/workspace-binding-2.rb new file mode 100644 index 0000000000..d005296f6e --- /dev/null +++ b/lib/irb/workspace-binding-2.rb @@ -0,0 +1,15 @@ +# +# bind.rb - +# $Release Version: $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +while true + IRB::BINDING_QUEUE.push b = binding +end diff --git a/lib/irb/workspace-binding.rb b/lib/irb/workspace-binding.rb new file mode 100644 index 0000000000..d58088d9dd --- /dev/null +++ b/lib/irb/workspace-binding.rb @@ -0,0 +1,77 @@ +# +# workspace-binding.rb - +# $Release Version: $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + + +module IRB + # create new workspace. + def IRB.workspace_binding(*main) + if @CONF[:SINGLE_IRB] + bind = TOPLEVEL_BINDING + else + case @CONF[:CONTEXT_MODE] + when 0 + bind = eval("proc{binding}.call", + TOPLEVEL_BINDING, + "(irb_local_binding)", + 1) + when 1 + require "tempfile" + f = Tempfile.open("irb-binding") + f.print <<EOF + $binding = binding +EOF + f.close + load f.path + bind = $binding + + when 2 + 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/workspace-binding-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__ + end + Thread.pass + + end + + bind = BINDING_QUEUE.pop + + when 3 + bind = eval("def irb_binding; binding; end; irb_binding", + TOPLEVEL_BINDING, + __FILE__, + __LINE__ - 3) + end + end + unless main.empty? + @CONF[:__MAIN__] = main[0] + case main[0] + when Module + bind = eval("IRB.conf[:__MAIN__].module_eval('binding')", bind) + else + begin + bind = eval("IRB.conf[:__MAIN__].instance_eval('binding')", bind) + rescue TypeError + IRB.fail CanNotChangeBinding, main[0].inspect + end + end + end + eval("_=nil", bind) + bind + end + + def IRB.delete_caller + end +end diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb new file mode 100644 index 0000000000..fc745a2757 --- /dev/null +++ b/lib/irb/xmp.rb @@ -0,0 +1,84 @@ +# +# xmp.rb - irb version of gotoken xmp +# $Release Version: 0.6$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "irb/irb" +require "irb/frame" + +class XMP + @RCS_ID='-$Id$-' + + def initialize(bind = nil) + #IRB.parse_opts + #IRB.load_modules + + bind = IRB::Frame.top(1) unless bind + main = eval("self", bind) + @io = StringInputMethod.new + @irb = IRB::Irb.new(main, bind, @io) + @irb.context.prompt_mode = :XMP + @irb.context.ignore_sigint = false + +# IRB.conf[:IRB_RC].call(@irb.context) if IRB.conf[:IRB_RC] + IRB.conf[:MAIN_CONTEXT] = @irb.context + end + + 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 + ensure + trap("SIGINT", trap_proc_b) + end + else + catch(:IRB_EXIT) do + @irb.eval_input + end + end + end + + class StringInputMethod < IRB::InputMethod + def initialize + super + @exps = [] + end + + def eof? + @exps.empty? + end + + def gets + while l = @exps.shift + next if /^\s+$/ =~ l + l.concat "\n" + print @prompt, l + break + end + l + end + + def puts(exps) + @exps.concat exps.split(/\n/) + end + end +end + +def xmp(exps, bind = nil) + bind = IRB::Frame.top(1) unless bind + xmp = XMP.new(bind) + xmp.puts exps + xmp +end |