Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AndNode #356

Merged
merged 8 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/syntax_tree/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ def AliasNode(left, right)
AliasNode.new(left: left, right: right, location: Location.default)
end

# Create a new AndNode node.
def AndNode(left, operator, right)
AndNode.new(
left: left,
operator: operator,
right: right,
location: Location.default
)
end

# Create a new ARef node.
def ARef(collection, index)
ARef.new(collection: collection, index: index, location: Location.default)
Expand Down
9 changes: 9 additions & 0 deletions lib/syntax_tree/field_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ def visit_alias(node)
end
end

def visit_and(node)
node(node, "and") do
field("left", node.left)
text("operator", node.operator)
field("right", node.right)
comments(node)
end
end

def visit_arg_block(node)
node(node, "arg_block") do
field("value", node.value) if node.value
Expand Down
5 changes: 5 additions & 0 deletions lib/syntax_tree/mutation_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ def visit_alias(node)
node.copy(left: visit(node.left), right: visit(node.right))
end

# Visit a AndNode node.
def visit_and(node)
node.copy(left: visit(node.left), right: visit(node.right))
end

# Visit a ARef node.
def visit_aref(node)
node.copy(index: visit(node.index))
Expand Down
92 changes: 91 additions & 1 deletion lib/syntax_tree/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,96 @@ def var_alias?
end
end

class AndNode < Node
# Since AndNode's operator is a symbol, it's better to use the `name` method
# than to allocate a new string every time. This is a tiny performance
# optimization, but enough that it shows up in the profiler. Adding this in
# for older Ruby versions.
unless :+.respond_to?(:name)
using Module.new {
refine Symbol do
def name
to_s.freeze
end
end
}
end

# [Node] the left side of the && operator
attr_reader :left

# [Symbol] the operator
attr_reader :operator

# [Node] the right side of the && operator
attr_reader :right

# [Array[ Comment | EmbDoc ]] the comments attached to this node
attr_reader :comments

def initialize(left:, operator:, right:, location:)
@left = left
@operator = operator
@right = right
@location = location
@comments = []
end

def accept(visitor)
visitor.visit_and(self)
end

def child_nodes
[left, right]
end

def copy(left: nil, operator: nil, right: nil, location: nil)
node =
AndNode.new(
left: left || self.left,
operator: operator || self.operator,
right: right || self.right,
location: location || self.location
)
node.comments.concat(comments.map(&:copy))
node
end

alias deconstruct child_nodes

def deconstruct_keys(_keys)
{
left: left,
operator: operator,
right: right,
location: location,
comments: comments
}
end

def format(q)
left = self.left

q.group do
q.group { q.format(left) }
q.text(" ")

q.group do
q.text(operator.name)
q.indent do
q.breakable_space
q.format(right)
end
end
end
end

def ===(other)
other.is_a?(AndNode) && left === other.left &&
operator === other.operator && right === other.right
end
end

# ARef represents when you're pulling a value out of a collection at a
# specific index. Put another way, it's any time you're calling the method
# #[].
Expand Down Expand Up @@ -6267,7 +6357,7 @@ def call(q, node)
# want to force it to not be a ternary, like if the predicate is an
# assignment because it's hard to read.
case node.predicate
when Assign, Binary, Command, CommandCall, MAssign, OpAssign
when Assign, AndNode, Binary, Command, CommandCall, MAssign, OpAssign
return false
when Not
return false unless node.predicate.parentheses?
Expand Down
22 changes: 16 additions & 6 deletions lib/syntax_tree/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -893,12 +893,22 @@ def on_binary(left, operator, right)
operator = tokens.delete(operator).value
end

Binary.new(
left: left,
operator: operator,
right: right,
location: left.location.to(right.location)
)
case operator
when :"&&", :and
AndNode.new(
left: left,
operator: operator,
right: right,
location: left.location.to(right.location)
)
else
Binary.new(
left: left,
operator: operator,
right: right,
location: left.location.to(right.location)
)
end
end

# :call-seq:
Expand Down
11 changes: 11 additions & 0 deletions lib/syntax_tree/pretty_print_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ def initialize(q)
@q = q
end

# This is here because we need to make sure the operator is cast to a string
# before we print it out.
def visit_and(node)
node(node, "and") do
field("left", node.left)
text("operator", node.operator.to_s)
field("right", node.right)
comments(node)
end
end

# This is here because we need to make sure the operator is cast to a string
# before we print it out.
def visit_binary(node)
Expand Down
16 changes: 14 additions & 2 deletions lib/syntax_tree/translation/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,18 @@ def visit_begin(node)
end
end

# Visit a AndNode node.
def visit_and(node)
s(
:and,
[visit(node.left), visit(node.right)],
smap_operator(
srange_find_between(node.left, node.right, node.operator.to_s),
srange_node(node)
)
)
end

# Visit a Binary node.
def visit_binary(node)
case node.operator
Expand All @@ -482,9 +494,9 @@ def visit_binary(node)
else
visit(canonical_binary(node))
end
when :"=>", :"&&", :and, :"||", :or
when :"=>", :"||", :or
s(
{ "=>": :match_as, "&&": :and, "||": :or }.fetch(
{ "=>": :match_as, "||": :or }.fetch(
node.operator,
node.operator
),
Expand Down
3 changes: 3 additions & 0 deletions lib/syntax_tree/visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Visitor < BasicVisitor
# Visit an AliasNode node.
alias visit_alias visit_child_nodes

# Visit an AndNode node.
alias visit_and visit_child_nodes

# Visit an ArgBlock node.
alias visit_arg_block visit_child_nodes

Expand Down
22 changes: 12 additions & 10 deletions lib/syntax_tree/yarv/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -543,18 +543,20 @@ def visit_bare_assoc_hash(node)
def visit_begin(node)
end

def visit_binary(node)
case node.operator
when :"&&"
done_label = iseq.label
def visit_and(node)
done_label = iseq.label

visit(node.left)
iseq.dup
iseq.branchunless(done_label)
visit(node.left)
iseq.dup
iseq.branchunless(done_label)

iseq.pop
visit(node.right)
iseq.push(done_label)
iseq.pop
visit(node.right)
iseq.push(done_label)
end

def visit_binary(node)
case node.operator
when :"||"
visit(node.left)
iseq.dup
Expand Down