jruby_visualizer 0.1
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.
- data/bin/jruby_visualizer +5 -0
- data/lib/jruby_visualizer.rb +9 -0
- data/lib/jruby_visualizer/about_page.rb +64 -0
- data/lib/jruby_visualizer/ast_tree_view_builder.rb +77 -0
- data/lib/jruby_visualizer/cfg_visualizer.rb +122 -0
- data/lib/jruby_visualizer/compiler_data.rb +133 -0
- data/lib/jruby_visualizer/control_flow_graph_view.rb +96 -0
- data/lib/jruby_visualizer/core_ext/ir_scope.rb +15 -0
- data/lib/jruby_visualizer/ir_pretty_printer.rb +57 -0
- data/lib/jruby_visualizer/ir_scope_registry.rb +41 -0
- data/lib/jruby_visualizer/ir_visualizer.rb +79 -0
- data/lib/jruby_visualizer/jruby_visualizer.rb +103 -0
- data/lib/jruby_visualizer/ui/about.fxml +35 -0
- data/lib/jruby_visualizer/ui/cfg-view.fxml +42 -0
- data/lib/jruby_visualizer/ui/img/jruby-icon-32.png +0 -0
- data/lib/jruby_visualizer/ui/img/jruby-icon-64.png +0 -0
- data/lib/jruby_visualizer/ui/img/jruby-logo.png +0 -0
- data/lib/jruby_visualizer/ui/ir-view.fxml +18 -0
- data/lib/jruby_visualizer/ui/jruby-visualizer.fxml +81 -0
- data/lib/jruby_visualizer/version.rb +20 -0
- data/lib/jruby_visualizer/visualizer_compiler_pass_listener.rb +40 -0
- data/lib/jruby_visualizer/visualizer_main_app.rb +284 -0
- data/spec/compiler_data_spec.rb +102 -0
- data/spec/ir_scope_registry_spec.rb +49 -0
- data/spec/jruby_visualizer_test_utils.rb +19 -0
- data/spec/spec_helper.rb +20 -0
- metadata +110 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
module JRubyVisualizer
|
19
|
+
VERSION = '0.1'
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'java'
|
19
|
+
|
20
|
+
class VisualizerCompilerPassListener
|
21
|
+
java_implements org.jruby.ir.passes.CompilerPassListener
|
22
|
+
|
23
|
+
def already_executed(pass, scope, data, child_scope)
|
24
|
+
# TODO add implementation logic
|
25
|
+
#puts "PassListener inside already_executed"
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_execute(pass, scope, child_scope)
|
30
|
+
# TODO implementation logic
|
31
|
+
#puts "PassListener inside start_execute"
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_execute(pass, scope, data, child_scope)
|
36
|
+
# TODO diff on start_execute or already_executed
|
37
|
+
#puts "PassListener inside end_execute"
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'jrubyfx'
|
19
|
+
require_relative 'ast_tree_view_builder'
|
20
|
+
require_relative 'compiler_data'
|
21
|
+
require_relative 'ir_visualizer'
|
22
|
+
require_relative 'cfg_visualizer'
|
23
|
+
require_relative 'jruby_visualizer'
|
24
|
+
require_relative 'about_page'
|
25
|
+
|
26
|
+
resource_root :images, File.join(File.dirname(__FILE__), 'ui', 'img'), 'ui/img'
|
27
|
+
fxml_root File.join(File.dirname(__FILE__), 'ui')
|
28
|
+
|
29
|
+
#
|
30
|
+
# A ListCell that enables to delete the cell by a right click (ContextMenu)
|
31
|
+
#
|
32
|
+
class DeletableListCell < Java::javafx.scene.control.ListCell
|
33
|
+
include JRubyFX
|
34
|
+
|
35
|
+
attr_reader :delete_menu
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
delete_info_item = MenuItem.new('Delete Information')
|
39
|
+
@delete_menu = ContextMenu.new(delete_info_item)
|
40
|
+
delete_info_item.on_action do
|
41
|
+
items = list_view.items
|
42
|
+
info_string = get_string
|
43
|
+
if items.include?(info_string)
|
44
|
+
items.remove_all(info_string)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def updateItem(item, empty)
|
50
|
+
super(item, empty)
|
51
|
+
|
52
|
+
if empty
|
53
|
+
set_text(nil)
|
54
|
+
set_graphic(nil)
|
55
|
+
else
|
56
|
+
set_text(get_string)
|
57
|
+
set_graphic(nil)
|
58
|
+
set_context_menu(@delete_menu)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def get_string
|
65
|
+
if item
|
66
|
+
item.to_s
|
67
|
+
else
|
68
|
+
''
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# A concurrent Task for a JRubyFX application from the Visualizer
|
76
|
+
#
|
77
|
+
class SubAppTask < Java::javafx.concurrent.Task
|
78
|
+
|
79
|
+
def initialize(view_name)
|
80
|
+
super()
|
81
|
+
# (select view)
|
82
|
+
case view_name
|
83
|
+
when :ir_view
|
84
|
+
@view = IRVisualizer.new
|
85
|
+
when :cfg_view
|
86
|
+
@view = CFGVisualizer.new
|
87
|
+
when :about_page
|
88
|
+
@view = AboutPage.new
|
89
|
+
else
|
90
|
+
raise "unknown name for a view: #{view_name}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def call
|
95
|
+
stage = Java::javafx.stage.Stage.new
|
96
|
+
@view.start(stage)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# The UI for the whole JRuby visualizer:
|
102
|
+
# * shows AST and Ruby code
|
103
|
+
# * enables traceability between AST nodes and Ruby code lines (clicks on AST)
|
104
|
+
# * launches other UI applications
|
105
|
+
# * can execute compiler passes
|
106
|
+
#
|
107
|
+
class VisualizerMainApp < JRubyFX::Application
|
108
|
+
|
109
|
+
def start(stage)
|
110
|
+
compiler_data = JRubyVisualizer.compiler_data
|
111
|
+
with(stage, title: 'JRuby Visualizer') do
|
112
|
+
fxml(JRubyVisualizerController, initialize: [compiler_data])
|
113
|
+
icons.add(Image.new(resource_url(:images, 'jruby-icon-32.png').to_s))
|
114
|
+
show
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Usual controller functionality:
|
121
|
+
# * loading fxml file
|
122
|
+
# * forward UI actions to the CompilerData container
|
123
|
+
# * launch other Applications as concurrent tasks
|
124
|
+
#
|
125
|
+
class JRubyVisualizerController
|
126
|
+
include JRubyFX::Controller
|
127
|
+
fxml 'jruby-visualizer.fxml'
|
128
|
+
|
129
|
+
attr_accessor :compiler_data, :information
|
130
|
+
|
131
|
+
def initialize(compiler_data)
|
132
|
+
@compiler_data = compiler_data
|
133
|
+
fill_ast_view(@compiler_data.ast_root)
|
134
|
+
# bind change of ast to redrawing AST
|
135
|
+
@compiler_data.ast_root_property.add_change_listener do |new_ast|
|
136
|
+
fill_ast_view(new_ast)
|
137
|
+
end
|
138
|
+
# bind ruby view to value of ruby_code
|
139
|
+
@ruby_view.text_property.bind(@compiler_data.ruby_code_property)
|
140
|
+
|
141
|
+
# enable scrolling to the ruby code on clicks within the AST
|
142
|
+
scroll_ruby_to_selected_ast
|
143
|
+
|
144
|
+
# display the IR compiler passes and set the selection to first pass
|
145
|
+
@ir_passes_names = CompilerData.compiler_passes_names
|
146
|
+
@ir_passes_box.items = FXCollections.observable_array_list(@ir_passes_names)
|
147
|
+
@ir_passes_box.value = @selected_ir_pass = @ir_passes_names[0]
|
148
|
+
|
149
|
+
# background tasks for other views
|
150
|
+
@ir_view_task = SubAppTask.new(:ir_view)
|
151
|
+
@cfg_view_task = SubAppTask.new(:cfg_view)
|
152
|
+
@about_task = SubAppTask.new(:about_page)
|
153
|
+
|
154
|
+
# Use ListCell with Delete Context Menu in the view for compile information
|
155
|
+
@compile_information.cell_factory = proc { DeletableListCell.new }
|
156
|
+
# information property back ended by the list view for compile information
|
157
|
+
@information = @compile_information.items
|
158
|
+
end
|
159
|
+
|
160
|
+
def select_ir_pass
|
161
|
+
@selected_ir_pass = @ir_passes_box.value
|
162
|
+
end
|
163
|
+
|
164
|
+
def run_previous_passes_for_selection
|
165
|
+
if @compiler_data.current_pass.nil?
|
166
|
+
return # beginning of ir passes
|
167
|
+
end
|
168
|
+
current_pass_name = CompilerData.pass_to_s(@compiler_data.current_pass)
|
169
|
+
select_pass_index = @ir_passes_names.index(@selected_ir_pass)
|
170
|
+
current_pass_index = @ir_passes_names.index(current_pass_name)
|
171
|
+
if select_pass_index == current_pass_index
|
172
|
+
return
|
173
|
+
elsif current_pass_index < select_pass_index
|
174
|
+
@compiler_data.step_ir_passes
|
175
|
+
run_previous_passes_for_selection
|
176
|
+
else
|
177
|
+
reset_passes
|
178
|
+
run_previous_passes_for_selection
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def step_ir_pass
|
183
|
+
if @compiler_data.next_pass
|
184
|
+
run_previous_passes_for_selection
|
185
|
+
@compiler_data.step_ir_passes
|
186
|
+
@selected_ir_pass = CompilerData.pass_to_s(@compiler_data.current_pass)
|
187
|
+
@ir_passes_box.value = @selected_ir_pass
|
188
|
+
@information << "Successfully passed #{@selected_ir_pass}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def clear_information_view
|
193
|
+
@information.clear
|
194
|
+
end
|
195
|
+
|
196
|
+
def reset_passes
|
197
|
+
@compiler_data.reset_scheduler
|
198
|
+
@selected_ir_pass = @ir_passes_names[0]
|
199
|
+
@ir_passes_box.value = @selected_ir_pass
|
200
|
+
clear_information_view
|
201
|
+
end
|
202
|
+
|
203
|
+
def fill_ast_view(root_node)
|
204
|
+
# clear view
|
205
|
+
@ast_view.root = nil
|
206
|
+
# refill it
|
207
|
+
tree_builder = ASTTreeViewBuilder.new(@ast_view)
|
208
|
+
tree_builder.build_view(root_node)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.pixel_height_of_line
|
212
|
+
monospaced = Java::javafx.scene.text.FontBuilder::create.name('monospaced').size(13).build
|
213
|
+
text = Text.new(' JRubyVisualizer.visualize(ruby_code)')
|
214
|
+
text.set_font(monospaced)
|
215
|
+
text.get_layout_bounds.get_height
|
216
|
+
end
|
217
|
+
|
218
|
+
def mark_selected_line(line_number)
|
219
|
+
ruby_code = @ruby_view.text
|
220
|
+
ruby_lines = ruby_code.lines.to_a
|
221
|
+
char_begin = ruby_lines[0...line_number].join.chars.count
|
222
|
+
@ruby_view.position_caret(char_begin)
|
223
|
+
char_end = ruby_lines[line_number].chars.count + char_begin
|
224
|
+
@ruby_view.extend_selection(char_end)
|
225
|
+
end
|
226
|
+
|
227
|
+
def scroll_ruby_to_selected_ast
|
228
|
+
@ast_view.selection_model.selected_item_property.add_change_listener do |ast_tree_cell|
|
229
|
+
start_line = ast_tree_cell.node.position.start_line
|
230
|
+
# first mark the line then scroll to it
|
231
|
+
mark_selected_line(start_line)
|
232
|
+
line_pixels = self.class.pixel_height_of_line
|
233
|
+
# calculate the actual height of the current line in pixels
|
234
|
+
scroll_to_pixels = line_pixels * start_line
|
235
|
+
# scroll to start position of current ast tree cell
|
236
|
+
@ruby_view.set_scroll_top(scroll_to_pixels)
|
237
|
+
end
|
238
|
+
@ast_view.selection_model.set_selected_item(@ast_view.root)
|
239
|
+
end
|
240
|
+
|
241
|
+
def close_app
|
242
|
+
Platform.exit
|
243
|
+
end
|
244
|
+
|
245
|
+
def launch_ir_view
|
246
|
+
worker_state = Java::javafx.concurrent.Worker::State
|
247
|
+
state = @ir_view_task.state
|
248
|
+
if state == worker_state::READY
|
249
|
+
Platform.run_later(@ir_view_task)
|
250
|
+
elsif state != worker_state::RUNNING
|
251
|
+
@ir_view_task = SubAppTask.new(:ir_view)
|
252
|
+
Platform.run_later(@ir_view_task)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def launch_cfg_view
|
257
|
+
worker_state = Java::javafx.concurrent.Worker::State
|
258
|
+
state = @cfg_view_task.state
|
259
|
+
if state == worker_state::READY
|
260
|
+
Platform.run_later(@cfg_view_task)
|
261
|
+
elsif state != worker_state::RUNNING
|
262
|
+
@cfg_view_task = SubAppTask.new(:cfg_view)
|
263
|
+
Platform.run_later(@cfg_view_task)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def launch_about
|
268
|
+
worker_state = Java::javafx.concurrent.Worker::State
|
269
|
+
state = @about_task.state
|
270
|
+
if state == worker_state::READY
|
271
|
+
Platform.run_later(@about_task)
|
272
|
+
elsif state != worker_state::RUNNING
|
273
|
+
@about_task = SubAppTask.new(:about_page)
|
274
|
+
Platform.run_later(@about_task)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
if __FILE__ == $PROGRAM_NAME
|
281
|
+
JRubyVisualizer.compiler_data = CompilerData.new(
|
282
|
+
"class Foo\n def bar\n 42\n end\nend\nFoo.new.bar\n")
|
283
|
+
VisualizerMainApp.launch
|
284
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'jruby_visualizer/compiler_data'
|
2
|
+
require 'jruby_visualizer_test_utils'
|
3
|
+
|
4
|
+
module CompilerDataTestUtils
|
5
|
+
include JRubyVisualizerTestUtils
|
6
|
+
@updated_ruby_code = false
|
7
|
+
@updated_ast_root = false
|
8
|
+
@updated_ir_scope = false
|
9
|
+
|
10
|
+
def add_ruby_code_listener
|
11
|
+
@compiler_data.ruby_code_property.add_invalidation_listener do |new_code|
|
12
|
+
@updated_ruby_code = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_ast_root_listener
|
17
|
+
@compiler_data.ast_root_property.add_invalidation_listener do |new_ast|
|
18
|
+
@updated_ast_root = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_ir_scope_listener
|
23
|
+
@compiler_data.ir_scope_property.add_invalidation_listener do |new_ir_scope|
|
24
|
+
@updated_ir_scope = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_listeners
|
29
|
+
add_ruby_code_listener
|
30
|
+
add_ast_root_listener
|
31
|
+
add_ir_scope_listener
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear_updates
|
35
|
+
@updated_ruby_code, @updated_ast_root, @updated_ir_scope = false, false, false
|
36
|
+
end
|
37
|
+
|
38
|
+
def should_has_ast(other_ast)
|
39
|
+
@compiler_data.ast_root.to_s == other_ast.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def should_has_ir_scope(other_ir_scope)
|
43
|
+
@compiler_data.ir_scope.to_s == other_ir_scope.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe CompilerData do
|
49
|
+
include CompilerDataTestUtils
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
@compiler_data = CompilerData.new
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should update AST and IR Scope after updating ruby code" do
|
56
|
+
add_listeners
|
57
|
+
|
58
|
+
@compiler_data.ruby_code = "i = 1 + 2; puts i"
|
59
|
+
@updated_ruby_code.should be_true
|
60
|
+
@updated_ast_root.should be_true
|
61
|
+
@updated_ir_scope.should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should only update IR Scope after assigning a new AST" do
|
65
|
+
add_listeners
|
66
|
+
|
67
|
+
@compiler_data.ast_root = ast_for("j = 2; j")
|
68
|
+
@updated_ruby_code.should be_false
|
69
|
+
@updated_ast_root.should be_true
|
70
|
+
@updated_ir_scope.should be_true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should parse the AST implicitly" do
|
74
|
+
ruby_code = "a = 1; b = 4; puts a + b"
|
75
|
+
@compiler_data.ruby_code = ruby_code
|
76
|
+
should_has_ast(ast_for(ruby_code))
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should build the IR implicitly" do
|
80
|
+
ast_root = ast_for("i = 42; puts i")
|
81
|
+
@compiler_data.ast_root = ast_root
|
82
|
+
should_has_ir_scope(ir_scope_for(ast_root))
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should update IR on executing IR passes" do
|
86
|
+
add_ir_scope_listener
|
87
|
+
while @compiler_data.next_pass
|
88
|
+
clear_updates
|
89
|
+
@compiler_data.step_ir_passes
|
90
|
+
@updated_ir_scope.should be_true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should return nil after stepping all IR passes" do
|
95
|
+
while @compiler_data.next_pass
|
96
|
+
@compiler_data.step_ir_passes
|
97
|
+
end
|
98
|
+
@compiler_data.step_ir_passes.should be_nil
|
99
|
+
@compiler_data.next_pass.should be_nil
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|