summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNoah Gibbs <the.codefolio.guy@gmail.com>2024-02-07 10:22:29 +0000
committergit <svn-admin@ruby-lang.org>2024-02-07 19:42:13 +0000
commitb1310940e36c14bd07dbc2db885fbd6ec8c35bf8 (patch)
tree110fca144cfec83dd9e0ea47781bbf875bbf5b1a
parenta5c871e20105859a2ee286742d9a4e0eb1b2d0be (diff)
[ruby/prism] RipperCompat: support more kinds of method calls and operators.
Add tests. Start parsing some simpler fixture code. https://github.com/ruby/prism/commit/997f4191d8
-rw-r--r--lib/prism/ripper_compat.rb103
-rw-r--r--test/prism/ripper_compat_test.rb45
2 files changed, 132 insertions, 16 deletions
diff --git a/lib/prism/ripper_compat.rb b/lib/prism/ripper_compat.rb
index 912114cfef..98dc762f62 100644
--- a/lib/prism/ripper_compat.rb
+++ b/lib/prism/ripper_compat.rb
@@ -116,32 +116,80 @@ module Prism
end
# Visit a CallNode node.
+ # Ripper distinguishes between many different method-call
+ # nodes -- unary and binary operators, "command" calls with
+ # no parentheses, and call/fcall/vcall.
def visit_call_node(node)
if node.variable_call?
- if node.message.match?(/^[[:alpha:]_]/)
- bounds(node.message_loc)
- return on_vcall(on_ident(node.message))
- end
+ raise NotImplementedError unless node.receiver.nil?
- raise NotImplementedError, "Non-alpha variable call"
+ bounds(node.message_loc)
+ return on_vcall(on_ident(node.message))
end
if node.opening_loc.nil?
- left = visit(node.receiver)
- if node.arguments&.arguments&.length == 1
- right = visit(node.arguments.arguments.first)
-
- on_binary(left, node.name, right)
- elsif !node.arguments || node.arguments.empty?
- on_unary(node.name, left)
+ # No opening_loc can mean an operator. It can also mean a
+ # method call with no parentheses.
+ if node.message.match?(/^[[:punct:]]/)
+ left = visit(node.receiver)
+ if node.arguments&.arguments&.length == 1
+ right = visit(node.arguments.arguments.first)
+
+ return on_binary(left, node.name, right)
+ elsif !node.arguments || node.arguments.empty?
+ return on_unary(node.name, left)
+ else
+ raise NotImplementedError, "More than two arguments for operator"
+ end
+ elsif node.call_operator_loc.nil?
+ # In Ripper a method call like "puts myvar" with no parenthesis is a "command"
+ bounds(node.message_loc)
+ ident_val = on_ident(node.message)
+ args = args_node_to_arguments(node.arguments)
+ return on_command(ident_val, args)
else
- raise NotImplementedError, "More than two arguments for operator"
+ operator = node.call_operator_loc.slice
+ if operator == "."
+ left_val = visit(node.receiver)
+
+ bounds(node.call_operator_loc)
+ dot_val = on_period(node.call_operator)
+
+ bounds(node.message_loc)
+ right_val = on_ident(node.message)
+
+ return on_call(left_val, dot_val, right_val)
+ else
+ raise NotImplementedError, "operator other than dot for call: #{operator.inspect}"
+ end
end
+ end
+
+ # A non-operator method call with parentheses
+ args = on_arg_paren(args_node_to_arguments(node.arguments))
+
+ bounds(node.message_loc)
+ ident_val = on_ident(node.message)
+
+ bounds(node.location)
+ args_call_val = on_method_add_arg(on_fcall(ident_val), args)
+ if node.block
+ raise NotImplementedError, "Method call with a block!"
else
- raise NotImplementedError, "Non-nil opening_loc"
+ return args_call_val
end
end
+ # Visit an AndNode
+ def visit_and_node(node)
+ visit_binary_operator(node)
+ end
+
+ # Visit an AndNode
+ def visit_or_node(node)
+ visit_binary_operator(node)
+ end
+
# Visit a FloatNode node.
def visit_float_node(node)
visit_number(node) { |text| on_float(text) }
@@ -203,6 +251,24 @@ module Prism
end
end
+ private
+
+ # Ripper generates an interesting format of argument list.
+ # We'd like to convert an ArgumentsNode to one.
+ def args_node_to_arguments(args_node)
+ return nil if args_node.nil?
+
+ args = on_args_new
+ args_node.arguments.each do |arg|
+ bounds(arg.location)
+ args = on_args_add(args, visit(arg))
+ end
+
+ on_args_add_block(args, false)
+ end
+
+ public
+
############################################################################
# Entrypoints for subclasses
############################################################################
@@ -238,13 +304,20 @@ module Prism
value = yield slice[1..-1]
bounds(node.location)
- on_unary(RUBY_ENGINE == "jruby" ? :- : :-@, value)
+ on_unary(RUBY_ENGINE == "jruby" && JRUBY_VERSION < "9.4.6.0" ? :- : :-@, value)
else
bounds(location)
yield slice
end
end
+ # Visit a binary operator node like an AndNode or OrNode
+ def visit_binary_operator(node)
+ left_val = visit(node.left)
+ right_val = visit(node.right)
+ on_binary(left_val, node.operator_loc.slice.to_sym, right_val)
+ end
+
# This method is responsible for updating lineno and column information
# to reflect the current node.
#
diff --git a/test/prism/ripper_compat_test.rb b/test/prism/ripper_compat_test.rb
index 8ca8545add..ae611e5e46 100644
--- a/test/prism/ripper_compat_test.rb
+++ b/test/prism/ripper_compat_test.rb
@@ -24,8 +24,30 @@ module Prism
assert_equivalent("(3 + 7) * 4")
end
- def test_ident
+ def test_method_calls_with_variable_names
assert_equivalent("foo")
+ assert_equivalent("foo()")
+ assert_equivalent("foo(-7)")
+ assert_equivalent("foo(1, 2, 3)")
+ assert_equivalent("foo 1")
+ assert_equivalent("foo bar")
+ assert_equivalent("foo 1, 2")
+ assert_equivalent("foo.bar")
+ assert_equivalent("🗻")
+ assert_equivalent("🗻.location")
+ assert_equivalent("foo.🗻")
+ assert_equivalent("🗻.😮!")
+ assert_equivalent("🗻 🗻,🗻,🗻")
+ end
+
+ def test_method_calls_on_immediate_values
+ assert_equivalent("7.even?")
+ assert_equivalent("!1")
+ assert_equivalent("7 && 7")
+ assert_equivalent("7 and 7")
+ assert_equivalent("7 || 7")
+ assert_equivalent("7 or 7")
+ #assert_equivalent("'racecar'.reverse")
end
def test_range
@@ -58,4 +80,25 @@ module Prism
assert_equal expected, RipperCompat.sexp_raw(source)
end
end
+
+ class RipperCompatFixturesTest < TestCase
+ #base = File.join(__dir__, "fixtures")
+ #relatives = ENV["FOCUS"] ? [ENV["FOCUS"]] : Dir["**/*.txt", base: base]
+ relatives = ["arithmetic.txt", "integer_operations.txt"]
+
+ relatives.each do |relative|
+ define_method "test_ripper_filepath_#{relative}" do
+ path = File.join(__dir__, "fixtures", relative)
+
+ # First, read the source from the path. Use binmode to avoid converting CRLF on Windows,
+ # and explicitly set the external encoding to UTF-8 to override the binmode default.
+ source = File.read(path, binmode: true, external_encoding: Encoding::UTF_8)
+
+ expected = Ripper.sexp_raw(source)
+ refute_nil expected
+ assert_equal expected, RipperCompat.sexp_raw(source)
+ end
+ end
+
+ end
end