summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Lo <stan001212@gmail.com>2023-08-16 11:13:41 +0100
committergit <svn-admin@ruby-lang.org>2023-08-16 10:13:46 +0000
commit5a40f7db54dfcc7dadb75dde32c25b88c78d6a85 (patch)
tree928a522f7c48a4c93fe0b7302ba469953e30c8d9
parent0982c5fa00f6163d04b17229e72d128263b98d50 (diff)
[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
-rw-r--r--lib/irb.rb42
-rw-r--r--lib/irb/ruby-lex.rb25
-rw-r--r--lib/irb/statement.rb78
3 files changed, 104 insertions, 41 deletions
diff --git a/lib/irb.rb b/lib/irb.rb
index c884d70a67..93ab6370ed 100644
--- a/lib/irb.rb
+++ b/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|
diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb
index 282e6ef05f..3a0173a6be 100644
--- a/lib/irb/ruby-lex.rb
+++ b/lib/irb/ruby-lex.rb
@@ -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)
diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb
new file mode 100644
index 0000000000..9493c3ffb1
--- /dev/null
+++ b/lib/irb/statement.rb
@@ -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