WriteExcel 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/examples/a_simple.rb +42 -0
  8. data/examples/autofilters.rb +266 -0
  9. data/examples/bigfile.rb +30 -0
  10. data/examples/copyformat.rb +51 -0
  11. data/examples/data_validate.rb +278 -0
  12. data/examples/date_time.rb +86 -0
  13. data/examples/demo.rb +118 -0
  14. data/examples/diag_border.rb +35 -0
  15. data/examples/formats.rb +489 -0
  16. data/examples/header.rb +136 -0
  17. data/examples/hidden.rb +28 -0
  18. data/examples/hyperlink.rb +42 -0
  19. data/examples/images.rb +52 -0
  20. data/examples/merge1.rb +39 -0
  21. data/examples/merge2.rb +44 -0
  22. data/examples/merge3.rb +65 -0
  23. data/examples/merge4.rb +82 -0
  24. data/examples/merge5.rb +79 -0
  25. data/examples/protection.rb +46 -0
  26. data/examples/regions.rb +52 -0
  27. data/examples/repeat.rb +42 -0
  28. data/examples/stats.rb +75 -0
  29. data/examples/stocks.rb +80 -0
  30. data/examples/tab_colors.rb +30 -0
  31. data/lib/WriteExcel.rb +30 -0
  32. data/lib/WriteExcel/biffwriter.rb +259 -0
  33. data/lib/WriteExcel/chart.rb +217 -0
  34. data/lib/WriteExcel/excelformula.y +138 -0
  35. data/lib/WriteExcel/excelformulaparser.rb +573 -0
  36. data/lib/WriteExcel/format.rb +1108 -0
  37. data/lib/WriteExcel/formula.rb +986 -0
  38. data/lib/WriteExcel/olewriter.rb +322 -0
  39. data/lib/WriteExcel/properties.rb +250 -0
  40. data/lib/WriteExcel/storage_lite.rb +590 -0
  41. data/lib/WriteExcel/workbook.rb +2602 -0
  42. data/lib/WriteExcel/worksheet.rb +6378 -0
  43. data/spec/WriteExcel_spec.rb +7 -0
  44. data/spec/spec.opts +1 -0
  45. data/spec/spec_helper.rb +9 -0
  46. data/test/tc_all.rb +31 -0
  47. data/test/tc_biff.rb +104 -0
  48. data/test/tc_chart.rb +22 -0
  49. data/test/tc_example_match.rb +1280 -0
  50. data/test/tc_format.rb +1264 -0
  51. data/test/tc_formula.rb +63 -0
  52. data/test/tc_ole.rb +110 -0
  53. data/test/tc_storage_lite.rb +102 -0
  54. data/test/tc_workbook.rb +115 -0
  55. data/test/tc_worksheet.rb +115 -0
  56. data/test/test_00_IEEE_double.rb +14 -0
  57. data/test/test_01_add_worksheet.rb +12 -0
  58. data/test/test_02_merge_formats.rb +58 -0
  59. data/test/test_04_dimensions.rb +397 -0
  60. data/test/test_05_rows.rb +182 -0
  61. data/test/test_06_extsst.rb +80 -0
  62. data/test/test_11_date_time.rb +484 -0
  63. data/test/test_12_date_only.rb +506 -0
  64. data/test/test_13_date_seconds.rb +486 -0
  65. data/test/test_21_escher.rb +629 -0
  66. data/test/test_22_mso_drawing_group.rb +739 -0
  67. data/test/test_23_note.rb +78 -0
  68. data/test/test_24_txo.rb +80 -0
  69. data/test/test_26_autofilter.rb +327 -0
  70. data/test/test_27_autofilter.rb +144 -0
  71. data/test/test_28_autofilter.rb +174 -0
  72. data/test/test_29_process_jpg.rb +131 -0
  73. data/test/test_30_validation_dval.rb +82 -0
  74. data/test/test_31_validation_dv_strings.rb +131 -0
  75. data/test/test_32_validation_dv_formula.rb +211 -0
  76. data/test/test_40_property_types.rb +191 -0
  77. data/test/test_41_properties.rb +238 -0
  78. data/test/test_42_set_properties.rb +430 -0
  79. data/test/ts_all.rb +34 -0
  80. metadata +154 -0
