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