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.
@@ -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