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,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
- class Visitor
5
- # This visitor walks through the tree and copies each node as it is being
6
- # visited. This is useful for mutating the tree before it is formatted.
7
- class MutationVisitor < BasicVisitor
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
- def initialize
11
- @mutations = []
12
- end
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
- # This is the base visit method for each node in the tree. It first
24
- # creates a copy of the node using the visit_* methods defined below. Then
25
- # it checks each mutation in sequence and calls it if it finds a match.
26
- def visit(node)
27
- return unless node
28
- result = node.accept(self)
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
- mutations.each do |(pattern, mutation)|
31
- result = mutation.call(result) if pattern.call(result)
32
- end
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
- result
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(