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,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# This visitor pretty-prints the AST into an equivalent s-expression.
|
5
|
+
class PrettyPrintVisitor < FieldVisitor
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(q)
|
9
|
+
@q = q
|
10
|
+
end
|
11
|
+
|
12
|
+
# This is here because we need to make sure the operator is cast to a string
|
13
|
+
# before we print it out.
|
14
|
+
def visit_binary(node)
|
15
|
+
node(node, "binary") do
|
16
|
+
field("left", node.left)
|
17
|
+
text("operator", node.operator.to_s)
|
18
|
+
field("right", node.right)
|
19
|
+
comments(node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This is here to make it a little nicer to look at labels since they
|
24
|
+
# typically have their : at the end of the value.
|
25
|
+
def visit_label(node)
|
26
|
+
node(node, "label") do
|
27
|
+
q.breakable
|
28
|
+
q.text(":")
|
29
|
+
q.text(node.value[0...-1])
|
30
|
+
comments(node)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def comments(node)
|
37
|
+
return if node.comments.empty?
|
38
|
+
|
39
|
+
q.breakable
|
40
|
+
q.group(2, "(", ")") do
|
41
|
+
q.seplist(node.comments) { |comment| q.pp(comment) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def field(_name, value)
|
46
|
+
q.breakable
|
47
|
+
q.pp(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def list(_name, values)
|
51
|
+
q.breakable
|
52
|
+
q.group(2, "(", ")") { q.seplist(values) { |value| q.pp(value) } }
|
53
|
+
end
|
54
|
+
|
55
|
+
def node(_node, type)
|
56
|
+
q.group(2, "(", ")") do
|
57
|
+
q.text(type)
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def pairs(_name, values)
|
63
|
+
q.group(2, "(", ")") do
|
64
|
+
q.seplist(values) do |(key, value)|
|
65
|
+
q.pp(key)
|
66
|
+
|
67
|
+
if value
|
68
|
+
q.text("=")
|
69
|
+
q.group(2) do
|
70
|
+
q.breakable("")
|
71
|
+
q.pp(value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def text(_name, value)
|
79
|
+
q.breakable
|
80
|
+
q.text(value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
# This module is used to provide some reflection on the various types of nodes
|
5
|
+
# and their attributes. As soon as it is required it collects all of its
|
6
|
+
# information.
|
7
|
+
module Reflection
|
8
|
+
# This module represents the type of the values being passed to attributes
|
9
|
+
# of nodes. It is used as part of the documentation of the attributes.
|
10
|
+
module Type
|
11
|
+
CONSTANTS = SyntaxTree.constants.to_h { [_1, SyntaxTree.const_get(_1)] }
|
12
|
+
|
13
|
+
# Represents an array type that holds another type.
|
14
|
+
class ArrayType
|
15
|
+
attr_reader :type
|
16
|
+
|
17
|
+
def initialize(type)
|
18
|
+
@type = type
|
19
|
+
end
|
20
|
+
|
21
|
+
def ===(value)
|
22
|
+
value.is_a?(Array) && value.all? { type === _1 }
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"Array<#{type.inspect}>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Represents a tuple type that holds a number of types in order.
|
31
|
+
class TupleType
|
32
|
+
attr_reader :types
|
33
|
+
|
34
|
+
def initialize(types)
|
35
|
+
@types = types
|
36
|
+
end
|
37
|
+
|
38
|
+
def ===(value)
|
39
|
+
value.is_a?(Array) && value.length == types.length &&
|
40
|
+
value.zip(types).all? { |item, type| type === item }
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"[#{types.map(&:inspect).join(", ")}]"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Represents a union type that can be one of a number of types.
|
49
|
+
class UnionType
|
50
|
+
attr_reader :types
|
51
|
+
|
52
|
+
def initialize(types)
|
53
|
+
@types = types
|
54
|
+
end
|
55
|
+
|
56
|
+
def ===(value)
|
57
|
+
types.any? { _1 === value }
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
types.map(&:inspect).join(" | ")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
def parse(comment)
|
67
|
+
comment = comment.gsub(/\n/, " ")
|
68
|
+
|
69
|
+
unless comment.start_with?("[")
|
70
|
+
raise "Comment does not start with a bracket: #{comment.inspect}"
|
71
|
+
end
|
72
|
+
|
73
|
+
count = 1
|
74
|
+
found =
|
75
|
+
comment.chars[1..]
|
76
|
+
.find
|
77
|
+
.with_index(1) do |char, index|
|
78
|
+
count += { "[" => 1, "]" => -1 }.fetch(char, 0)
|
79
|
+
break index if count == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
# If we weren't able to find the end of the balanced brackets, then
|
83
|
+
# the comment is malformed.
|
84
|
+
if found.nil?
|
85
|
+
raise "Comment does not have balanced brackets: #{comment.inspect}"
|
86
|
+
end
|
87
|
+
|
88
|
+
parse_type(comment[1...found].strip)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def parse_type(value)
|
94
|
+
case value
|
95
|
+
when "Integer"
|
96
|
+
Integer
|
97
|
+
when "String"
|
98
|
+
String
|
99
|
+
when "Symbol"
|
100
|
+
Symbol
|
101
|
+
when "boolean"
|
102
|
+
UnionType.new([TrueClass, FalseClass])
|
103
|
+
when "nil"
|
104
|
+
NilClass
|
105
|
+
when ":\"::\""
|
106
|
+
:"::"
|
107
|
+
when ":call"
|
108
|
+
:call
|
109
|
+
when ":nil"
|
110
|
+
:nil
|
111
|
+
when /\AArray\[(.+)\]\z/
|
112
|
+
ArrayType.new(parse_type($1.strip))
|
113
|
+
when /\A\[(.+)\]\z/
|
114
|
+
TupleType.new($1.strip.split(/\s*,\s*/).map { parse_type(_1) })
|
115
|
+
else
|
116
|
+
if value.include?("|")
|
117
|
+
UnionType.new(value.split(/\s*\|\s*/).map { parse_type(_1) })
|
118
|
+
else
|
119
|
+
CONSTANTS.fetch(value.to_sym)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# This class represents one of the attributes on a node in the tree.
|
127
|
+
class Attribute
|
128
|
+
attr_reader :name, :comment, :type
|
129
|
+
|
130
|
+
def initialize(name, comment)
|
131
|
+
@name = name
|
132
|
+
@comment = comment
|
133
|
+
@type = Type.parse(comment)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# This class represents one of our nodes in the tree. We're going to use it
|
138
|
+
# as a placeholder for collecting all of the various places that nodes are
|
139
|
+
# used.
|
140
|
+
class Node
|
141
|
+
attr_reader :name, :comment, :attributes
|
142
|
+
|
143
|
+
def initialize(name, comment, attributes)
|
144
|
+
@name = name
|
145
|
+
@comment = comment
|
146
|
+
@attributes = attributes
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class << self
|
151
|
+
# This is going to hold a hash of all of the nodes in the tree. The keys
|
152
|
+
# are the names of the nodes as symbols.
|
153
|
+
attr_reader :nodes
|
154
|
+
|
155
|
+
# This expects a node name as a symbol and returns the node object for
|
156
|
+
# that node.
|
157
|
+
def node(name)
|
158
|
+
nodes.fetch(name)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def parse_comments(statements, index)
|
164
|
+
statements[0...index]
|
165
|
+
.reverse_each
|
166
|
+
.take_while { _1.is_a?(SyntaxTree::Comment) }
|
167
|
+
.reverse_each
|
168
|
+
.map { _1.value[2..] }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
@nodes = {}
|
173
|
+
|
174
|
+
# For each node, we're going to parse out its attributes and other metadata.
|
175
|
+
# We'll use this as the basis for our report.
|
176
|
+
program =
|
177
|
+
SyntaxTree.parse(SyntaxTree.read(File.expand_path("node.rb", __dir__)))
|
178
|
+
|
179
|
+
main_statements = program.statements.body.last.bodystmt.statements.body
|
180
|
+
main_statements.each_with_index do |main_statement, main_statement_index|
|
181
|
+
# Ensure we are only looking at class declarations.
|
182
|
+
next unless main_statement.is_a?(SyntaxTree::ClassDeclaration)
|
183
|
+
|
184
|
+
# Ensure we're looking at class declarations with superclasses.
|
185
|
+
next unless main_statement.superclass.is_a?(SyntaxTree::VarRef)
|
186
|
+
|
187
|
+
# Ensure we're looking at class declarations that inherit from Node.
|
188
|
+
next unless main_statement.superclass.value.value == "Node"
|
189
|
+
|
190
|
+
# All child nodes inherit the location attr_reader from Node, so we'll add
|
191
|
+
# that to the list of attributes first.
|
192
|
+
attributes = {
|
193
|
+
location:
|
194
|
+
Attribute.new(:location, "[Location] the location of this node")
|
195
|
+
}
|
196
|
+
|
197
|
+
statements = main_statement.bodystmt.statements.body
|
198
|
+
statements.each_with_index do |statement, statement_index|
|
199
|
+
case statement
|
200
|
+
when SyntaxTree::Command
|
201
|
+
# We only use commands in node classes to define attributes. So, we
|
202
|
+
# can safely assume that we're looking at an attribute definition.
|
203
|
+
unless %w[attr_reader attr_accessor].include?(statement.message.value)
|
204
|
+
raise "Unexpected command: #{statement.message.value.inspect}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# The arguments to the command are the attributes that we're defining.
|
208
|
+
# We want to ensure that we're only defining one at a time.
|
209
|
+
if statement.arguments.parts.length != 1
|
210
|
+
raise "Declaring more than one attribute at a time is not permitted"
|
211
|
+
end
|
212
|
+
|
213
|
+
attribute =
|
214
|
+
Attribute.new(
|
215
|
+
statement.arguments.parts.first.value.value.to_sym,
|
216
|
+
"#{parse_comments(statements, statement_index).join("\n")}\n"
|
217
|
+
)
|
218
|
+
|
219
|
+
# Ensure that we don't already have an attribute named the same as
|
220
|
+
# this one, and then add it to the list of attributes.
|
221
|
+
if attributes.key?(attribute.name)
|
222
|
+
raise "Duplicate attribute: #{attribute.name}"
|
223
|
+
end
|
224
|
+
|
225
|
+
attributes[attribute.name] = attribute
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Finally, set it up in the hash of nodes so that we can use it later.
|
230
|
+
comments = parse_comments(main_statements, main_statement_index)
|
231
|
+
node =
|
232
|
+
Node.new(
|
233
|
+
main_statement.constant.constant.value.to_sym,
|
234
|
+
"#{comments.join("\n")}\n",
|
235
|
+
attributes
|
236
|
+
)
|
237
|
+
|
238
|
+
@nodes[node.name] = node
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|