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