seafoam 0.2
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.
- checksums.yaml +7 -0
- data/.github/probots.yml +2 -0
- data/.github/workflows/rubocop.yml +10 -0
- data/.github/workflows/specs.yml +19 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.seafoam/config +1 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +5 -0
- data/Gemfile +2 -0
- data/LICENSE.md +7 -0
- data/README.md +298 -0
- data/bin/bgv2isabelle +53 -0
- data/bin/bgv2json +42 -0
- data/bin/seafoam +24 -0
- data/docs/annotators.md +43 -0
- data/docs/bgv.md +284 -0
- data/docs/getting-graphs.md +47 -0
- data/examples/Fib.java +24 -0
- data/examples/MatMult.java +39 -0
- data/examples/fib-java.bgv +0 -0
- data/examples/fib-js.bgv +0 -0
- data/examples/fib-ruby.bgv +0 -0
- data/examples/fib.js +15 -0
- data/examples/fib.rb +15 -0
- data/examples/identity.bgv +0 -0
- data/examples/identity.rb +13 -0
- data/examples/java/Irreducible.j +35 -0
- data/examples/java/IrreducibleDecompiled.java +21 -0
- data/examples/java/JavaExamples.java +418 -0
- data/examples/java/exampleArithOperator.bgv +0 -0
- data/examples/java/exampleArithOperator.cfg +925 -0
- data/examples/java/exampleArrayAllocation.bgv +0 -0
- data/examples/java/exampleArrayAllocation.cfg +5268 -0
- data/examples/java/exampleArrayRead.bgv +0 -0
- data/examples/java/exampleArrayRead.cfg +2263 -0
- data/examples/java/exampleArrayWrite.bgv +0 -0
- data/examples/java/exampleArrayWrite.cfg +2315 -0
- data/examples/java/exampleCatch.bgv +0 -0
- data/examples/java/exampleCatch.cfg +4150 -0
- data/examples/java/exampleCompareOperator.bgv +0 -0
- data/examples/java/exampleCompareOperator.cfg +1109 -0
- data/examples/java/exampleDoubleSynchronized.bgv +0 -0
- data/examples/java/exampleDoubleSynchronized.cfg +26497 -0
- data/examples/java/exampleExactArith.bgv +0 -0
- data/examples/java/exampleExactArith.cfg +1888 -0
- data/examples/java/exampleFieldRead.bgv +0 -0
- data/examples/java/exampleFieldRead.cfg +1228 -0
- data/examples/java/exampleFieldWrite.bgv +0 -0
- data/examples/java/exampleFieldWrite.cfg +1102 -0
- data/examples/java/exampleFor.bgv +0 -0
- data/examples/java/exampleFor.cfg +3936 -0
- data/examples/java/exampleFullEscape.bgv +0 -0
- data/examples/java/exampleFullEscape.cfg +5893 -0
- data/examples/java/exampleIf.bgv +0 -0
- data/examples/java/exampleIf.cfg +2462 -0
- data/examples/java/exampleIfNeverTaken.bgv +0 -0
- data/examples/java/exampleIfNeverTaken.cfg +2476 -0
- data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
- data/examples/java/exampleInstanceOfManyImpls.cfg +6391 -0
- data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
- data/examples/java/exampleInstanceOfOneImpl.cfg +2604 -0
- data/examples/java/exampleIntSwitch.bgv +0 -0
- data/examples/java/exampleIntSwitch.cfg +3121 -0
- data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
- data/examples/java/exampleInterfaceCallManyImpls.cfg +1358 -0
- data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
- data/examples/java/exampleInterfaceCallOneImpl.cfg +3859 -0
- data/examples/java/exampleLocalInstanceOf.bgv +0 -0
- data/examples/java/exampleLocalInstanceOf.cfg +5276 -0
- data/examples/java/exampleLocalSynchronized.bgv +0 -0
- data/examples/java/exampleLocalSynchronized.cfg +1364 -0
- data/examples/java/exampleLocalVariables.bgv +0 -0
- data/examples/java/exampleLocalVariables.cfg +1195 -0
- data/examples/java/exampleLocalVariablesState.bgv +0 -0
- data/examples/java/exampleLocalVariablesState.cfg +1673 -0
- data/examples/java/exampleNestedWhile.bgv +0 -0
- data/examples/java/exampleNestedWhile.cfg +15499 -0
- data/examples/java/exampleNestedWhileBreak.bgv +0 -0
- data/examples/java/exampleNestedWhileBreak.cfg +11162 -0
- data/examples/java/exampleNoEscape.bgv +0 -0
- data/examples/java/exampleNoEscape.cfg +974 -0
- data/examples/java/exampleObjectAllocation.bgv +0 -0
- data/examples/java/exampleObjectAllocation.cfg +5287 -0
- data/examples/java/examplePartialEscape.bgv +0 -0
- data/examples/java/examplePartialEscape.cfg +7042 -0
- data/examples/java/examplePhi.bgv +0 -0
- data/examples/java/examplePhi.cfg +3227 -0
- data/examples/java/exampleReducible.bgv +0 -0
- data/examples/java/exampleReducible.cfg +5578 -0
- data/examples/java/exampleSimpleCall.bgv +0 -0
- data/examples/java/exampleSimpleCall.cfg +1435 -0
- data/examples/java/exampleStamp.bgv +0 -0
- data/examples/java/exampleStamp.cfg +913 -0
- data/examples/java/exampleStaticCall.bgv +0 -0
- data/examples/java/exampleStaticCall.cfg +1154 -0
- data/examples/java/exampleStringSwitch.bgv +0 -0
- data/examples/java/exampleStringSwitch.cfg +15377 -0
- data/examples/java/exampleSynchronized.bgv +0 -0
- data/examples/java/exampleSynchronized.cfg +26027 -0
- data/examples/java/exampleThrow.bgv +0 -0
- data/examples/java/exampleThrow.cfg +780 -0
- data/examples/java/exampleThrowCatch.bgv +0 -0
- data/examples/java/exampleThrowCatch.cfg +744 -0
- data/examples/java/exampleUnsafeRead.bgv +0 -0
- data/examples/java/exampleUnsafeRead.cfg +912 -0
- data/examples/java/exampleUnsafeWrite.bgv +0 -0
- data/examples/java/exampleUnsafeWrite.cfg +962 -0
- data/examples/java/exampleWhile.bgv +0 -0
- data/examples/java/exampleWhile.cfg +3936 -0
- data/examples/java/exampleWhileBreak.bgv +0 -0
- data/examples/java/exampleWhileBreak.cfg +5963 -0
- data/examples/matmult-java.bgv +0 -0
- data/examples/matmult-ruby.bgv +0 -0
- data/examples/matmult.rb +29 -0
- data/examples/overflow.bgv +0 -0
- data/examples/overflow.rb +13 -0
- data/lib/seafoam.rb +13 -0
- data/lib/seafoam/annotators.rb +54 -0
- data/lib/seafoam/annotators/fallback.rb +27 -0
- data/lib/seafoam/annotators/graal.rb +376 -0
- data/lib/seafoam/bgv/bgv_parser.rb +602 -0
- data/lib/seafoam/binary/binary_reader.rb +21 -0
- data/lib/seafoam/binary/io_binary_reader.rb +88 -0
- data/lib/seafoam/colors.rb +18 -0
- data/lib/seafoam/commands.rb +447 -0
- data/lib/seafoam/config.rb +34 -0
- data/lib/seafoam/graph.rb +91 -0
- data/lib/seafoam/graphviz_writer.rb +213 -0
- data/lib/seafoam/spotlight.rb +28 -0
- data/lib/seafoam/version.rb +5 -0
- data/seafoam.gemspec +20 -0
- data/spec/seafoam/annotators/fallback_spec.rb +69 -0
- data/spec/seafoam/annotators/graal_spec.rb +96 -0
- data/spec/seafoam/annotators_spec.rb +61 -0
- data/spec/seafoam/bgv/bgv_parser_spec.rb +144 -0
- data/spec/seafoam/bgv/fixtures/not.bgv +1 -0
- data/spec/seafoam/bgv/fixtures/unsupported.bgv +1 -0
- data/spec/seafoam/binary/io_binary_reader_spec.rb +176 -0
- data/spec/seafoam/command_spec.rb +252 -0
- data/spec/seafoam/graph_spec.rb +172 -0
- data/spec/seafoam/graphviz_writer_spec.rb +63 -0
- data/spec/seafoam/spec_helpers.rb +30 -0
- data/spec/seafoam/spotlight_spec.rb +38 -0
- data/tools/render-all +36 -0
- 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
|