Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4079147

Browse files
authored
Merge pull request #290 from ruby-syntax-tree/mermaid
Mermaid
2 parents e8e043c + e642348 commit 4079147

File tree

7 files changed

+224
-5
lines changed

7 files changed

+224
-5
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ Style/CaseLikeIf:
9090
Style/ClassVars:
9191
Enabled: false
9292

93+
Style/CombinableLoops:
94+
Enabled: false
95+
9396
Style/DocumentDynamicEvalDefinition:
9497
Enabled: false
9598

lib/syntax_tree.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require "cgi"
34
require "etc"
45
require "fiddle"
56
require "json"
@@ -18,6 +19,7 @@
1819
require_relative "syntax_tree/visitor/field_visitor"
1920
require_relative "syntax_tree/visitor/json_visitor"
2021
require_relative "syntax_tree/visitor/match_visitor"
22+
require_relative "syntax_tree/visitor/mermaid_visitor"
2123
require_relative "syntax_tree/visitor/mutation_visitor"
2224
require_relative "syntax_tree/visitor/pretty_print_visitor"
2325
require_relative "syntax_tree/visitor/environment"

lib/syntax_tree/node.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,19 @@ def format(q)
127127
end
128128

129129
def pretty_print(q)
130-
visitor = Visitor::PrettyPrintVisitor.new(q)
131-
visitor.visit(self)
130+
accept(Visitor::PrettyPrintVisitor.new(q))
132131
end
133132

134133
def to_json(*opts)
135-
visitor = Visitor::JSONVisitor.new
136-
visitor.visit(self).to_json(*opts)
134+
accept(Visitor::JSONVisitor.new).to_json(*opts)
135+
end
136+
137+
def to_mermaid
138+
accept(Visitor::MermaidVisitor.new)
137139
end
138140

139141
def construct_keys
140-
PrettierPrint.format(+"") { |q| Visitor::MatchVisitor.new(q).visit(self) }
142+
PrettierPrint.format(+"") { |q| accept(Visitor::MatchVisitor.new(q)) }
141143
end
142144
end
143145

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
module SyntaxTree
4+
class Visitor
5+
# This visitor transforms the AST into a mermaid flow chart.
6+
class MermaidVisitor < FieldVisitor
7+
attr_reader :output, :target
8+
9+
def initialize
10+
@output = StringIO.new
11+
@output.puts("flowchart TD")
12+
13+
@target = nil
14+
end
15+
16+
def visit_program(node)
17+
super
18+
output.string
19+
end
20+
21+
private
22+
23+
def comments(node)
24+
# Ignore
25+
end
26+
27+
def field(name, value)
28+
case value
29+
when Node
30+
node_id = visit(value)
31+
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
32+
when String
33+
node_id = "#{target}_#{name}"
34+
output.puts(" #{node_id}([#{CGI.escapeHTML(value.inspect)}])")
35+
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
36+
when nil
37+
# skip
38+
else
39+
node_id = "#{target}_#{name}"
40+
output.puts(" #{node_id}([\"#{CGI.escapeHTML(value.inspect)}\"])")
41+
output.puts(" #{target} -- \"#{name}\" --> #{node_id}")
42+
end
43+
end
44+
45+
def list(name, values)
46+
values.each_with_index do |value, index|
47+
field("#{name}[#{index}]", value)
48+
end
49+
end
50+
51+
def node(node, type)
52+
previous_target = target
53+
54+
begin
55+
@target = "node_#{node.object_id}"
56+
57+
yield
58+
59+
output.puts(" #{@target}[\"#{type}\"]")
60+
@target
61+
ensure
62+
@target = previous_target
63+
end
64+
end
65+
66+
def pairs(name, values)
67+
values.each_with_index do |(key, value), index|
68+
node_id = "#{target}_#{name}_#{index}"
69+
output.puts(" #{node_id}((\"&nbsp;\"))")
70+
output.puts(" #{target} -- \"#{name}[#{index}]\" --> #{node_id}")
71+
output.puts(" #{node_id} -- \"[0]\" --> #{visit(key)}")
72+
output.puts(" #{node_id} -- \"[1]\" --> #{visit(value)}") if value
73+
end
74+
end
75+
76+
def text(name, value)
77+
field(name, value)
78+
end
79+
end
80+
end
81+
end

lib/syntax_tree/yarv/control_flow_graph.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,41 @@ def disasm
5555
fmt.string
5656
end
5757