@@ -0,0 +1,986 @@
1
+ ###############################################################################
2
+ #
3
+ # Formula - A class for generating Excel formulas.
4
+ #
5
+ #
6
+ # Used in conjunction with Spreadsheet::WriteExcel
7
+ #
8
+ # Copyright 2000-2008, John McNamara, jmcnamara@cpan.org
9
+ #
10
+ # original written in Perl by John McNamara
11
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
12
+ #
13
+ #require 'nkf'
14
+ #require 'strscan'
15
+ #require 'WriteExcel/excelformulaparser'
16
+
17
+ class Formula < ExcelFormulaParser
18
+
19
+ NonAscii = /[^!"#\$%&'\(\)\*\+,\-\.\/\:\;<=>\?@0-9A-Za-z_\[\\\]^` ~\0\n]/
20
+
21
+ attr_accessor :byte_order, :workbook, :ext_sheets, :ext_refs, :ext_ref_count
22
+
23
+ def initialize(byte_order)
24
+ @byte_order = byte_order
25
+ @workbook = ""
26
+ @ext_sheets = {}
27
+ @ext_refs = {}
28
+ @ext_ref_count = 0
29
+ initialize_hashes
30
+ end
31
+
32
+ ###############################################################################
33
+ #
34
+ # parse_formula()
35
+ #
36
+ # Takes a textual description of a formula and returns a RPN encoded byte
37
+ # string.
38
+ #
39
+ def parse_formula(formula, byte_stream = false)
40
+ # Build the parse tree for the formula
41
+ tokens = reverse(parse(formula))
42
+
43
+ # Add a volatile token if the formula contains a volatile function.
44
+ # This must be the first token in the list
45
+ #
46
+ tokens.unshift('_vol') if check_volatile(tokens) != 0
47
+
48
+ # The return value depends on which Worksheet.pm method is the caller
49
+ unless byte_stream
50
+ # Parse formula to see if it throws any errors and then
51
+ # return raw tokens to Worksheet::store_formula()
52
+ #
53
+ tokens
54
+ else
55
+ # Return byte stream to Worksheet::write_formula()
56
+ parse_tokens(tokens)
57
+ end
58
+ end
59
+
60
+ ###############################################################################
61
+ #
62
+ # parse_tokens()
63
+ #
64
+ # Convert each token or token pair to its Excel 'ptg' equivalent.
65
+ #
66
+ def parse_tokens(tokens)
67
+ parse_str = ''
68
+ last_type = ''
69
+ modifier = ''
70
+ num_args = 0
71
+ _class = 0
72
+ _classary = [1]
73
+ args = tokens.dup
74
+ # A note about the class modifiers used below. In general the class,
75
+ # "reference" or "value", of a function is applied to all of its operands.
76
+ # However, in certain circumstances the operands can have mixed classes,
77
+ # e.g. =VLOOKUP with external references. These will eventually be dealt
78
+ # with by the parser. However, as a workaround the class type of a token
79
+ # can be changed via the repeat_formula interface. Thus, a _ref2d token can
80
+ # be changed by the user to _ref2dA or _ref2dR to change its token class.
81
+ #
82
+ while (!args.empty?)
83
+ token = args.shift
84
+
85
+ if (token == '_arg')
86
+ num_args = args.shift
87
+ elsif (token == '_class')
88
+ token = args.shift
89
+ _class = @functions[token][2]
90
+ # If _class is undef then it means that the function isn't valid.
91
+ exit "Unknown function #{token}() in formula\n" if _class.nil?
92
+ _classary.push(_class)
93
+ elsif (token == '_vol')
94
+ parse_str = parse_str + convert_volatile()
95
+ elsif (token == 'ptgBool')
96
+ token = args.shift
97
+ parse_str = parse_str + convert_bool(token)
98
+ elsif (token == '_num')
99
+ token = args.shift
100
+ parse_str = parse_str + convert_number(token)
101
+ elsif (token == '_str')
102
+ token = args.shift
103
+ parse_str = parse_str + convert_string(token)
104
+ elsif (token =~ /^_ref2d/)
105
+ modifier = token.sub(/_ref2d/, '')
106
+ _class = _classary[-1]
107
+ _class = 0 if modifier == 'R'
108
+ _class = 1 if modifier == 'V'
109
+ token = args.shift
110
+ parse_str = parse_str + convert_ref2d(token, _class)
111
+ elsif (token =~ /^_ref3d/)
112
+ modifier = token.sub(/_ref3d/,'')
113
+ _class = _classary[-1]
114
+ _class = 0 if modifier == 'R'
115
+ _class = 1 if modifier == 'V'
116
+ token = args.shift
117
+ parse_str = parse_str + convert_ref3d(token, _class)
118
+ elsif (token =~ /^_range2d/)
119
+ modifier = token.sub(/_range2d/,'')
120
+ _class = _classary[-1]
121
+ _class = 0 if modifier == 'R'
122
+ _class = 1 if modifier == 'V'
123
+ token = args.shift
124
+ parse_str = parse_str + convert_range2d(token, _class)
125
+ elsif (token =~ /^_range3d/)
126
+ modifier = token.sub(/_range3d/,'')
127
+ _class = _classary[-1]
128
+ _class = 0 if modifier == 'R'
129
+ _class = 1 if modifier == 'V'
130
+ token = args.shift
131
+ parse_str = parse_str + convert_range3d(token, _class)
132
+ elsif (token == '_func')
133
+ token = args.shift
134
+ parse_str = parse_str + convert_function(token, num_args.to_i)
135
+ _classary.pop
136
+ num_args = 0 # Reset after use
137
+ elsif @ptg[token]
138
+ parse_str = parse_str + [@ptg[token]].pack("C")
139
+ else
140
+ # Unrecognised token
141
+ return nil
142
+ end
143
+ end
144
+
145
+
146
+ return parse_str
147
+ end
148
+
149
+ def scan(formula)
150
+ s = StringScanner.new(formula)
151
+ q = []
152
+ until s.eos?
153
+ # order is important.
154
+ if s.scan(/(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/)
155
+ q.push [:NUMBER, s.matched]
156
+ elsif s.scan(/"([^"]|"")*"/)
157
+ q.push [:STRING, s.matched]
158
+ elsif s.scan(/\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
159
+ q.push [:RANGE2D , s.matched]
160
+ elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
161
+ q.push [:RANGE3D , s.matched]
162
+ elsif s.scan(/\$?[A-I]?[A-Z]\$?\d+/)
163
+ q.push [:REF2D, s.matched]
164
+ elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?\d+/)
165
+ q.push [:REF3D , s.matched]
166
+ elsif s.scan(/'[^']+'!\$?[A-I]?[A-Z]\$?\d+/)
167
+ q.push [:REF3D , s.matched]
168
+ elsif s.scan(/<=/)
169
+ q.push [:LE , s.matched]
170
+ elsif s.scan(/>=/)
171
+ q.push [:GE , s.matched]
172
+ elsif s.scan(/<>/)
173
+ q.push [:NE , s.matched]
174
+ elsif s.scan(/</)
175
+ q.push [:LT , s.matched]
176
+ elsif s.scan(/>/)
177
+ q.push [:GT , s.matched]
178
+ elsif s.scan(/TRUE/)
179
+ q.push [:TRUE, s.matched]
180
+ elsif s.scan(/FALSE/)
181
+ q.push [:FALSE, s.matched]
182
+ elsif s.scan(/[A-Z0-9_.]+/)
183
+ q.push [:FUNC, s.matched]
184
+ elsif s.scan(/\s+/)
185
+ ;
186
+ elsif s.scan(/./)
187
+ q.push [s.matched, s.matched]
188
+ end
189
+ end
190
+ q.push [:EOL, nil]
191
+ end
192
+
193
+ def parse(formula)
194
+ @q = scan(formula)
195
+ @q.push [false, nil]
196
+ @yydebug=true
197
+ do_parse
198
+ end
199
+
200
+ def next_token
201
+ @q.shift
202
+ end
203
+
204
+ def reverse(expression)
205
+ expression.flatten
206
+ end
207
+
208
+ ###############################################################################
209
+ #
210
+ # get_ext_sheets()
211
+ #
212
+ # This semi-public method is used to update the hash of sheet names. It is
213
+ # updated by the add_worksheet() method of the Workbook class.
214
+ #
215
+ # TODO
216
+ #
217
+ def get_ext_sheets
218
+ # TODO
219
+ refs = @ext_refs
220
+ return refs
221
+
222
+ #my @refs = sort {$refs{$a} <=> $refs{$b}} keys %refs;
223
+
224
+ #foreach my $ref (@refs) {
225
+ # $ref = [split /:/, $ref];
226
+ #}
227
+
228
+ #return @refs;
229
+ end
230
+
231
+
232
+ ###############################################################################
233
+
234
+ private
235
+
236
+ ###############################################################################
237
+
238
+
239
+ ###############################################################################
240
+ #
241
+ # _check_volatile()
242
+ #
243
+ # Check if the formula contains a volatile function, i.e. a function that must
244
+ # be recalculated each time a cell is updated. These formulas require a ptgAttr
245
+ # with the volatile flag set as the first token in the parsed expression.
246
+ #
247
+ # Examples of volatile functions: RAND(), NOW(), TODAY()
248
+ #
249
+ def check_volatile(tokens)
250
+ volatile = 0
251
+
252
+ (0..tokens.size-1).each do |i|
253
+ # If the next token is a function check if it is volatile.
254
+ if tokens[i] == '_func' and @functions[tokens[i+1]][3] != 0
255
+ volatile = 1
256
+ break
257
+ end
258
+ end
259
+
260
+ return volatile
261
+ end
262
+
263
+ ###############################################################################
264
+ #
265
+ # _convert_volatile()
266
+ #
267
+ # Convert _vol to a ptgAttr tag formatted to indicate that the formula contains
268
+ # a volatile function. See _check_volatile()
269
+ #
270
+ def convert_volatile
271
+ # Set bitFattrSemi flag to indicate volatile function, "w" is set to zero.
272
+ return [@ptg['ptgAttr'], 0x1, 0x0].pack("CCv")
273
+ end
274
+
275
+ ###############################################################################
276
+ #
277
+ # _convert_bool()
278
+ #
279
+ # Convert a boolean token to ptgBool
280
+ #
281
+ def convert_bool(bool)
282
+ return [@ptg['ptgBool'], bool.to_i].pack("CC")
283
+ end
284
+
285
+
286
+ ###############################################################################
287
+ #
288
+ # _convert_number()
289
+ #
290
+ # Convert a number token to ptgInt or ptgNum
291
+ #
292
+ def convert_number(num)
293
+ # Integer in the range 0..2**16-1
294
+ if ((num =~ /^\d+$/) && (num.to_i <= 65535))
295
+ return [@ptg['ptgInt'], num.to_i].pack("Cv")
296
+ else # A float
297
+ num = [num].pack("d")
298
+ num.reverse! if @byte_order != 0 && @byte_order != ''
299
+ return [@ptg['ptgNum']].pack("C") + num
300
+ end
301
+ end
302
+
303
+ ###############################################################################
304
+ #
305
+ # _convert_string()
306
+ #
307
+ # Convert a string to a ptg Str.
308
+ #
309
+ def convert_string(str)
310
+ encoding = 0
311
+
312
+ str.sub!(/^"/,'') # Remove leading "
313
+ str.sub!(/"$/,'') # Remove trailing "
314
+ str.gsub!(/""/,'"') # Substitute Excel's escaped double quote "" for "
315
+
316
+ length = str.length
317
+
318
+ # Handle utf8 strings
319
+ if str =~ NonAscii
320
+ str = NKF.nkf('-w16L0 -m0 -W', str)
321
+ encoding = 1
322
+ end
323
+
324
+ exit "String in formula has more than 255 chars\n" if length > 255
325
+
326
+ return [@ptg['ptgStr'], length, encoding].pack("CCC") + str
327
+ end
328
+
329
+ ###############################################################################
330
+ #
331
+ # _convert_ref2d()
332
+ #
333
+ # Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
334
+ #
335
+ def convert_ref2d(cell, _class)
336
+ # Convert the cell reference
337
+ row, col = cell_to_packed_rowcol(cell)
338
+
339
+ # The ptg value depends on the class of the ptg.
340
+ if (_class == 0)
341
+ ptgref = [@ptg['ptgRef']].pack("C")
342
+ elsif (_class == 1)
343
+ ptgref = [@ptg['ptgRefV']].pack("C")
344
+ elsif (_class == 2)
345
+ ptgref = [@ptg['ptgRefA']].pack("C")
346
+ else
347
+ exit "Unknown function class in formula\n"
348
+ end
349
+
350
+ return ptgref + row + col
351
+ end
352
+
353
+ ###############################################################################
354
+ #
355
+ # _convert_ref3d
356
+ #
357
+ # Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
358
+ # ptgRef3dV.
359
+ #
360
+ def convert_ref3d(token, _class)
361
+ # Split the ref at the ! symbol
362
+ ext_ref, cell = token.split('!')
363
+
364
+ # Convert the external reference part
365
+ ext_ref = pack_ext_ref(ext_ref)
366
+
367
+ # Convert the cell reference part
368
+ row, col = cell_to_packed_rowcol(cell)
369
+
370
+ # The ptg value depends on the class of the ptg.
371
+ if (_class == 0)
372
+ ptgref = [@ptg['ptgRef3d']].pack("C")
373
+ elsif (_class == 1)
374
+ ptgref = [@ptg['ptgRef3dV']].pack("C")
375
+ elsif (_class == 2)
376
+ ptgref = [@ptg['ptgRef3dA']].pack("C")
377
+ else
378
+ exit "Unknown function class in formula\n"
379
+ end
380
+
381
+ return ptgref + ext_ref + row + col
382
+ end
383
+
384
+ ###############################################################################
385
+ #
386
+ # _convert_range2d()
387
+ #
388
+ # Convert an Excel range such as A1:D4 or A:D to a ptgRefV.
389
+ #
390
+ def convert_range2d(range, _class)
391
+ # Split the range into 2 cell refs
392
+ cell1, cell2 = range.split(':')
393
+
394
+ # A range such as A:D is equivalent to A1:D65536, so add rows as required
395
+ cell1 = cell1 + '1' unless cell1 =~ /\d/
396
+ cell2 = cell2 + '65536' unless cell2 =~ /\d/
397
+
398
+ # Convert the cell references
399
+ row1, col1 = cell_to_packed_rowcol(cell1)
400
+ row2, col2 = cell_to_packed_rowcol(cell2)
401
+
402
+ # The ptg value depends on the class of the ptg.
403
+ if (_class == 0)
404
+ ptgarea = [@ptg['ptgArea']].pack("C")
405
+ elsif (_class == 1)
406
+ ptgarea = [@ptg['ptgAreaV']].pack("C")
407
+ elsif (_class == 2)
408
+ ptgarea = [@ptg['ptgAreaA']].pack("C")
409
+ else
410
+ exit "Unknown function class in formula\n"
411
+ end
412
+
413
+ return ptgarea + row1 + row2 + col1 + col2
414
+ end
415
+
416
+ ###############################################################################
417
+ #
418
+ # _convert_range3d
419
+ #
420
+ # Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
421
+ # a ptgArea3dV.
422
+ #
423
+ def convert_range3d(token, _class)
424
+ # Split the ref at the ! symbol
425
+ ext_ref, range = token.split('!')
426
+
427
+ # Convert the external reference part
428
+ ext_ref = pack_ext_ref(ext_ref)
429
+
430
+ # Split the range into 2 cell refs
431
+ cell1, cell2 = range.split(':')
432
+
433
+ # A range such as A:D is equivalent to A1:D65536, so add rows as required
434
+ cell1 = cell1 + '1' unless cell1 =~ /\d/
435
+ cell2 = cell2 + '65536' unless cell2 =~ /\d/
436
+
437
+ # Convert the cell references
438
+ row1, col1 = cell_to_packed_rowcol(cell1)
439
+ row2, col2 = cell_to_packed_rowcol(cell2)
440
+
441
+ # The ptg value depends on the class of the ptg.
442
+ if (_class == 0)
443
+ ptgarea = [@ptg['ptgArea3d']].pack("C")
444
+ elsif (_class == 1)
445
+ ptgarea = [@ptg['ptgArea3dV']].pack("C")
446
+ elsif (_class == 2)
447
+ ptgarea = [@ptg['ptgArea3dA']].pack("C")
448
+ else
449
+ exit "Unknown function class in formula\n"
450
+ end
451
+
452
+ return ptgarea + ext_ref + row1 + row2 + col1+ col2
453
+ end
454
+
455
+ ###############################################################################
456
+ #
457
+ # _pack_ext_ref()
458
+ #
459
+ # Convert the sheet name part of an external reference, for example "Sheet1" or
460
+ # "Sheet1:Sheet2", to a packed structure.
461
+ #
462
+ def pack_ext_ref(ext_ref)
463
+ ext_ref.sub!(/^'/,'') # Remove leading ' if any.
464
+ ext_ref.sub!(/'$/,'') # Remove trailing ' if any.
465
+
466
+ # Check if there is a sheet range eg., Sheet1:Sheet2.
467
+ if (ext_ref =~ /:/)
468
+ sheet1, sheet2 = ext_ref.split(':')
469
+
470
+ sheet1 = get_sheet_index(sheet1)
471
+ sheet2 = get_sheet_index(sheet2)
472
+
473
+ # Reverse max and min sheet numbers if necessary
474
+ if (sheet1 > sheet2)
475
+ sheet1, sheet2 = [sheet2, sheet1]
476
+ end
477
+ else
478
+ # Single sheet name only.
479
+ sheet1, sheet2 = [ext_ref, ext_ref]
480
+
481
+ sheet1 = get_sheet_index(sheet1)
482
+ sheet2 = sheet1
483
+ end
484
+
485
+ key = "#{sheet1}:#{sheet2}"
486
+
487
+ unless @ext_refs[key]
488
+ index = @ext_refs[key]
489
+ else
490
+ index = @ext_ref_count
491
+ @ext_refs[key] = index
492
+ @ext_ref_count += 1
493
+ end
494
+
495
+ return [index].pack("v")
496
+ end
497
+
498
+ ###############################################################################
499
+ #
500
+ # _get_sheet_index()
501
+ #
502
+ # Look up the index that corresponds to an external sheet name. The hash of
503
+ # sheet names is updated by the add_worksheet() method of the Workbook class.
504
+ #
505
+ def get_sheet_index(sheet_name)
506
+ # Handle utf8 sheetnames
507
+ if sheet_name =~ NonAscii
508
+ sheet_name = NKF.nkf('-w16B0 -m0 -W', sheet_name)
509
+ end
510
+
511
+ if @ext_sheets[sheet_name].nil?
512
+ exit "Unknown sheet name #{sheet_name} in formula\n"
513
+ else
514
+ return @ext_sheets[sheet_name]
515
+ end
516
+ end
517
+
518
+ ###############################################################################
519
+ #
520
+ # get_ext_ref_count()
521
+ #
522
+ # TODO This semi-public method is used to update the hash of sheet names. It is
523
+ # updated by the add_worksheet() method of the Workbook class.
524
+ #
525
+ def get_ext_ref_count
526
+ return @ext_ref_count
527
+ end
528
+
529
+ ###############################################################################
530
+ #
531
+ # _convert_function()
532
+ #
533
+ # Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
534
+ # args that it takes.
535
+ #
536
+ def convert_function(token, num_args)
537
+ exit "Unknown function #{token}() in formula\n" if @functions[token][0].nil?
538
+
539
+ args = @functions[token][1]
540
+
541
+ # Fixed number of args eg. TIME($i,$j,$k).
542
+ if (args >= 0)
543
+ # Check that the number of args is valid.
544
+ if (args != num_args)
545
+ raise "Incorrect number of arguments for #{token}() in formula\n";
546
+ else
547
+ return [@ptg['ptgFuncV'], @functions[token][0]].pack("Cv")
548
+ end
549
+ end
550
+
551
+ # Variable number of args eg. SUM(i,j,k, ..).
552
+ if (args == -1)
553
+ return [@ptg['ptgFuncVarV'], num_args, @functions[token][0]].pack("CCv")
554
+ end
555
+ end
556
+
557
+ ###############################################################################
558
+ #
559
+ # _cell_to_rowcol($cell_ref)
560
+ #
561
+ # Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
562
+ # indexed row and column number. Also returns two boolean values to indicate
563
+ # whether the row or column are relative references.
564
+ # TODO use function in Utility.pm
565
+ #
566
+ def cell_to_rowcol(cell)
567
+ cell =~ /(\$?)([A-I]?[A-Z])(\$?)(\d+)/
568
+
569
+ col_rel = $1 == "" ? 1 : 0
570
+ col = $2
571
+ row_rel = $3 == "" ? 1 : 0
572
+ row = $4.to_i
573
+
574
+ # Convert base26 column string to a number.
575
+ # All your Base are belong to us.
576
+ chars = col.split(//)
577
+ expn = 0
578
+ col = 0
579
+
580
+ while (!chars.empty?)
581
+ char = chars.pop # LS char first
582
+ col = col + (char[0] - "A"[0] + 1) * (26**expn)
583
+ expn += 1
584
+ end
585
+ # Convert 1-index to zero-index
586
+ row -= 1
587
+ col -= 1
588
+
589
+ return [row, col, row_rel, col_rel]
590
+ end
591
+
592
+ ###############################################################################
593
+ #
594
+ # _cell_to_packed_rowcol($row, $col, $row_rel, $col_rel)
595
+ #
596
+ # pack() row and column into the required 3 byte format.
597
+ #
598
+ def cell_to_packed_rowcol(cell)
599
+ row, col, row_rel, col_rel = cell_to_rowcol(cell)
600
+
601
+ exit "Column #{cell} greater than IV in formula\n" if col >= 256
602
+ exit "Row #{cell} greater than 65536 in formula\n" if row >= 65536
603
+
604
+ # Set the high bits to indicate if row or col are relative.
605
+ col |= col_rel << 14
606
+ col |= row_rel << 15
607
+
608
+ row = [row].pack('v')
609
+ col = [col].pack('v')
610
+
611
+ return [row, col]
612
+ end
613
+
614
+ ###############################################################################
615
+ #
616
+ # _initialize_hashes()
617
+ #
618
+ def initialize_hashes
619
+
620
+ # The Excel ptg indices
621
+ @ptg = {
622
+ 'ptgExp' => 0x01,
623
+ 'ptgTbl' => 0x02,
624
+ 'ptgAdd' => 0x03,
625
+ 'ptgSub' => 0x04,
626
+ 'ptgMul' => 0x05,
627
+ 'ptgDiv' => 0x06,
628
+ 'ptgPower' => 0x07,
629
+ 'ptgConcat' => 0x08,
630
+ 'ptgLT' => 0x09,
631
+ 'ptgLE' => 0x0A,
632
+ 'ptgEQ' => 0x0B,
633
+ 'ptgGE' => 0x0C,
634
+ 'ptgGT' => 0x0D,
635
+ 'ptgNE' => 0x0E,
636
+ 'ptgIsect' => 0x0F,
637
+ 'ptgUnion' => 0x10,
638
+ 'ptgRange' => 0x11,
639
+ 'ptgUplus' => 0x12,
640
+ 'ptgUminus' => 0x13,
641
+ 'ptgPercent' => 0x14,
642
+ 'ptgParen' => 0x15,
643
+ 'ptgMissArg' => 0x16,
644
+ 'ptgStr' => 0x17,
645
+ 'ptgAttr' => 0x19,
646
+ 'ptgSheet' => 0x1A,
647
+ 'ptgEndSheet' => 0x1B,
648
+ 'ptgErr' => 0x1C,
649
+ 'ptgBool' => 0x1D,
650
+ 'ptgInt' => 0x1E,
651
+ 'ptgNum' => 0x1F,
652
+ 'ptgArray' => 0x20,
653
+ 'ptgFunc' => 0x21,
654
+ 'ptgFuncVar' => 0x22,
655
+ 'ptgName' => 0x23,
656
+ 'ptgRef' => 0x24,
657
+ 'ptgArea' => 0x25,
658
+ 'ptgMemArea' => 0x26,
659
+ 'ptgMemErr' => 0x27,
660
+ 'ptgMemNoMem' => 0x28,
661
+ 'ptgMemFunc' => 0x29,
662
+ 'ptgRefErr' => 0x2A,
663
+ 'ptgAreaErr' => 0x2B,
664
+ 'ptgRefN' => 0x2C,
665
+ 'ptgAreaN' => 0x2D,
666
+ 'ptgMemAreaN' => 0x2E,
667
+ 'ptgMemNoMemN' => 0x2F,
668
+ 'ptgNameX' => 0x39,
669
+ 'ptgRef3d' => 0x3A,
670
+ 'ptgArea3d' => 0x3B,
671
+ 'ptgRefErr3d' => 0x3C,
672
+ 'ptgAreaErr3d' => 0x3D,
673
+ 'ptgArrayV' => 0x40,
674
+ 'ptgFuncV' => 0x41,
675
+ 'ptgFuncVarV' => 0x42,
676
+ 'ptgNameV' => 0x43,
677
+ 'ptgRefV' => 0x44,
678
+ 'ptgAreaV' => 0x45,
679
+ 'ptgMemAreaV' => 0x46,
680
+ 'ptgMemErrV' => 0x47,
681
+ 'ptgMemNoMemV' => 0x48,
682
+ 'ptgMemFuncV' => 0x49,
683
+ 'ptgRefErrV' => 0x4A,
684
+ 'ptgAreaErrV' => 0x4B,
685
+ 'ptgRefNV' => 0x4C,
686
+ 'ptgAreaNV' => 0x4D,
687
+ 'ptgMemAreaNV' => 0x4E,
688
+ 'ptgMemNoMemN' => 0x4F,
689
+ 'ptgFuncCEV' => 0x58,
690
+ 'ptgNameXV' => 0x59,
691
+ 'ptgRef3dV' => 0x5A,
692
+ 'ptgArea3dV' => 0x5B,
693
+ 'ptgRefErr3dV' => 0x5C,
694
+ 'ptgAreaErr3d' => 0x5D,
695
+ 'ptgArrayA' => 0x60,
696
+ 'ptgFuncA' => 0x61,
697
+ 'ptgFuncVarA' => 0x62,
698
+ 'ptgNameA' => 0x63,
699
+ 'ptgRefA' => 0x64,
700
+ 'ptgAreaA' => 0x65,
701
+ 'ptgMemAreaA' => 0x66,
702
+ 'ptgMemErrA' => 0x67,
703
+ 'ptgMemNoMemA' => 0x68,
704
+ 'ptgMemFuncA' => 0x69,
705
+ 'ptgRefErrA' => 0x6A,
706
+ 'ptgAreaErrA' => 0x6B,
707
+ 'ptgRefNA' => 0x6C,
708
+ 'ptgAreaNA' => 0x6D,
709
+ 'ptgMemAreaNA' => 0x6E,
710
+ 'ptgMemNoMemN' => 0x6F,
711
+ 'ptgFuncCEA' => 0x78,
712
+ 'ptgNameXA' => 0x79,
713
+ 'ptgRef3dA' => 0x7A,
714
+ 'ptgArea3dA' => 0x7B,
715
+ 'ptgRefErr3dA' => 0x7C,
716
+ 'ptgAreaErr3d' => 0x7D
717
+ };
718
+
719
+ # Thanks to Michael Meeks and Gnumeric for the initial arg values.
720
+ #
721
+ # The following hash was generated by "function_locale.pl" in the distro.
722
+ # Refer to function_locale.pl for non-English function names.
723
+ #
724
+ # The array elements are as follow:
725
+ # ptg: The Excel function ptg code.
726
+ # args: The number of arguments that the function takes:
727
+ # >=0 is a fixed number of arguments.
728
+ # -1 is a variable number of arguments.
729
+ # class: The reference, value or array class of the function args.
730
+ # vol: The function is volatile.
731
+ #
732
+ @functions = {
733
+ # ptg args class vol
734
+ 'COUNT' => [ 0, -1, 0, 0 ],
735
+ 'IF' => [ 1, -1, 1, 0 ],
736
+ 'ISNA' => [ 2, 1, 1, 0 ],
737
+ 'ISERROR' => [ 3, 1, 1, 0 ],
738
+ 'SUM' => [ 4, -1, 0, 0 ],
739
+ 'AVERAGE' => [ 5, -1, 0, 0 ],
740
+ 'MIN' => [ 6, -1, 0, 0 ],
741
+ 'MAX' => [ 7, -1, 0, 0 ],
742
+ 'ROW' => [ 8, -1, 0, 0 ],
743
+ 'COLUMN' => [ 9, -1, 0, 0 ],
744
+ 'NA' => [ 10, 0, 0, 0 ],
745
+ 'NPV' => [ 11, -1, 1, 0 ],
746
+ 'STDEV' => [ 12, -1, 0, 0 ],
747
+ 'DOLLAR' => [ 13, -1, 1, 0 ],
748
+ 'FIXED' => [ 14, -1, 1, 0 ],
749
+ 'SIN' => [ 15, 1, 1, 0 ],
750
+ 'COS' => [ 16, 1, 1, 0 ],
751
+ 'TAN' => [ 17, 1, 1, 0 ],
752
+ 'ATAN' => [ 18, 1, 1, 0 ],
753
+ 'PI' => [ 19, 0, 1, 0 ],
754
+ 'SQRT' => [ 20, 1, 1, 0 ],
755
+ 'EXP' => [ 21, 1, 1, 0 ],
756
+ 'LN' => [ 22, 1, 1, 0 ],
757
+ 'LOG10' => [ 23, 1, 1, 0 ],
758
+ 'ABS' => [ 24, 1, 1, 0 ],
759
+ 'INT' => [ 25, 1, 1, 0 ],
760
+ 'SIGN' => [ 26, 1, 1, 0 ],
761
+ 'ROUND' => [ 27, 2, 1, 0 ],
762
+ 'LOOKUP' => [ 28, -1, 0, 0 ],
763
+ 'INDEX' => [ 29, -1, 0, 1 ],
764
+ 'REPT' => [ 30, 2, 1, 0 ],
765
+ 'MID' => [ 31, 3, 1, 0 ],
766
+ 'LEN' => [ 32, 1, 1, 0 ],
767
+ 'VALUE' => [ 33, 1, 1, 0 ],
768
+ 'TRUE' => [ 34, 0, 1, 0 ],
769
+ 'FALSE' => [ 35, 0, 1, 0 ],
770
+ 'AND' => [ 36, -1, 1, 0 ],
771
+ 'OR' => [ 37, -1, 1, 0 ],
772
+ 'NOT' => [ 38, 1, 1, 0 ],
773
+ 'MOD' => [ 39, 2, 1, 0 ],
774
+ 'DCOUNT' => [ 40, 3, 0, 0 ],
775
+ 'DSUM' => [ 41, 3, 0, 0 ],
776
+ 'DAVERAGE' => [ 42, 3, 0, 0 ],
777
+ 'DMIN' => [ 43, 3, 0, 0 ],
778
+ 'DMAX' => [ 44, 3, 0, 0 ],
779
+ 'DSTDEV' => [ 45, 3, 0, 0 ],
780
+ 'VAR' => [ 46, -1, 0, 0 ],
781
+ 'DVAR' => [ 47, 3, 0, 0 ],
782
+ 'TEXT' => [ 48, 2, 1, 0 ],
783
+ 'LINEST' => [ 49, -1, 0, 0 ],
784
+ 'TREND' => [ 50, -1, 0, 0 ],
785
+ 'LOGEST' => [ 51, -1, 0, 0 ],
786
+ 'GROWTH' => [ 52, -1, 0, 0 ],
787
+ 'PV' => [ 56, -1, 1, 0 ],
788
+ 'FV' => [ 57, -1, 1, 0 ],
789
+ 'NPER' => [ 58, -1, 1, 0 ],
790
+ 'PMT' => [ 59, -1, 1, 0 ],
791
+ 'RATE' => [ 60, -1, 1, 0 ],
792
+ 'MIRR' => [ 61, 3, 0, 0 ],
793
+ 'IRR' => [ 62, -1, 0, 0 ],
794
+ 'RAND' => [ 63, 0, 1, 1 ],
795
+ 'MATCH' => [ 64, -1, 0, 0 ],
796
+ 'DATE' => [ 65, 3, 1, 0 ],
797
+ 'TIME' => [ 66, 3, 1, 0 ],
798
+ 'DAY' => [ 67, 1, 1, 0 ],
799
+ 'MONTH' => [ 68, 1, 1, 0 ],
800
+ 'YEAR' => [ 69, 1, 1, 0 ],
801
+ 'WEEKDAY' => [ 70, -1, 1, 0 ],
802
+ 'HOUR' => [ 71, 1, 1, 0 ],
803
+ 'MINUTE' => [ 72, 1, 1, 0 ],
804
+ 'SECOND' => [ 73, 1, 1, 0 ],
805
+ 'NOW' => [ 74, 0, 1, 1 ],
806
+ 'AREAS' => [ 75, 1, 0, 1 ],
807
+ 'ROWS' => [ 76, 1, 0, 1 ],
808
+ 'COLUMNS' => [ 77, 1, 0, 1 ],
809
+ 'OFFSET' => [ 78, -1, 0, 1 ],
810
+ 'SEARCH' => [ 82, -1, 1, 0 ],
811
+ 'TRANSPOSE' => [ 83, 1, 1, 0 ],
812
+ 'TYPE' => [ 86, 1, 1, 0 ],
813
+ 'ATAN2' => [ 97, 2, 1, 0 ],
814
+ 'ASIN' => [ 98, 1, 1, 0 ],
815
+ 'ACOS' => [ 99, 1, 1, 0 ],
816
+ 'CHOOSE' => [ 100, -1, 1, 0 ],
817
+ 'HLOOKUP' => [ 101, -1, 0, 0 ],
818
+ 'VLOOKUP' => [ 102, -1, 0, 0 ],
819
+ 'ISREF' => [ 105, 1, 0, 0 ],
820
+ 'LOG' => [ 109, -1, 1, 0 ],
821
+ 'CHAR' => [ 111, 1, 1, 0 ],
822
+ 'LOWER' => [ 112, 1, 1, 0 ],
823
+ 'UPPER' => [ 113, 1, 1, 0 ],
824
+ 'PROPER' => [ 114, 1, 1, 0 ],
825
+ 'LEFT' => [ 115, -1, 1, 0 ],
826
+ 'RIGHT' => [ 116, -1, 1, 0 ],
827
+ 'EXACT' => [ 117, 2, 1, 0 ],
828
+ 'TRIM' => [ 118, 1, 1, 0 ],
829
+ 'REPLACE' => [ 119, 4, 1, 0 ],
830
+ 'SUBSTITUTE' => [ 120, -1, 1, 0 ],
831
+ 'CODE' => [ 121, 1, 1, 0 ],
832
+ 'FIND' => [ 124, -1, 1, 0 ],
833
+ 'CELL' => [ 125, -1, 0, 1 ],
834
+ 'ISERR' => [ 126, 1, 1, 0 ],
835
+ 'ISTEXT' => [ 127, 1, 1, 0 ],
836
+ 'ISNUMBER' => [ 128, 1, 1, 0 ],
837
+ 'ISBLANK' => [ 129, 1, 1, 0 ],
838
+ 'T' => [ 130, 1, 0, 0 ],
839
+ 'N' => [ 131, 1, 0, 0 ],
840
+ 'DATEVALUE' => [ 140, 1, 1, 0 ],
841
+ 'TIMEVALUE' => [ 141, 1, 1, 0 ],
842
+ 'SLN' => [ 142, 3, 1, 0 ],
843
+ 'SYD' => [ 143, 4, 1, 0 ],
844
+ 'DDB' => [ 144, -1, 1, 0 ],
845
+ 'INDIRECT' => [ 148, -1, 1, 1 ],
846
+ 'CALL' => [ 150, -1, 1, 0 ],
847
+ 'CLEAN' => [ 162, 1, 1, 0 ],
848
+ 'MDETERM' => [ 163, 1, 2, 0 ],
849
+ 'MINVERSE' => [ 164, 1, 2, 0 ],
850
+ 'MMULT' => [ 165, 2, 2, 0 ],
851
+ 'IPMT' => [ 167, -1, 1, 0 ],
852
+ 'PPMT' => [ 168, -1, 1, 0 ],
853
+ 'COUNTA' => [ 169, -1, 0, 0 ],
854
+ 'PRODUCT' => [ 183, -1, 0, 0 ],
855
+ 'FACT' => [ 184, 1, 1, 0 ],
856
+ 'DPRODUCT' => [ 189, 3, 0, 0 ],
857
+ 'ISNONTEXT' => [ 190, 1, 1, 0 ],
858
+ 'STDEVP' => [ 193, -1, 0, 0 ],
859
+ 'VARP' => [ 194, -1, 0, 0 ],
860
+ 'DSTDEVP' => [ 195, 3, 0, 0 ],
861
+ 'DVARP' => [ 196, 3, 0, 0 ],
862
+ 'TRUNC' => [ 197, -1, 1, 0 ],
863
+ 'ISLOGICAL' => [ 198, 1, 1, 0 ],
864
+ 'DCOUNTA' => [ 199, 3, 0, 0 ],
865
+ 'ROUNDUP' => [ 212, 2, 1, 0 ],
866
+ 'ROUNDDOWN' => [ 213, 2, 1, 0 ],
867
+ 'RANK' => [ 216, -1, 0, 0 ],
868
+ 'ADDRESS' => [ 219, -1, 1, 0 ],
869
+ 'DAYS360' => [ 220, -1, 1, 0 ],
870
+ 'TODAY' => [ 221, 0, 1, 1 ],
871
+ 'VDB' => [ 222, -1, 1, 0 ],
872
+ 'MEDIAN' => [ 227, -1, 0, 0 ],
873
+ 'SUMPRODUCT' => [ 228, -1, 2, 0 ],
874
+ 'SINH' => [ 229, 1, 1, 0 ],
875
+ 'COSH' => [ 230, 1, 1, 0 ],
876
+ 'TANH' => [ 231, 1, 1, 0 ],
877
+ 'ASINH' => [ 232, 1, 1, 0 ],
878
+ 'ACOSH' => [ 233, 1, 1, 0 ],
879
+ 'ATANH' => [ 234, 1, 1, 0 ],
880
+ 'DGET' => [ 235, 3, 0, 0 ],
881
+ 'INFO' => [ 244, 1, 1, 1 ],
882
+ 'DB' => [ 247, -1, 1, 0 ],
883
+ 'FREQUENCY' => [ 252, 2, 0, 0 ],
884
+ 'ERROR.TYPE' => [ 261, 1, 1, 0 ],
885
+ 'REGISTER.ID' => [ 267, -1, 1, 0 ],
886
+ 'AVEDEV' => [ 269, -1, 0, 0 ],
887
+ 'BETADIST' => [ 270, -1, 1, 0 ],
888
+ 'GAMMALN' => [ 271, 1, 1, 0 ],
889
+ 'BETAINV' => [ 272, -1, 1, 0 ],
890
+ 'BINOMDIST' => [ 273, 4, 1, 0 ],
891
+ 'CHIDIST' => [ 274, 2, 1, 0 ],
892
+ 'CHIINV' => [ 275, 2, 1, 0 ],
893
+ 'COMBIN' => [ 276, 2, 1, 0 ],
894
+ 'CONFIDENCE' => [ 277, 3, 1, 0 ],
895
+ 'CRITBINOM' => [ 278, 3, 1, 0 ],
896
+ 'EVEN' => [ 279, 1, 1, 0 ],
897
+ 'EXPONDIST' => [ 280, 3, 1, 0 ],
898
+ 'FDIST' => [ 281, 3, 1, 0 ],
899
+ 'FINV' => [ 282, 3, 1, 0 ],
900
+ 'FISHER' => [ 283, 1, 1, 0 ],
901
+ 'FISHERINV' => [ 284, 1, 1, 0 ],
902
+ 'FLOOR' => [ 285, 2, 1, 0 ],
903
+ 'GAMMADIST' => [ 286, 4, 1, 0 ],
904
+ 'GAMMAINV' => [ 287, 3, 1, 0 ],
905
+ 'CEILING' => [ 288, 2, 1, 0 ],
906
+ 'HYPGEOMDIST' => [ 289, 4, 1, 0 ],
907
+ 'LOGNORMDIST' => [ 290, 3, 1, 0 ],
908
+ 'LOGINV' => [ 291, 3, 1, 0 ],
909
+ 'NEGBINOMDIST' => [ 292, 3, 1, 0 ],
910
+ 'NORMDIST' => [ 293, 4, 1, 0 ],
911
+ 'NORMSDIST' => [ 294, 1, 1, 0 ],
912
+ 'NORMINV' => [ 295, 3, 1, 0 ],
913
+ 'NORMSINV' => [ 296, 1, 1, 0 ],
914
+ 'STANDARDIZE' => [ 297, 3, 1, 0 ],
915
+ 'ODD' => [ 298, 1, 1, 0 ],
916
+ 'PERMUT' => [ 299, 2, 1, 0 ],
917
+ 'POISSON' => [ 300, 3, 1, 0 ],
918
+ 'TDIST' => [ 301, 3, 1, 0 ],
919
+ 'WEIBULL' => [ 302, 4, 1, 0 ],
920
+ 'SUMXMY2' => [ 303, 2, 2, 0 ],
921
+ 'SUMX2MY2' => [ 304, 2, 2, 0 ],
922
+ 'SUMX2PY2' => [ 305, 2, 2, 0 ],
923
+ 'CHITEST' => [ 306, 2, 2, 0 ],
924
+ 'CORREL' => [ 307, 2, 2, 0 ],
925
+ 'COVAR' => [ 308, 2, 2, 0 ],
926
+ 'FORECAST' => [ 309, 3, 2, 0 ],
927
+ 'FTEST' => [ 310, 2, 2, 0 ],
928
+ 'INTERCEPT' => [ 311, 2, 2, 0 ],
929
+ 'PEARSON' => [ 312, 2, 2, 0 ],
930
+ 'RSQ' => [ 313, 2, 2, 0 ],
931
+ 'STEYX' => [ 314, 2, 2, 0 ],
932
+ 'SLOPE' => [ 315, 2, 2, 0 ],
933
+ 'TTEST' => [ 316, 4, 2, 0 ],
934
+ 'PROB' => [ 317, -1, 2, 0 ],
935
+ 'DEVSQ' => [ 318, -1, 0, 0 ],
936
+ 'GEOMEAN' => [ 319, -1, 0, 0 ],
937
+ 'HARMEAN' => [ 320, -1, 0, 0 ],
938
+ 'SUMSQ' => [ 321, -1, 0, 0 ],
939
+ 'KURT' => [ 322, -1, 0, 0 ],
940
+ 'SKEW' => [ 323, -1, 0, 0 ],
941
+ 'ZTEST' => [ 324, -1, 0, 0 ],
942
+ 'LARGE' => [ 325, 2, 0, 0 ],
943
+ 'SMALL' => [ 326, 2, 0, 0 ],
944
+ 'QUARTILE' => [ 327, 2, 0, 0 ],
945
+ 'PERCENTILE' => [ 328, 2, 0, 0 ],
946
+ 'PERCENTRANK' => [ 329, -1, 0, 0 ],
947
+ 'MODE' => [ 330, -1, 2, 0 ],
948
+ 'TRIMMEAN' => [ 331, 2, 0, 0 ],
949
+ 'TINV' => [ 332, 2, 1, 0 ],
950
+ 'CONCATENATE' => [ 336, -1, 1, 0 ],
951
+ 'POWER' => [ 337, 2, 1, 0 ],
952
+ 'RADIANS' => [ 342, 1, 1, 0 ],
953
+ 'DEGREES' => [ 343, 1, 1, 0 ],
954
+ 'SUBTOTAL' => [ 344, -1, 0, 0 ],
955
+ 'SUMIF' => [ 345, -1, 0, 0 ],
956
+ 'COUNTIF' => [ 346, 2, 0, 0 ],
957
+ 'COUNTBLANK' => [ 347, 1, 0, 0 ],
958
+ 'ROMAN' => [ 354, -1, 1, 0 ]
959
+ }
960
+
961
+ end
962
+
963
+
964
+ end
965
+
966
+ if $0 ==__FILE__
967
+
968
+
969
+ parser = Formula.new
970
+ puts
971
+ puts 'type "Q" to quit.'
972
+ puts
973
+ while true
974
+ puts
975
+ print '? '
976
+ str = gets.chop!
977
+ break if /q/i =~ str
978
+ begin
979
+ e = parser.parse(str)
980
+ p parser.reverse(e)
981
+ rescue ParseError
982
+ puts $!
983
+ end
984
+ end
985
+
986
+ end