seafoam 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/rubocop.yml +10 -0
  4. data/.github/workflows/specs.yml +19 -0
  5. data/.gitignore +7 -0
  6. data/.rubocop.yml +34 -0
  7. data/.ruby-version +1 -0
  8. data/.seafoam/config +1 -0
  9. data/CODE_OF_CONDUCT.md +128 -0
  10. data/CONTRIBUTING.md +5 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE.md +7 -0
  13. data/README.md +298 -0
  14. data/bin/bgv2isabelle +53 -0
  15. data/bin/bgv2json +42 -0
  16. data/bin/seafoam +24 -0
  17. data/docs/annotators.md +43 -0
  18. data/docs/bgv.md +284 -0
  19. data/docs/getting-graphs.md +47 -0
  20. data/examples/Fib.java +24 -0
  21. data/examples/MatMult.java +39 -0
  22. data/examples/fib-java.bgv +0 -0
  23. data/examples/fib-js.bgv +0 -0
  24. data/examples/fib-ruby.bgv +0 -0
  25. data/examples/fib.js +15 -0
  26. data/examples/fib.rb +15 -0
  27. data/examples/identity.bgv +0 -0
  28. data/examples/identity.rb +13 -0
  29. data/examples/java/Irreducible.j +35 -0
  30. data/examples/java/IrreducibleDecompiled.java +21 -0
  31. data/examples/java/JavaExamples.java +418 -0
  32. data/examples/java/exampleArithOperator.bgv +0 -0
  33. data/examples/java/exampleArithOperator.cfg +925 -0
  34. data/examples/java/exampleArrayAllocation.bgv +0 -0
  35. data/examples/java/exampleArrayAllocation.cfg +5268 -0
  36. data/examples/java/exampleArrayRead.bgv +0 -0
  37. data/examples/java/exampleArrayRead.cfg +2263 -0
  38. data/examples/java/exampleArrayWrite.bgv +0 -0
  39. data/examples/java/exampleArrayWrite.cfg +2315 -0
  40. data/examples/java/exampleCatch.bgv +0 -0
  41. data/examples/java/exampleCatch.cfg +4150 -0
  42. data/examples/java/exampleCompareOperator.bgv +0 -0
  43. data/examples/java/exampleCompareOperator.cfg +1109 -0
  44. data/examples/java/exampleDoubleSynchronized.bgv +0 -0
  45. data/examples/java/exampleDoubleSynchronized.cfg +26497 -0
  46. data/examples/java/exampleExactArith.bgv +0 -0
  47. data/examples/java/exampleExactArith.cfg +1888 -0
  48. data/examples/java/exampleFieldRead.bgv +0 -0
  49. data/examples/java/exampleFieldRead.cfg +1228 -0
  50. data/examples/java/exampleFieldWrite.bgv +0 -0
  51. data/examples/java/exampleFieldWrite.cfg +1102 -0
  52. data/examples/java/exampleFor.bgv +0 -0
  53. data/examples/java/exampleFor.cfg +3936 -0
  54. data/examples/java/exampleFullEscape.bgv +0 -0
  55. data/examples/java/exampleFullEscape.cfg +5893 -0
  56. data/examples/java/exampleIf.bgv +0 -0
  57. data/examples/java/exampleIf.cfg +2462 -0
  58. data/examples/java/exampleIfNeverTaken.bgv +0 -0
  59. data/examples/java/exampleIfNeverTaken.cfg +2476 -0
  60. data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
  61. data/examples/java/exampleInstanceOfManyImpls.cfg +6391 -0
  62. data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
  63. data/examples/java/exampleInstanceOfOneImpl.cfg +2604 -0
  64. data/examples/java/exampleIntSwitch.bgv +0 -0
  65. data/examples/java/exampleIntSwitch.cfg +3121 -0
  66. data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
  67. data/examples/java/exampleInterfaceCallManyImpls.cfg +1358 -0
  68. data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
  69. data/examples/java/exampleInterfaceCallOneImpl.cfg +3859 -0
  70. data/examples/java/exampleLocalInstanceOf.bgv +0 -0
  71. data/examples/java/exampleLocalInstanceOf.cfg +5276 -0
  72. data/examples/java/exampleLocalSynchronized.bgv +0 -0
  73. data/examples/java/exampleLocalSynchronized.cfg +1364 -0
  74. data/examples/java/exampleLocalVariables.bgv +0 -0
  75. data/examples/java/exampleLocalVariables.cfg +1195 -0
  76. data/examples/java/exampleLocalVariablesState.bgv +0 -0
  77. data/examples/java/exampleLocalVariablesState.cfg +1673 -0
  78. data/examples/java/exampleNestedWhile.bgv +0 -0
  79. data/examples/java/exampleNestedWhile.cfg +15499 -0
  80. data/examples/java/exampleNestedWhileBreak.bgv +0 -0
  81. data/examples/java/exampleNestedWhileBreak.cfg +11162 -0
  82. data/examples/java/exampleNoEscape.bgv +0 -0
  83. data/examples/java/exampleNoEscape.cfg +974 -0
  84. data/examples/java/exampleObjectAllocation.bgv +0 -0
  85. data/examples/java/exampleObjectAllocation.cfg +5287 -0
  86. data/examples/java/examplePartialEscape.bgv +0 -0
  87. data/examples/java/examplePartialEscape.cfg +7042 -0
  88. data/examples/java/examplePhi.bgv +0 -0
  89. data/examples/java/examplePhi.cfg +3227 -0
  90. data/examples/java/exampleReducible.bgv +0 -0
  91. data/examples/java/exampleReducible.cfg +5578 -0
  92. data/examples/java/exampleSimpleCall.bgv +0 -0
  93. data/examples/java/exampleSimpleCall.cfg +1435 -0
  94. data/examples/java/exampleStamp.bgv +0 -0
  95. data/examples/java/exampleStamp.cfg +913 -0
  96. data/examples/java/exampleStaticCall.bgv +0 -0
  97. data/examples/java/exampleStaticCall.cfg +1154 -0
  98. data/examples/java/exampleStringSwitch.bgv +0 -0
  99. data/examples/java/exampleStringSwitch.cfg +15377 -0
  100. data/examples/java/exampleSynchronized.bgv +0 -0
  101. data/examples/java/exampleSynchronized.cfg +26027 -0
  102. data/examples/java/exampleThrow.bgv +0 -0
  103. data/examples/java/exampleThrow.cfg +780 -0
  104. data/examples/java/exampleThrowCatch.bgv +0 -0
  105. data/examples/java/exampleThrowCatch.cfg +744 -0
  106. data/examples/java/exampleUnsafeRead.bgv +0 -0
  107. data/examples/java/exampleUnsafeRead.cfg +912 -0
  108. data/examples/java/exampleUnsafeWrite.bgv +0 -0
  109. data/examples/java/exampleUnsafeWrite.cfg +962 -0
  110. data/examples/java/exampleWhile.bgv +0 -0
  111. data/examples/java/exampleWhile.cfg +3936 -0
  112. data/examples/java/exampleWhileBreak.bgv +0 -0
  113. data/examples/java/exampleWhileBreak.cfg +5963 -0
  114. data/examples/matmult-java.bgv +0 -0
  115. data/examples/matmult-ruby.bgv +0 -0
  116. data/examples/matmult.rb +29 -0
  117. data/examples/overflow.bgv +0 -0
  118. data/examples/overflow.rb +13 -0
  119. data/lib/seafoam.rb +13 -0
  120. data/lib/seafoam/annotators.rb +54 -0
  121. data/lib/seafoam/annotators/fallback.rb +27 -0
  122. data/lib/seafoam/annotators/graal.rb +376 -0
  123. data/lib/seafoam/bgv/bgv_parser.rb +602 -0
  124. data/lib/seafoam/binary/binary_reader.rb +21 -0
  125. data/lib/seafoam/binary/io_binary_reader.rb +88 -0
  126. data/lib/seafoam/colors.rb +18 -0
  127. data/lib/seafoam/commands.rb +447 -0
  128. data/lib/seafoam/config.rb +34 -0
  129. data/lib/seafoam/graph.rb +91 -0
  130. data/lib/seafoam/graphviz_writer.rb +213 -0
  131. data/lib/seafoam/spotlight.rb +28 -0
  132. data/lib/seafoam/version.rb +5 -0
  133. data/seafoam.gemspec +20 -0
  134. data/spec/seafoam/annotators/fallback_spec.rb +69 -0
  135. data/spec/seafoam/annotators/graal_spec.rb +96 -0
  136. data/spec/seafoam/annotators_spec.rb +61 -0
  137. data/spec/seafoam/bgv/bgv_parser_spec.rb +144 -0
  138. data/spec/seafoam/bgv/fixtures/not.bgv +1 -0
  139. data/spec/seafoam/bgv/fixtures/unsupported.bgv +1 -0
  140. data/spec/seafoam/binary/io_binary_reader_spec.rb +176 -0
  141. data/spec/seafoam/command_spec.rb +252 -0
  142. data/spec/seafoam/graph_spec.rb +172 -0
  143. data/spec/seafoam/graphviz_writer_spec.rb +63 -0
  144. data/spec/seafoam/spec_helpers.rb +30 -0
  145. data/spec/seafoam/spotlight_spec.rb +38 -0
  146. data/tools/render-all +36 -0
  147. metadata +238 -0
