syntax_tree 5.3.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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(
|