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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -1
  3. data/CHANGELOG.md +64 -1
  4. data/Gemfile.lock +2 -2
  5. data/README.md +28 -9
  6. data/Rakefile +12 -8
  7. data/bin/console +1 -0
  8. data/bin/whitequark +79 -0
  9. data/doc/changing_structure.md +16 -0
  10. data/lib/syntax_tree/basic_visitor.rb +44 -5
  11. data/lib/syntax_tree/cli.rb +2 -2
  12. data/lib/syntax_tree/dsl.rb +23 -11
  13. data/lib/syntax_tree/{visitor/field_visitor.rb → field_visitor.rb} +54 -55
  14. data/lib/syntax_tree/formatter.rb +1 -1
  15. data/lib/syntax_tree/index.rb +56 -54
  16. data/lib/syntax_tree/json_visitor.rb +55 -0
  17. data/lib/syntax_tree/language_server.rb +157 -2
  18. data/lib/syntax_tree/match_visitor.rb +120 -0
  19. data/lib/syntax_tree/mermaid.rb +177 -0
  20. data/lib/syntax_tree/mermaid_visitor.rb +69 -0
  21. data/lib/syntax_tree/{visitor/mutation_visitor.rb → mutation_visitor.rb} +27 -27
  22. data/lib/syntax_tree/node.rb +198 -107
  23. data/lib/syntax_tree/parser.rb +322 -118
  24. data/lib/syntax_tree/pretty_print_visitor.rb +83 -0
  25. data/lib/syntax_tree/reflection.rb +241 -0
  26. data/lib/syntax_tree/translation/parser.rb +3019 -0
  27. data/lib/syntax_tree/translation/rubocop_ast.rb +21 -0
  28. data/lib/syntax_tree/translation.rb +28 -0
  29. data/lib/syntax_tree/version.rb +1 -1
  30. data/lib/syntax_tree/with_scope.rb +244 -0
  31. data/lib/syntax_tree/yarv/basic_block.rb +53 -0
  32. data/lib/syntax_tree/yarv/calldata.rb +91 -0
  33. data/lib/syntax_tree/yarv/compiler.rb +110 -100
  34. data/lib/syntax_tree/yarv/control_flow_graph.rb +257 -0
  35. data/lib/syntax_tree/yarv/data_flow_graph.rb +338 -0
  36. data/lib/syntax_tree/yarv/decompiler.rb +1 -1
  37. data/lib/syntax_tree/yarv/disassembler.rb +104 -80
  38. data/lib/syntax_tree/yarv/instruction_sequence.rb +43 -18
  39. data/lib/syntax_tree/yarv/instructions.rb +203 -649
  40. data/lib/syntax_tree/yarv/legacy.rb +12 -24
  41. data/lib/syntax_tree/yarv/sea_of_nodes.rb +534 -0
  42. data/lib/syntax_tree/yarv.rb +18 -0
  43. data/lib/syntax_tree.rb +88 -56
  44. data/tasks/sorbet.rake +277 -0
  45. data/tasks/whitequark.rake +87 -0
  46. metadata +23 -11
  47. data/.gitmodules +0 -9
  48. data/lib/syntax_tree/language_server/inlay_hints.rb +0 -159
  49. data/lib/syntax_tree/visitor/environment.rb +0 -84
  50. data/lib/syntax_tree/visitor/json_visitor.rb +0 -55
  51. data/lib/syntax_tree/visitor/match_visitor.rb +0 -122
  52. data/lib/syntax_tree/visitor/pretty_print_visitor.rb +0 -85
  53. 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