58+
def to_mermaid
59+
output = StringIO.new
60+
output.puts("flowchart TD")
61+
62+
fmt = Disassembler::Mermaid.new
63+
blocks.each do |block|
64+
output.puts(" subgraph #{block.id}")
65+
previous = nil
66+
67+
block.each_with_length do |insn, length|
68+
node_id = "node_#{length}"
69+
label = "%04d %s" % [length, insn.disasm(fmt)]
70+
71+
output.puts(" #{node_id}(\"#{CGI.escapeHTML(label)}\")")
72+
output.puts(" #{previous} --> #{node_id}") if previous
73+
74+
previous = node_id
75+
end
76+
77+
output.puts(" end")
78+
end
79+
80+
blocks.each do |block|
81+
block.outgoing_blocks.each do |outgoing|
82+
offset =
83+
block.block_start + block.insns.sum(&:length) -
84+
block.insns.last.length
85+
86+
output.puts(" node_#{offset} --> node_#{outgoing.block_start}")
87+
end
88+
end
89+
90+
output.string
91+
end
92+
5893
# This method is used to verify that the control flow graph is well
5994
# formed. It does this by checking that each basic block is itself well
6095
# formed.

lib/syntax_tree/yarv/data_flow_graph.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,67 @@ def disasm
8080
fmt.string
8181
end
8282

83+
def to_mermaid
84+
output = StringIO.new
85+
output.puts("flowchart TD")
86+
87+
fmt = Disassembler::Mermaid.new
88+
links = []
89+
90+
cfg.blocks.each do |block|
91+
block_flow = block_flows.fetch(block.id)
92+
graph_name =
93+
if block_flow.in.any?
94+
"#{block.id} #{block_flows[block.id].in.join(", ")}"
95+
else
96+
block.id
97+
end
98+
99+
output.puts(" subgraph \"#{CGI.escapeHTML(graph_name)}\"")
100+
previous = nil
101+
102+
block.each_with_length do |insn, length|
103+
node_id = "node_#{length}"
104+
label = "%04d %s" % [length, insn.disasm(fmt)]
105+
106+
output.puts(" #{node_id}(\"#{CGI.escapeHTML(label)}\")")
107+
108+
if previous
109+
output.puts(" #{previous} --> #{node_id}")
110+
links << "red"
111+
end
112+
113+
insn_flows[length].in.each do |input|
114+
if input.is_a?(Integer)
115+
output.puts(" node_#{input} --> #{node_id}")
116+
links << "green"
117+
end
118+
end
119+
120+
previous = node_id
121+
end
122+
123+
output.puts(" end")
124+
end
125+
126+
cfg.blocks.each do |block|
127+
block.outgoing_blocks.each do |outgoing|
128+
offset =
129+
block.block_start + block.insns.sum(&:length) -
130+
block.insns.last.length
131+
132+
output.puts(" node_#{offset} --> node_#{outgoing.block_start}")
133+
links << "red"
134+
end
135+
end
136+
137+
links.each_with_index do |color, index|
138+
output.puts(" linkStyle #{index} stroke:#{color}")
139+
end
140+
141+
output.string
142+
end
143+
83144
# Verify that we constructed the data flow graph correctly.
84145
def verify
85146
# Check that the first block has no arguments.

lib/syntax_tree/yarv/disassembler.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,41 @@
33
module SyntaxTree
44
module YARV
55
class Disassembler
6+
# This class is another object that handles disassembling a YARV
7+
# instruction sequence but it does so in order to provide a label for a
8+
# mermaid diagram.
9+
class Mermaid
10+
def calldata(value)
11+
value.inspect
12+
end
13+
14+
def enqueue(iseq)
15+
end
16+
17+
def event(name)
18+
end
19+
20+
def inline_storage(cache)
21+
"<is:#{cache}>"
22+
end
23+
24+
def instruction(name, operands = [])
25+
operands.empty? ? name : "#{name} #{operands.join(", ")}"
26+
end
27+
28+
def label(value)
29+
"%04d" % value.name["label_".length..]
30+
end
31+
32+
def local(index, **)
33+
index.inspect
34+
end
35+
36+
def object(value)
37+
value.inspect
38+
end
39+
end
40+
641
attr_reader :output, :queue
742

843
attr_reader :current_prefix

0 commit comments

Comments
 (0)