зеркало из https://github.com/github/ruby.git
[ruby/irb] Encapsulate input details in Statement objects
(https://github.com/ruby/irb/pull/682) * Introduce Statement class * Split Statement class for better clarity https://github.com/ruby/irb/commit/65e8e68690
This commit is contained in:
Родитель
0982c5fa00
Коммит
5a40f7db54
42
lib/irb.rb
42
lib/irb.rb
|
@ -570,26 +570,19 @@ module IRB
|
|||
|
||||
configure_io
|
||||
|
||||
@scanner.each_top_level_statement do |line, line_no, is_assignment|
|
||||
@scanner.each_top_level_statement do |statement, line_no|
|
||||
signal_status(:IN_EVAL) do
|
||||
begin
|
||||
# If the integration with debugger is activated, we need to handle certain input differently
|
||||
if @context.with_debugger
|
||||
command_class = load_command_class(line)
|
||||
# First, let's pass debugging command's input to debugger
|
||||
# Secondly, we need to let debugger evaluate non-command input
|
||||
# Otherwise, the expression will be evaluated in the debugger's main session thread
|
||||
# This is the only way to run the user's program in the expected thread
|
||||
if !command_class || ExtendCommand::DebugCommand > command_class
|
||||
return line
|
||||
end
|
||||
if @context.with_debugger && statement.should_be_handled_by_debugger?
|
||||
return statement.code
|
||||
end
|
||||
|
||||
evaluate_line(line, line_no)
|
||||
@context.evaluate(statement.evaluable_code, line_no)
|
||||
|
||||
# Don't echo if the line ends with a semicolon
|
||||
if @context.echo? && !line.match?(/;\s*\z/)
|
||||
if is_assignment
|
||||
if @context.echo? && !statement.suppresses_echo?
|
||||
if statement.is_assignment?
|
||||
if @context.echo_on_assignment?
|
||||
output_value(@context.echo_on_assignment? == :truncate)
|
||||
end
|
||||
|
@ -659,29 +652,6 @@ module IRB
|
|||
end
|
||||
end
|
||||
|
||||
def evaluate_line(line, line_no)
|
||||
# Transform a non-identifier alias (@, $) or keywords (next, break)
|
||||
command, args = line.split(/\s/, 2)
|
||||
if original = @context.command_aliases[command.to_sym]
|
||||
line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s)
|
||||
command = original
|
||||
end
|
||||
|
||||
# Hook command-specific transformation
|
||||
command_class = ExtendCommandBundle.load_command(command)
|
||||
if command_class&.respond_to?(:transform_args)
|
||||
line = "#{command} #{command_class.transform_args(args)}"
|
||||
end
|
||||
|
||||
@context.evaluate(line, line_no)
|
||||
end
|
||||
|
||||
def load_command_class(line)
|
||||
command, _ = line.split(/\s/, 2)
|
||||
command_name = @context.command_aliases[command.to_sym]
|
||||
ExtendCommandBundle.load_command(command_name || command)
|
||||
end
|
||||
|
||||
def convert_invalid_byte_sequence(str, enc)
|
||||
str.force_encoding(enc)
|
||||
str.scrub { |c|
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
require "ripper"
|
||||
require "jruby" if RUBY_ENGINE == "jruby"
|
||||
require_relative "nesting_parser"
|
||||
require_relative "statement"
|
||||
|
||||
# :stopdoc:
|
||||
class RubyLex
|
||||
|
@ -221,16 +222,30 @@ class RubyLex
|
|||
break unless code
|
||||
|
||||
if code != "\n"
|
||||
code.force_encoding(@context.io.encoding)
|
||||
yield code, @line_no, assignment_expression?(code)
|
||||
yield build_statement(code), @line_no
|
||||
end
|
||||
increase_line_no(code.count("\n"))
|
||||
rescue TerminateLineInput
|
||||
end
|
||||
end
|
||||
|
||||
def assignment_expression?(line)
|
||||
# Try to parse the line and check if the last of possibly multiple
|
||||
def build_statement(code)
|
||||
code.force_encoding(@context.io.encoding)
|
||||
command_or_alias, arg = code.split(/\s/, 2)
|
||||
# Transform a non-identifier alias (@, $) or keywords (next, break)
|
||||
command_name = @context.command_aliases[command_or_alias.to_sym]
|
||||
command = command_name || command_or_alias
|
||||
command_class = IRB::ExtendCommandBundle.load_command(command)
|
||||
|
||||
if command_class
|
||||
IRB::Statement::Command.new(code, command, arg, command_class)
|
||||
else
|
||||
IRB::Statement::Expression.new(code, assignment_expression?(code))
|
||||
end
|
||||
end
|
||||
|
||||
def assignment_expression?(code)
|
||||
# Try to parse the code and check if the last of possibly multiple
|
||||
# expressions is an assignment type.
|
||||
|
||||
# If the expression is invalid, Ripper.sexp should return nil which will
|
||||
|
@ -239,7 +254,7 @@ class RubyLex
|
|||
# array of parsed expressions. The first element of each expression is the
|
||||
# expression's type.
|
||||
verbose, $VERBOSE = $VERBOSE, nil
|
||||
code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{line}"
|
||||
code = "#{RubyLex.generate_local_variables_assign_code(@context.local_variables) || 'nil;'}\n#{code}"
|
||||
# Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part.
|
||||
node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0)
|
||||
ASSIGNMENT_NODE_TYPES.include?(node_type)
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IRB
|
||||
class Statement
|
||||
attr_reader :code
|
||||
|
||||
def is_assignment?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def suppresses_echo?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def should_be_handled_by_debugger?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def evaluable_code
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
class Expression < Statement
|
||||
def initialize(code, is_assignment)
|
||||
@code = code
|
||||
@is_assignment = is_assignment
|
||||
end
|
||||
|
||||
def suppresses_echo?
|
||||
@code.match?(/;\s*\z/)
|
||||
end
|
||||
|
||||
def should_be_handled_by_debugger?
|
||||
true
|
||||
end
|
||||
|
||||
def is_assignment?
|
||||
@is_assignment
|
||||
end
|
||||
|
||||
def evaluable_code
|
||||
@code
|
||||
end
|
||||
end
|
||||
|
||||
class Command < Statement
|
||||
def initialize(code, command, arg, command_class)
|
||||
@code = code
|
||||
@command = command
|
||||
@arg = arg
|
||||
@command_class = command_class
|
||||
end
|
||||
|
||||
def is_assignment?
|
||||
false
|
||||
end
|
||||
|
||||
def suppresses_echo?
|
||||
false
|
||||
end
|
||||
|
||||
def should_be_handled_by_debugger?
|
||||
IRB::ExtendCommand::DebugCommand > @command_class
|
||||
end
|
||||
|
||||
def evaluable_code
|
||||
# Hook command-specific transformation to return valid Ruby code
|
||||
if @command_class.respond_to?(:transform_args)
|
||||
arg = @command_class.transform_args(@arg)
|
||||
else
|
||||
arg = @arg
|
||||
end
|
||||
|
||||
[@command, arg].compact.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Загрузка…
Ссылка в новой задаче