slayer-surpass 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.Rakefile.swp +0 -0
- data/.bnsignore +26 -0
- data/History.txt +4 -0
- data/LICENSE.txt +110 -0
- data/README.txt +26 -0
- data/Rakefile +36 -0
- data/bin/surpass +8 -0
- data/lib/surpass.rb +64 -0
- data/lib/surpass/ExcelFormula.g +393 -0
- data/lib/surpass/ExcelFormula.tokens +32 -0
- data/lib/surpass/ExcelFormulaLexer.rb +1490 -0
- data/lib/surpass/ExcelFormulaParser.rb +1822 -0
- data/lib/surpass/biff_record.rb +2173 -0
- data/lib/surpass/bitmap.rb +218 -0
- data/lib/surpass/cell.rb +187 -0
- data/lib/surpass/chart.rb +16 -0
- data/lib/surpass/column.rb +40 -0
- data/lib/surpass/document.rb +406 -0
- data/lib/surpass/excel_magic.rb +1016 -0
- data/lib/surpass/formatting.rb +605 -0
- data/lib/surpass/formula.rb +25 -0
- data/lib/surpass/row.rb +173 -0
- data/lib/surpass/style.rb +194 -0
- data/lib/surpass/tokens.txt +2 -0
- data/lib/surpass/utilities.rb +118 -0
- data/lib/surpass/workbook.rb +207 -0
- data/lib/surpass/worksheet.rb +574 -0
- data/surpass.gemspec +39 -0
- metadata +119 -0
data/.Rakefile.swp
ADDED
Binary file
|
data/.bnsignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# PROJ.ignore_file = '.gitignore'
|
8
|
+
#
|
9
|
+
# For a project with a C extension, the following would be a good set of
|
10
|
+
# exclude patterns (uncomment them if you want to use them):
|
11
|
+
# *.[oa]
|
12
|
+
# *~
|
13
|
+
announcement.txt
|
14
|
+
coverage
|
15
|
+
doc
|
16
|
+
pkg
|
17
|
+
.DS_Store
|
18
|
+
.bzr
|
19
|
+
.bzrignore
|
20
|
+
examples
|
21
|
+
debug-examples
|
22
|
+
webby
|
23
|
+
stats
|
24
|
+
spec
|
25
|
+
*.swp
|
26
|
+
|
data/History.txt
ADDED
data/LICENSE.txt
ADDED
@@ -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
|
data/README.txt
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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 'slayer-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
|
data/bin/surpass
ADDED
data/lib/surpass.rb
ADDED
@@ -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()};
|