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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +7 -0
  5. data/HISTORY.md +15 -0
  6. data/LICENSE.txt +675 -0
  7. data/README.md +287 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/example/blank.slr +3 -0
  12. data/example/category.slr +5 -0
  13. data/example/example_read.slr +10 -0
  14. data/example/iris.csv +151 -0
  15. data/example/mtcars.rda +0 -0
  16. data/example/new_mtcars.csv +33 -0
  17. data/example/new_mtcars.rda +0 -0
  18. data/example/plot_reg_example.slr +55 -0
  19. data/example/scatter.png +0 -0
  20. data/exe/sailr +54 -0
  21. data/exe/sailrREPL +75 -0
  22. data/lib/statsailr.rb +7 -0
  23. data/lib/statsailr/block_builder/sts_block.rb +167 -0
  24. data/lib/statsailr/block_builder/sts_block_parse_proc_opts.rb +168 -0
  25. data/lib/statsailr/block_to_r/proc_setting_support/proc_opt_validator.rb +52 -0
  26. data/lib/statsailr/block_to_r/proc_setting_support/proc_setting_manager.rb +49 -0
  27. data/lib/statsailr/block_to_r/proc_setting_support/proc_setting_module.rb +44 -0
  28. data/lib/statsailr/block_to_r/sts_block_to_r.rb +98 -0
  29. data/lib/statsailr/block_to_r/sts_lazy_func_gen.rb +236 -0
  30. data/lib/statsailr/block_to_r/top_stmt/top_stmt_to_r_func.rb +182 -0
  31. data/lib/statsailr/parser/sts_gram_node.rb +9 -0
  32. data/lib/statsailr/parser/sts_parse.output +831 -0
  33. data/lib/statsailr/parser/sts_parse.ry +132 -0
  34. data/lib/statsailr/parser/sts_parse.tab.rb +682 -0
  35. data/lib/statsailr/scanner/sample1.sts +37 -0
  36. data/lib/statsailr/scanner/sts_scanner.rb +433 -0
  37. data/lib/statsailr/scanner/test_sample1.rb +8 -0
  38. data/lib/statsailr/sts_build_exec.rb +304 -0
  39. data/lib/statsailr/sts_controller.rb +66 -0
  40. data/lib/statsailr/sts_output/output_manager.rb +192 -0
  41. data/lib/statsailr/sts_runner.rb +17 -0
  42. data/lib/statsailr/sts_server.rb +85 -0
  43. data/lib/statsailr/version.rb +3 -0
  44. data/statsailr.gemspec +32 -0
  45. metadata +133 -0
@@ -0,0 +1,8 @@
1
+ load("./sts_scanner.rb")
2
+
3
+ s = StatSailr::ScanDriver.new("./sample1.sts")
4
+ tokens = s.tokenize()
5
+ puts "Showing tokens"
6
+
7
+ require "pp"
8
+ pp tokens
@@ -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
+