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,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
|