rmasalov-surpass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 0.0.9 / 2010-05-03
2
+
3
+ * 1 major enhancement
4
+ * Some formula support enabled, see examples/formulas.rb to see what is supported.
@@ -0,0 +1,110 @@
1
+ Portions Copyright (c) 2008-9, Ana Nelson
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
19
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+
27
+
28
+
29
+ Surpass is a Ruby port of Python projects xlwt and pyExcelerator, whose
30
+ licenses are below.
31
+
32
+
33
+
34
+ Portions copyright © 2007, Stephen John Machin, Lingfo Pty Ltd
35
+ All rights reserved.
36
+
37
+ Redistribution and use in source and binary forms, with or without
38
+ modification, are permitted provided that the following conditions are met:
39
+
40
+ 1. Redistributions of source code must retain the above copyright notice,
41
+ this list of conditions and the following disclaimer.
42
+
43
+ 2. Redistributions in binary form must reproduce the above copyright notice,
44
+ this list of conditions and the following disclaimer in the documentation
45
+ and/or other materials provided with the distribution.
46
+
47
+ 3. None of the names of Stephen John Machin, Lingfo Pty Ltd and any
48
+ contributors may be used to endorse or promote products derived from this
49
+ software without specific prior written permission.
50
+
51
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
52
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
53
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
54
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
55
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
56
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
57
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
58
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
59
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
60
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
61
+ THE POSSIBILITY OF SUCH DAMAGE.
62
+
63
+
64
+ Copyright (C) 2005 Roman V. Kiseliov
65
+ All rights reserved.
66
+
67
+ Redistribution and use in source and binary forms, with or without
68
+ modification, are permitted provided that the following conditions
69
+ are met:
70
+
71
+ 1. Redistributions of source code must retain the above copyright
72
+ notice, this list of conditions and the following disclaimer.
73
+
74
+ 2. Redistributions in binary form must reproduce the above copyright
75
+ notice, this list of conditions and the following disclaimer in
76
+ the documentation and/or other materials provided with the
77
+ distribution.
78
+
79
+ 3. All advertising materials mentioning features or use of this
80
+ software must display the following acknowledgment:
81
+ "This product includes software developed by
82
+ Roman V. Kiseliov <roman@kiseliov.ru>."
83
+
84
+ 4. Redistributions of any form whatsoever must retain the following
85
+ acknowledgment:
86
+ "This product includes software developed by
87
+ Roman V. Kiseliov <roman@kiseliov.ru>."
88
+
89
+ THIS SOFTWARE IS PROVIDED BY Roman V. Kiseliov ``AS IS'' AND ANY
90
+ EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
91
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
92
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Roman V. Kiseliov OR
93
+ ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
94
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
95
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
96
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
97
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
98
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
99
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
100
+ OF THE POSSIBILITY OF SUCH DAMAGE.
101
+
102
+ Roman V. Kiseliov
103
+ Russia
104
+ Kursk
105
+ Libknecht St., 4
106
+
107
+ +7(0712)56-09-83
108
+
109
+ <roman@kiseliov.ru>
110
+ Subject: pyExcelerator
@@ -0,0 +1,26 @@
1
+ surpass
2
+ by Ana Nelson
3
+ http://ananelson.com
4
+ Based on original Python code by Roman V. Kiseliov
5
+
6
+ == DESCRIPTION:
7
+
8
+ Surpass is writing (and eventually reading) excel workbooks in pure Ruby. Surpass is based on xlwt (and pyExcelerator).
9
+
10
+ For comprehensive documentation, please refer to the PDF manual which is available from http://surpass.rubyforge.org or in the root directory of the source code repository.
11
+
12
+ If you like to learn from playing with working examples, then there are plenty in the examples/ and webby/examples directories of the source code.
13
+
14
+ == REQUIREMENTS:
15
+
16
+ Tested with Ruby 1.8.6 (MRI or JRuby)
17
+
18
+ This is just standard Ruby, so please feel free to try on other Ruby platforms and let me know how it goes.
19
+
20
+ == INSTALL:
21
+
22
+ sudo gem install surpass
23
+
24
+ == LICENSE:
25
+
26
+ Please see LICENSE.txt in this directory.
@@ -0,0 +1,36 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ rescue LoadError
8
+ abort '### Please install the "bones" gem ###'
9
+ end
10
+
11
+ require 'lib/surpass'
12
+
13
+ Bones {
14
+ name 'rmasalov-surpass'
15
+ authors 'Ana Nelson'
16
+ email 'ana@ananelson.com'
17
+ url 'http://surpass.rubyforge.org'
18
+ version Surpass::VERSION
19
+ }
20
+
21
+ task :default => 'spec:run'
22
+ task 'gem:release' => 'test:run'
23
+
24
+ desc "run antlr compiler"
25
+ task :antlr do
26
+ `cd lib/surpass; antlr4ruby ExcelFormula.g`
27
+ end
28
+
29
+ desc "run examples"
30
+ task :examples do
31
+ `rm examples/*.xls`
32
+ `cd examples; ls *.rb`.chomp.split.each do |f|
33
+ next if f =~ /big/
34
+ `jruby #{File.expand_path(f, "examples")}`
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib surpass]))
5
+
6
+ # Put your code here
7
+
8
+ # EOF
@@ -0,0 +1,64 @@
1
+ module Surpass
2
+
3
+ # :stopdoc:
4
+ VERSION = '0.1.0'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = "." )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each do |rb|
42
+ next if File.basename(rb) === File.basename(__FILE__) # skip surpass.rb
43
+ next if File.basename(rb) =~ /^ExcelFormula/ unless FORMULAS_AVAILABLE
44
+ next if File.basename(rb) =~ /^excel_magic/ # already loaded this
45
+ require rb
46
+ end
47
+ end
48
+
49
+ end # module Surpass
50
+
51
+ begin
52
+ require 'rubygems'
53
+ require 'antlr3'
54
+ FORMULAS_AVAILABLE = true
55
+ rescue Exception => e
56
+ puts "antlr3 gem not found, formulas not available. Install antlr3 to enable formulas."
57
+ FORMULAS_AVAILABLE = false
58
+ end
59
+
60
+ require File.join(File.dirname(__FILE__), 'surpass', 'excel_magic')
61
+ include ExcelMagic
62
+ Surpass.require_all_libs_relative_to(__FILE__)
63
+ require 'date'
64
+
@@ -0,0 +1,393 @@
1
+ grammar ExcelFormula;
2
+
3
+ options {
4
+ language = Ruby;
5
+ k = 2;
6
+ }
7
+
8
+ @header {
9
+ RVA_DELTA = {"R" => 0, "V" => 0x20, "A" => 0x40}
10
+ RVA_DELTA_REF = {"R" => 0, "V" => 0x20, "A" => 0x40, "D" => 0x20}
11
+ RVA_DELTA_AREA = {"R" => 0, "V" => 0x20, "A" => 0x40, "D" => 0}
12
+ }
13
+
14
+ @init {
15
+ @rpn = ''
16
+ @sheet_references = []
17
+ @xcall_references = []
18
+ }
19
+
20
+ @members {
21
+ attr_accessor :rpn
22
+ }
23
+
24
+ /// @export "formula"
25
+ formula
26
+ : expr["V"]
27
+ ;
28
+
29
+ /// @export "expr"
30
+ expr[arg_type]
31
+ : prec0_expr[arg_type]
32
+ (
33
+ (
34
+ EQ { op = [PTGEQ].pack('C') }
35
+ | NE { op = [PTGNE].pack('C') }
36
+ | GT { op = [PTGGT].pack('C') }
37
+ | LT { op = [PTGLT].pack('C') }
38
+ | GE { op = [PTGGE].pack('C') }
39
+ | LE { op = [PTGLE].pack('C') }
40
+
41
+ )
42
+ prec0_expr[arg_type] { @rpn += op }
43
+ )*
44
+ ;
45
+ /// @export "prec0_expr"
46
+ prec0_expr[arg_type]
47
+ : prec1_expr[arg_type]
48
+ (
49
+ (
50
+ CONCAT { op = [PTGCONCAT].pack('C') }
51
+ )
52
+ prec1_expr[arg_type] { @rpn += op }
53
+ )*
54
+ ;
55
+ /// @export "prec1_expr"
56
+ prec1_expr[arg_type]
57
+ : prec2_expr[arg_type]
58
+ (
59
+ (
60
+ ADD { op = [PTGADD].pack('C') }
61
+ | SUB { op = [PTGSUB].pack('C') }
62
+ )
63
+ prec2_expr[arg_type] { @rpn += op }
64
+ )*
65
+ ;
66
+ /// @export "prec2_expr"
67
+ prec2_expr[arg_type]
68
+ : prec3_expr[arg_type]
69
+ (
70
+ (
71
+ MUL { op = [PTGMUL].pack('C') }
72
+ | DIV { op = [PTGDIV].pack('C') }
73
+ )
74
+ prec3_expr[arg_type] { @rpn += op }
75
+ )*
76
+ ;
77
+
78
+ /// @export "prec3_expr"
79
+ prec3_expr[arg_type]
80
+ : prec4_expr[arg_type]
81
+ (
82
+ (
83
+ POWER { op = [PTGPOWER].pack('C') }
84
+ )
85
+ prec4_expr[arg_type] { @rpn += op }
86
+ )*
87
+ ;
88
+ /// @export "prec4_expr"
89
+ prec4_expr[arg_type]
90
+ : prec5_expr[arg_type]
91
+ (
92
+ PERCENT { @rpn += [PTGPERCENT].pack('C') }
93
+ )?
94
+ ;
95
+ /// @export "prec5_expr"
96
+ prec5_expr[arg_type]
97
+ : primary[arg_type]
98
+ | SUB primary[arg_type] { @rpn += [PTGUMINUS].pack('C') }
99
+ ;
100
+ /// @end
101
+ primary[arg_type]
102
+ /// @export "boolean-string"
103
+ : TRUE_CONST
104
+ {
105
+ @rpn += [PTGBOOL, 1].pack("C2")
106
+ }
107
+ | FALSE_CONST
108
+ {
109
+ @rpn += [PTGBOOL, 0].pack("C2")
110
+ }
111
+ | str_tok = STR_CONST
112
+ {
113
+ s = str_tok.text.gsub("\"", "")
114
+ @rpn += [PTGSTR].pack("C") + [s.length].pack('v') + s
115
+ }
116
+ /// @export "numeric"
117
+ | int_tok = INT_CONST
118
+ {
119
+ int_value = int_tok.text.to_i
120
+ if int_value <= 65535
121
+ @rpn += [PTGINT, int_value].pack("Cv")
122
+ else
123
+ @rpn += [PTGNUM, int_value.to_f].pack("CE")
124
+ end
125
+ }
126
+ | num_tok = NUM_CONST
127
+ {
128
+ @rpn += [PTGNUM, num_tok.text.to_f].pack("CE")
129
+ }
130
+ /// @end
131
+ | ref2d_tok = REF2D
132
+ {
133
+ r, c = Utilities.cell_to_packed_rowcol(ref2d_tok.text)
134
+ ptg = PTGREFR + RVA_DELTA_REF[arg_type]
135
+ @rpn += [ptg, r, c].pack("Cv2")
136
+ }
137
+ | ref2d1_tok = REF2D COLON ref2d2_tok = REF2D
138
+ {
139
+ r1, c1 = Utilities.cell_to_packed_rowcol(ref2d1_tok.text)
140
+ r2, c2 = Utilities.cell_to_packed_rowcol(ref2d2_tok.text)
141
+ ptg = PTGAREAR + RVA_DELTA_AREA[arg_type]
142
+ @rpn += [ptg, r1, r2, c1, c2].pack("Cv4")
143
+ }
144
+ /// @export "parens"
145
+ | LP expr[arg_type] RP
146
+ {
147
+ @rpn += [PTGPAREN].pack('C')
148
+ }
149
+ /// @end
150
+ | sheet1 = sheet
151
+ {
152
+ sheet2 = sheet1
153
+ }
154
+ ( COLON sheet2 = sheet )? BANG ref3d_ref2d=REF2D
155
+ {
156
+ ptg = PTGREF3DR + RVA_DELTA_REF[arg_type]
157
+ r1, c1 = Utilities.cell_to_packed_rowcol(ref3d_ref2d.text)
158
+ rpn_ref2d = [0x0000, r1, c1].pack("v3")
159
+ }
160
+ ( COLON ref3d_ref2d2= REF2D
161
+ {
162
+ ptg = PTGAREA3DR + RVA_DELTA_AREA[arg_type]
163
+ r2, c2 = Utilities.cell_to_packed_rowcol(ref3d_ref2d2.text)
164
+ rpn_ref2d = [0x0000, r1, r2, c1, c2].pack("v5")
165
+ }
166
+ )?
167
+ {
168
+ @rpn += [ptg].pack("C")
169
+ @sheet_references << [sheet1, sheet2, @rpn.size]
170
+ @rpn += rpn_ref2d
171
+ }
172
+ /// @export "if-fn"
173
+ | FUNC_IF
174
+ LP expr["V"] (SEMICOLON | COMMA)
175
+ {
176
+ @rpn += [PTGATTR, 0x02, 0].pack("C2v") # tAttrIf
177
+ pos0 = @rpn.size - 2
178
+ }
179
+ expr[arg_type] (SEMICOLON | COMMA)
180
+ {
181
+ @rpn += [PTGATTR, 0x08, 0].pack("C2v") # tAttrSkip
182
+ pos1 = @rpn.size - 2
183
+
184
+ @rpn = @rpn[0...pos0] + [pos1-pos0].pack("v") + @rpn[(pos0+2)...(@rpn.size)]
185
+ }
186
+ expr[arg_type] RP
187
+ {
188
+ @rpn += [PTGATTR, 0x08, 3].pack("C2v") # tAttrSkip
189
+ @rpn += [PTGFUNCVARR, 3, 1].pack("C2v") # 3 = nargs, 1 = IF func
190
+ pos2 = @rpn.size
191
+
192
+ @rpn = @rpn[0...pos1] + [pos2-(pos1+2)-1].pack("v") + @rpn[(pos1+2)...(@rpn.size)]
193
+ }
194
+ /// @end
195
+ | FUNC_CHOOSE
196
+ {
197
+ arg_type = "R"
198
+ rpn_chunks = []
199
+ }
200
+ LP expr["V"]
201
+ {
202
+ rpn_start = @rpn.size
203
+ ref_markers = [@sheet_references.size]
204
+ }
205
+ (
206
+ (SEMICOLON | COMMA)
207
+ { mark = @rpn.size }
208
+ (
209
+ expr[arg_type]
210
+ | { @rpn += [PTGMISSARG].pack("C") }
211
+ )
212
+ {
213
+ rem = @rpn.size - mark
214
+ rpn_chunks << @rpn[mark, rem]
215
+ ref_markers << @sheet_references.size
216
+ }
217
+ )*
218
+ RP
219
+ {
220
+ @rpn = @rpn[0...rpn_start]
221
+ nc = rpn_chunks.length
222
+ chunklens = rpn_chunks.collect {|c| c.length}
223
+ skiplens = [0] * nc
224
+ skiplens[nc-1] = 3
225
+
226
+ (nc-1).downto(1) do |i|
227
+ skiplens[i-1] = skiplens[i] + chunklens[i] + 4
228
+ end
229
+ jump_pos = [2*nc + 2]
230
+
231
+ (0...nc).each do |i|
232
+ jump_pos << (jump_pos.last + chunklens[i] + 4)
233
+ end
234
+ chunk_shift = 2*nc + 6 # size of tAttrChoose
235
+
236
+ (0...nc).each do |i|
237
+ (ref_markers[i]...ref_markers[i+1]).each do |r|
238
+ ref = @sheet_references[r]
239
+ @sheet_references[r] = [ref[0], ref[1], ref[2] + chunk_shift]
240
+ end
241
+ chunk_shift += 4 # size of tAttrSkip
242
+ end
243
+
244
+ choose_rpn = []
245
+ choose_rpn << [PTGATTR, 0x04, nc].pack("CCv") # 0x04 is tAttrChoose
246
+ choose_rpn << jump_pos.pack("v*")
247
+
248
+ (0...nc).each do |i|
249
+ choose_rpn << rpn_chunks[i]
250
+ choose_rpn << [PTGATTR, 0x08, skiplens[i]].pack("CCv") # 0x08 is tAttrSkip
251
+ end
252
+ choose_rpn << [PTGFUNCVARV, nc+1, 100].pack("CCv") # 100 is CHOOSE fn
253
+ @rpn += choose_rpn.join
254
+ }
255
+ | name_tok = NAME
256
+ {
257
+ raise "[formula] found unexpected NAME token #{name_tok.text}"
258
+ }
259
+ | func_tok = NAME
260
+ {
261
+ func_toku = func_tok.text.upcase
262
+ if STD_FUNC_BY_NAME.has_key?(func_toku)
263
+ opcode, min_argc, max_argc, func_type, arg_type_str = STD_FUNC_BY_NAME[func_toku]
264
+ arg_type_list = arg_type_str.split
265
+ else
266
+ raise "[formula] unknown function #{func_tok.text}"
267
+ end
268
+ xcall = (opcode < 0)
269
+
270
+ if xcall
271
+ @xcall_references << [func_toku, @rpn.size + 1]
272
+ @rpn += [PTGNAMEXR, 0xadde, 0xefbe, 0x0000].pack("Cvvv")
273
+ end
274
+ }
275
+ LP arg_count = expr_list[arg_type_list, min_argc, max_argc] RP
276
+ {
277
+ if (arg_count > max_argc) || (arg_count < min_argc)
278
+ raise "incorrect number #{arg_count} of parameters for function: #{func_tok.text}"
279
+ end
280
+
281
+ if xcall
282
+ func_ptg = PTGFUNCVARR + RVA_DELTA[func_type]
283
+ @rpn += [func_ptg, arg_count + 1, 255].pack("CCv") # 255 is magic XCALL function
284
+ elsif (min_argc == max_argc)
285
+ func_ptg = PTGFUNCR + RVA_DELTA[func_type]
286
+ @rpn += [func_ptg, opcode].pack("Cv")
287
+ elsif (arg_count == 1) && (func_tok.text.upcase === "SUM")
288
+ @rpn += [PTGATTR, 0x10, 0].pack("CCv") # tAttrSum
289
+ else
290
+ func_ptg = PTGFUNCVARR + RVA_DELTA[func_type]
291
+ @rpn += [func_ptg, arg_count, opcode].pack("CCv")
292
+ end
293
+ }
294
+ ;
295
+
296
+ // Process arguments to a function.
297
+ /// @export "expr_list"
298
+ expr_list[arg_type_list, min_argc, max_argc] returns [arg_cnt]
299
+ @init
300
+ {
301
+ arg_cnt = 0
302
+
303
+ # Set these for processing first argument,
304
+ # it's simpler because first argument type can't be '...'
305
+ arg_type = arg_type_list.first
306
+
307
+ # need to check for '-' for a fn with no arguments
308
+ arg_cnt += 1 unless arg_type === '-'
309
+ }
310
+ :
311
+ (expr[arg_type]
312
+ (
313
+ (SEMICOLON | COMMA) { arg_cnt += 1 }
314
+ (
315
+ // *either* we find an argument after the comma/semicolon in which case process it...
316
+ expr[arg_type]
317
+ {
318
+ if arg_cnt - 2 < arg_type_list.size
319
+ arg_type = arg_type_list[arg_cnt - 2]
320
+ else
321
+ if arg_type_list.last === "..."
322
+ # e.g. "V R ..." arbitrary number of args of type R
323
+ # this will always be last element in arg_type_list
324
+ # 2nd to last element will provide type
325
+ arg_type = arg_type_list[arg_type_list.size - 2]
326
+ else
327
+ # Just read last element normally.
328
+ arg_type = arg_type_list[arg_cnt - 2]
329
+ end
330
+ end
331
+ }
332
+ // ... *or* we have a missing argument and need to insert a placeholder
333
+ | { @rpn += [PTGMISSARG].pack("C") }
334
+ )
335
+ )*
336
+ )
337
+ | // Some functions have no arguments e.g. pi()
338
+ ;
339
+ /// @end
340
+ sheet returns[ref]
341
+ : sheet_ref_name = NAME
342
+ { ref = sheet_ref_name.text }
343
+ | sheet_ref_int = INT_CONST
344
+ { ref = sheet_ref_int.text }
345
+ | sheet_ref_quote = QUOTENAME
346
+ { ref = sheet_ref_quote.text[1, len(sheet_ref_quote.text) - 1].replace("''", "'") }
347
+ ;
348
+
349
+ /// @export "expr-tokens"
350
+ EQ: '=';
351
+ LT: '<';
352
+ GT: '>';
353
+ NE: '<>';
354
+ LE: '<=';
355
+ GE: '>=';
356
+ /// @export "prec0_expr-tokens"
357
+ CONCAT: '&';
358
+ /// @export "prec1_expr-tokens"
359
+ ADD: '+';
360
+ SUB: '-';
361
+ /// @export "prec2_expr-tokens"
362
+ MUL: '*';
363
+ DIV: '/';
364
+ /// @export "prec3_expr-tokens"
365
+ POWER: '^';
366
+ /// @export "prec4_expr-tokens"
367
+ PERCENT: '\%';
368
+ /// @end
369
+
370
+ COLON: ':';
371
+ SEMICOLON: ';';
372
+ COMMA: ',';
373
+
374
+ LP: '(';
375
+ RP: ')';
376
+ BANG: '!';
377
+
378
+ fragment DIGIT: '0'..'9';
379
+
380
+ INT_CONST: DIGIT+ ;
381
+ NUM_CONST: DIGIT* '.' DIGIT+ (('E'|'e') ('+'|'-')? DIGIT+)?;
382
+ STR_CONST: '"' (~'"')+ '"';
383
+ REF2D: '$'? ('A'..'I')? ('A'..'Z') '$'? DIGIT+;
384
+ TRUE_CONST: ('T'|'t') ('R'|'r') ('U'|'u') ('E'|'e') ;
385
+ FALSE_CONST: ('F'|'f') ('A'|'a') ('L'|'l') ('S'|'s') ('E'|'e') ;
386
+ QUOTENAME: '\'(?:[^\']|\'\')*\'';
387
+ FUNC_IF: 'IF';
388
+ FUNC_CHOOSE: 'CHOOSE';
389
+
390
+ fragment ALPHA: 'a'..'z' | 'A'..'Z';
391
+ NAME: ALPHA+ ;
392
+
393
+ WS: (' ')+ {skip()};