diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 70a7e930..24a080a8 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -534,45 +534,54 @@ class Pry prompt_stack.size > 1 ? prompt_stack.pop : prompt end - if RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == "ruby" - require 'ripper' - - # Determine if a string of code is a valid Ruby expression. - # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser. - # @param [String] code The code to validate. - # @return [Boolean] Whether or not the code is a valid Ruby expression. - # @example - # valid_expression?("class Hello") #=> false - # valid_expression?("class Hello; end") #=> true - def valid_expression?(code) - !!Ripper::SexpBuilder.new(code).parse + # Determine if a string of code is a valid Ruby expression. + # @param [String] code The code to validate. + # @return [Boolean] Whether or not the code is a valid Ruby expression. + # @example + # valid_expression?("class Hello") #=> false + # valid_expression?("class Hello; end") #=> true + def valid_expression?(str) + if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/ + Rubinius::Melbourne19.parse_string(str, Pry.eval_path) + elsif defined?(Rubinius::Melbourne) + Rubinius::Melbourne.parse_string(str, Pry.eval_path) + else + catch(:valid) { + eval("BEGIN{throw :valid, true}\n#{str}", binding, Pry.eval_path) + } end - - elsif RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == 'jruby' - - # JRuby doesn't have Ripper, so use its native parser for 1.9 mode. - def valid_expression?(code) - JRuby.parse(code) - true - rescue SyntaxError + rescue SyntaxError => e + if incomplete_user_input_exception?(e, caller) false + else + raise e end + end - else - require 'ruby_parser' + # Check whether the exception indicates that the user should input more. + # + # The first part of the check verifies that the exception was raised from + # the input to the eval, taking care not to be confused if the input contains + # an eval() with incomplete syntax. + # + # @param [SyntaxError] the exception object that was raised. + # @param [Array] The stack frame of the function that executed eval. + # @return [Boolean] + # + def incomplete_user_input_exception?(ex, stack) + # deliberate use of ^ instead of \A, error messages can be two lines long. + from_pry_input = /^#{Regexp.escape(Pry.eval_path)}/ - # Determine if a string of code is a valid Ruby expression. - # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser. - # @param [String] code The code to validate. - # @return [Boolean] Whether or not the code is a valid Ruby expression. - # @example - # valid_expression?("class Hello") #=> false - # valid_expression?("class Hello; end") #=> true - def valid_expression?(code) - # NOTE: we're using .dup because RubyParser mutates the input - RubyParser.new.parse(code.dup) - true - rescue Racc::ParseError, SyntaxError + return false unless SyntaxError === ex && ex.message =~ from_pry_input + + case ex.message + when /unexpected (\$end|end-of-file|END_OF_FILE)/, # mri, jruby, ironruby + /unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby + /missing 'end' for/, /: expecting '[})\]]'$/, /can't find string ".*" anywhere before EOF/, /expecting keyword_end/ # rbx + backtrace = ex.backtrace + backtrace = backtrace.drop(1) if RUBY_VERSION =~ /^1\.8/ && RUBY_PLATFORM !~ /java/ + backtrace.grep(from_pry_input).count <= stack.grep(from_pry_input).count + else false end end diff --git a/test/helper.rb b/test/helper.rb index 2a8e40dc..5c0107b4 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -180,3 +180,11 @@ CommandTester = Pry::CommandSet.new do output.puts arg end end + +# to help with tracking down bugs that cause an infinite loop in the test suite +if ENV["SET_TRACE_FUNC"] + require 'set_trace' if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ # gem install 'rbx-tracer' + set_trace_func proc { |event, file, line, id, binding, classname| + STDERR.printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname + } +end