[ruby/yarp] Provide a desugar visitor
https://github.com/ruby/yarp/commit/9fad513089
This commit is contained in:
parent
a38ca45b65
commit
2ebaf077f6
11
lib/yarp.rb
11
lib/yarp.rb
@ -52,6 +52,16 @@ module YARP
|
|||||||
@length = length
|
@length = length
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Create a new location object with the given options.
|
||||||
|
def copy(**options)
|
||||||
|
Location.new(
|
||||||
|
options.fetch(:source) { source },
|
||||||
|
options.fetch(:start_offset) { start_offset },
|
||||||
|
options.fetch(:length) { length }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a string representation of this location.
|
||||||
def inspect
|
def inspect
|
||||||
"#<YARP::Location @start_offset=#{@start_offset} @length=#{@length}>"
|
"#<YARP::Location @start_offset=#{@start_offset} @length=#{@length}>"
|
||||||
end
|
end
|
||||||
@ -508,6 +518,7 @@ end
|
|||||||
|
|
||||||
require_relative "yarp/lex_compat"
|
require_relative "yarp/lex_compat"
|
||||||
require_relative "yarp/mutation_visitor"
|
require_relative "yarp/mutation_visitor"
|
||||||
|
require_relative "yarp/desugar_visitor"
|
||||||
require_relative "yarp/node"
|
require_relative "yarp/node"
|
||||||
require_relative "yarp/ripper_compat"
|
require_relative "yarp/ripper_compat"
|
||||||
require_relative "yarp/serialize"
|
require_relative "yarp/serialize"
|
||||||
|
267
lib/yarp/desugar_visitor.rb
Normal file
267
lib/yarp/desugar_visitor.rb
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module YARP
|
||||||
|
class DesugarVisitor < MutationVisitor
|
||||||
|
# @@foo &&= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @@foo && @@foo = bar
|
||||||
|
def visit_class_variable_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
ClassVariableReadNode.new(node.name_loc),
|
||||||
|
ClassVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @@foo ||= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @@foo || @@foo = bar
|
||||||
|
def visit_class_variable_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
ClassVariableReadNode.new(node.name_loc),
|
||||||
|
ClassVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @@foo += bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @@foo = @@foo + bar
|
||||||
|
def visit_class_variable_operator_write_node(node)
|
||||||
|
desugar_operator_write_node(node, ClassVariableWriteNode, ClassVariableReadNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo &&= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo && Foo = bar
|
||||||
|
def visit_constant_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
ConstantReadNode.new(node.name_loc),
|
||||||
|
ConstantWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo ||= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo || Foo = bar
|
||||||
|
def visit_constant_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
ConstantReadNode.new(node.name_loc),
|
||||||
|
ConstantWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo += bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo = Foo + bar
|
||||||
|
def visit_constant_operator_write_node(node)
|
||||||
|
desugar_operator_write_node(node, ConstantWriteNode, ConstantReadNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo::Bar &&= baz
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo::Bar && Foo::Bar = baz
|
||||||
|
def visit_constant_path_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
node.target,
|
||||||
|
ConstantPathWriteNode.new(node.target, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo::Bar ||= baz
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo::Bar || Foo::Bar = baz
|
||||||
|
def visit_constant_path_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
node.target,
|
||||||
|
ConstantPathWriteNode.new(node.target, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Foo::Bar += baz
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# Foo::Bar = Foo::Bar + baz
|
||||||
|
def visit_constant_path_operator_write_node(node)
|
||||||
|
ConstantPathWriteNode.new(
|
||||||
|
node.target,
|
||||||
|
CallNode.new(
|
||||||
|
node.target,
|
||||||
|
nil,
|
||||||
|
node.operator_loc.copy(length: node.operator_loc.length - 1),
|
||||||
|
nil,
|
||||||
|
ArgumentsNode.new([node.value], node.value.location),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
0,
|
||||||
|
node.operator_loc.slice.chomp("="),
|
||||||
|
node.location
|
||||||
|
),
|
||||||
|
node.operator_loc.copy(start_offset: node.operator_loc.end_offset - 1, length: 1),
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# $foo &&= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# $foo && $foo = bar
|
||||||
|
def visit_global_variable_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
GlobalVariableReadNode.new(node.name_loc),
|
||||||
|
GlobalVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# $foo ||= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# $foo || $foo = bar
|
||||||
|
def visit_global_variable_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
GlobalVariableReadNode.new(node.name_loc),
|
||||||
|
GlobalVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# $foo += bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# $foo = $foo + bar
|
||||||
|
def visit_global_variable_operator_write_node(node)
|
||||||
|
desugar_operator_write_node(node, GlobalVariableWriteNode, GlobalVariableReadNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @foo &&= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @foo && @foo = bar
|
||||||
|
def visit_instance_variable_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
InstanceVariableReadNode.new(node.name_loc),
|
||||||
|
InstanceVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @foo ||= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @foo || @foo = bar
|
||||||
|
def visit_instance_variable_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
InstanceVariableReadNode.new(node.name_loc),
|
||||||
|
InstanceVariableWriteNode.new(node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @foo += bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# @foo = @foo + bar
|
||||||
|
def visit_instance_variable_operator_write_node(node)
|
||||||
|
desugar_operator_write_node(node, InstanceVariableWriteNode, InstanceVariableReadNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
# foo &&= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# foo && foo = bar
|
||||||
|
def visit_local_variable_and_write_node(node)
|
||||||
|
AndNode.new(
|
||||||
|
LocalVariableReadNode.new(node.constant_id, node.depth, node.name_loc),
|
||||||
|
LocalVariableWriteNode.new(node.constant_id, node.depth, node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# foo ||= bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# foo || foo = bar
|
||||||
|
def visit_local_variable_or_write_node(node)
|
||||||
|
OrNode.new(
|
||||||
|
LocalVariableReadNode.new(node.constant_id, node.depth, node.name_loc),
|
||||||
|
LocalVariableWriteNode.new(node.constant_id, node.depth, node.name_loc, node.value, node.operator_loc, node.location),
|
||||||
|
node.operator_loc,
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# foo += bar
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# foo = foo + bar
|
||||||
|
def visit_local_variable_operator_write_node(node)
|
||||||
|
desugar_operator_write_node(node, LocalVariableWriteNode, LocalVariableReadNode, arguments: [node.constant_id, node.depth])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Desugar `x += y` to `x = x + y`
|
||||||
|
def desugar_operator_write_node(node, write_class, read_class, arguments: [])
|
||||||
|
write_class.new(
|
||||||
|
*arguments,
|
||||||
|
node.name_loc,
|
||||||
|
CallNode.new(
|
||||||
|
read_class.new(*arguments, node.name_loc),
|
||||||
|
nil,
|
||||||
|
node.operator_loc.copy(length: node.operator_loc.length - 1),
|
||||||
|
nil,
|
||||||
|
ArgumentsNode.new([node.value], node.value.location),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
0,
|
||||||
|
node.operator_loc.slice.chomp("="),
|
||||||
|
node.location
|
||||||
|
),
|
||||||
|
node.operator_loc.copy(start_offset: node.operator_loc.end_offset - 1, length: 1),
|
||||||
|
node.location
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -59,6 +59,7 @@ Gem::Specification.new do |spec|
|
|||||||
"include/yarp/util/yp_strpbrk.h",
|
"include/yarp/util/yp_strpbrk.h",
|
||||||
"include/yarp/version.h",
|
"include/yarp/version.h",
|
||||||
"lib/yarp.rb",
|
"lib/yarp.rb",
|
||||||
|
"lib/yarp/desugar_visitor.rb",
|
||||||
"lib/yarp/ffi.rb",
|
"lib/yarp/ffi.rb",
|
||||||
"lib/yarp/lex_compat.rb",
|
"lib/yarp/lex_compat.rb",
|
||||||
"lib/yarp/mutation_visitor.rb",
|
"lib/yarp/mutation_visitor.rb",
|
||||||
|
57
test/desugar_visitor_test.rb
Normal file
57
test/desugar_visitor_test.rb
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "yarp_test_helper"
|
||||||
|
|
||||||
|
class DesugarVisitorTest < Test::Unit::TestCase
|
||||||
|
def test_and_write
|
||||||
|
assert_desugars("(AndNode (ClassVariableReadNode) (ClassVariableWriteNode (CallNode)))", "@@foo &&= bar")
|
||||||
|
assert_desugars("(AndNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (ConstantPathWriteNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (CallNode)))", "Foo::Bar &&= baz")
|
||||||
|
assert_desugars("(AndNode (ConstantReadNode) (ConstantWriteNode (CallNode)))", "Foo &&= bar")
|
||||||
|
assert_desugars("(AndNode (GlobalVariableReadNode) (GlobalVariableWriteNode (CallNode)))", "$foo &&= bar")
|
||||||
|
assert_desugars("(AndNode (InstanceVariableReadNode) (InstanceVariableWriteNode (CallNode)))", "@foo &&= bar")
|
||||||
|
assert_desugars("(AndNode (LocalVariableReadNode) (LocalVariableWriteNode (CallNode)))", "foo &&= bar")
|
||||||
|
assert_desugars("(AndNode (LocalVariableReadNode) (LocalVariableWriteNode (CallNode)))", "foo = 1; foo &&= bar")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_or_write
|
||||||
|
assert_desugars("(OrNode (ClassVariableReadNode) (ClassVariableWriteNode (CallNode)))", "@@foo ||= bar")
|
||||||
|
assert_desugars("(OrNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (ConstantPathWriteNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (CallNode)))", "Foo::Bar ||= baz")
|
||||||
|
assert_desugars("(OrNode (ConstantReadNode) (ConstantWriteNode (CallNode)))", "Foo ||= bar")
|
||||||
|
assert_desugars("(OrNode (GlobalVariableReadNode) (GlobalVariableWriteNode (CallNode)))", "$foo ||= bar")
|
||||||
|
assert_desugars("(OrNode (InstanceVariableReadNode) (InstanceVariableWriteNode (CallNode)))", "@foo ||= bar")
|
||||||
|
assert_desugars("(OrNode (LocalVariableReadNode) (LocalVariableWriteNode (CallNode)))", "foo ||= bar")
|
||||||
|
assert_desugars("(OrNode (LocalVariableReadNode) (LocalVariableWriteNode (CallNode)))", "foo = 1; foo ||= bar")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_operator_write
|
||||||
|
assert_desugars("(ClassVariableWriteNode (CallNode (ClassVariableReadNode) (ArgumentsNode (CallNode))))", "@@foo += bar")
|
||||||
|
assert_desugars("(ConstantPathWriteNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (CallNode (ConstantPathNode (ConstantReadNode) (ConstantReadNode)) (ArgumentsNode (CallNode))))", "Foo::Bar += baz")
|
||||||
|
assert_desugars("(ConstantWriteNode (CallNode (ConstantReadNode) (ArgumentsNode (CallNode))))", "Foo += bar")
|
||||||
|
assert_desugars("(GlobalVariableWriteNode (CallNode (GlobalVariableReadNode) (ArgumentsNode (CallNode))))", "$foo += bar")
|
||||||
|
assert_desugars("(InstanceVariableWriteNode (CallNode (InstanceVariableReadNode) (ArgumentsNode (CallNode))))", "@foo += bar")
|
||||||
|
assert_desugars("(LocalVariableWriteNode (CallNode (LocalVariableReadNode) (ArgumentsNode (CallNode))))", "foo += bar")
|
||||||
|
assert_desugars("(LocalVariableWriteNode (CallNode (LocalVariableReadNode) (ArgumentsNode (CallNode))))", "foo = 1; foo += bar")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ast_inspect(node)
|
||||||
|
parts = [node.class.name.split("::").last]
|
||||||
|
|
||||||
|
node.deconstruct_keys(nil).each do |_, value|
|
||||||
|
case value
|
||||||
|
when YARP::Node
|
||||||
|
parts << ast_inspect(value)
|
||||||
|
when Array
|
||||||
|
parts.concat(value.map { |element| ast_inspect(element) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"(#{parts.join(" ")})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_desugars(expected, source)
|
||||||
|
ast = YARP.parse(source).value.accept(YARP::DesugarVisitor.new)
|
||||||
|
assert_equal expected, ast_inspect(ast.statements.body.last)
|
||||||
|
end
|
||||||
|
end
|
@ -9,7 +9,7 @@ module YARP
|
|||||||
def visit_<%= node.human %>(node)
|
def visit_<%= node.human %>(node)
|
||||||
<%- params = node.params.select { |param| [NodeParam, OptionalNodeParam, NodeListParam].include?(param.class) } -%>
|
<%- params = node.params.select { |param| [NodeParam, OptionalNodeParam, NodeListParam].include?(param.class) } -%>
|
||||||
<%- if params.any? -%>
|
<%- if params.any? -%>
|
||||||
node.copy(<%= params.map { |param| "#{param.name}: visit(node.#{param.name})" }.join(", ") %>)
|
node.copy(<%= params.map { |param| "#{param.name}: #{param.is_a?(NodeListParam) ? "visit_all" : "visit"}(node.#{param.name})" }.join(", ") %>)
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
node.copy
|
node.copy
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
@ -52,7 +52,7 @@ module YARP
|
|||||||
def copy(**params)
|
def copy(**params)
|
||||||
<%= node.name %>.new(
|
<%= node.name %>.new(
|
||||||
<%- (node.params.map(&:name) + ["location"]).map do |name| -%>
|
<%- (node.params.map(&:name) + ["location"]).map do |name| -%>
|
||||||
<%= name %>: params.fetch(:<%= name %>) { self.<%= name %> },
|
params.fetch(:<%= name %>) { <%= name %> },
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user