syntax_tree 5.3.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -1
- data/CHANGELOG.md +64 -1
- data/Gemfile.lock +2 -2
- data/README.md +28 -9
- data/Rakefile +12 -8
- data/bin/console +1 -0
- data/bin/whitequark +79 -0
- data/doc/changing_structure.md +16 -0
- data/lib/syntax_tree/basic_visitor.rb +44 -5
- data/lib/syntax_tree/cli.rb +2 -2
- data/lib/syntax_tree/dsl.rb +23 -11
- data/lib/syntax_tree/{visitor/field_visitor.rb → field_visitor.rb} +54 -55
- data/lib/syntax_tree/formatter.rb +1 -1
- data/lib/syntax_tree/index.rb +56 -54
- data/lib/syntax_tree/json_visitor.rb +55 -0
- data/lib/syntax_tree/language_server.rb +157 -2
- data/lib/syntax_tree/match_visitor.rb +120 -0
- data/lib/syntax_tree/mermaid.rb +177 -0
- data/lib/syntax_tree/mermaid_visitor.rb +69 -0
- data/lib/syntax_tree/{visitor/mutation_visitor.rb → mutation_visitor.rb} +27 -27
- data/lib/syntax_tree/node.rb +198 -107
- data/lib/syntax_tree/parser.rb +322 -118
- data/lib/syntax_tree/pretty_print_visitor.rb +83 -0
- data/lib/syntax_tree/reflection.rb +241 -0
- data/lib/syntax_tree/translation/parser.rb +3019 -0
- data/lib/syntax_tree/translation/rubocop_ast.rb +21 -0
- data/lib/syntax_tree/translation.rb +28 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/with_scope.rb +244 -0
- data/lib/syntax_tree/yarv/basic_block.rb +53 -0
- data/lib/syntax_tree/yarv/calldata.rb +91 -0
- data/lib/syntax_tree/yarv/compiler.rb +110 -100
- data/lib/syntax_tree/yarv/control_flow_graph.rb +257 -0
- data/lib/syntax_tree/yarv/data_flow_graph.rb +338 -0
- data/lib/syntax_tree/yarv/decompiler.rb +1 -1
- data/lib/syntax_tree/yarv/disassembler.rb +104 -80
- data/lib/syntax_tree/yarv/instruction_sequence.rb +43 -18
- data/lib/syntax_tree/yarv/instructions.rb +203 -649
- data/lib/syntax_tree/yarv/legacy.rb +12 -24
- data/lib/syntax_tree/yarv/sea_of_nodes.rb +534 -0
- data/lib/syntax_tree/yarv.rb +18 -0
- data/lib/syntax_tree.rb +88 -56
- data/tasks/sorbet.rake +277 -0
- data/tasks/whitequark.rake +87 -0
- metadata +23 -11
- data/.gitmodules +0 -9
- data/lib/syntax_tree/language_server/inlay_hints.rb +0 -159
- data/lib/syntax_tree/visitor/environment.rb +0 -84
- data/lib/syntax_tree/visitor/json_visitor.rb +0 -55
- data/lib/syntax_tree/visitor/match_visitor.rb +0 -122
- data/lib/syntax_tree/visitor/pretty_print_visitor.rb +0 -85
- data/lib/syntax_tree/visitor/with_environment.rb +0 -140
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
module SyntaxTree
|
7
|
+
# This module is responsible for rendering mermaid (https://mermaid.js.org/)
|
8
|
+
# flow charts.
|
9
|
+
module Mermaid
|
10
|
+
# This is the main class that handles rendering a flowchart. It keeps track
|
11
|
+
# of its nodes and links and renders them according to the mermaid syntax.
|
12
|
+
class FlowChart
|
13
|
+
attr_reader :output, :prefix, :nodes, :links
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@output = StringIO.new
|
17
|
+
@output.puts("flowchart TD")
|
18
|
+
@prefix = " "
|
19
|
+
|
20
|
+
@nodes = {}
|
21
|
+
@links = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve a node that has already been added to the flowchart by its id.
|
25
|
+
def fetch(id)
|
26
|
+
nodes.fetch(id)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add a link to the flowchart between two nodes with an optional label.
|
30
|
+
def link(from, to, label = nil, type: :directed, color: nil)
|
31
|
+
link = Link.new(from, to, label, type, color)
|
32
|
+
links << link
|
33
|
+
|
34
|
+
output.puts("#{prefix}#{link.render}")
|
35
|
+
link
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add a node to the flowchart with an optional label.
|
39
|
+
def node(id, label = " ", shape: :rectangle)
|
40
|
+
node = Node.new(id, label, shape)
|
41
|
+
nodes[id] = node
|
42
|
+
|
43
|
+
output.puts("#{prefix}#{nodes[id].render}")
|
44
|
+
node
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add a subgraph to the flowchart. Within the given block, all of the
|
48
|
+
# nodes will be rendered within the subgraph.
|
49
|
+
def subgraph(label)
|
50
|
+
output.puts("#{prefix}subgraph #{Mermaid.escape(label)}")
|
51
|
+
|
52
|
+
previous = prefix
|
53
|
+
@prefix = "#{prefix} "
|
54
|
+
|
55
|
+
begin
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@prefix = previous
|
59
|
+
output.puts("#{prefix}end")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return the rendered flowchart.
|
64
|
+
def render
|
65
|
+
links.each_with_index do |link, index|
|
66
|
+
if link.color
|
67
|
+
output.puts("#{prefix}linkStyle #{index} stroke:#{link.color}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
output.string
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# This class represents a link between two nodes in a flowchart. It is not
|
76
|
+
# meant to be interacted with directly, but rather used as a data structure
|
77
|
+
# by the FlowChart class.
|
78
|
+
class Link
|
79
|
+
TYPES = %i[directed dotted].freeze
|
80
|
+
COLORS = %i[green red].freeze
|
81
|
+
|
82
|
+
attr_reader :from, :to, :label, :type, :color
|
83
|
+
|
84
|
+
def initialize(from, to, label, type, color)
|
85
|
+
raise unless TYPES.include?(type)
|
86
|
+
raise if color && !COLORS.include?(color)
|
87
|
+
|
88
|
+
@from = from
|
89
|
+
@to = to
|
90
|
+
@label = label
|
91
|
+
@type = type
|
92
|
+
@color = color
|
93
|
+
end
|
94
|
+
|
95
|
+
def render
|
96
|
+
left_side, right_side, full_side = sides
|
97
|
+
|
98
|
+
if label
|
99
|
+
escaped = Mermaid.escape(label)
|
100
|
+
"#{from.id} #{left_side} #{escaped} #{right_side} #{to.id}"
|
101
|
+
else
|
102
|
+
"#{from.id} #{full_side} #{to.id}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def sides
|
109
|
+
case type
|
110
|
+
when :directed
|
111
|
+
%w[-- --> -->]
|
112
|
+
when :dotted
|
113
|
+
%w[-. .-> -.->]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# This class represents a node in a flowchart. Unlike the Link class, it can
|
119
|
+
# be used directly. It is the return value of the #node method, and is meant
|
120
|
+
# to be passed around to #link methods to create links between nodes.
|
121
|
+
class Node
|
122
|
+
SHAPES = %i[circle rectangle rounded stadium].freeze
|
123
|
+
|
124
|
+
attr_reader :id, :label, :shape
|
125
|
+
|
126
|
+
def initialize(id, label, shape)
|
127
|
+
raise unless SHAPES.include?(shape)
|
128
|
+
|
129
|
+
@id = id
|
130
|
+
@label = label
|
131
|
+
@shape = shape
|
132
|
+
end
|
133
|
+
|
134
|
+
def render
|
135
|
+
left_bound, right_bound = bounds
|
136
|
+
"#{id}#{left_bound}#{Mermaid.escape(label)}#{right_bound}"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def bounds
|
142
|
+
case shape
|
143
|
+
when :circle
|
144
|
+
%w[(( ))]
|
145
|
+
when :rectangle
|
146
|
+
["[", "]"]
|
147
|
+
when :rounded
|
148
|
+
%w[( )]
|
149
|
+
when :stadium
|
150
|
+
["([", "])"]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class << self
|
156
|
+
# Escape a label to be used in the mermaid syntax. This is used to escape
|
157
|
+
# HTML entities such that they render properly within the quotes.
|
158
|
+
def escape(label)
|
159
|
+
"\"#{CGI.escapeHTML(label)}\""
|
160
|
+
end
|
161
|
+
|
162
|
+
# Create a new flowchart. If a block is given, it will be yielded to and
|
163
|
+
# the flowchart will be rendered. Otherwise, the flowchart will be
|
164
|
+
# returned.
|
165
|
+
def flowchart
|
166
|
+
flowchart = FlowChart.new
|
167
|
+
|
168
|
+
if block_given?
|
169
|
+
yield flowchart
|
170
|
+
flowchart.render
|
171
|
+
else
|
172
|
+
flowchart
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# This visitor transforms the AST into a mermaid flow chart.
|
5
|
+
class MermaidVisitor < FieldVisitor
|
6
|
+
attr_reader :flowchart, :target
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@flowchart = Mermaid.flowchart
|
10
|
+
@target = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit_program(node)
|
14
|
+
super
|
15
|
+
flowchart.render
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def comments(node)
|
21
|
+
# Ignore
|
22
|
+
end
|
23
|
+
|
24
|
+
def field(name, value)
|
25
|
+
case value
|
26
|
+
when nil
|
27
|
+
# skip
|
28
|
+
when Node
|
29
|
+
flowchart.link(target, visit(value), name)
|
30
|
+
else
|
31
|
+
to =
|
32
|
+
flowchart.node("#{target.id}_#{name}", value.inspect, shape: :stadium)
|
33
|
+
flowchart.link(target, to, name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def list(name, values)
|
38
|
+
values.each_with_index do |value, index|
|
39
|
+
field("#{name}[#{index}]", value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def node(node, type)
|
44
|
+
previous_target = target
|
45
|
+
|
46
|
+
begin
|
47
|
+
@target = flowchart.node("node_#{node.object_id}", type)
|
48
|
+
yield
|
49
|
+
@target
|
50
|
+
ensure
|
51
|
+
@target = previous_target
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def pairs(name, values)
|
56
|
+
values.each_with_index do |(key, value), index|
|
57
|
+
to = flowchart.node("#{target.id}_#{name}_#{index}", shape: :circle)
|
58
|
+
|
59
|
+
flowchart.link(target, to, "#{name}[#{index}]")
|
60
|
+
flowchart.link(to, visit(key), "[0]")
|
61
|
+
flowchart.link(to, visit(value), "[1]") if value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def text(name, value)
|
66
|
+
field(name, value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,39 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SyntaxTree
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :mutations
|
4
|
+
# This visitor walks through the tree and copies each node as it is being
|
5
|
+
# visited. This is useful for mutating the tree before it is formatted.
|
6
|
+
class MutationVisitor < BasicVisitor
|
7
|
+
attr_reader :mutations
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# Create a new mutation based on the given query that will mutate the node
|
15
|
-
# using the given block. The block should return a new node that will take
|
16
|
-
# the place of the given node in the tree. These blocks frequently make
|
17
|
-
# use of the `copy` method on nodes to create a new node with the same
|
18
|
-
# properties as the original node.
|
19
|
-
def mutate(query, &block)
|
20
|
-
mutations << [Pattern.new(query).compile, block]
|
21
|
-
end
|
9
|
+
def initialize
|
10
|
+
@mutations = []
|
11
|
+
end
|
22
12
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
13
|
+
# Create a new mutation based on the given query that will mutate the node
|
14
|
+
# using the given block. The block should return a new node that will take
|
15
|
+
# the place of the given node in the tree. These blocks frequently make use
|
16
|
+
# of the `copy` method on nodes to create a new node with the same
|
17
|
+
# properties as the original node.
|
18
|
+
def mutate(query, &block)
|
19
|
+
mutations << [Pattern.new(query).compile, block]
|
20
|
+
end
|
29
21
|
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
# This is the base visit method for each node in the tree. It first creates
|
23
|
+
# a copy of the node using the visit_* methods defined below. Then it checks
|
24
|
+
# each mutation in sequence and calls it if it finds a match.
|
25
|
+
def visit(node)
|
26
|
+
return unless node
|
27
|
+
result = node.accept(self)
|
33
28
|
|
34
|
-
|
29
|
+
mutations.each do |(pattern, mutation)|
|
30
|
+
result = mutation.call(result) if pattern.call(result)
|
35
31
|
end
|
36
32
|
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
visit_methods do
|
37
37
|
# Visit a BEGINBlock node.
|
38
38
|
def visit_BEGIN(node)
|
39
39
|
node.copy(
|