statsailr 0.7.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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/HISTORY.md +15 -0
- data/LICENSE.txt +675 -0
- data/README.md +287 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/blank.slr +3 -0
- data/example/category.slr +5 -0
- data/example/example_read.slr +10 -0
- data/example/iris.csv +151 -0
- data/example/mtcars.rda +0 -0
- data/example/new_mtcars.csv +33 -0
- data/example/new_mtcars.rda +0 -0
- data/example/plot_reg_example.slr +55 -0
- data/example/scatter.png +0 -0
- data/exe/sailr +54 -0
- data/exe/sailrREPL +75 -0
- data/lib/statsailr.rb +7 -0
- data/lib/statsailr/block_builder/sts_block.rb +167 -0
- data/lib/statsailr/block_builder/sts_block_parse_proc_opts.rb +168 -0
- data/lib/statsailr/block_to_r/proc_setting_support/proc_opt_validator.rb +52 -0
- data/lib/statsailr/block_to_r/proc_setting_support/proc_setting_manager.rb +49 -0
- data/lib/statsailr/block_to_r/proc_setting_support/proc_setting_module.rb +44 -0
- data/lib/statsailr/block_to_r/sts_block_to_r.rb +98 -0
- data/lib/statsailr/block_to_r/sts_lazy_func_gen.rb +236 -0
- data/lib/statsailr/block_to_r/top_stmt/top_stmt_to_r_func.rb +182 -0
- data/lib/statsailr/parser/sts_gram_node.rb +9 -0
- data/lib/statsailr/parser/sts_parse.output +831 -0
- data/lib/statsailr/parser/sts_parse.ry +132 -0
- data/lib/statsailr/parser/sts_parse.tab.rb +682 -0
- data/lib/statsailr/scanner/sample1.sts +37 -0
- data/lib/statsailr/scanner/sts_scanner.rb +433 -0
- data/lib/statsailr/scanner/test_sample1.rb +8 -0
- data/lib/statsailr/sts_build_exec.rb +304 -0
- data/lib/statsailr/sts_controller.rb +66 -0
- data/lib/statsailr/sts_output/output_manager.rb +192 -0
- data/lib/statsailr/sts_runner.rb +17 -0
- data/lib/statsailr/sts_server.rb +85 -0
- data/lib/statsailr/version.rb +3 -0
- data/statsailr.gemspec +32 -0
- metadata +133 -0
@@ -0,0 +1,304 @@
|
|
1
|
+
require "r_bridge"
|
2
|
+
require "statsailr/sts_output/output_manager"
|
3
|
+
require "statsailr/block_to_r/proc_setting_support/proc_setting_manager.rb"
|
4
|
+
|
5
|
+
module StatSailr
|
6
|
+
def self.initR()
|
7
|
+
RBridge.init_embedded_r()
|
8
|
+
puts "R program initialized"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.initial_setting_for_r(device_info)
|
12
|
+
p device_info
|
13
|
+
if (! device_info.nil?) && (device_info.is_a? Array ) # (e.g.) ["Gtk3", <FFI::Pointer>]
|
14
|
+
device_type = device_info[0]
|
15
|
+
case device_type.downcase
|
16
|
+
when "gtk3"
|
17
|
+
puts "Use asCairoDevice function in cairoDeviceGtk3 library"
|
18
|
+
if ! device_info[1].is_a?(FFI::Pointer)
|
19
|
+
raise "Pointer to GtkWidget needs to be specified"
|
20
|
+
end
|
21
|
+
p device_info
|
22
|
+
p_widget = device_info[1]
|
23
|
+
lib_func = RBridge.create_library_function("cairoDeviceGtk3")
|
24
|
+
RBridge.exec_function_no_return(lib_func)
|
25
|
+
attach_widget_func = RBridge.create_function_call("asCairoDevice", {"widget" => RBridge.create_extptr(p_widget) } )
|
26
|
+
RBridge.exec_function_no_return(attach_widget_func)
|
27
|
+
@new_device_info = { "file_output" => false, "dev_off_required" => false }
|
28
|
+
|
29
|
+
when "cairoraster" # (e.g.) ["CairoRaster", {"width" => 800, "height" => 600, "dev.copy_opt" => {"dir_path"=> dir_to_save , "prefix"=> "plot", "type" => "png"} }]
|
30
|
+
puts "Use Cairo function in Cairo library"
|
31
|
+
if ! device_info[1].is_a?(Hash)
|
32
|
+
raise "The second element of device info needs to be Hash"
|
33
|
+
end
|
34
|
+
cairo_info = device_info[1]
|
35
|
+
if ! ["png", "jpeg"].include? cairo_info["dev.copy_opt"]["type"]
|
36
|
+
raise "only png or jpeg is supported for type"
|
37
|
+
else
|
38
|
+
case cairo_info["dev.copy_opt"]["type"]
|
39
|
+
when "png"
|
40
|
+
# load png library
|
41
|
+
lib_func = RBridge.create_library_function("png")
|
42
|
+
RBridge.exec_function_no_return(lib_func)
|
43
|
+
when "jpeg"
|
44
|
+
# use default jpeg device
|
45
|
+
end
|
46
|
+
end
|
47
|
+
lib_func = RBridge.create_library_function("Cairo")
|
48
|
+
RBridge.exec_function_no_return(lib_func)
|
49
|
+
new_cairo_device_func = RBridge.create_function_call("Cairo", { "width" => RBridge.create_intvec([ cairo_info["width"] ]),
|
50
|
+
"height" => RBridge.create_intvec([ cairo_info["height"] ]),
|
51
|
+
"type" => RBridge.create_strvec([ "raster" ]) })
|
52
|
+
RBridge.exec_function_no_return(new_cairo_device_func)
|
53
|
+
@new_device_info = { "file_output" => true, "dev_off_required" => true ,
|
54
|
+
"opt" => cairo_info["dev.copy_opt"].merge( {
|
55
|
+
"device_func" => RBridge::SymbolR.new( cairo_info["dev.copy_opt"]["type"] ).to_r_symbol,
|
56
|
+
"default_width" => cairo_info["width"], "default_height" => cairo_info["height"]})}
|
57
|
+
else
|
58
|
+
puts "Unknown device type: #{device_type}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
RBridge.exec_function_no_return( RBridge.create_function_call("options", {"warn" => RBridge.create_intvec([ 1 ])} ))
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.initial_procs_registration( procs_gem )
|
65
|
+
if @proc_setting_manager.nil?
|
66
|
+
@proc_setting_manager = ProcSettingManager.new
|
67
|
+
|
68
|
+
if procs_gem.nil?
|
69
|
+
puts "No PROC(s) are instructed to be registered. nil specified."
|
70
|
+
elsif ! [String, Array].include? procs_gem.class
|
71
|
+
raise "procs_gem needs to be specified in String or Array."
|
72
|
+
else
|
73
|
+
if procs_gem.class == String
|
74
|
+
procs_gem = [procs_gem]
|
75
|
+
end
|
76
|
+
|
77
|
+
procs_gems_name_class_array = procs_gem.filter_map(){|gem_name|
|
78
|
+
if gem_name =~ /^statsailr_(\w+)/
|
79
|
+
class_name = $1.split("_").map(){|elem| elem.capitalize()}.join("")
|
80
|
+
[ gem_name, class_name ]
|
81
|
+
else
|
82
|
+
raise 'gem name specified for procs_gem is not appropriate. The name pattern should be /^statsailr_(\w+)/'
|
83
|
+
end
|
84
|
+
}
|
85
|
+
|
86
|
+
puts ("Load gems for PROC settings")
|
87
|
+
procs_gems_name_class_array.each{|procs_gem_name, procs_class_name|
|
88
|
+
# Add PROCs from gems
|
89
|
+
begin
|
90
|
+
require(procs_gem_name)
|
91
|
+
@proc_setting_manager.add_proc_settings_from_dir( Module.const_get( "StatSailr::" + procs_class_name).send( "path_to_proc_setting") )
|
92
|
+
puts "#{procs_gem_name} gem is loaded (ver. #{Gem.loaded_specs[procs_gem_name].version.to_s})"
|
93
|
+
rescue LoadError => e
|
94
|
+
puts e.message
|
95
|
+
puts "#{procs_gem_name} gem failed to be loaded"
|
96
|
+
e.set_backtrace( [] )
|
97
|
+
rescue NameError
|
98
|
+
rescue RuntimeError
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.endR()
|
106
|
+
if ! @new_device_info.nil?
|
107
|
+
if @new_device_info["dev_off_required"]
|
108
|
+
RBridge.exec_function_no_return( RBridge.create_function_call("dev.off", {}))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
RBridge.end_embedded_r()
|
112
|
+
puts "R program safely finished"
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.change_working_dir( set_working_dir )
|
116
|
+
unless Dir.exists?(set_working_dir)
|
117
|
+
raise "New working directory not found: #{set_working_dir}"
|
118
|
+
end
|
119
|
+
RBridge.exec_function_no_return( RBridge.create_function_call("setwd", {"dir" => RBridge.create_strvec([set_working_dir])}))
|
120
|
+
puts "R program working directory is set to #{set_working_dir}"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def self.build_exec( script , initR_beforeExec: false , endR_afterExec: false , block_idx_start: 1, set_working_dir: nil, device_info: nil,
|
125
|
+
output_mngr: Output::OutputManager.new(capture: false),
|
126
|
+
procs_gem: "statsailr_procs_base" )
|
127
|
+
|
128
|
+
require_relative("scanner/sts_scanner.rb")
|
129
|
+
|
130
|
+
output_mngr.move_to_new_node("Tokenize code")
|
131
|
+
tokens = []
|
132
|
+
output_mngr.add_new_message(:output).run($stdout){
|
133
|
+
s = STSScanDriver.new( script )
|
134
|
+
tokens = s.tokenize()
|
135
|
+
}
|
136
|
+
output_mngr.move_up()
|
137
|
+
|
138
|
+
if tokens.empty?
|
139
|
+
puts "Input token is empty"
|
140
|
+
if initR_beforeExec
|
141
|
+
initR()
|
142
|
+
initial_setting_for_r( device_info )
|
143
|
+
|
144
|
+
output_mngr.move_to_new_node("Load PROCs")
|
145
|
+
output_mngr.add_new_message(:output).run($stdout){
|
146
|
+
initial_procs_registration( procs_gem )
|
147
|
+
}
|
148
|
+
output_mngr.move_up()
|
149
|
+
end
|
150
|
+
if endR_afterExec
|
151
|
+
endR()
|
152
|
+
end
|
153
|
+
return 0
|
154
|
+
end
|
155
|
+
|
156
|
+
require_relative("parser/sts_parse.tab.rb")
|
157
|
+
|
158
|
+
gram_nodes = nil
|
159
|
+
output_mngr.move_to_new_node("Parse tokens")
|
160
|
+
output_mngr.add_new_message(:output).run($stdout){
|
161
|
+
gram_nodes = STSParserDriver.run( tokens )
|
162
|
+
}
|
163
|
+
output_mngr.move_up()
|
164
|
+
|
165
|
+
require_relative("block_builder/sts_block.rb")
|
166
|
+
|
167
|
+
blocks = []
|
168
|
+
gram_nodes.each(){|node|
|
169
|
+
case node.type
|
170
|
+
when :TOP_BLOCK
|
171
|
+
blocks << TopStmt.new_from_gram_node(node)
|
172
|
+
when :DATA_BLOCK
|
173
|
+
blocks << DataBlock.new_from_gram_node(node)
|
174
|
+
when :PROC_BLOCK
|
175
|
+
blocks << ProcBlock.new_from_gram_node(node)
|
176
|
+
end
|
177
|
+
}
|
178
|
+
|
179
|
+
require_relative("block_to_r/sts_block_to_r.rb")
|
180
|
+
|
181
|
+
if initR_beforeExec
|
182
|
+
initR()
|
183
|
+
initial_setting_for_r( device_info )
|
184
|
+
|
185
|
+
output_mngr.move_to_new_node("Load PROCs")
|
186
|
+
output_mngr.add_new_message(:output).run($stdout){
|
187
|
+
initial_procs_registration( procs_gem )
|
188
|
+
}
|
189
|
+
output_mngr.move_up()
|
190
|
+
end
|
191
|
+
|
192
|
+
if ! set_working_dir.nil?
|
193
|
+
change_working_dir( set_working_dir )
|
194
|
+
end
|
195
|
+
|
196
|
+
begin
|
197
|
+
output_mngr.move_to_new_node("BLOCK_TO_R")
|
198
|
+
block_idx = block_idx_start
|
199
|
+
blocks.each(){|blk|
|
200
|
+
RBridge.ptr_manager_open ("Block No. " + block_idx.to_s) {
|
201
|
+
begin
|
202
|
+
block_num_str = "BLOCK NO. " + block_idx.to_s + "\n"
|
203
|
+
case blk
|
204
|
+
when TopStmt
|
205
|
+
output_mngr.move_to_new_node("TopStmt")
|
206
|
+
output_mngr.add_new_message(:text).set_content( block_num_str )
|
207
|
+
output_mngr.add_new_message(:output).run($stdout){
|
208
|
+
func = TopStmtToR.create_function( blk )
|
209
|
+
RBridge.exec_function_no_return( func ) unless func.nil?
|
210
|
+
puts ""
|
211
|
+
}
|
212
|
+
output_mngr.move_up()
|
213
|
+
when DataBlock
|
214
|
+
output_mngr.move_to_new_node("DataBlock")
|
215
|
+
output_mngr.add_new_message(:text).set_content( block_num_str )
|
216
|
+
output_mngr.add_new_message(:output).run($stdout){
|
217
|
+
func = DataBlockToR.create_function( blk )
|
218
|
+
RBridge.exec_function_no_return( func )
|
219
|
+
puts ""
|
220
|
+
}
|
221
|
+
output_mngr.move_up()
|
222
|
+
when ProcBlock
|
223
|
+
output_mngr.move_to_new_node(["ProcBlock", blk.command ])
|
224
|
+
output_mngr.add_new_message(:text).set_content( block_num_str )
|
225
|
+
result_manager = RBridge::RResultManager.new
|
226
|
+
lazy_funcs_with_print_result_opts = ProcBlockToR.create_lazy_funcs( blk , @proc_setting_manager )
|
227
|
+
lazy_funcs_with_print_result_opts.each(){|lazy_func, print_opt, plot_opt, store_result , result_name |
|
228
|
+
output_mngr.move_to_new_node(["inst", result_name])
|
229
|
+
output_mngr.add_new_message(:text).set_content( "inst: #{result_name}\n" )
|
230
|
+
output_mngr.add_new_message(:output).run($stdout){
|
231
|
+
r_obj = RBridge.exec_lazy_function( lazy_func , result_manager , allow_nil_result: true)
|
232
|
+
if(store_result)
|
233
|
+
result_manager.add_inst_r_obj( result_name, r_obj)
|
234
|
+
end
|
235
|
+
if(print_opt.nil? || print_opt == false)
|
236
|
+
# nop
|
237
|
+
elsif(print_opt == true )
|
238
|
+
print_func = RBridge::create_function_call("print", { "x" => r_obj } )
|
239
|
+
RBridge::exec_function_no_return(print_func)
|
240
|
+
elsif(print_opt.is_a? String)
|
241
|
+
func_before_print = print_opt
|
242
|
+
print_func = RBridge::create_function_call("print", {"x" => RBridge::create_function_call( func_before_print , { "" => r_obj }) })
|
243
|
+
RBridge::exec_function_no_return(print_func)
|
244
|
+
else
|
245
|
+
raise "print_opt needs to be true, false, String."
|
246
|
+
end
|
247
|
+
}
|
248
|
+
output_mngr.add_new_message(:new_line).set_content("\n")
|
249
|
+
|
250
|
+
if( plot_opt.nil? || plot_opt == false || @new_device_info.nil? )
|
251
|
+
#nop
|
252
|
+
else
|
253
|
+
if @new_device_info["file_output"] == true
|
254
|
+
# The plot needs to be saved on disk.
|
255
|
+
dev_info_opt = @new_device_info["opt"]
|
256
|
+
temp_path = ""
|
257
|
+
temp_file = Tempfile.new( [dev_info_opt["prefix"] , "." + dev_info_opt["type"] ] , dev_info_opt["dir_path"] )
|
258
|
+
temp_path = temp_file.path
|
259
|
+
temp_file.close(true)
|
260
|
+
dev_copy_func = RBridge::create_function_call("dev.copy", { "device" => dev_info_opt["device_func"], "file" => RBridge::create_strvec([temp_path]),
|
261
|
+
"width" => RBridge::create_intvec( [dev_info_opt["default_width"]] ),
|
262
|
+
"height" => RBridge::create_intvec( [dev_info_opt["default_height"]] ) })
|
263
|
+
RBridge::exec_function_no_return(dev_copy_func)
|
264
|
+
dev_off_func = RBridge::create_function_call("dev.off", {})
|
265
|
+
RBridge::exec_function_no_return(dev_off_func)
|
266
|
+
if(File.exist? temp_path)
|
267
|
+
output_mngr.add_new_message(:plot_file).set_content( temp_path )
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
output_mngr.move_up()
|
272
|
+
}
|
273
|
+
end
|
274
|
+
rescue => e
|
275
|
+
e.class.module_eval { attr_accessor :block_num_executed} # Add block_num_executed attribute to the current error object dynamically.
|
276
|
+
e.block_num_executed = block_idx - block_idx_start
|
277
|
+
raise e
|
278
|
+
end
|
279
|
+
}
|
280
|
+
puts()
|
281
|
+
block_idx = block_idx + 1
|
282
|
+
}
|
283
|
+
rescue => error
|
284
|
+
puts "stopped working in block no." + block_idx.to_s
|
285
|
+
RBridge.gc_all()
|
286
|
+
puts "gc is explicitly executed for this block"
|
287
|
+
raise
|
288
|
+
ensure
|
289
|
+
output_mngr.move_to_root()
|
290
|
+
end
|
291
|
+
|
292
|
+
if endR_afterExec
|
293
|
+
output_mngr.move_to_new_node("INIT_R")
|
294
|
+
output_mngr.add_new_message(:output).run($stdout){
|
295
|
+
endR()
|
296
|
+
}
|
297
|
+
output_mngr.move_up()
|
298
|
+
end
|
299
|
+
|
300
|
+
return ( block_idx - block_idx_start ) # number of blocks processed.
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
STDOUT.sync = true
|
2
|
+
|
3
|
+
require "statsailr"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
require "statsailr/sts_build_exec"
|
7
|
+
require "statsailr/sts_output/output_manager"
|
8
|
+
|
9
|
+
class StatSailrController
|
10
|
+
INITIAL_BLOCK_COUNTER = 1
|
11
|
+
@block_counter = INITIAL_BLOCK_COUNTER
|
12
|
+
|
13
|
+
def self.init( working_dir: nil, device_info: nil, **kwargs )
|
14
|
+
if ! device_info.nil?
|
15
|
+
# This parameter should be something like ["Gtk3", <FFI::Pointer>]
|
16
|
+
raise "device_info parameter needs to be an Array" unless device_info.is_a? Array
|
17
|
+
raise "device_info parameter needs to be size 2" unless device_info.size == 2
|
18
|
+
raise "device_info parameter requires String for its first element" unless device_info[0].is_a? String
|
19
|
+
raise "device_info parameter requires FFI::Pointer to GtkWidget for its second element" unless device_info[1].is_a? FFI::Pointer
|
20
|
+
end
|
21
|
+
|
22
|
+
if working_dir.nil?
|
23
|
+
working_dir = File.expand_path('~')
|
24
|
+
end
|
25
|
+
|
26
|
+
@output_mngr = StatSailr::Output::OutputManager.new(capture: true)
|
27
|
+
|
28
|
+
num_executed = 0
|
29
|
+
num_executed = StatSailr.build_exec( " ", initR_beforeExec: true, endR_afterExec: false,
|
30
|
+
block_idx_start: @block_counter, set_working_dir: working_dir, device_info: device_info,
|
31
|
+
output_mngr: @output_mngr,
|
32
|
+
**kwargs )
|
33
|
+
@block_counter = num_executed + @block_counter
|
34
|
+
return @output_mngr.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.run(script)
|
38
|
+
begin
|
39
|
+
num_executed = 0
|
40
|
+
output = ""
|
41
|
+
num_executed = StatSailr.build_exec( script, initR_beforeExec: false, endR_afterExec: false,
|
42
|
+
block_idx_start: @block_counter, output_mngr: @output_mngr.reset )
|
43
|
+
rescue RuntimeError => e
|
44
|
+
print e.backtrace.map.with_index{|elem, idx| idx.to_s + " " + elem }.reverse.join("\n")
|
45
|
+
puts " \e[1m#{e.message}\e[22m" # show in bold
|
46
|
+
output << "Error\n"
|
47
|
+
output << "\n" << e.message << "\n\n" # output error for user
|
48
|
+
# ToDo, obtain num_executed before error rased.
|
49
|
+
num_executed = e.block_num_executed
|
50
|
+
ensure
|
51
|
+
output << @output_mngr.to_s
|
52
|
+
@block_counter = num_executed + @block_counter
|
53
|
+
script = ""
|
54
|
+
end
|
55
|
+
|
56
|
+
return output
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.stop
|
60
|
+
StatSailr.build_exec( " ", initR_beforeExec: false, endR_afterExec: true , block_idx_start: @block_counter, output_mngr: @output_mngr.reset )
|
61
|
+
output = @output_mngr.to_s
|
62
|
+
return output
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module StatSailr
|
4
|
+
module Output
|
5
|
+
class OutputMessage
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@parent, :capture
|
8
|
+
|
9
|
+
attr :type
|
10
|
+
attr :content, true
|
11
|
+
|
12
|
+
def initialize(type, content, parent)
|
13
|
+
@type = type
|
14
|
+
@content = content
|
15
|
+
@parent = parent
|
16
|
+
|
17
|
+
if capture == false
|
18
|
+
print @content
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_content( content )
|
23
|
+
if capture == false
|
24
|
+
print content
|
25
|
+
end
|
26
|
+
@content = content
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
return @content.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(stream)
|
34
|
+
raise ArgumentError, 'missing block' unless block_given?
|
35
|
+
if capture == false
|
36
|
+
begin
|
37
|
+
yield
|
38
|
+
rescue => e
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
else
|
42
|
+
orig_stream = stream.dup
|
43
|
+
IO.pipe do |r, w|
|
44
|
+
# system call dup2() replaces the file descriptor
|
45
|
+
stream.reopen(w)
|
46
|
+
# there must be only one write end of the pipe;
|
47
|
+
# otherwise the read end does not get an EOF
|
48
|
+
# by the final `reopen`
|
49
|
+
w.close # Now 'stream=$stdout' and 'r' are paired. orig_stream points to original stream(STDOUT).
|
50
|
+
t = Thread.new { r.read }
|
51
|
+
begin
|
52
|
+
yield
|
53
|
+
rescue => e
|
54
|
+
raise e
|
55
|
+
ensure
|
56
|
+
stream.reopen orig_stream # restore file descriptor
|
57
|
+
@content << t.value # join and get the result of the thread
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class OutputNode
|
65
|
+
extend Forwardable
|
66
|
+
def_delegators :@parent, :capture
|
67
|
+
|
68
|
+
attr :tag, :parent, :messages
|
69
|
+
def initialize(tag , parent)
|
70
|
+
@tag = tag
|
71
|
+
@children = []
|
72
|
+
@parent = parent
|
73
|
+
@messages = []
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_node(tag)
|
77
|
+
@children << OutputNode.new(tag, self)
|
78
|
+
return @children.last
|
79
|
+
end
|
80
|
+
|
81
|
+
def new_message( type, content )
|
82
|
+
@messages << OutputMessage.new(type, content, self )
|
83
|
+
return @messages.last
|
84
|
+
end
|
85
|
+
|
86
|
+
def each_node(&blk)
|
87
|
+
@children.each(&blk)
|
88
|
+
end
|
89
|
+
|
90
|
+
def each_message(&blk)
|
91
|
+
@messages.each(&blk)
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s()
|
95
|
+
str = ""
|
96
|
+
if ! @messages.empty?
|
97
|
+
each_message(){|message|
|
98
|
+
str << message.to_s()
|
99
|
+
}
|
100
|
+
end
|
101
|
+
if ! @children.empty?
|
102
|
+
each_node(){|node|
|
103
|
+
str << node.to_s()
|
104
|
+
}
|
105
|
+
end
|
106
|
+
return str
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class OutputManager
|
111
|
+
attr :root_node, :current_node, :capture
|
112
|
+
alias_method :parent, :capture
|
113
|
+
|
114
|
+
def initialize( capture: true )
|
115
|
+
@root_node = OutputNode.new("root", self)
|
116
|
+
@current_node = @root_node
|
117
|
+
@capture = capture
|
118
|
+
end
|
119
|
+
|
120
|
+
def reset
|
121
|
+
@root_node = OutputNode.new("root", self)
|
122
|
+
@current_node = @root_node
|
123
|
+
return self
|
124
|
+
end
|
125
|
+
|
126
|
+
def move_to_new_node(tag)
|
127
|
+
@current_node = @current_node.new_node(tag)
|
128
|
+
return @current_node
|
129
|
+
end
|
130
|
+
|
131
|
+
def recurse_move_to_new_node(tag , *tags)
|
132
|
+
@current_node = @current_node.new_node(tag)
|
133
|
+
if ! tags.nil?
|
134
|
+
tags.each(){|child_tag|
|
135
|
+
@current_node = @current_node.new_node(child_tag)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
return @current_node
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_new_message( type, content: "" )
|
142
|
+
message = @current_node.new_message(type, content)
|
143
|
+
return message
|
144
|
+
end
|
145
|
+
|
146
|
+
def move_to_root()
|
147
|
+
@current_node = @root_node
|
148
|
+
end
|
149
|
+
|
150
|
+
def move_up()
|
151
|
+
@current_node = @current_node.parent
|
152
|
+
end
|
153
|
+
|
154
|
+
def move_down()
|
155
|
+
if @current_node.children.size >= 1
|
156
|
+
@current_node = @current_node.children.last
|
157
|
+
else
|
158
|
+
return nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def to_s()
|
163
|
+
@root_node.to_s()
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# require_relative "./output_manager.rb"
|
171
|
+
#
|
172
|
+
# mngr = StatSailr::Output::OutputManager.new(capture: true) # Level0 (root)
|
173
|
+
#
|
174
|
+
# mngr.move_to_new_node("BLOCK_TO_R") # Level1
|
175
|
+
# mngr.recurse_move_to_new_node(["PROC","PRINT"], "head") # Level2 and 3
|
176
|
+
# mngr.add_new_message(:text).run($stdout){
|
177
|
+
# puts "Hello"
|
178
|
+
# puts "World"
|
179
|
+
# }
|
180
|
+
#
|
181
|
+
# p mngr.current_node.messages[0].content
|
182
|
+
# p mngr.current_node.tag
|
183
|
+
#
|
184
|
+
# mngr.move_up
|
185
|
+
# p mngr.current_node.tag
|
186
|
+
#
|
187
|
+
# mngr.move_up
|
188
|
+
# p mngr.current_node.tag
|
189
|
+
#
|
190
|
+
# p "root_node"
|
191
|
+
# p mngr.root_node.tag
|
192
|
+
|