Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 45ebf0b

Browse files
wildmaplesKaanOzkanMorriarvinistockegiurleo
committed
Implement visitor pattern to SyntaxTree::Node
This pattern will expand our horizons and allow us to do fun stuff with the nodes through the SyntaxTree::Visitor class. We've chosen to require the Visitor class in node.rb to make it clear that the file requires it. Otherwise, we'd have to add it to lib/syntax_tree.rb in an order-dependent way, which is undesirable. Co-authored-by: Kaan Ozkan <kaan.ozkan@shopify.com> Co-authored-by: Alexandre Terrasa <alexandre.terrasa@shopify.com> Co-authored-by: Vinicius Stock <vinicius.stock@shopify.com> Co-authored-by: Emily Giurleo <emily.giurleo@shopify.com> Co-authored-by: Rafael Franca <rafael.franca@shopify.com>
1 parent 2080149 commit 45ebf0b

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

lib/syntax_tree/node.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "syntax_tree/visitor"
4+
35
module SyntaxTree
46
# Represents the location of a node in the tree from the source code.
57
class Location
@@ -56,6 +58,29 @@ class Node
5658
# [Location] the location of this node
5759
attr_reader :location
5860

61+
def self.inherited(child)
62+
child.class_eval(<<~EOS, __FILE__, __LINE__ + 1)
63+
def accept(visitor)
64+
visitor.#{child.visit_method_name}(self)
65+
end
66+
EOS
67+
68+
Visitor.class_eval(<<~EOS, __FILE__, __LINE__ + 1)
69+
def #{child.visit_method_name}(node)
70+
visit_all(node.child_nodes)
71+
end
72+
EOS
73+
end
74+
75+
def self.visit_method_name
76+
method_suffix = name.split("::").last
77+
method_suffix.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
78+
method_suffix.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
79+
method_suffix.tr!("-", "_")
80+
method_suffix.downcase!
81+
"visit_#{method_suffix}"
82+
end
83+
5984
def child_nodes
6085
raise NotImplementedError
6186
end

lib/syntax_tree/visitor.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxTree
4+
class Visitor
5+
def visit_all(nodes)
6+
nodes.each do |node|
7+
visit(node)
8+
end
9+
end
10+
11+
def visit(node)
12+
node&.accept(self)
13+
end
14+
end
15+
end

test/visitor_test.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "test_helper"
4+
require "objspace"
5+
6+
class VisitorTest < Minitest::Test
7+
def test_can_visit_all_nodes
8+
visitor = SyntaxTree::Visitor.new
9+
10+
ObjectSpace.each_object(SyntaxTree::Node.singleton_class)
11+
.reject { |node| node.singleton_class? || node == SyntaxTree::Node }
12+
.each { |node| assert_respond_to(visitor, node.visit_method_name) }
13+
end
14+
15+
def test_node_visit_method_name
16+
assert_equal("visit_t_string_end", SyntaxTree::TStringEnd.visit_method_name)
17+
end
18+
19+
def test_visit_tree
20+
parsed_tree = SyntaxTree.parse(<<~RUBY)
21+
class Foo
22+
def foo; end
23+
24+
class Bar
25+
def bar; end
26+
end
27+
end
28+
29+
def baz; end
30+
RUBY
31+
32+
visitor = DummyVisitor.new
33+
visitor.visit(parsed_tree)
34+
assert_equal(["Foo", "foo", "Bar", "bar", "baz"], visitor.visited_nodes)
35+
end
36+
37+
class DummyVisitor < SyntaxTree::Visitor
38+
attr_reader :visited_nodes
39+
40+
def initialize
41+
super
42+
@visited_nodes = []
43+
end
44+
45+
def visit_class_declaration(node)
46+
@visited_nodes << node.constant.constant.value
47+
super
48+
end
49+
50+
def visit_def(node)
51+
@visited_nodes << node.name.value
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)