@@ -0,0 +1,21 @@
1
+ require 'stringio'
2
+
3
+ module Seafoam
4
+ module Binary
5
+ # Factory for binary readers based on the input.
6
+ module BinaryReader
7
+ def self.for(source)
8
+ case source
9
+ when File
10
+ IOBinaryReader.new(StringIO.new(File.read(source)))
11
+ when IO
12
+ IOBinaryReader.new(source)
13
+ when String
14
+ IOBinaryReader.new(StringIO.new(source))
15
+ else
16
+ raise source.class
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ module Seafoam
2
+ module Binary
3
+ # An adapter to read binary values from an IO stream.
4
+ class IOBinaryReader
5
+ def initialize(io)
6
+ @io = io
7
+ end
8
+
9
+ def read_utf8(length)
10
+ read_bytes(length).force_encoding Encoding::UTF_8
11
+ end
12
+
13
+ def read_bytes(length)
14
+ @io.read(length)
15
+ end
16
+
17
+ def read_float64
18
+ @io.read(8).unpack1('G')
19
+ end
20
+
21
+ def read_float32
22
+ @io.read(4).unpack1('g')
23
+ end
24
+
25
+ def read_sint64
26
+ @io.read(8).unpack1('q>')
27
+ end
28
+
29
+ def read_sint32
30
+ @io.read(4).unpack1('l>')
31
+ end
32
+
33
+ def read_sint16
34
+ @io.read(2).unpack1('s>')
35
+ end
36
+
37
+ def read_uint16
38
+ @io.read(2).unpack1('S>')
39
+ end
40
+
41
+ def read_sint8
42
+ @io.read(1).unpack1('c')
43
+ end
44
+
45
+ def read_uint8
46
+ @io.readbyte
47
+ end
48
+
49
+ def peek_sint8
50
+ byte = @io.read(1).unpack1('c')
51
+ @io.ungetbyte byte
52
+ byte
53
+ end
54
+
55
+ def skip_float64(count = 1)
56
+ skip count * 8
57
+ end
58
+
59
+ def skip_float32(count = 1)
60
+ skip count * 4
61
+ end
62
+
63
+ def skip_int64(count = 1)
64
+ skip count * 8
65
+ end
66
+
67
+ def skip_int32(count = 1)
68
+ skip count * 4
69
+ end
70
+
71
+ def skip_int16(count = 1)
72
+ skip count * 2
73
+ end
74
+
75
+ def skip_int8(count = 1)
76
+ skip count
77
+ end
78
+
79
+ def skip(count)
80
+ @io.seek count, IO::SEEK_CUR
81
+ end
82
+
83
+ def eof?
84
+ @io.eof?
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,18 @@
1
+ module Seafoam
2
+ # Colors from and derived from the TruffleRuby logo.
3
+
4
+ # TruffleRuby logo Copyright (C) 2017 Talkdesk, Inc.
5
+ # Licensed under CC BY 4.0: https://creativecommons.org/licenses/by/4.0/
6
+
7
+ WHITE_ICE = '#d7ede7'
8
+ CRUISE = '#b8ddd1'
9
+ KEPPEL = '#3cb4a4'
10
+ CARISSMA = '#e98693'
11
+ AMARANTH = '#da2d4f'
12
+ BLACK = '#1a1919'
13
+ WHITE = '#ffffff'
14
+ DUST = '#f9f9f9'
15
+ BIG_STONE = '#343d46'
16
+ ICE_STONE = '#b3bbc3'
17
+ ORANGE = '#ffa500'
18
+ end
@@ -0,0 +1,447 @@
1
+ require 'json'
2
+
3
+ module Seafoam
4
+ # Implementations of the command-line commands that you can run in Seafoam.
5
+ class Commands
6
+ def initialize(out, config)
7
+ @out = out
8
+ @config = config
9
+ end
10
+
11
+ # Run any command.
12
+ def run(*args)
13
+ first, *args = args
14
+ case first
15
+ when nil, 'help', '-h', '--help', '-help'
16
+ help(*args)
17
+ when 'version', '-v', '-version', '--version'
18
+ version(*args)
19
+ else
20
+ name = first
21
+ command, *args = args
22
+ case command
23
+ when nil
24
+ help(*args)
25
+ when 'info'
26
+ info name, *args
27
+ when 'list'
28
+ list name, *args
29
+ when 'search'
30
+ search name, *args
31
+ when 'edges'
32
+ edges name, *args
33
+ when 'props'
34
+ props name, *args
35
+ when 'render'
36
+ render name, *args
37
+ when 'debug'
38
+ debug name, *args
39
+ else
40
+ raise ArgumentError, "unknown command #{command}"
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # seafoam file.bgv info
48
+ def info(name, *args)
49
+ file, *rest = parse_name(name)
50
+ raise ArgumentError, 'info only works with a file' unless rest == [nil, nil, nil]
51
+
52
+ raise ArgumentError, 'info does not take arguments' unless args.empty?
53
+
54
+ parser = BGV::BGVParser.new(File.new(file))
55
+ major, minor = parser.read_file_header(version_check: false)
56
+ @out.puts "BGV #{major}.#{minor}"
57
+ end
58
+
59
+ # seafoam file.bgv list
60
+ def list(name, *args)
61
+ file, *rest = parse_name(name)
62
+ raise ArgumentError, 'list only works with a file' unless rest == [nil, nil, nil]
63
+
64
+ raise ArgumentError, 'list does not take arguments' unless args.empty?
65
+
66
+ parser = BGV::BGVParser.new(File.new(file))
67
+ parser.read_file_header
68
+ parser.skip_document_props
69
+ loop do
70
+ index, = parser.read_graph_preheader
71
+ break unless index
72
+
73
+ graph_header = parser.read_graph_header
74
+ @out.puts "#{file}:#{index} #{parser.graph_name(graph_header)}"
75
+ parser.skip_graph
76
+ end
77
+ end
78
+
79
+ # seafoam file.bgv:n... search term...
80
+ def search(name, *terms)
81
+ file, graph_index, node_id, = parse_name(name)
82
+ raise ArgumentError, 'search only works with a file or graph' if node_id
83
+
84
+ parser = BGV::BGVParser.new(File.new(file))
85
+ parser.read_file_header
86
+ parser.skip_document_props
87
+ loop do
88
+ index, = parser.read_graph_preheader
89
+ break unless index
90
+
91
+ if !graph_index || index == graph_index
92
+ header = parser.read_graph_header
93
+ search_object "#{file}:#{index}", header, terms
94
+ graph = parser.read_graph
95
+ graph.nodes.each_value do |node|
96
+ search_object "#{file}:#{index}:#{node.id}", node.props, terms
97
+ end
98
+ graph.edges.each do |edge|
99
+ search_object "#{file}:#{index}:#{edge.from.id}-#{edge.to.id}", edge.props, terms
100
+ end
101
+ else
102
+ parser.skip_graph_header
103
+ parser.skip_graph
104
+ end
105
+ end
106
+ end
107
+
108
+ def search_object(tag, object, terms)
109
+ full_text = JSON.generate(object)
110
+ full_text_down = full_text.downcase
111
+ start = 0
112
+ terms.each do |t|
113
+ loop do
114
+ index = full_text_down.index(t.downcase, start)
115
+ break unless index
116
+
117
+ context = 40
118
+ before = full_text[index - context, context]
119
+ match = full_text[index, t.size]
120
+ after = full_text[index + t.size, context]
121
+ if @out.tty?
122
+ highlight_on = "\033[1m"
123
+ highlight_off = "\033[0m"
124
+ else
125
+ highlight_on = ''
126
+ highlight_off = ''
127
+ end
128
+ @out.puts "#{tag} ...#{before}#{highlight_on}#{match}#{highlight_off}#{after}..."
129
+ start = index + t.size
130
+ end
131
+ end
132
+ end
133
+
134
+ # seafoam file.bgv:n... edges
135
+ def edges(name, *args)
136
+ file, graph_index, node_id, edge_id = parse_name(name)
137
+ raise ArgumentError, 'edges needs at least a graph' unless graph_index
138
+
139
+ raise ArgumentError, 'edges does not take arguments' unless args.empty?
140
+
141
+ with_graph(file, graph_index) do |parser|
142
+ parser.read_graph_header
143
+ graph = parser.read_graph
144
+ if node_id
145
+ Annotators.apply graph
146
+ node = graph.nodes[node_id]
147
+ raise ArgumentError, 'node not found' unless node
148
+
149
+ if edge_id
150
+ to = graph.nodes[edge_id]
151
+ raise ArgumentError, 'edge node not found' unless to
152
+
153
+ edges = node.outputs.select { |edge| edge.to == to }
154
+ raise ArgumentError, 'edge not found' if edges.empty?
155
+
156
+ edges.each do |edge|
157
+ @out.puts "#{edge.from.id_and_label} ->(#{edge.props[:label]}) #{edge.to.id_and_label}"
158
+ end
159
+ else
160
+ @out.puts 'Input:'
161
+ node.inputs.each do |input|
162
+ @out.puts " #{node.id_and_label} <-(#{input.props[:label]}) #{input.from.id_and_label}"
163
+ end
164
+ @out.puts 'Output:'
165
+ node.outputs.each do |output|
166
+ @out.puts " #{node.id_and_label} ->(#{output.props[:label]}) #{output.to.id_and_label}"
167
+ end
168
+ end
169
+ break
170
+ else
171
+ @out.puts "#{graph.nodes.count} nodes, #{graph.edges.count} edges"
172
+ end
173
+ end
174
+ end
175
+
176
+ # seafoam file.bgv... props
177
+ def props(name, *args)
178
+ file, graph_index, node_id, edge_id = parse_name(name)
179
+ raise ArgumentError, 'props does not take arguments' unless args.empty?
180
+
181
+ if graph_index
182
+ with_graph(file, graph_index) do |parser|
183
+ graph_header = parser.read_graph_header
184
+ if node_id
185
+ graph = parser.read_graph
186
+ node = graph.nodes[node_id]
187
+ raise ArgumentError, 'node not found' unless node
188
+
189
+ if edge_id
190
+ to = graph.nodes[edge_id]
191
+ raise ArgumentError, 'edge node not found' unless to
192
+
193
+ edges = node.outputs.select { |edge| edge.to == to }
194
+ raise ArgumentError, 'edge not found' if edges.empty?
195
+
196
+ if edges.size > 1
197
+ edges.each do |edge|
198
+ pretty_print edge.props
199
+ @out.puts
200
+ end
201
+ else
202
+ pretty_print edges.first.props
203
+ end
204
+ else
205
+ pretty_print node.props
206
+ end
207
+ break
208
+ else
209
+ pretty_print graph_header
210
+ parser.skip_graph
211
+ end
212
+ end
213
+ else
214
+ parser = BGV::BGVParser.new(File.new(file))
215
+ parser.read_file_header
216
+ document_props = parser.read_document_props
217
+ pretty_print document_props || {}
218
+ end
219
+ end
220
+
221
+ # seafoam file.bgv:0 render options...
222
+ def render(name, *args)
223
+ file, graph_index, *rest = parse_name(name)
224
+ raise ArgumentError, 'render needs at least a graph' unless graph_index
225
+ raise ArgumentError, 'render only works with a graph' unless rest == [nil, nil]
226
+
227
+ annotator_options = {
228
+ hide_frame_state: true,
229
+ hide_floating: false,
230
+ reduce_edges: true
231
+ }
232
+ spotlight_nodes = nil
233
+ args = args.dup
234
+ out_file = nil
235
+ explicit_out_file = false
236
+ until args.empty?
237
+ arg = args.shift
238
+ case arg
239
+ when '--out'
240
+ out_file = args.shift
241
+ explicit_out_file = true
242
+ raise ArgumentError, 'no file for --out' unless out_file
243
+ when '--spotlight'
244
+ spotlight_arg = args.shift
245
+ raise ArgumentError, 'no list for --spotlight' unless spotlight_arg
246
+
247
+ spotlight_nodes = spotlight_arg.split(',').map { |n| Integer(n) }
248
+ when '--show-frame-state'
249
+ annotator_options[:hide_frame_state] = false
250
+ when '--hide-floating'
251
+ annotator_options[:hide_floating] = true
252
+ when '--no-reduce-edges'
253
+ annotator_options[:reduce_edges] = false
254
+ when '--option'
255
+ key = args.shift
256
+ raise ArgumentError, 'no key for --option' unless key
257
+
258
+ value = args.shift
259
+ raise ArgumentError, "no value for --option #{key}" unless out_file
260
+
261
+ value = { 'true' => true, 'false' => 'false' }.fetch(key, value)
262
+ annotator_options[key.to_sym] = value
263
+ else
264
+ raise ArgumentError, "unexpected option #{arg}"
265
+ end
266
+ end
267
+ out_file ||= 'graph.pdf'
268
+ out_ext = File.extname(out_file).downcase
269
+ case out_ext
270
+ when '.pdf'
271
+ out_format = :pdf
272
+ when '.svg'
273
+ out_format = :svg
274
+ when '.png'
275
+ out_format = :png
276
+ when '.dot'
277
+ out_format = :dot
278
+ else
279
+ raise ArgumentError, "unknown render format #{out_ext}"
280
+ end
281
+
282
+ with_graph(file, graph_index) do |parser|
283
+ parser.skip_graph_header
284
+ graph = parser.read_graph
285
+ Annotators.apply graph, annotator_options
286
+ if spotlight_nodes
287
+ spotlight = Spotlight.new(graph)
288
+ spotlight_nodes.each do |node_id|
289
+ node = graph.nodes[node_id]
290
+ raise ArgumentError, 'node not found' unless node
291
+
292
+ spotlight.light node
293
+ end
294
+ spotlight.shade
295
+ end
296
+ if out_format == :dot
297
+ File.open(out_file, 'w') do |stream|
298
+ writer = GraphvizWriter.new(stream)
299
+ writer.write_graph graph
300
+ end
301
+ else
302
+ IO.popen(['dot', "-T#{out_format}", '-o', out_file], 'w') do |stream|
303
+ writer = GraphvizWriter.new(stream)
304
+ hidpi = out_format == :png
305
+ writer.write_graph graph, hidpi
306
+ end
307
+ autoopen out_file unless explicit_out_file
308
+ end
309
+ end
310
+ end
311
+
312
+ # seafoam file.bgv debug options...
313
+ def debug(name, *args)
314
+ file, *rest = parse_name(name)
315
+ raise ArgumentError, 'debug only works with a file' unless rest == [nil, nil, nil]
316
+
317
+ skip = false
318
+ args.each do |arg|
319
+ case arg
320
+ when '--skip'
321
+ skip = true
322
+ else
323
+ raise ArgumentError, "unexpected option #{arg}"
324
+ end
325
+ end
326
+
327
+ File.open(file) do |stream|
328
+ parser = BGVDebugParser.new(@out, stream)
329
+ begin
330
+ pretty_print parser.read_file_header
331
+ document_props = parser.read_document_props
332
+ if document_props
333
+ pretty_print document_props
334
+ end
335
+ loop do
336
+ index, id = parser.read_graph_preheader
337
+ break unless index
338
+
339
+ @out.puts "graph #{index}, id=#{id}"
340
+ if skip
341
+ parser.skip_graph_header
342
+ parser.skip_graph
343
+ else
344
+ pretty_print parser.read_graph_header
345
+ pretty_print parser.read_graph
346
+ end
347
+ end
348
+ rescue StandardError => e
349
+ @out.puts "#{e} before byte #{stream.tell}"
350
+ @out.puts e.backtrace
351
+ end
352
+ end
353
+ end
354
+
355
+ # A subclass of BGVParser which prints when pool entries are added.
356
+ class BGVDebugParser < BGV::BGVParser
357
+ def initialize(out, *args)
358
+ super(*args)
359
+ @out = out
360
+ end
361
+
362
+ def set_pool_entry(id, object)
363
+ @out.puts "pool #{id} = #{object}"
364
+ super
365
+ end
366
+ end
367
+
368
+ # Reads a file and yields just the graph requested by the index - skipping
369
+ # the rest of the file as best as possible.
370
+ def with_graph(file, graph_index)
371
+ parser = BGV::BGVParser.new(File.new(file))
372
+ parser.read_file_header
373
+ parser.skip_document_props
374
+ graph_found = false
375
+ loop do
376
+ index, = parser.read_graph_preheader
377
+ break unless index
378
+
379
+ if index == graph_index
380
+ graph_found = true
381
+ yield parser
382
+ break
383
+ else
384
+ parser.skip_graph_header
385
+ parser.skip_graph
386
+ end
387
+ end
388
+ raise ArgumentError, 'graph not found' unless graph_found
389
+ end
390
+
391
+ # Prints help.
392
+ def help(*args)
393
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
394
+
395
+ @out.puts 'seafoam file.bgv info'
396
+ @out.puts ' file.bgv list'
397
+ @out.puts ' file.bgv[:graph][:node[-edge]] search term...'
398
+ @out.puts ' file.bgv[:graph][:node[-edge]] edges'
399
+ @out.puts ' file.bgv[:graph][:node[-edge]] props'
400
+ @out.puts ' file.bgv:graph render'
401
+ @out.puts ' --spotlight n,n,n...'
402
+ @out.puts ' --out graph.pdf'
403
+ @out.puts ' graph.svg'
404
+ @out.puts ' graph.png'
405
+ @out.puts ' graph.dot'
406
+ @out.puts ' --show-frame-state'
407
+ @out.puts ' --hide-floating'
408
+ @out.puts ' --no-reduce-edges'
409
+ @out.puts ' --option key value'
410
+ end
411
+
412
+ # Prints the version.
413
+ def version(*args)
414
+ raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
415
+
416
+ @out.puts "seafoam #{VERSION}"
417
+ end
418
+
419
+ # Parse a name like file.bgv:g:n-e to [file.bgv, g, n, e].
420
+ def parse_name(name)
421
+ *pre, file, graph, node = name.split(':')
422
+ file = [*pre, file].join(':')
423
+
424
+ if node
425
+ node, edge, *rest = node.split('-')
426
+ raise ArgumentError, "too many parts to edge name in #{name}" unless rest.empty?
427
+ else
428
+ node = nil
429
+ edge = nil
430
+ end
431
+ [file] + [graph, node, edge].map { |i| i.nil? ? nil : Integer(i) }
432
+ end
433
+
434
+ # Pretty-print a JSON-style object.
435
+ def pretty_print(props)
436
+ @out.puts JSON.pretty_generate(props)
437
+ end
438
+
439
+ # Open a file for the user if possible.
440
+ def autoopen(file)
441
+ if RUBY_PLATFORM.include?('darwin') && @out.tty?
442
+ system 'open', file
443
+ # Don't worry if it fails.
444
+ end
445
+ end
446
+ end
447
+ end