trac_lang 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/exe/trac_lang ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'optparse'
5
+ require 'trac_lang'
6
+
7
+ USAGE_INSTRUCTIONS = ''
8
+
9
+ def parse_options
10
+ options = {}
11
+ op = OptionParser.new do |opt|
12
+ opt.banner = 'Usage: trac_lang [OPTIONS] file1.trl file2.trl...'
13
+ options[:save_dir] = Dir.pwd
14
+ options[:save_dir] = ENV['TRAC-HOME'] if ENV['TRAC-HOME']
15
+ opt.on('-d', '--save-dir', 'Directory to save TRAC blocks to') do |dir|
16
+ options[:save_dir] = dir
17
+ end
18
+ exit_on_eof = false
19
+ opt.on('-x', '--exit-on-eof', 'Exit when finished processing files') do
20
+ options[:exit_on_eof] = true
21
+ end
22
+ options[:trace] = false
23
+ opt.on('-t', '--trace', 'Turn on trace') do
24
+ options[:trace] = true
25
+ end
26
+ opt.on( '-h', '--help', 'Display this screen' ) do
27
+ puts opt
28
+ exit
29
+ end
30
+ end
31
+ USAGE_INSTRUCTIONS << op.to_s
32
+ op.parse!
33
+ options
34
+ end
35
+
36
+ def print_error(error)
37
+ case error
38
+ when OptionParser::InvalidOption
39
+ puts "trac_lang: illegal option #{error.args.join(' ')}"
40
+ puts USAGE_INSTRUCTIONS
41
+ else
42
+ puts "An unexpected error occured while running TRAC Lang"
43
+ puts " #{error}\n"
44
+ end
45
+ end
46
+
47
+ begin
48
+ d = TracLang::Dispatch.new(parse_options)
49
+ e = TracLang::Executor.new(d)
50
+ catch :done do
51
+ last_file = ''
52
+ last_line = 0
53
+ current_line = 1
54
+ ARGF.each_line do |line|
55
+ if ARGF.filename != last_file
56
+ e.restore_dir unless last_file.empty?
57
+ e.save_dir(ARGF.filename)
58
+ current_line = 1
59
+ last_file = ARGF.filename
60
+ else
61
+ current_line += 1
62
+ end
63
+ e.load(ARGF.filename, current_line, line)
64
+ end
65
+ exit if parse_options[:exit_on_eof]
66
+ e.restore_dir
67
+ e.prompt
68
+ end
69
+ puts 'Exiting...'
70
+ puts
71
+ rescue => error
72
+ print_error(error)
73
+ exit(false)
74
+ end
data/lib/trac_lang.rb ADDED
@@ -0,0 +1,16 @@
1
+ require_relative 'trac_lang/bindings'
2
+ require_relative 'trac_lang/block'
3
+ require_relative 'trac_lang/decimal'
4
+ require_relative 'trac_lang/dispatch'
5
+ require_relative 'trac_lang/executor'
6
+ require_relative 'trac_lang/expression'
7
+ require_relative 'trac_lang/form'
8
+ require_relative 'trac_lang/immediate_read'
9
+ require_relative 'trac_lang/octal'
10
+ require_relative 'trac_lang/parser'
11
+ require_relative 'trac_lang/version'
12
+
13
+ # Module containing code for processing TRAC language.
14
+ module TracLang
15
+
16
+ end
@@ -0,0 +1,53 @@
1
+
2
+
3
+ module TracLang
4
+
5
+ # Binding of name to Form represented by the name.
6
+ class Bindings
7
+ include Enumerable
8
+
9
+ # Hash map of names to Forms.
10
+ attr_reader :bindings
11
+
12
+ # Creates bindings from list of names and Forms.
13
+ def initialize(*bindings)
14
+ @bindings = Hash[bindings]
15
+ end
16
+
17
+ # Clears all bindings.
18
+ def clear
19
+ @bindings.clear
20
+ end
21
+
22
+ # Adds a binding to the map of bindings. Will replace a binding with the same name.
23
+ def add(*binding)
24
+ if binding[0].is_a? Array
25
+ @bindings.merge!(Hash[binding])
26
+ else
27
+ @bindings.merge!(Hash[[binding]])
28
+ end
29
+ end
30
+
31
+ # Fetches the Form with the given name. Returns nil if no Form has the given name.
32
+ def fetch(name)
33
+ @bindings.fetch(name, nil)
34
+ end
35
+
36
+ # Fetches the binding for the given name, or nil if nothing has the given name.
37
+ def fetch_binding(name)
38
+ f = @bindings.fetch(name, nil)
39
+ return f ? [name, f] : nil
40
+ end
41
+
42
+ # Unbinds any Form bound to the given name.
43
+ def delete(name)
44
+ @bindings.delete(name)
45
+ end
46
+
47
+ # Passes each binding to the given block.
48
+ def each(&blk)
49
+ @bindings.each(&blk)
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,46 @@
1
+
2
+
3
+ module TracLang
4
+
5
+ # Class for storing forms under their names.
6
+ class Block
7
+
8
+ # Reads block from the given file. The file may have any valid TRAC
9
+ # commands in it, as well as ordinary text, which will be ignored.
10
+ # The options for trace and savedir will be inherited from the Dispatch
11
+ # calling this method.
12
+ def self.read(filename, dispatch)
13
+ Executor.new(dispatch).load_file(filename)
14
+ end
15
+
16
+ # Writes block to the given file. Block is written with the following:
17
+ # 1. Version of the TRAC Language processor
18
+ # 2. Current time
19
+ # 3. #(DS) commands for each bound form
20
+ # 4. #(SS) commands for each bound form that has segments
21
+ # 5. A mix of #(CN) and #(CS) commands to position the form pointer
22
+ def self.write(filename, bindings)
23
+ begin
24
+ File.open(filename, "w") do |f|
25
+ f.puts "TRAC Lang Version #{VERSION}"
26
+ f.puts "Saved: #{Time.now}"
27
+ f.puts
28
+ bindings.each { |name, form| f.puts(form.to_trac(name)) if form }
29
+ end
30
+ rescue
31
+ # do nothing if file open fails
32
+ end
33
+ end
34
+
35
+ # Deletes given file.
36
+ def self.delete(filename)
37
+ begin
38
+ File.delete(filename)
39
+ rescue Errno::ENOENT
40
+ # ignore non-existant file
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module TracLang
3
+
4
+ # Integer for TRAC Language. Consists of
5
+ # * string prefix
6
+ # * sign
7
+ # * numeric value
8
+ # The string prefix can be used to label the
9
+ # number and is carried over by operations.
10
+ # The sign is separate from the numeric value
11
+ # so that -0 can be distinguished from +0.
12
+ # This is needed when testing for Form pointers
13
+ # for EndOfString.
14
+ class Decimal
15
+
16
+ # String prefix of this number. Used to label the number,
17
+ # such as +Apples5+ or +Balance-100+.
18
+ attr_accessor :prefix
19
+
20
+ # Flag for negativity. Needed to distinguish between -0 and +0.
21
+ attr_accessor :negative
22
+
23
+ # Numeric value of this number.
24
+ attr_accessor :value
25
+
26
+ alias_method :negative?, :negative
27
+
28
+ # Create a TRAC decimal from a string. Any leading characters are
29
+ # saved as a prefix. The last sign character before the numeric
30
+ # portion is sign. If there are no numeric characters in the given
31
+ # string, zero is assumed.
32
+ def initialize(str = '')
33
+ raise ArgumentError unless str.is_a? String
34
+ n = str.partition(/[+-]?[0-9]*$/)
35
+ @prefix = n[0]
36
+ @value = n[1].to_i
37
+ @negative = n[1][0] == '-'
38
+ end
39
+
40
+ # Tests for equality. This is different from numeric equality
41
+ # because prefixes are tested as well as the numeric value.
42
+ def ==(d)
43
+ return super unless d.is_a? TracLang::Decimal
44
+ d.prefix == @prefix && d.value == @value && d.negative == @negative
45
+ end
46
+
47
+ # Returns string value of decimal. A sign is added to negative zeros.
48
+ def to_s
49
+ prefix + (negative? && value == 0 ? '-' : '') + value.to_s
50
+ end
51
+
52
+ # Defines method for given arithmetical operation. Result has string
53
+ # prefix of self. The operations defined are:
54
+ # [+]
55
+ # Sum of two decimals.
56
+ # [-]
57
+ # Difference.
58
+ # [*]
59
+ # Product.
60
+ # [/]
61
+ # Quotient.
62
+ def self.define_operation(symbol)
63
+ define_method(symbol) do |other|
64
+ result = Decimal.new
65
+ result.prefix = prefix
66
+ result.value = value.send(symbol, other.value)
67
+ result.negative = result.value < 0
68
+ result
69
+ end
70
+ end
71
+
72
+ define_operation :+
73
+ define_operation :-
74
+ define_operation :*
75
+ define_operation :/
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,421 @@
1
+
2
+ require_relative 'block'
3
+ require_relative 'decimal'
4
+ require_relative 'expression'
5
+ require_relative 'form'
6
+ require_relative 'octal'
7
+
8
+ module TracLang
9
+
10
+ # Class to dispatch TRAC commands to the proper code. Also stores the following options needed to run TRAC:
11
+ # [trace] Determines whether to display TRAC commands before executing them
12
+ # [meta] Meta character used to signal the end of input
13
+ # [savedir] Directory to save blocks to and load them from
14
+ class Dispatch
15
+
16
+ class << self
17
+ # Dispatch table. All basic TRAC commands are stored in the dispatch table.
18
+ # This is done instead of methods to prevent someone from inadvertently (or
19
+ # on purpose) calling a Ruby internal method instead of a TRAC command.
20
+ attr_accessor :table
21
+ end
22
+
23
+ # Initialize dispatch table.
24
+ @table = {}
25
+
26
+ # Defines a TRAC command to be added to the dispatch table.
27
+ # The following commands are defined.
28
+ #
29
+ # ---
30
+ # <em>System Commands</em>
31
+ #
32
+ # [on :hl]
33
+ # Halt. Halts the TRAC processor.
34
+ # [on :tn]
35
+ # Trace On. While trace is on, each Expression to be executed will be displayed to the
36
+ # user first. If the user presses return, the Expression will be executed. If any other
37
+ # key is pressed, the active string will be cleared and the idle string will be reloaded.
38
+ # [on :tf]
39
+ # Trace Off. When trace is off, execution proceeds as normal.
40
+ # [on :pf do |name = ''|]
41
+ # Print Form. Prints the form with the given name on the console. The form text, segments, and form pointer
42
+ # will all be displayed.
43
+ #
44
+ # ---
45
+ # <em>Basic Commands</em>
46
+ #
47
+ # [on :ds do |name = '', value = ''|]
48
+ # Define String. Creates a new Form with the given name and value, and stores it in the current set of bindings.
49
+ # [on :eq do |str1 = '', str2 = '', t = '', f = ''|]
50
+ # Equal. Tests if two strings are equal and returns the string t or f depending on the result of the test. Note that
51
+ # this is a string test for equality, so will not work for numerics. To compare numerically see the greater than command.
52
+ # [on :gr do |num1 = '', num2 = '', t = '', f = ''|]
53
+ # Greater Than. Compares the given Decimal values and returns the string t or f depending on the result of the test. To
54
+ # test two Decimal values for equality, test if neither is greater than the other.
55
+ #
56
+ # ---
57
+ # <em>I/O Commands</em>
58
+ #
59
+ # [on :ps do |str = ''|]
60
+ # Print String. Prints to the console the first argument.
61
+ # [on :rc]
62
+ # Read Character. Reads a single character from the keyboard. This will read control
63
+ # characters as well as printable characters.
64
+ # [on :rs]
65
+ # Read String. Reads characters until the meta character is typed. Primitive editing
66
+ # characters are available:
67
+ # [/] Erases the previous character
68
+ # [@] Erases the entire input string
69
+ # The editing characters don't change the appearance of input, they just change what the
70
+ # processor eventually sees. So for example, the following:
71
+ #
72
+ # <tt>#(DD@#(PS,Hellp\o World!)'</tt>
73
+ #
74
+ # will cause TRAC to print Hello World!
75
+ # [on :cm do |str|]
76
+ # Change Meta. Changes the meta character to the first character of the given string.
77
+ #
78
+ # ---
79
+ # <em>Block Commands</em>
80
+ #
81
+ # [on :dd do |*names|]
82
+ # Delete Definitions. Deletes Form definitions that are bound to the given names. Names that are
83
+ # not bound will be ignored.
84
+ # [on :da]
85
+ # Delete all definitions. A synonym for +#(DD,#(LN,(,)))+.
86
+ # [on :ln do |delimiter = ''|]
87
+ # List Names. Lists names defined in the current binding using the given delimiter. The delimiter defaults
88
+ # to the empty string, which means the names will all run together.
89
+ # [on :sb do |name, *fnames|]
90
+ # Store Block. Creates a new block with the given name and stores the given forms in it. This will
91
+ # create a file in the savedir with the name given and an extension of .trl. The file itself will contain
92
+ # the TRAC commands necessary to recreate the forms given and position their form pointers to the correct place.
93
+ # [on :fb do |name = ''|]
94
+ # Fetch Block. Fetches the block with the given name and adds its forms to this environment. This will read the
95
+ # file given by the form value and execute each line of it as series of TRAC commands. The contents of the block
96
+ # Form given by the name does not have to use the same directory as the +savedir+, so you can load files from other
97
+ # directories if you want. However, the Executor that is executing the TRAC commands in the file is using the +savedir+
98
+ # option, so if the file in question has a #(SB) command, it will use the +savedir+ in the Executor.
99
+ # [on :eb do |name = ''|]
100
+ # Erase Block. Deletes block and its corresponding file.
101
+ #
102
+ # ---
103
+ # <em>Math Commands</em>
104
+ #
105
+ # [on :bc do |str = ''|]
106
+ # Bit Complement. Maps the mnemonic <tt>:bc</tt> to Octal.~.
107
+ #
108
+ def self.on(sym)
109
+ table[sym] = Proc.new
110
+ end
111
+
112
+ # Returns empty string. All returns are wrapped in a hash containing
113
+ # the return value and the force flag.
114
+ def return_empty
115
+ {value: '', force: false}
116
+ end
117
+
118
+ # Returns value. All returns are wrapped in a hash containing the return
119
+ # value and the force flag.
120
+ def return_value(val)
121
+ {value: val, force: false}
122
+ end
123
+
124
+ # Returns value and forces it to the active string.
125
+ def return_force(val)
126
+ {value: val, force: true}
127
+ end
128
+
129
+ # Determines the TRAC mnemonic name from the given method name. This is
130
+ # used to map TRAC names to Form methods.
131
+ def self.mnemonic(name)
132
+ name.to_s.split('_').map {|w| w[0]}.join.to_sym
133
+ end
134
+
135
+ # Dispatches command to TRAC procedure. If command received is a TRAC
136
+ # command, it's looked up in the table and executed. If not, the #(CL)
137
+ # command is called, and the result is forced to the active string.
138
+ def dispatch(exp)
139
+ if Dispatch.table.has_key?(exp.command)
140
+ self.instance_exec(*exp.trac_args, &Dispatch.table[exp.command])
141
+ else
142
+ self.instance_exec(*exp.args, &Dispatch.table[:cl]).merge({force: true})
143
+ end
144
+ end
145
+
146
+ # Flag for whether trace is on or off. When trace is on,
147
+ # each time an Expression is parsed for execution, the Executor
148
+ # will display the Expression and wait for user input. If the
149
+ # user presses enter, TRAC proceeds as normal. If any other key
150
+ # is pressed, the Executor is reset.
151
+ attr_accessor :trace
152
+
153
+ # Directory that blocks are read from and written to.
154
+ attr_accessor :save_dir
155
+
156
+ # Meta character to end input. When the Executor is reading from a file,
157
+ # it will only pass the string on to the Parser after a meta character is
158
+ # received. Also, the TRAC #(RS) command will only return after a meta
159
+ # character is pressed.
160
+ attr_reader :meta
161
+
162
+ # Initializes environment for TRAC with a set of options.
163
+ # Options are:
164
+ # [bindings] Bindings to use
165
+ # [trace] Turn trace on or off
166
+ # [savedir] Directory that blocks are written to and read from.
167
+ def initialize(**options)
168
+ @root = options[:bindings] || Bindings.new
169
+ @trace = options[:trace] || false
170
+ @savedir = options[:savedir] || './'
171
+ @meta = "'"
172
+ end
173
+
174
+ # Halt command.
175
+ on :hl do
176
+ throw :done
177
+ end
178
+
179
+ # Trace on command.
180
+ on :tn do
181
+ @trace = true
182
+ return_empty
183
+ end
184
+
185
+ # Trace off command.
186
+ on :tf do
187
+ @trace = false
188
+ return_empty
189
+ end
190
+
191
+ # Equal command.
192
+ on :eq do |str1 = '', str2 = '', t = '', f = ''|
193
+ return_force(str1 == str2 ? t : f)
194
+ end
195
+
196
+ # Print string command.
197
+ on :ps do |str = ''|
198
+ print str
199
+ return_empty
200
+ end
201
+
202
+ # Read character command.
203
+ on :rc do
204
+ return_value(ImmediateRead.new.getch)
205
+ end
206
+
207
+ # Read string command.
208
+ on :rs do
209
+ str = ''
210
+ loop do
211
+ c = ImmediateRead.new.getch
212
+ case c
213
+ when @meta then break
214
+ when '\\' then str.slice!(-1)
215
+ when '@' then str = ''
216
+ else
217
+ str << c
218
+ end
219
+ end
220
+ return_value(str)
221
+ end
222
+
223
+ # Change meta command.
224
+ on :cm do |str|
225
+ @meta = str[0] if str
226
+ return_empty
227
+ end
228
+
229
+ # Define string command.
230
+ on :ds do |name = '', value = ''|
231
+ @root.add([name, Form.new(value)])
232
+ return_empty
233
+ end
234
+
235
+ # Defines mapping from dispatch command to Form method. The parameters are:
236
+ # [sym]
237
+ # Method name in Form to map to.
238
+ # [type]
239
+ # One of
240
+ # * :returning_empty
241
+ # To return an empty string.
242
+ # * :returning_value
243
+ # To return the value returned from the Form method.
244
+ # * :rescuing_eos
245
+ # To rescue in case EndOfStringError is raised.
246
+ # [pos]
247
+ # Used when type is +:rescuing_eos+. Index of argument
248
+ # that is returned in case EndOfStringError is raised.
249
+ #
250
+ # The following mnemonics are mapped to Form methods:
251
+ # [:ss]
252
+ # Segment String. Mapped to Form.segment_string.
253
+ # [:cr]
254
+ # Call Return. Mapped to Form.call_return.
255
+ # [:cl]
256
+ # Call Lookup. Mapped to Form.call_lookup.
257
+ # [:cc]
258
+ # Call Character. Mapped to Form.call_character.
259
+ # [:cs]
260
+ # Call Segment. Mapped to Form.call_segment.
261
+ # [:cn]
262
+ # Call N Characters. Mapped to Form.call_n.
263
+ # [:in]
264
+ # In String. Mapped to Form.in_neutral.
265
+ def self.dispatch_form(sym, type, pos = -1)
266
+ table[mnemonic(sym)] = Proc.new do |name = '', *args|
267
+ f = @root.fetch(name)
268
+ if !f
269
+ return_empty
270
+ else
271
+ case type
272
+ when :returning_empty
273
+ f.send(sym, *args)
274
+ return_empty
275
+ when :returning_value
276
+ return_value(f.send(sym, *args))
277
+ when :rescuing_eos_at
278
+ eos_result = args.slice!(pos) || ''
279
+ begin
280
+ return_value(f.send(sym, *args))
281
+ rescue TracLang::Form::EndOfStringError
282
+ return_force(eos_result)
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ dispatch_form(:segment_string, :returning_empty)
290
+ dispatch_form(:call_return, :returning_empty)
291
+ dispatch_form(:call_lookup, :returning_value)
292
+ dispatch_form(:call_character, :rescuing_eos_at, 0)
293
+ dispatch_form(:call_segment, :rescuing_eos_at, 0)
294
+ dispatch_form(:call_n, :rescuing_eos_at, 1)
295
+ dispatch_form(:in_neutral, :rescuing_eos_at, 1)
296
+
297
+ # Print form command.
298
+ on :pf do |name = ''|
299
+ f = @root.fetch(name)
300
+ puts f if f
301
+ return_empty
302
+ end
303
+
304
+ # Delete definitions command.
305
+ on :dd do |*names|
306
+ names.each { |name| @root.delete(name) }
307
+ return_empty
308
+ end
309
+
310
+ # Delete all command.
311
+ on :da do
312
+ @root.clear
313
+ return_empty
314
+ end
315
+
316
+ # List names command.
317
+ on :ln do |delimiter = ''|
318
+ return_value(@root.map { |n, v| n }.join(delimiter))
319
+ end
320
+
321
+ # Store block command.
322
+ on :sb do |name, *fnames|
323
+ if name
324
+ to_save = fnames.map { |n| @root.fetch_binding(n) }.compact
325
+ b = Bindings.new(*to_save)
326
+ filename = @savedir + name + '.trl'
327
+ Block.write(filename, b)
328
+ fnames.each { |n| @root.delete(n) }
329
+ @root.add([name, Form.new(filename)])
330
+ end
331
+ return_empty
332
+ end
333
+
334
+ # Fetch block command.
335
+ on :fb do |name = ''|
336
+ f = @root.fetch(name)
337
+ Block.read(f.value, self) if f
338
+ return_empty
339
+ end
340
+
341
+ # Erase block command.
342
+ on :eb do |name = ''|
343
+ f = @root.fetch(name)
344
+ Block.delete(f.value) if f
345
+ @root.delete(name)
346
+ return_empty
347
+ end
348
+
349
+ # Greater command.
350
+ on :gr do |num1 = '', num2 = '', t = '', f = ''|
351
+ unless num1 && num2
352
+ return_empty
353
+ else
354
+ n1 = Decimal.new(num1)
355
+ n2 = Decimal.new(num2)
356
+ return_force(n1.value > n2.value ? t : f)
357
+ end
358
+ end
359
+
360
+ # Maps a mnemonic to a Decimal operation. The following are mapped:
361
+ # [:ad] Add. Maps to Decimal.+.
362
+ # [:su] Subtract. Maps to Decimal.-.
363
+ # [:ml] Maps to Decimal.*.
364
+ # [:dv] Maps to Decimal./. If a ZeroDivisionError is detected, the overflow string is forced to output.
365
+ def self.dispatch_to_decimal(symbol)
366
+ Proc.new do |s1 = '', s2 = '', overflow = ''|
367
+ d1 = Decimal.new(s1)
368
+ d2 = Decimal.new(s2)
369
+ begin
370
+ return_value(d1.send(symbol, d2).to_s)
371
+ rescue ZeroDivisionError
372
+ return_force(overflow)
373
+ end
374
+ end
375
+ end
376
+
377
+ on :ad, &dispatch_to_decimal(:+)
378
+ on :su, &dispatch_to_decimal(:-)
379
+ on :ml, &dispatch_to_decimal(:*)
380
+ on :dv, &dispatch_to_decimal(:/)
381
+
382
+ # Maps a mnemonic to a Octal operation. The following are mapped:
383
+ # [:bu] Bit Union. Mapped to Octal.|.
384
+ # [:bi] Bit Intersection. Mapped to Octal.&.
385
+ def self.dispatch_to_octal(symbol)
386
+ Proc.new do |s1 = '', s2 = ''|
387
+ o1 = Octal.new(s1)
388
+ o2 = Octal.new(s2)
389
+ return_value(o1.send(symbol, o2).to_s)
390
+ end
391
+ end
392
+
393
+ on :bu, &dispatch_to_octal(:|)
394
+ on :bi, &dispatch_to_octal(:&)
395
+
396
+ # Bit complement command.
397
+ on :bc do |str = ''|
398
+ return_value(Octal.new(str).send(:~).to_s)
399
+ end
400
+
401
+ # Maps a mnemonic to a mixed Octal and Decimal operation. The following are mapped:
402
+ # [:bs] Bit Shift. Mapped to Octal.shift.
403
+ # [:br] Bit Rotate. Mapped to Octal.rotate.
404
+ def self.dispatch_to_mixed(sym)
405
+ Proc.new do |oct = '', dec = ''|
406
+ if dec.empty? || oct.empty?
407
+ return_empty
408
+ else
409
+ o1 = Octal.new(oct)
410
+ d1 = Decimal.new(dec)
411
+ return_value(o1.send(sym, d1).to_s)
412
+ end
413
+ end
414
+ end
415
+
416
+ on :bs, &dispatch_to_mixed(:shift)
417
+ on :br, &dispatch_to_mixed(:rotate)
418
+
419
+ end
420
+
421
+ end