diff options
| author | Noah Gibbs <the.codefolio.guy@gmail.com> | 2024-02-07 10:22:29 +0000 |
|---|---|---|
| committer | git <svn-admin@ruby-lang.org> | 2024-02-07 19:42:13 +0000 |
| commit | b1310940e36c14bd07dbc2db885fbd6ec8c35bf8 (patch) | |
| tree | 110fca144cfec83dd9e0ea47781bbf875bbf5b1a | |
| parent | a5c871e20105859a2ee286742d9a4e0eb1b2d0be (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.rb | 103 | ||||
| -rw-r--r-- | test/prism/ripper_compat_test.rb | 45 |
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 |
